mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:31:27 +00:00
Various tweaks (#381)
* Add the message delivery status to the plain timeline styler and display it outside of the main content * Fix glitchy timeline scroll to bottom button * Fixed bubbled reactions padding * Simplified the emoji picker, double tapping a timeline item directly opens it now and added a context menu option. Linked it to rust side reaction sending * Fix the sliding sync cold cache: treat invalidated rooms as filled * Make splash screen view full screen * Fix the offline indicator popping up when first setting up the network monitor * Expose presentationDetents on the NavigationStackCoordinator and start using them for the EmojiPicker * Fix link tint color * Linked TimelineReactionsView reaction sending * Remove now unused/unnecessary classes * Add changelog * Fix formatting issue
This commit is contained in:
@@ -29,7 +29,6 @@
|
|||||||
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
|
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
|
||||||
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
|
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
|
||||||
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
||||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
|
||||||
0BEFE400B4802FE8C9DB39B3 /* FilePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */; };
|
0BEFE400B4802FE8C9DB39B3 /* FilePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */; };
|
||||||
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
|
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
|
||||||
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
|
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
|
||||||
@@ -208,7 +207,6 @@
|
|||||||
6CA81428F0970785CDCC5E86 /* UserNotificationToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */; };
|
6CA81428F0970785CDCC5E86 /* UserNotificationToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */; };
|
||||||
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
||||||
6E6E0AAF6C44C0B117EBBE5A /* SlidingSyncViewProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F3B445BD6EF1C751806B22 /* SlidingSyncViewProxy.swift */; };
|
6E6E0AAF6C44C0B117EBBE5A /* SlidingSyncViewProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F3B445BD6EF1C751806B22 /* SlidingSyncViewProxy.swift */; };
|
||||||
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */; };
|
|
||||||
6F2AB43A1EFAD8A97AF41A15 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
|
6F2AB43A1EFAD8A97AF41A15 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
|
||||||
6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */; };
|
6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */; };
|
||||||
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; };
|
7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; };
|
||||||
@@ -303,7 +301,6 @@
|
|||||||
9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */; };
|
9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */; };
|
||||||
9D2E03DB175A6AB14589076D /* AppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = AA4E1BEB4E9BC2467006E12B /* AppAuth */; };
|
9D2E03DB175A6AB14589076D /* AppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = AA4E1BEB4E9BC2467006E12B /* AppAuth */; };
|
||||||
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
|
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
|
||||||
9DAF683E0CD7D70C8862EC98 /* TimelineItemReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD61385D39FD330AFB928E6A /* TimelineItemReactionsMenuView.swift */; };
|
|
||||||
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */; };
|
9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */; };
|
||||||
9E8AE387FD03E4F1C1B8815A /* SessionVerificationStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */; };
|
9E8AE387FD03E4F1C1B8815A /* SessionVerificationStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */; };
|
||||||
9F41FF9C53F7A6EAEA6259C9 /* InviteFriendsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7AB0A148FCCAC28681C190 /* InviteFriendsCoordinator.swift */; };
|
9F41FF9C53F7A6EAEA6259C9 /* InviteFriendsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7AB0A148FCCAC28681C190 /* InviteFriendsCoordinator.swift */; };
|
||||||
@@ -383,7 +380,6 @@
|
|||||||
C94A6048C654B01163AE1BF1 /* VideoPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */; };
|
C94A6048C654B01163AE1BF1 /* VideoPlayerViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282B7A2DCD076AD2CF27F46 /* VideoPlayerViewModelProtocol.swift */; };
|
||||||
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; };
|
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; };
|
||||||
CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; };
|
CB137BFB3E083C33E398A6CB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; };
|
||||||
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; };
|
|
||||||
CB498F4E27AA0545DCEF0F6F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; };
|
CB498F4E27AA0545DCEF0F6F /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4003BC24B24C9E63D3304177 /* DeviceKit */; };
|
||||||
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
|
CB99B0FA38A4AC596F38CC13 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
|
||||||
CBF64DE774298D773DBD5354 /* VideoPlayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */; };
|
CBF64DE774298D773DBD5354 /* VideoPlayerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */; };
|
||||||
@@ -447,7 +443,6 @@
|
|||||||
F06CE9132855E81EBB6DDC32 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 800631D7250B7F93195035F1 /* KeychainAccess */; };
|
F06CE9132855E81EBB6DDC32 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 800631D7250B7F93195035F1 /* KeychainAccess */; };
|
||||||
F0F82C3C848C865C3098AA52 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
F0F82C3C848C865C3098AA52 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; };
|
||||||
F257F964493A9CD02A6F720C /* OnboardingPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF2717AB91060260E5F4781 /* OnboardingPageView.swift */; };
|
F257F964493A9CD02A6F720C /* OnboardingPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF2717AB91060260E5F4781 /* OnboardingPageView.swift */; };
|
||||||
F3B941FE3BBAA3320F18E73F /* EmojiPickerSearchFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C615E29435BC651DD2E95B67 /* EmojiPickerSearchFieldView.swift */; };
|
|
||||||
F425C3F85BFF28C9AC593F52 /* MockNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */; };
|
F425C3F85BFF28C9AC593F52 /* MockNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */; };
|
||||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
||||||
F61AFA8BF2E739FBC30472F5 /* NotificationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD5FEE195446A9E458DDDAF /* NotificationServiceProxyProtocol.swift */; };
|
F61AFA8BF2E739FBC30472F5 /* NotificationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD5FEE195446A9E458DDDAF /* NotificationServiceProxyProtocol.swift */; };
|
||||||
@@ -561,7 +556,6 @@
|
|||||||
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||||
124D85E85505B6B81845235F /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fy; path = fy.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
124D85E85505B6B81845235F /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fy; path = fy.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizer.swift; sourceTree = "<group>"; };
|
|
||||||
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||||
142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryDetails.swift; sourceTree = "<group>"; };
|
142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryDetails.swift; sourceTree = "<group>"; };
|
||||||
@@ -675,7 +669,6 @@
|
|||||||
4990FDBDA96B88E214F92F48 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = "<group>"; };
|
4990FDBDA96B88E214F92F48 /* SettingsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModels.swift; sourceTree = "<group>"; };
|
||||||
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
|
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
|
||||||
49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = "<group>"; };
|
|
||||||
4A57A4AFA6A068668AFBD070 /* UIActivityViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityViewControllerWrapper.swift; sourceTree = "<group>"; };
|
4A57A4AFA6A068668AFBD070 /* UIActivityViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityViewControllerWrapper.swift; sourceTree = "<group>"; };
|
||||||
4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettings.swift; sourceTree = "<group>"; };
|
4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettings.swift; sourceTree = "<group>"; };
|
||||||
4B40B7F6FCCE2D8C242492D9 /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = ga.lproj/Localizable.strings; sourceTree = "<group>"; };
|
4B40B7F6FCCE2D8C242492D9 /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = ga.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -716,7 +709,6 @@
|
|||||||
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
|
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
|
||||||
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
|
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizerTests.swift; sourceTree = "<group>"; };
|
|
||||||
607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageProtocol.swift; sourceTree = "<group>"; };
|
607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageProtocol.swift; sourceTree = "<group>"; };
|
||||||
616197D81103330BF2ADD559 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
616197D81103330BF2ADD559 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -866,7 +858,6 @@
|
|||||||
ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = "<group>"; };
|
ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = "<group>"; };
|
||||||
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
AD61385D39FD330AFB928E6A /* TimelineItemReactionsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReactionsMenuView.swift; sourceTree = "<group>"; };
|
|
||||||
AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportCoordinator.swift; sourceTree = "<group>"; };
|
AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportCoordinator.swift; sourceTree = "<group>"; };
|
||||||
ADB3A7BCE745626EC61EF3C3 /* FilePreviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewCoordinator.swift; sourceTree = "<group>"; };
|
ADB3A7BCE745626EC61EF3C3 /* FilePreviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewCoordinator.swift; sourceTree = "<group>"; };
|
||||||
ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
ADCB8A232D3A8FB3E16A7303 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -912,7 +903,6 @@
|
|||||||
C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelProtocol.swift; sourceTree = "<group>"; };
|
C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
||||||
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||||
C615E29435BC651DD2E95B67 /* EmojiPickerSearchFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerSearchFieldView.swift; sourceTree = "<group>"; };
|
|
||||||
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
|
C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = "<group>"; };
|
||||||
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = "<group>"; };
|
C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = "<group>"; };
|
||||||
C75EF87651B00A176AB08E97 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
C75EF87651B00A176AB08E97 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
@@ -1269,7 +1259,6 @@
|
|||||||
children = (
|
children = (
|
||||||
AB785716B9212C093704E767 /* EmojiPickerHeaderView.swift */,
|
AB785716B9212C093704E767 /* EmojiPickerHeaderView.swift */,
|
||||||
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */,
|
00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */,
|
||||||
C615E29435BC651DD2E95B67 /* EmojiPickerSearchFieldView.swift */,
|
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1677,7 +1666,6 @@
|
|||||||
F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */,
|
F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */,
|
||||||
DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */,
|
DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */,
|
||||||
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
|
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
|
||||||
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */,
|
|
||||||
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */,
|
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */,
|
||||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */,
|
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */,
|
||||||
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
|
3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */,
|
||||||
@@ -2139,9 +2127,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */,
|
E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */,
|
||||||
49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */,
|
|
||||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */,
|
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */,
|
||||||
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
|
|
||||||
6A580295A56B55A856CC4084 /* InfoPlistReader.swift */,
|
6A580295A56B55A856CC4084 /* InfoPlistReader.swift */,
|
||||||
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
|
||||||
C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */,
|
C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */,
|
||||||
@@ -2192,7 +2178,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
|
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
|
||||||
AD61385D39FD330AFB928E6A /* TimelineItemReactionsMenuView.swift */,
|
|
||||||
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
|
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
|
||||||
);
|
);
|
||||||
path = Supplementary;
|
path = Supplementary;
|
||||||
@@ -2837,7 +2822,6 @@
|
|||||||
7E7DF1867F98B0D10A6C0A63 /* FileCacheTests.swift in Sources */,
|
7E7DF1867F98B0D10A6C0A63 /* FileCacheTests.swift in Sources */,
|
||||||
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */,
|
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */,
|
||||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
|
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
|
||||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */,
|
|
||||||
A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */,
|
A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */,
|
||||||
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */,
|
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */,
|
||||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */,
|
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */,
|
||||||
@@ -2903,7 +2887,6 @@
|
|||||||
D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */,
|
D876EC0FED3B6D46C806912A /* AvatarSize.swift in Sources */,
|
||||||
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */,
|
E0A4DCA633D174EB43AD599F /* BackgroundTaskProtocol.swift in Sources */,
|
||||||
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */,
|
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */,
|
||||||
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */,
|
|
||||||
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */,
|
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */,
|
||||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */,
|
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */,
|
||||||
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */,
|
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */,
|
||||||
@@ -2938,7 +2921,6 @@
|
|||||||
748F482FEF4E04D61C39AAD7 /* EmojiPickerScreenModels.swift in Sources */,
|
748F482FEF4E04D61C39AAD7 /* EmojiPickerScreenModels.swift in Sources */,
|
||||||
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */,
|
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */,
|
||||||
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */,
|
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */,
|
||||||
F3B941FE3BBAA3320F18E73F /* EmojiPickerSearchFieldView.swift in Sources */,
|
|
||||||
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */,
|
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */,
|
||||||
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */,
|
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */,
|
||||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */,
|
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */,
|
||||||
@@ -2963,7 +2945,6 @@
|
|||||||
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
|
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
|
||||||
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
|
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
|
||||||
03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */,
|
03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */,
|
||||||
6EA61FCA55D950BDE326A1A7 /* ImageAnonymizer.swift in Sources */,
|
|
||||||
BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */,
|
BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */,
|
||||||
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */,
|
DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */,
|
||||||
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */,
|
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */,
|
||||||
@@ -3127,7 +3108,6 @@
|
|||||||
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */,
|
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */,
|
||||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */,
|
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */,
|
||||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
||||||
9DAF683E0CD7D70C8862EC98 /* TimelineItemReactionsMenuView.swift in Sources */,
|
|
||||||
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
|
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
|
||||||
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */,
|
ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */,
|
||||||
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,
|
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
|
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
|
||||||
|
|
||||||
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
|
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
|
||||||
|
"room_timeline_backpagination_failure" = "Failed loading messages";
|
||||||
|
|
||||||
"room_timeline_replying_to" = "Replying to %@";
|
"room_timeline_replying_to" = "Replying to %@";
|
||||||
"room_timeline_editing" = "Editing";
|
"room_timeline_editing" = "Editing";
|
||||||
|
|||||||
@@ -377,6 +377,8 @@ class NavigationStackCoordinator: ObservableObject, CoordinatorProtocol, CustomS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var presentationDetents: Set<PresentationDetent> = []
|
||||||
|
|
||||||
// The currently presented sheet coordinator
|
// The currently presented sheet coordinator
|
||||||
// Sheets will be presented through the NavigationSplitCoordinator if provided
|
// Sheets will be presented through the NavigationSplitCoordinator if provided
|
||||||
var sheetCoordinator: (any CoordinatorProtocol)? {
|
var sheetCoordinator: (any CoordinatorProtocol)? {
|
||||||
@@ -489,7 +491,8 @@ class NavigationStackCoordinator: ObservableObject, CoordinatorProtocol, CustomS
|
|||||||
// MARK: - CoordinatorProtocol
|
// MARK: - CoordinatorProtocol
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(NavigationStackCoordinatorView(navigationStackCoordinator: self))
|
AnyView(NavigationStackCoordinatorView(navigationStackCoordinator: self)
|
||||||
|
.presentationDetents(presentationDetents))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CustomStringConvertible
|
// MARK: - CustomStringConvertible
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ extension ElementL10n {
|
|||||||
public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title")
|
public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title")
|
||||||
/// Info
|
/// Info
|
||||||
public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title")
|
public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title")
|
||||||
|
/// Failed loading messages
|
||||||
|
public static let roomTimelineBackpaginationFailure = ElementL10n.tr("Untranslated", "room_timeline_backpagination_failure")
|
||||||
/// Editing
|
/// Editing
|
||||||
public static let roomTimelineEditing = ElementL10n.tr("Untranslated", "room_timeline_editing")
|
public static let roomTimelineEditing = ElementL10n.tr("Untranslated", "room_timeline_editing")
|
||||||
/// Failed creating the permalink
|
/// Failed creating the permalink
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct Benchmark {
|
|
||||||
private static var trackingIdentifiers = [String: CFAbsoluteTime]()
|
|
||||||
|
|
||||||
public static var trackingEnabled = false
|
|
||||||
|
|
||||||
static func startTrackingForIdentifier(_ identifier: String, message: String? = nil) {
|
|
||||||
guard trackingEnabled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
|
||||||
trackingIdentifiers[identifier] = startTime
|
|
||||||
|
|
||||||
if let message {
|
|
||||||
MXLog.verbose("⏰ \(message).")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func logElapsedDurationForIdentifier(_ identifier: String, message: String? = nil) {
|
|
||||||
guard trackingEnabled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let start = trackingIdentifiers[identifier] else {
|
|
||||||
assertionFailure("⏰ Invalid tracking identifier")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let elapsedTime = CFAbsoluteTimeGetCurrent() - start
|
|
||||||
if let message {
|
|
||||||
MXLog.verbose("⏰ \(message). Elapsed time: \(elapsedTime.round(to: 4)) seconds.")
|
|
||||||
} else {
|
|
||||||
MXLog.verbose("⏰ Elapsed time: \(elapsedTime.round(to: 4)) seconds.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func endTrackingForIdentifier(_ identifier: String, message: String? = nil) {
|
|
||||||
guard trackingEnabled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logElapsedDurationForIdentifier(identifier, message: message)
|
|
||||||
trackingIdentifiers[identifier] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension Double {
|
|
||||||
func round(to places: Int) -> Double {
|
|
||||||
let divisor = pow(10.0, Double(places))
|
|
||||||
return (self * divisor).rounded() / divisor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Vision
|
|
||||||
|
|
||||||
enum ImageAnonymizerError: Error {
|
|
||||||
case noCgImageBased
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ImageAnonymizer {
|
|
||||||
private static var allowedTextItems: [String] = [
|
|
||||||
"#",
|
|
||||||
"@",
|
|
||||||
"%",
|
|
||||||
"&",
|
|
||||||
"+",
|
|
||||||
"-",
|
|
||||||
"_",
|
|
||||||
"\"",
|
|
||||||
"?",
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
|
|
||||||
static func anonymizedImage(from image: UIImage,
|
|
||||||
confidenceLevel: Float = 0.5,
|
|
||||||
fillColor: UIColor = .red) async throws -> UIImage {
|
|
||||||
guard let cgImage = image.cgImage else {
|
|
||||||
throw ImageAnonymizerError.noCgImageBased
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a handler with cgImage
|
|
||||||
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
|
|
||||||
var observations: [VNDetectedObjectObservation] = []
|
|
||||||
|
|
||||||
// create a text request
|
|
||||||
let textRequest = VNRecognizeTextRequest { request, error in
|
|
||||||
guard let results = request.results as? [VNRecognizedTextObservation],
|
|
||||||
error == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
observations.append(contentsOf: results)
|
|
||||||
}
|
|
||||||
textRequest.recognitionLevel = .accurate
|
|
||||||
textRequest.revision = VNRecognizeTextRequestRevision2
|
|
||||||
|
|
||||||
// create a face request
|
|
||||||
let faceRequest = VNDetectFaceRectanglesRequest { request, error in
|
|
||||||
guard let results = request.results as? [VNFaceObservation],
|
|
||||||
error == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
observations.append(contentsOf: results)
|
|
||||||
}
|
|
||||||
// revision3 doesn't work!
|
|
||||||
faceRequest.revision = VNDetectFaceRectanglesRequestRevision2
|
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
// Avoid `Could not create inference context` errors on Apple Silicon
|
|
||||||
// https://www.caseyliss.com/2022/6/20/feedback-is-broken-stop-trying-to-make-radar-happen
|
|
||||||
faceRequest.usesCPUOnly = true
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// perform requests
|
|
||||||
try handler.perform([
|
|
||||||
textRequest,
|
|
||||||
faceRequest
|
|
||||||
])
|
|
||||||
|
|
||||||
return render(image: image,
|
|
||||||
confidenceLevel: confidenceLevel,
|
|
||||||
fillColor: fillColor,
|
|
||||||
observations: observations)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func render(image: UIImage,
|
|
||||||
confidenceLevel: Float,
|
|
||||||
fillColor: UIColor,
|
|
||||||
observations: [VNDetectedObjectObservation]) -> UIImage {
|
|
||||||
let size = image.size
|
|
||||||
let result = UIGraphicsImageRenderer(size: size).image { rendererContext in
|
|
||||||
// first draw self
|
|
||||||
image.draw(in: CGRect(origin: .zero, size: size))
|
|
||||||
// set fill color
|
|
||||||
fillColor.setFill()
|
|
||||||
for observation in observations {
|
|
||||||
guard observation.confidence >= confidenceLevel else {
|
|
||||||
// ensure observation's confidence level
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if let textObservation = observation as? VNRecognizedTextObservation,
|
|
||||||
let text = textObservation.topCandidates(1).first?.string {
|
|
||||||
if Double(text) != nil || Self.allowedTextItems.contains(text) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let box = observation.boundingBox
|
|
||||||
// boc is normalized (and in starts from the lower left corner)
|
|
||||||
// convert it to a rect in the image
|
|
||||||
let rect = CGRect(x: box.minX * size.width,
|
|
||||||
y: size.height - box.maxY * size.height,
|
|
||||||
width: box.width * size.width,
|
|
||||||
height: box.height * size.height)
|
|
||||||
rendererContext.fill(rect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ class NetworkMonitor {
|
|||||||
init() {
|
init() {
|
||||||
queue = DispatchQueue(label: "io.element.elementx.networkmonitor")
|
queue = DispatchQueue(label: "io.element.elementx.networkmonitor")
|
||||||
pathMonitor = NWPathMonitor()
|
pathMonitor = NWPathMonitor()
|
||||||
reachabilityPublisher = CurrentValueSubject<Bool, Never>(pathMonitor.currentPath.status == .satisfied)
|
reachabilityPublisher = CurrentValueSubject<Bool, Never>(true)
|
||||||
|
|
||||||
pathMonitor.pathUpdateHandler = { [weak self] path in
|
pathMonitor.pathUpdateHandler = { [weak self] path in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -53,23 +53,11 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
|||||||
private func submitBugReport() async {
|
private func submitBugReport() async {
|
||||||
callback?(.submitStarted)
|
callback?(.submitStarted)
|
||||||
do {
|
do {
|
||||||
var files: [URL] = []
|
|
||||||
if let screenshot = state.screenshot {
|
|
||||||
let anonymized = try await ImageAnonymizer.anonymizedImage(from: screenshot)
|
|
||||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("screenshot").appendingPathExtension("png")
|
|
||||||
// remove old screenshot if exists
|
|
||||||
if FileManager.default.fileExists(atPath: tmpUrl.path) {
|
|
||||||
try FileManager.default.removeItem(at: tmpUrl)
|
|
||||||
}
|
|
||||||
try anonymized.dataForPNGRepresentation().write(to: tmpUrl)
|
|
||||||
files.append(tmpUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = try await bugReportService.submitBugReport(text: context.reportText,
|
let result = try await bugReportService.submitBugReport(text: context.reportText,
|
||||||
includeLogs: context.sendingLogsEnabled,
|
includeLogs: context.sendingLogsEnabled,
|
||||||
includeCrashLog: true,
|
includeCrashLog: true,
|
||||||
githubLabels: ServiceLocator.shared.settings.bugReportGHLabels,
|
githubLabels: ServiceLocator.shared.settings.bugReportGHLabels,
|
||||||
files: files)
|
files: [])
|
||||||
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
|
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
|
||||||
callback?(.submitFinished)
|
callback?(.submitFinished)
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct EmojiPickerScreenCoordinatorParameters {
|
|||||||
|
|
||||||
enum EmojiPickerScreenCoordinatorAction {
|
enum EmojiPickerScreenCoordinatorAction {
|
||||||
case emojiSelected(emoji: String, itemId: String)
|
case emojiSelected(emoji: String, itemId: String)
|
||||||
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
|
final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -44,12 +45,13 @@ final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case let .emojiSelected(emoji: emoji):
|
case let .emojiSelected(emoji: emoji):
|
||||||
self.callback?(.emojiSelected(emoji: emoji, itemId: self.parameters.itemId))
|
self.callback?(.emojiSelected(emoji: emoji, itemId: self.parameters.itemId))
|
||||||
|
case .dismiss:
|
||||||
|
self.callback?(.dismiss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(EmojiPickerScreen(context: viewModel.context)
|
AnyView(EmojiPickerScreen(context: viewModel.context))
|
||||||
.presentationDetents([.medium, .large]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import Foundation
|
|||||||
|
|
||||||
enum EmojiPickerScreenViewModelAction {
|
enum EmojiPickerScreenViewModelAction {
|
||||||
case emojiSelected(emoji: String)
|
case emojiSelected(emoji: String)
|
||||||
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EmojiPickerScreenViewState: BindableState {
|
struct EmojiPickerScreenViewState: BindableState {
|
||||||
@@ -27,6 +28,7 @@ struct EmojiPickerScreenViewState: BindableState {
|
|||||||
enum EmojiPickerScreenViewAction {
|
enum EmojiPickerScreenViewAction {
|
||||||
case search(searchString: String)
|
case search(searchString: String)
|
||||||
case emojiTapped(emoji: EmojiPickerEmojiViewData)
|
case emojiTapped(emoji: EmojiPickerEmojiViewData)
|
||||||
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EmojiPickerEmojiCategoryViewData: Identifiable {
|
struct EmojiPickerEmojiCategoryViewData: Identifiable {
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
|
|||||||
state.categories = convert(emojiCategories: categories)
|
state.categories = convert(emojiCategories: categories)
|
||||||
case let .emojiTapped(emoji: emoji):
|
case let .emojiTapped(emoji: emoji):
|
||||||
callback?(.emojiSelected(emoji: emoji.value))
|
callback?(.emojiSelected(emoji: emoji.value))
|
||||||
|
case .dismiss:
|
||||||
|
callback?(.dismiss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,39 +21,49 @@ struct EmojiPickerScreen: View {
|
|||||||
@State var searchString = ""
|
@State var searchString = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
ScrollView {
|
||||||
Text(ElementL10n.reactions)
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 45))], spacing: 3) {
|
||||||
.padding(.top, 20)
|
ForEach(context.viewState.categories) { category in
|
||||||
EmojiPickerSearchFieldView(searchString: $searchString)
|
Section(header: EmojiPickerHeaderView(title: category.name)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 13)
|
||||||
ScrollView {
|
.padding(.top, 10)) {
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 45))], spacing: 3) {
|
ForEach(category.emojis) { emoji in
|
||||||
ForEach(context.viewState.categories) { category in
|
Text(emoji.value)
|
||||||
Section(header: EmojiPickerHeaderView(title: category.name)
|
.frame(width: 45, height: 45)
|
||||||
.padding(.horizontal, 13)
|
.onTapGesture {
|
||||||
.padding(.top, 10)) {
|
context.send(viewAction: .emojiTapped(emoji: emoji))
|
||||||
ForEach(category.emojis) { emoji in
|
}
|
||||||
Text(emoji.value)
|
|
||||||
.frame(width: 45, height: 45)
|
|
||||||
.onTapGesture {
|
|
||||||
context.send(viewAction: .emojiTapped(emoji: emoji))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.navigationTitle(ElementL10n.reactions)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar { toolbar }
|
||||||
|
.searchable(text: $searchString)
|
||||||
.onChange(of: searchString) { _ in
|
.onChange(of: searchString) { _ in
|
||||||
context.send(viewAction: .search(searchString: searchString))
|
context.send(viewAction: .search(searchString: searchString))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ToolbarContentBuilder
|
||||||
|
var toolbar: some ToolbarContent {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button { context.send(viewAction: .dismiss) } label: {
|
||||||
|
Text(ElementL10n.actionCancel)
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier("dismissButton")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct EmojiPickerScreen_Previews: PreviewProvider {
|
struct EmojiPickerScreen_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
EmojiPickerScreen(context: EmojiPickerScreenViewModel(emojiProvider: EmojiProvider()).context)
|
NavigationStack {
|
||||||
|
EmojiPickerScreen(context: EmojiPickerScreenViewModel(emojiProvider: EmojiProvider()).context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct EmojiPickerSearchFieldView: View {
|
|
||||||
@Binding var searchString: String
|
|
||||||
@FocusState private var isSearchFocused: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "magnifyingglass")
|
|
||||||
TextField(ElementL10n.search, text: $searchString)
|
|
||||||
.focused($isSearchFocused)
|
|
||||||
if isSearchFocused {
|
|
||||||
Spacer()
|
|
||||||
Button {
|
|
||||||
searchString = ""
|
|
||||||
isSearchFocused = false
|
|
||||||
} label: {
|
|
||||||
Text(ElementL10n.actionCancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmojiPickerSearchFieldView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
EmojiPickerSearchFieldView(searchString: .constant(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,10 @@ import SwiftUI
|
|||||||
struct SplashScreenCoordinator: CoordinatorProtocol {
|
struct SplashScreenCoordinator: CoordinatorProtocol {
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(
|
AnyView(
|
||||||
Image(asset: Asset.Images.appLogo)
|
ZStack {
|
||||||
|
Image(asset: Asset.Images.appLogo)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
let timelineController = parameters?.timelineController else {
|
let timelineController = parameters?.timelineController else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let emojiPickerNavigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider,
|
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider,
|
||||||
itemId: itemId)
|
itemId: itemId)
|
||||||
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
|
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
|
||||||
@@ -118,10 +121,15 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
Task {
|
Task {
|
||||||
await timelineController.sendReaction(emoji, for: itemId)
|
await timelineController.sendReaction(emoji, for: itemId)
|
||||||
}
|
}
|
||||||
|
case .dismiss:
|
||||||
|
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
navigationStackCoordinator.setSheetCoordinator(coordinator)
|
emojiPickerNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
emojiPickerNavigationStackCoordinator.presentationDetents = [.medium, .large]
|
||||||
|
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(emojiPickerNavigationStackCoordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func displayRoomDetails() {
|
private func displayRoomDetails() {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ enum RoomScreenComposerMode: Equatable {
|
|||||||
enum RoomScreenViewAction {
|
enum RoomScreenViewAction {
|
||||||
case headerTapped
|
case headerTapped
|
||||||
case displayEmojiPicker(itemId: String)
|
case displayEmojiPicker(itemId: String)
|
||||||
case emojiTapped(emoji: String, itemId: String)
|
|
||||||
case paginateBackwards
|
case paginateBackwards
|
||||||
case itemAppeared(id: String)
|
case itemAppeared(id: String)
|
||||||
case itemDisappeared(id: String)
|
case itemDisappeared(id: String)
|
||||||
@@ -43,7 +42,6 @@ enum RoomScreenViewAction {
|
|||||||
case sendReaction(key: String, eventID: String)
|
case sendReaction(key: String, eventID: String)
|
||||||
case cancelReply
|
case cancelReply
|
||||||
case cancelEdit
|
case cancelEdit
|
||||||
case displayReactionsMenuForItemId(itemId: String)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomScreenViewState: BindableState {
|
struct RoomScreenViewState: BindableState {
|
||||||
@@ -54,7 +52,6 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var isBackPaginating = false
|
var isBackPaginating = false
|
||||||
var showLoading = false
|
var showLoading = false
|
||||||
var bindings: RoomScreenViewStateBindings
|
var bindings: RoomScreenViewStateBindings
|
||||||
var displayReactionsMenuForItemId = ""
|
|
||||||
|
|
||||||
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?
|
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?
|
||||||
|
|
||||||
|
|||||||
@@ -102,20 +102,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
MXLog.warning("Link clicked: \(url)")
|
MXLog.warning("Link clicked: \(url)")
|
||||||
case .sendMessage:
|
case .sendMessage:
|
||||||
await sendCurrentMessage()
|
await sendCurrentMessage()
|
||||||
case .sendReaction(let key, _):
|
case .sendReaction(let emoji, let itemId):
|
||||||
#warning("Reaction implementation awaiting SDK support.")
|
await timelineController.sendReaction(emoji, for: itemId)
|
||||||
MXLog.warning("React with \(key) failed. Not implemented.")
|
|
||||||
case .displayEmojiPicker(let itemId):
|
case .displayEmojiPicker(let itemId):
|
||||||
callback?(.displayEmojiPicker(itemId: itemId))
|
callback?(.displayEmojiPicker(itemId: itemId))
|
||||||
case .displayReactionsMenuForItemId(let itemId):
|
|
||||||
state.displayReactionsMenuForItemId = itemId
|
|
||||||
case .cancelReply:
|
case .cancelReply:
|
||||||
state.composerMode = .default
|
state.composerMode = .default
|
||||||
case .cancelEdit:
|
case .cancelEdit:
|
||||||
state.composerMode = .default
|
state.composerMode = .default
|
||||||
case .emojiTapped(let emoji, let itemId):
|
|
||||||
await timelineController.sendReaction(emoji, for: itemId)
|
|
||||||
state.displayReactionsMenuForItemId = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +122,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
private func paginateBackwards() async {
|
private func paginateBackwards() async {
|
||||||
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
|
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
|
||||||
|
case .failure:
|
||||||
|
displayError(.alert(ElementL10n.roomTimelineBackpaginationFailure))
|
||||||
default:
|
default:
|
||||||
#warning("Treat errors")
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +197,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
var actions: [TimelineItemContextMenuAction] = [
|
var actions: [TimelineItemContextMenuAction] = [
|
||||||
.copy, .quote, .copyPermalink, .reply
|
.react, .copy, .quote, .copyPermalink, .reply
|
||||||
]
|
]
|
||||||
|
|
||||||
if item.isEditable {
|
if item.isEditable {
|
||||||
@@ -215,6 +211,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
return .init(actions: actions)
|
return .init(actions: actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
private func processContentMenuAction(_ action: TimelineItemContextMenuAction, itemId: String) {
|
private func processContentMenuAction(_ action: TimelineItemContextMenuAction, itemId: String) {
|
||||||
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemId }),
|
guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemId }),
|
||||||
let item = timelineItem as? EventBasedTimelineItemProtocol else {
|
let item = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
@@ -222,6 +219,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
|
case .react:
|
||||||
|
callback?(.displayEmojiPicker(itemId: item.id))
|
||||||
case .copy:
|
case .copy:
|
||||||
UIPasteboard.general.string = item.text
|
UIPasteboard.general.string = item.text
|
||||||
case .edit:
|
case .edit:
|
||||||
|
|||||||
+18
-22
@@ -27,28 +27,25 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
@ScaledMetric private var senderNameVerticalPadding = 3
|
@ScaledMetric private var senderNameVerticalPadding = 3
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: alignment, spacing: -12) {
|
ZStack(alignment: .trailingFirstTextBaseline) {
|
||||||
if !timelineItem.isOutgoing {
|
VStack(alignment: alignment, spacing: -12) {
|
||||||
header
|
if !timelineItem.isOutgoing {
|
||||||
.zIndex(1)
|
header
|
||||||
}
|
.zIndex(1)
|
||||||
VStack(alignment: alignment) {
|
|
||||||
if timelineItem.isOutgoing {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
styledContentWithReactions
|
|
||||||
if timelineItem.isOutgoing {
|
|
||||||
TimelineDeliveryStatusView(deliveryStatus: timelineItem.properties.deliveryStatus)
|
|
||||||
.padding(.top, 6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.trailing, 16)
|
|
||||||
.padding(.leading, 56)
|
|
||||||
} else {
|
|
||||||
styledContentWithReactions
|
|
||||||
.padding(.leading, 24)
|
|
||||||
.padding(.trailing, 56)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
if timelineItem.isOutgoing {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
styledContentWithReactions
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timelineItem.isOutgoing {
|
||||||
|
TimelineDeliveryStatusView(deliveryStatus: timelineItem.properties.deliveryStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +82,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
alignment: alignment) { key in
|
alignment: alignment) { key in
|
||||||
context.send(viewAction: .sendReaction(key: key, eventID: timelineItem.id))
|
context.send(viewAction: .sendReaction(key: key, eventID: timelineItem.id))
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 12)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,15 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
|||||||
header
|
header
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
content()
|
HStack(alignment: .firstTextBaseline) {
|
||||||
|
content()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if timelineItem.isOutgoing {
|
||||||
|
TimelineDeliveryStatusView(deliveryStatus: timelineItem.properties.deliveryStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
supplementaryViews
|
supplementaryViews
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -43,17 +43,18 @@ struct TimelineDeliveryStatusView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if showDeliveryStatus {
|
Image(systemName: systemImageName)
|
||||||
Image(systemName: systemImageName)
|
.resizable()
|
||||||
.task {
|
.frame(width: 12.0, height: 12.0)
|
||||||
if case .sent = deliveryStatus {
|
.opacity(showDeliveryStatus ? 1.0 : 0.0)
|
||||||
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
.task {
|
||||||
withAnimation {
|
if case .sent = deliveryStatus {
|
||||||
showDeliveryStatus = false
|
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||||||
}
|
withAnimation {
|
||||||
|
showDeliveryStatus = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-56
@@ -1,56 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct TimelineItemReactionsMenuView: View {
|
|
||||||
private let emojis = ["👍🏼", "👎🏼", "😄", "🙏🏼", "😇"]
|
|
||||||
|
|
||||||
var onEmojiSelected: ((String) -> Void)?
|
|
||||||
var onDisplayEmojiPicker: (() -> Void)?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
HStack(spacing: 10) {
|
|
||||||
ForEach(emojis, id: \.self) { emoji in
|
|
||||||
Button {
|
|
||||||
onEmojiSelected?(emoji)
|
|
||||||
} label: {
|
|
||||||
Text(emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(10)
|
|
||||||
.background(.gray)
|
|
||||||
.cornerRadius(15)
|
|
||||||
HStack(spacing: 10) {
|
|
||||||
Text("➕")
|
|
||||||
}
|
|
||||||
.padding(10)
|
|
||||||
.background(.gray)
|
|
||||||
.cornerRadius(15)
|
|
||||||
.onTapGesture {
|
|
||||||
onDisplayEmojiPicker?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimelineItemReactionsMenuView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
TimelineItemReactionsMenuView(onDisplayEmojiPicker: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,8 @@ struct FormattedBodyText: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tint(.element.accent)
|
// Should be .element.accent but that's currently black
|
||||||
|
.tint(.blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var blockquoteAttributes: AttributeContainer {
|
private var blockquoteAttributes: AttributeContainer {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct TimelineItemContextMenuActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum TimelineItemContextMenuAction: Identifiable, Hashable {
|
enum TimelineItemContextMenuAction: Identifiable, Hashable {
|
||||||
|
case react
|
||||||
case copy
|
case copy
|
||||||
case edit
|
case edit
|
||||||
case quote
|
case quote
|
||||||
@@ -59,6 +60,10 @@ public struct TimelineItemContextMenu: View {
|
|||||||
private func viewsForActions(_ actions: [TimelineItemContextMenuAction]) -> some View {
|
private func viewsForActions(_ actions: [TimelineItemContextMenuAction]) -> some View {
|
||||||
ForEach(actions, id: \.self) { item in
|
ForEach(actions, id: \.self) { item in
|
||||||
switch item {
|
switch item {
|
||||||
|
case .react:
|
||||||
|
Button { callback(item) } label: {
|
||||||
|
Label(ElementL10n.reactions, systemImage: "face.smiling")
|
||||||
|
}
|
||||||
case .copy:
|
case .copy:
|
||||||
Button { callback(item) } label: {
|
Button { callback(item) } label: {
|
||||||
Label(ElementL10n.actionCopy, systemImage: "doc.on.doc")
|
Label(ElementL10n.actionCopy, systemImage: "doc.on.doc")
|
||||||
|
|||||||
@@ -69,13 +69,7 @@ class TimelineTableViewController: UIViewController {
|
|||||||
applySnapshot()
|
applySnapshot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayReactionsMenuForItemId = "" {
|
|
||||||
didSet {
|
|
||||||
tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?
|
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?
|
||||||
|
|
||||||
@Binding private var scrollToBottomButtonVisible: Bool
|
@Binding private var scrollToBottomButtonVisible: Bool
|
||||||
@@ -193,7 +187,6 @@ class TimelineTableViewController: UIViewController {
|
|||||||
// A local reference to avoid capturing self in the cell configuration.
|
// A local reference to avoid capturing self in the cell configuration.
|
||||||
let coordinator = self.coordinator
|
let coordinator = self.coordinator
|
||||||
let opacity = self.opacity(for: timelineItem)
|
let opacity = self.opacity(for: timelineItem)
|
||||||
let displayReactionsMenuForItemId = self.displayReactionsMenuForItemId
|
|
||||||
let contextMenuBuilder = self.contextMenuBuilder
|
let contextMenuBuilder = self.contextMenuBuilder
|
||||||
|
|
||||||
cell.item = timelineItem
|
cell.item = timelineItem
|
||||||
@@ -201,38 +194,28 @@ class TimelineTableViewController: UIViewController {
|
|||||||
if case .backPaginationIndicator = timelineItem {
|
if case .backPaginationIndicator = timelineItem {
|
||||||
timelineItem
|
timelineItem
|
||||||
} else {
|
} else {
|
||||||
VStack {
|
timelineItem
|
||||||
if displayReactionsMenuForItemId == timelineItem.id {
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
TimelineItemReactionsMenuView { emoji in
|
.opacity(opacity)
|
||||||
coordinator.send(viewAction: .emojiTapped(emoji: emoji, itemId: timelineItem.id))
|
.contextMenu {
|
||||||
} onDisplayEmojiPicker: {
|
contextMenuBuilder?(timelineItem.id)
|
||||||
coordinator.send(viewAction: .displayEmojiPicker(itemId: timelineItem.id))
|
}
|
||||||
}
|
.onAppear {
|
||||||
|
coordinator.send(viewAction: .itemAppeared(id: timelineItem.id))
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
coordinator.send(viewAction: .itemDisappeared(id: timelineItem.id))
|
||||||
|
}
|
||||||
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
coordinator.send(viewAction: .linkClicked(url: url))
|
||||||
|
return .systemAction
|
||||||
|
})
|
||||||
|
.onTapGesture(count: 2) {
|
||||||
|
coordinator.send(viewAction: .displayEmojiPicker(itemId: timelineItem.id))
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
coordinator.send(viewAction: .itemTapped(id: timelineItem.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineItem
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.opacity(opacity)
|
|
||||||
.contextMenu {
|
|
||||||
contextMenuBuilder?(timelineItem.id)
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
coordinator.send(viewAction: .itemAppeared(id: timelineItem.id))
|
|
||||||
}
|
|
||||||
.onDisappear {
|
|
||||||
coordinator.send(viewAction: .itemDisappeared(id: timelineItem.id))
|
|
||||||
}
|
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
|
||||||
coordinator.send(viewAction: .linkClicked(url: url))
|
|
||||||
return .systemAction
|
|
||||||
})
|
|
||||||
.onTapGesture(count: 2) {
|
|
||||||
coordinator.send(viewAction: .displayReactionsMenuForItemId(itemId: timelineItem.id))
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
coordinator.send(viewAction: .itemTapped(id: timelineItem.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.margins(.all, self.timelineStyle.rowInsets)
|
.margins(.all, self.timelineStyle.rowInsets)
|
||||||
@@ -375,15 +358,9 @@ class TimelineTableViewController: UIViewController {
|
|||||||
|
|
||||||
extension TimelineTableViewController: UITableViewDelegate {
|
extension TimelineTableViewController: UITableViewDelegate {
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
let isAtBottom = isAtBottom()
|
|
||||||
|
|
||||||
// Dispatches fix runtime warnings about making changes during a view update.
|
// Dispatches fix runtime warnings about making changes during a view update.
|
||||||
if !scrollToBottomButtonVisible, isAtBottom {
|
DispatchQueue.main.async { self.scrollToBottomButtonVisible = self.isAtBottom() }
|
||||||
DispatchQueue.main.async { self.scrollToBottomButtonVisible = true }
|
|
||||||
} else if scrollToBottomButtonVisible, !isAtBottom {
|
|
||||||
DispatchQueue.main.async { self.scrollToBottomButtonVisible = false }
|
|
||||||
}
|
|
||||||
|
|
||||||
paginateBackwardsPublisher.send(())
|
paginateBackwardsPublisher.send(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,6 @@ struct TimelineView: UIViewControllerRepresentable {
|
|||||||
if tableViewController.composerMode != context.viewState.composerMode {
|
if tableViewController.composerMode != context.viewState.composerMode {
|
||||||
tableViewController.composerMode = context.viewState.composerMode
|
tableViewController.composerMode = context.viewState.composerMode
|
||||||
}
|
}
|
||||||
if tableViewController.displayReactionsMenuForItemId != context.viewState.displayReactionsMenuForItemId {
|
|
||||||
tableViewController.displayReactionsMenuForItemId = context.viewState.displayReactionsMenuForItemId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doesn't have an equatable conformance :(
|
// Doesn't have an equatable conformance :(
|
||||||
tableViewController.contextMenuBuilder = context.viewState.contextMenuBuilder
|
tableViewController.contextMenuBuilder = context.viewState.contextMenuBuilder
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
|
|||||||
|
|
||||||
func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
func login(username: String, password: String, initialDeviceName: String?, deviceId: String?) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
|
||||||
do {
|
do {
|
||||||
Benchmark.startTrackingForIdentifier("Login", message: "Started new login")
|
|
||||||
|
|
||||||
let client = try await Task.dispatch(on: .global()) {
|
let client = try await Task.dispatch(on: .global()) {
|
||||||
try self.authenticationService.login(username: username,
|
try self.authenticationService.login(username: username,
|
||||||
password: password,
|
password: password,
|
||||||
@@ -104,11 +102,8 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
|
|||||||
deviceId: deviceId)
|
deviceId: deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Benchmark.endTrackingForIdentifier("Login", message: "Finished login")
|
|
||||||
return await userSession(for: client)
|
return await userSession(for: client)
|
||||||
} catch {
|
} catch {
|
||||||
Benchmark.endTrackingForIdentifier("Login", message: "Login failed")
|
|
||||||
|
|
||||||
MXLog.error("Failed logging in with error: \(error)")
|
MXLog.error("Failed logging in with error: \(error)")
|
||||||
guard let error = error as? AuthenticationError else { return .failure(.failedLoggingIn) }
|
guard let error = error as? AuthenticationError else { return .failure(.failedLoggingIn) }
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
|
|
||||||
private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.serial")
|
private let serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.serial")
|
||||||
|
|
||||||
private var sendMessageBgTask: BackgroundTaskProtocol?
|
private var sendMessageBackgroundTask: BackgroundTaskProtocol?
|
||||||
|
|
||||||
private var memberAvatars = [String: String]()
|
private var memberAvatars = [String: String]()
|
||||||
private var memberDisplayNames = [String: String]()
|
private var memberDisplayNames = [String: String]()
|
||||||
@@ -151,10 +151,7 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
let outcome: PaginationOutcome = try await Task.dispatch(on: .global()) {
|
let outcome: PaginationOutcome = try await Task.dispatch(on: .global()) {
|
||||||
Benchmark.startTrackingForIdentifier("BackPagination \(id)", message: "Backpaginating \(count) message(s) in room \(id)")
|
try self.room.paginateBackwards(limit: UInt16(count))
|
||||||
let outcome = try self.room.paginateBackwards(limit: UInt16(count))
|
|
||||||
Benchmark.endTrackingForIdentifier("BackPagination \(id)", message: "Finished backpaginating \(count) message(s) in room \(id)")
|
|
||||||
return outcome
|
|
||||||
}
|
}
|
||||||
update(backPaginationOutcome: outcome)
|
update(backPaginationOutcome: outcome)
|
||||||
return .success(())
|
return .success(())
|
||||||
@@ -164,9 +161,9 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result<Void, RoomProxyError> {
|
func sendMessage(_ message: String, inReplyToEventId: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||||
sendMessageBgTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||||
defer {
|
defer {
|
||||||
sendMessageBgTask?.stop()
|
sendMessageBackgroundTask?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
let transactionId = genTransactionId()
|
let transactionId = genTransactionId()
|
||||||
@@ -187,15 +184,25 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
|
func sendReaction(_ reaction: String, for eventId: String) async -> Result<Void, RoomProxyError> {
|
||||||
await Task.dispatch(on: .global()) {
|
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||||
.success(())
|
defer {
|
||||||
|
sendMessageBackgroundTask?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Task.dispatch(on: .global()) {
|
||||||
|
do {
|
||||||
|
try self.room.sendReaction(eventId: eventId, key: reaction)
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
return .failure(.failedSendingReaction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
|
func editMessage(_ newMessage: String, originalEventId: String) async -> Result<Void, RoomProxyError> {
|
||||||
sendMessageBgTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: "SendMessage", isReusable: true)
|
||||||
defer {
|
defer {
|
||||||
sendMessageBgTask?.stop()
|
sendMessageBackgroundTask?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
let transactionId = genTransactionId()
|
let transactionId = genTransactionId()
|
||||||
@@ -205,7 +212,7 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
try self.room.edit(newMsg: newMessage, originalEventId: originalEventId, txnId: transactionId)
|
try self.room.edit(newMsg: newMessage, originalEventId: originalEventId, txnId: transactionId)
|
||||||
return .success(())
|
return .success(())
|
||||||
} catch {
|
} catch {
|
||||||
return .failure(.failedSendingMessage)
|
return .failure(.failedEditingMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ enum RoomProxyError: Error {
|
|||||||
case failedRetrievingMemberAvatarURL
|
case failedRetrievingMemberAvatarURL
|
||||||
case failedRetrievingMemberDisplayName
|
case failedRetrievingMemberDisplayName
|
||||||
case failedSendingMessage
|
case failedSendingMessage
|
||||||
|
case failedSendingReaction
|
||||||
|
case failedEditingMessage
|
||||||
case failedRedactingEvent
|
case failedRedactingEvent
|
||||||
case failedAddingTimelineListener
|
case failedAddingTimelineListener
|
||||||
case failedRetrievingMembers
|
case failedRetrievingMembers
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
return buildEmptyRoomSummary()
|
return buildEmptyRoomSummary()
|
||||||
case .filled(let roomId):
|
case .filled(let roomId):
|
||||||
return buildRoomSummaryForIdentifier(roomId)
|
return buildRoomSummaryForIdentifier(roomId)
|
||||||
case .invalidated:
|
case .invalidated(let roomId):
|
||||||
return buildEmptyRoomSummary()
|
return buildRoomSummaryForIdentifier(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ enum RoomTimelineItemFixtures {
|
|||||||
senderId: "",
|
senderId: "",
|
||||||
senderDisplayName: "Helena"),
|
senderDisplayName: "Helena"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString,
|
TextRoomTimelineItem(id: UUID().uuidString,
|
||||||
text: "And John's speech was amazing!",
|
text: "And John's speech was amazing!And John's speech was amazing!And John's speech was amazing!",
|
||||||
timestamp: "5 PM",
|
timestamp: "5 PM",
|
||||||
inGroupState: .beginning,
|
inGroupState: .beginning,
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
|
||||||
Benchmark.startTrackingForIdentifier("Login", message: "Started restoring previous login")
|
|
||||||
|
|
||||||
let builder = ClientBuilder()
|
let builder = ClientBuilder()
|
||||||
.basePath(path: baseDirectory.path)
|
.basePath(path: baseDirectory.path)
|
||||||
.username(username: credentials.userID)
|
.username(username: credentials.userID)
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
@testable import ElementX
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
enum ImageAnonymizerTestsError: String, Error {
|
|
||||||
case screenshotNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImageAnonymizerTests: XCTestCase {
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleScreenshot() throws -> UIImage {
|
|
||||||
let bundle = Bundle(for: classForCoder)
|
|
||||||
guard let path = bundle.path(forResource: "sample_screenshot", ofType: "png"),
|
|
||||||
let image = UIImage(contentsOfFile: path) else {
|
|
||||||
throw ImageAnonymizerTestsError.screenshotNotFound
|
|
||||||
}
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
|
|
||||||
func testImageAnonymizationConfidenceLevel() async throws {
|
|
||||||
let image = try sampleScreenshot()
|
|
||||||
|
|
||||||
let anonymized5 = try await ImageAnonymizer.anonymizedImage(from: image)
|
|
||||||
let anonymized1 = try await ImageAnonymizer.anonymizedImage(from: image, confidenceLevel: 0.1)
|
|
||||||
|
|
||||||
// comparing colors is a complicated process, just compare images for now
|
|
||||||
XCTAssertNotEqual(image, anonymized5)
|
|
||||||
XCTAssertNotEqual(anonymized1, anonymized5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testImageAnonymizationFillColor() async throws {
|
|
||||||
let image = try sampleScreenshot()
|
|
||||||
|
|
||||||
let anonymizedRed = try await ImageAnonymizer.anonymizedImage(from: image)
|
|
||||||
let anonymizedBlue = try await ImageAnonymizer.anonymizedImage(from: image, fillColor: .blue)
|
|
||||||
|
|
||||||
// comparing colors is a complicated process, just compare images for now
|
|
||||||
XCTAssertNotEqual(image, anonymizedRed)
|
|
||||||
XCTAssertNotEqual(anonymizedBlue, anonymizedRed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
* moved the message delivery status outside of the main content and added it to the plain timeline as well
|
||||||
|
* fixed glithcy scroll to bottom timeline button
|
||||||
|
* simplified the emoji picker, double tapping a timeline item directly opens it now and added a context menu option. Linked it to rust side reaction sending
|
||||||
|
* fixed cold cache seemingly not working (invalid rooms treated as empty)
|
||||||
|
* made splash screen full screen
|
||||||
|
* fixed connectivity indicator starting off as offline
|
||||||
|
* added presentation detents on the NavigationStackCoordinator as they're not inherited from the child
|
||||||
|
* fixed timeline item link tint colors
|
||||||
|
* removed some unnecessary classes
|
||||||
Reference in New Issue
Block a user