Updated the colours to source from direct theme values (instead of individual)

Removed an unused notification
Refactored the PrivacySettingsViewController
Refactored the ScreenLock code to Swift
Fixed an issue where the match dark/light setting wasn't getting applied on launch
Update the modal styling for the various settings modals
This commit is contained in:
Morgan Pretty 2022-08-24 17:33:10 +10:00
parent 20d63d106c
commit 823006a892
85 changed files with 2634 additions and 2206 deletions

View File

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
1FFD68A448D5A1439F2F02FD /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; };
3289CA2E9E89DA9D4D52A90C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF4561630A52BE96F164CF6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; };
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */; };
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */; };
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */; };
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; };
@ -38,7 +37,6 @@
34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */ = {isa = PBXBuildFile; fileRef = 34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */; };
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */; };
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */; };
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; };
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; };
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
@ -113,7 +111,6 @@
7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */; };
7B0EFDF62755CC5400FFAAE7 /* CallMissedTipsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */; };
7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */; };
7B13E1EB2811138200BD4F64 /* PrivacySettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */; };
7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; };
7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; };
7B1581E827210ECC00848B49 /* RenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E727210ECC00848B49 /* RenderView.swift */; };
@ -153,7 +150,6 @@
7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; };
7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; };
7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */ = {isa = PBXBuildFile; fileRef = 7BFD1A962747689000FB91B9 /* Session-Turn-Server */; };
7BFFB33C27D02F5800BEA04E /* CallPermissionRequestModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */; };
821EFD1644285AC2D3733D27 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C58C3ADF46C718488458C2 /* Pods_GlobalDependencies_SessionUIKit.framework */; };
92EB2776D36B22D2E0552A05 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; };
98547545DAF8E7916DF9F0BF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F84A214B9A1C0CCF6DB09C8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; };
@ -315,7 +311,6 @@
C331FF982558FA6B00070591 /* AppMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9689023FA1401005F64E0 /* AppMode.swift */; };
C331FF992558FA6B00070591 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; };
C331FF9A2558FA6B00070591 /* Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A1238F356100BA5194 /* Values.swift */; };
C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A8238F62FB00BA5194 /* Gradients.swift */; };
C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8544E3023D16CA500299F14 /* DeviceUtilities.swift */; };
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; };
C331FFE02558FB0000070591 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B02390C37000BA5194 /* SearchBar.swift */; };
@ -401,7 +396,7 @@
C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; };
C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; };
C38EF2B4255B6D9C007E1867 /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */; };
C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */; };
C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; };
C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; };
C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */; };
C38EF324255B6DBF007E1867 /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FA255B6DBD007E1867 /* Bench.swift */; };
@ -416,9 +411,7 @@
C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF344255B6DC5007E1867 /* OWSViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */; };
C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF34B255B6DC8007E1867 /* OWSTableViewController.m */; };
C38EF366255B6DCC007E1867 /* ScreenLockViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF34D255B6DC8007E1867 /* OWSTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C38EF36B255B6DCC007E1867 /* ScreenLockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */; };
C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF355255B6DCB007E1867 /* OWSViewController.m */; };
C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF356255B6DCB007E1867 /* OWSNavigationController.m */; };
C38EF372255B6DCC007E1867 /* MediaMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF358255B6DCC007E1867 /* MediaMessageView.swift */; };
@ -640,7 +633,7 @@
FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; };
FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; };
FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; };
FD37E9C828A1D73F003AE748 /* Theme+PrimaryColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */; };
FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; };
FD37E9CA28A1E4BD003AE748 /* Theme+ClassicLight.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */; };
FD37E9CC28A1E578003AE748 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */; };
FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CE28A1EB1B003AE748 /* Theme.swift */; };
@ -677,6 +670,11 @@
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; };
FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; };
FD52090328B4680F006098F6 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090228B4680F006098F6 /* RadioButton.swift */; };
FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; };
FD52090728B49738006098F6 /* ConfirmationModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090628B49738006098F6 /* ConfirmationModal.swift */; };
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; };
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; };
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
@ -1026,9 +1024,7 @@
2581AFACDDDC1404866D7B8C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
29CF8C79F41BF00B1C2E59A0 /* Pods-SessionUIKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.app store release.xcconfig"; path = "Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.app store release.xcconfig"; sourceTree = "<group>"; };
340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivacySettingsTableViewController.m; sourceTree = "<group>"; };
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = "<group>"; };
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = "<group>"; };
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = "<group>"; };
340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = "<group>"; };
340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSConversationSettingsViewController.m; sourceTree = "<group>"; };
@ -1063,8 +1059,6 @@
34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = end_call_tone_cept.caf; path = Session/Meta/AudioFiles/end_call_tone_cept.caf; sourceTree = SOURCE_ROOT; };
34D1F04F1F7D45A60066283D /* GifPickerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerCell.swift; sourceTree = "<group>"; };
34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyDownloader.swift; sourceTree = "<group>"; };
34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScreenLockUI.h; sourceTree = "<group>"; };
34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSScreenLockUI.m; sourceTree = "<group>"; };
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
34D99CE3217509C1000AFB39 /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = "<group>"; };
@ -1156,7 +1150,6 @@
7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringing.mp3; sourceTree = "<group>"; };
7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMissedTipsModal.swift; sourceTree = "<group>"; };
7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+Action.swift"; sourceTree = "<group>"; };
7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsTableViewController.swift; sourceTree = "<group>"; };
7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = "<group>"; };
7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = "<group>"; };
7B1581E727210ECC00848B49 /* RenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderView.swift; sourceTree = "<group>"; };
@ -1199,7 +1192,6 @@
7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = "<group>"; };
7BFD1A962747689000FB91B9 /* Session-Turn-Server */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Session-Turn-Server"; sourceTree = "<group>"; };
7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallPermissionRequestModal.swift; sourceTree = "<group>"; };
82099864FD91C9126A750313 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
8E029A324780A800DE6B70B3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; sourceTree = "<group>"; };
96ED0C9B69379BE6FF4E9DA6 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
@ -1292,7 +1284,6 @@
B8BB829F238F322400BA5194 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
B8BB82A1238F356100BA5194 /* Values.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Values.swift; sourceTree = "<group>"; };
B8BB82A4238F627000BA5194 /* HomeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeVC.swift; sourceTree = "<group>"; };
B8BB82A8238F62FB00BA5194 /* Gradients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gradients.swift; sourceTree = "<group>"; };
B8BB82AA238F669C00BA5194 /* FullConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullConversationCell.swift; sourceTree = "<group>"; };
B8BB82B02390C37000BA5194 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
B8BB82B423947F2D00BA5194 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
@ -1469,7 +1460,7 @@
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = "SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift"; sourceTree = SOURCE_ROOT; };
C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIViewController+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift"; sourceTree = SOURCE_ROOT; };
C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIView+Utilities.swift"; sourceTree = SOURCE_ROOT; };
C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift"; sourceTree = SOURCE_ROOT; };
C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/ScreenLock.swift"; sourceTree = SOURCE_ROOT; };
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; };
C38EF2EF255B6DBB007E1867 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Weak.swift; path = SessionUtilitiesKit/General/Weak.swift; sourceTree = SOURCE_ROOT; };
C38EF2F2255B6DBC007E1867 /* Searcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Searcher.swift; path = SignalUtilitiesKit/Utilities/Searcher.swift; sourceTree = SOURCE_ROOT; };
@ -1491,9 +1482,7 @@
C38EF344255B6DC5007E1867 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSViewController.h; path = "SignalUtilitiesKit/Shared View Controllers/OWSViewController.h"; sourceTree = SOURCE_ROOT; };
C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ModalActivityIndicatorViewController.swift; path = "SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift"; sourceTree = SOURCE_ROOT; };
C38EF34B255B6DC8007E1867 /* OWSTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSTableViewController.m; path = "SignalUtilitiesKit/Shared View Controllers/OWSTableViewController.m"; sourceTree = SOURCE_ROOT; };
C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScreenLockViewController.h; path = "SignalUtilitiesKit/Screen Lock/ScreenLockViewController.h"; sourceTree = SOURCE_ROOT; };
C38EF34D255B6DC8007E1867 /* OWSTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSTableViewController.h; path = "SignalUtilitiesKit/Shared View Controllers/OWSTableViewController.h"; sourceTree = SOURCE_ROOT; };
C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ScreenLockViewController.m; path = "SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m"; sourceTree = SOURCE_ROOT; };
C38EF355255B6DCB007E1867 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSViewController.m; path = "SignalUtilitiesKit/Shared View Controllers/OWSViewController.m"; sourceTree = SOURCE_ROOT; };
C38EF356255B6DCB007E1867 /* OWSNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSNavigationController.m; path = "SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.m"; sourceTree = SOURCE_ROOT; };
C38EF358255B6DCC007E1867 /* MediaMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaMessageView.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift"; sourceTree = SOURCE_ROOT; };
@ -1706,7 +1695,7 @@
FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = "<group>"; };
FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+PrimaryColors.swift"; sourceTree = "<group>"; };
FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = "<group>"; };
FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicLight.swift"; sourceTree = "<group>"; };
FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceViewController.swift; sourceTree = "<group>"; };
FD37E9CE28A1EB1B003AE748 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
@ -1741,6 +1730,11 @@
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = "<group>"; };
FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = "<group>"; };
FD52090228B4680F006098F6 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewModel.swift; sourceTree = "<group>"; };
FD52090628B49738006098F6 /* ConfirmationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationModal.swift; sourceTree = "<group>"; };
FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = "<group>"; };
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
@ -2297,7 +2291,6 @@
B848A4C4269EAAA200617031 /* UserDetailsSheet.swift */,
FD4B200D283492210034334B /* InsetLockableTableView.swift */,
7B1581E3271FC59C00848B49 /* CallModal.swift */,
7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */,
);
path = "Views & Modals";
sourceTree = "<group>";
@ -2504,8 +2497,7 @@
B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */,
C31D1DDC25217014005D4DA8 /* UserCell.swift */,
C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */,
34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */,
34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */,
FD52090828B59411006098F6 /* ScreenLockUI.swift */,
);
path = Shared;
sourceTree = "<group>";
@ -2729,7 +2721,6 @@
B8BB829F238F322400BA5194 /* Colors.swift */,
C39DD28724F3318C008590FC /* Colors.xcassets */,
B8BB82BD2394D4CE00BA5194 /* Fonts.swift */,
B8BB82A8238F62FB00BA5194 /* Gradients.swift */,
B8BB82A1238F356100BA5194 /* Values.swift */,
);
path = "Style Guide";
@ -2751,6 +2742,7 @@
isa = PBXGroup;
children = (
B8B5BCEB2394D869003823C9 /* OutlineButton.swift */,
FD52090228B4680F006098F6 /* RadioButton.swift */,
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
B8BB82B82394911B00BA5194 /* Separator.swift */,
B8CCF638239721E20091D419 /* TabBar.swift */,
@ -2828,13 +2820,11 @@
isa = PBXGroup;
children = (
FD37E9CD28A1E682003AE748 /* Views */,
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */,
340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */,
7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */,
B8CCF6422397711F0091D419 /* SettingsVC.swift */,
B886B4A62398B23E00211ABE /* QRCodeVC.swift */,
FD37EA0828AA2D27003AE748 /* SettingsTableViewModel.swift */,
FD37EA0628AA2CCA003AE748 /* SettingsTableViewController.swift */,
FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */,
FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */,
FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */,
FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */,
@ -2888,6 +2878,7 @@
children = (
B86BD08323399ACF000F5AE3 /* Modal.swift */,
C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */,
FD52090628B49738006098F6 /* ConfirmationModal.swift */,
);
path = "Sheets & Modals";
sourceTree = "<group>";
@ -2955,9 +2946,8 @@
C36096EE25AD21BC008B62B2 /* Screen Lock */ = {
isa = PBXGroup;
children = (
C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */,
C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */,
C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */,
C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */,
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */,
);
path = "Screen Lock";
sourceTree = "<group>";
@ -3626,7 +3616,7 @@
isa = PBXGroup;
children = (
FD37E9CE28A1EB1B003AE748 /* Theme.swift */,
FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */,
FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */,
FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */,
FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */,
FD37E9D228A1FCDB003AE748 /* Theme+OceanDark.swift */,
@ -4030,7 +4020,6 @@
C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */,
C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */,
C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */,
C38EF366255B6DCC007E1867 /* ScreenLockViewController.h in Headers */,
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */,
C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */,
@ -4910,13 +4899,12 @@
buildActionMask = 2147483647;
files = (
C331FF972558FA6B00070591 /* Fonts.swift in Sources */,
C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */,
FD37E9D328A1FCDB003AE748 /* Theme+OceanDark.swift in Sources */,
C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */,
C331FFE72558FB0000070591 /* TextField.swift in Sources */,
C331FFE32558FB0000070591 /* TabBar.swift in Sources */,
FD37E9D528A1FCE8003AE748 /* Theme+OceanLight.swift in Sources */,
FD37E9C828A1D73F003AE748 /* Theme+PrimaryColors.swift in Sources */,
FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */,
FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */,
FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */,
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */,
@ -4924,6 +4912,7 @@
FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */,
C331FFE02558FB0000070591 /* SearchBar.swift in Sources */,
C331FF982558FA6B00070591 /* AppMode.swift in Sources */,
FD52090328B4680F006098F6 /* RadioButton.swift in Sources */,
C331FFE82558FB0000070591 /* TextView.swift in Sources */,
FD37E9D728A20B5D003AE748 /* UIColor+Utilities.swift in Sources */,
FD37E9F928A5F14A003AE748 /* _001_ThemePreferences.swift in Sources */,
@ -4961,7 +4950,7 @@
C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */,
C38EF3FB255B6DF7007E1867 /* UIAlertController+OWS.swift in Sources */,
C33FDC53255A582000E217F9 /* OutageDetection.swift in Sources */,
C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */,
C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */,
C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */,
C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */,
C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */,
@ -5003,11 +4992,11 @@
C38EF22B255B6D5D007E1867 /* ShareViewDelegate.swift in Sources */,
C38EF3BF255B6DE7007E1867 /* ImageEditorView.swift in Sources */,
C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */,
C38EF36B255B6DCC007E1867 /* ScreenLockViewController.m in Sources */,
C38EF40C255B6DF7007E1867 /* GradientView.swift in Sources */,
C38EF3FA255B6DF7007E1867 /* DirectionalPanGestureRecognizer.swift in Sources */,
C38EF3BB255B6DE7007E1867 /* ImageEditorStrokeItem.swift in Sources */,
C38EF3C0255B6DE7007E1867 /* ImageEditorCropViewController.swift in Sources */,
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */,
C38EF401255B6DF7007E1867 /* VideoPlayerView.swift in Sources */,
B8856D60256F129B001CE70E /* OWSAlerts.swift in Sources */,
C38EF3FE255B6DF7007E1867 /* OWSTextField.m in Sources */,
@ -5350,14 +5339,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */,
FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */,
B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */,
B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */,
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */,
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */,
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */,
7B13E1EB2811138200BD4F64 /* PrivacySettingsTableViewController.swift in Sources */,
C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */,
FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */,
FD37EA0928AA2D27003AE748 /* SettingsTableViewModel.swift in Sources */,
7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */,
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */,
@ -5422,6 +5412,7 @@
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */,
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */,
FD52090728B49738006098F6 /* ConfirmationModal.swift in Sources */,
B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */,
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */,
B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */,
@ -5446,7 +5437,6 @@
B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */,
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
C331FFF32558FF0300070591 /* PathStatusView.swift in Sources */,
7BFFB33C27D02F5800BEA04E /* CallPermissionRequestModal.swift in Sources */,
B848A4C5269EAAA200617031 /* UserDetailsSheet.swift in Sources */,
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */,
@ -5478,7 +5468,6 @@
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */,
45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */,
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */,
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */,
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
@ -5508,7 +5497,6 @@
C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */,
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
FD37E9D128A1F2EB003AE748 /* ThemeSelectionView.swift in Sources */,
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */,
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */,
7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */,

View File

@ -55,12 +55,29 @@ extension ConversationVC:
@objc func startCall(_ sender: Any?) {
guard SessionCall.isEnabled else { return }
guard Storage.shared[.areCallsEnabled] else {
let callPermissionRequestModal = CallPermissionRequestModal()
self.navigationController?.present(callPermissionRequestModal, animated: true, completion: nil)
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_call_permission_request_title".localized(),
explanation: "modal_call_permission_request_explanation".localized(),
confirmTitle: "vc_settings_title".localized()
)
) { [weak self] _ in
self?.dismiss(animated: true) {
let navController: OWSNavigationController = OWSNavigationController(
rootViewController: SettingsTableViewController(
viewModel: PrivacySettingsViewModel(),
shouldShowCloseButton: true
)
)
navController.modalPresentationStyle = .fullScreen
self?.present(navController, animated: true, completion: nil)
}
}
self.navigationController?.present(confirmationModal, animated: true, completion: nil)
return
}
requestMicrophonePermissionIfNeeded { }
requestMicrophonePermissionIfNeeded {}
let threadId: String = self.viewModel.threadData.threadId

View File

@ -10,7 +10,6 @@ import SignalUtilitiesKit
final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate {
private static let loadingHeaderHeight: CGFloat = 20
private static let messageRequestButtonHeight: CGFloat = 34
internal let viewModel: ConversationViewModel
private var dataChangeObservable: DatabaseCancellable?
@ -209,11 +208,11 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
lazy var messageRequestView: UIView = {
let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.themeBackgroundColor = .backgroundPrimary
result.isHidden = (
self.viewModel.threadData.threadIsMessageRequest == false ||
self.viewModel.threadData.threadRequiresApproval == true
)
result.setGradient(Gradients.defaultBackground)
return result
}()
@ -222,8 +221,8 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
let result: UILabel = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.font = UIFont.systemFont(ofSize: 12)
result.text = NSLocalizedString("MESSAGE_REQUESTS_INFO", comment: "")
result.textColor = Colors.sessionMessageRequestsInfoText
result.text = "MESSAGE_REQUESTS_INFO".localized()
result.themeTextColor = .textSecondary
result.textAlignment = .center
result.numberOfLines = 2
@ -231,50 +230,18 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
}()
private lazy var messageRequestAcceptButton: UIButton = {
let result: UIButton = UIButton()
let result: OutlineButton = OutlineButton(style: .regular, size: .medium)
result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true
result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
result.setTitle(NSLocalizedString("TXT_DELETE_ACCEPT", comment: ""), for: .normal)
result.setTitleColor(Colors.sessionHeading, for: .normal)
result.setBackgroundImage(
Colors.sessionHeading
.withAlphaComponent(isDarkMode ? 0.2 : 0.06)
.toImage(isDarkMode: isDarkMode),
for: .highlighted
)
result.layer.cornerRadius = (ConversationVC.messageRequestButtonHeight / 2)
result.layer.borderColor = Colors.sessionHeading
.resolvedColor(
// Note: This is needed for '.cgColor' to support dark mode
with: UITraitCollection(userInterfaceStyle: isDarkMode ? .dark : .light)
).cgColor
result.layer.borderWidth = 1
result.setTitle("TXT_DELETE_ACCEPT".localized(), for: .normal)
result.addTarget(self, action: #selector(acceptMessageRequest), for: .touchUpInside)
return result
}()
private lazy var messageRequestDeleteButton: UIButton = {
let result: UIButton = UIButton()
let result: OutlineButton = OutlineButton(style: .destructive, size: .medium)
result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true
result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
result.setTitle(NSLocalizedString("TXT_DELETE_TITLE", comment: ""), for: .normal)
result.setTitleColor(Colors.destructive, for: .normal)
result.setBackgroundImage(
Colors.destructive
.withAlphaComponent(isDarkMode ? 0.2 : 0.06)
.toImage(isDarkMode: isDarkMode),
for: .highlighted
)
result.layer.cornerRadius = (ConversationVC.messageRequestButtonHeight / 2)
result.layer.borderColor = Colors.destructive
.resolvedColor(
// Note: This is needed for '.cgColor' to support dark mode
with: UITraitCollection(userInterfaceStyle: isDarkMode ? .dark : .light)
).cgColor
result.layer.borderWidth = 1
result.setTitle("TXT_DELETE_TITLE".localized(), for: .normal)
result.addTarget(self, action: #selector(deleteMessageRequest), for: .touchUpInside)
return result
@ -353,14 +320,12 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
messageRequestAcceptButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20)
messageRequestAcceptButton.pin(.left, to: .left, of: messageRequestView, withInset: 20)
messageRequestAcceptButton.pin(.bottom, to: .bottom, of: messageRequestView)
messageRequestAcceptButton.set(.height, to: ConversationVC.messageRequestButtonHeight)
messageRequestDeleteButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20)
messageRequestDeleteButton.pin(.left, to: .right, of: messageRequestAcceptButton, withInset: UIDevice.current.isIPad ? Values.iPadButtonSpacing : 20)
messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20)
messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView)
messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton)
messageRequestDeleteButton.set(.height, to: ConversationVC.messageRequestButtonHeight)
// Unread count view
view.addSubview(unreadCountView)

View File

@ -1,79 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
@objc
final class CallPermissionRequestModal : Modal {
// MARK: Lifecycle
@objc
init() {
super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(onCallEnabled:) instead.")
}
override init(nibName: String?, bundle: Bundle?) {
preconditionFailure("Use init(onCallEnabled:) instead.")
}
override func populateContentView() {
// Title
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
titleLabel.text = NSLocalizedString("modal_call_permission_request_title", comment: "")
titleLabel.textAlignment = .center
// Message
let messageLabel = UILabel()
messageLabel.textColor = Colors.text
messageLabel.font = .systemFont(ofSize: Values.smallFontSize)
let message = NSLocalizedString("modal_call_permission_request_explanation", comment: "")
messageLabel.text = message
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.textAlignment = .center
// Enable button
let goToSettingsButton = UIButton()
goToSettingsButton.set(.height, to: Values.mediumButtonHeight)
goToSettingsButton.layer.cornerRadius = Modal.buttonCornerRadius
goToSettingsButton.backgroundColor = Colors.buttonBackground
goToSettingsButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
goToSettingsButton.setTitleColor(Colors.text, for: UIControl.State.normal)
goToSettingsButton.setTitle(NSLocalizedString("vc_settings_title", comment: ""), for: UIControl.State.normal)
goToSettingsButton.addTarget(self, action: #selector(goToSettings), for: UIControl.Event.touchUpInside)
// Content stack view
let contentStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel ])
contentStackView.axis = .vertical
contentStackView.spacing = Values.largeSpacing
// Button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, goToSettingsButton ])
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
// Main stack view
let spacing = Values.largeSpacing - Values.smallFontSize / 2
let mainStackView = UIStackView(arrangedSubviews: [ contentStackView, buttonStackView ])
mainStackView.axis = .vertical
mainStackView.spacing = spacing
contentView.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: spacing)
}
// MARK: Interaction
@objc func goToSettings(_ sender: Any) {
dismiss(animated: true, completion: {
if let vc = CurrentAppContext().frontmostViewController() {
let privacySettingsVC = PrivacySettingsTableViewController()
privacySettingsVC.shouldShowCloseButton = true
let nav = OWSNavigationController(rootViewController: privacySettingsVC)
nav.modalPresentationStyle = .fullScreen
vc.present(nav, animated: true, completion: nil)
}
})
}
}

View File

@ -537,7 +537,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
let hide = UITableViewRowAction(style: .destructive, title: "TXT_HIDE_TITLE".localized()) { _, _ in
Storage.shared.write { db in db[.hasHiddenMessageRequests] = true }
}
hide.themeBackgroundColor = .danger
hide.themeBackgroundColor = .conversationButton_swipeDestructive
return [hide]
@ -586,7 +586,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
self?.present(alert, animated: true, completion: nil)
}
delete.themeBackgroundColor = .danger
delete.themeBackgroundColor = .conversationButton_swipeDestructive
let pin: UITableViewRowAction = UITableViewRowAction(
style: .normal,
@ -601,7 +601,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
.updateAll(db, SessionThread.Columns.isPinned.set(to: !threadViewModel.threadIsPinned))
}
}
pin.themeBackgroundColor = .conversationButton_pinBackground
pin.themeBackgroundColor = .conversationButton_swipeTertiary
guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else {
return [ delete, pin ]
@ -631,7 +631,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
.retainUntilComplete()
}
}
block.themeBackgroundColor = .backgroundTertiary
block.themeBackgroundColor = .conversationButton_swipeSecondary
return [ delete, block, pin ]

View File

@ -353,7 +353,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
) { [weak self] _, _ in
self?.delete(threadId)
}
delete.backgroundColor = Colors.destructive
delete.themeBackgroundColor = .conversationButton_swipeDestructive
return [ delete ]

View File

@ -383,7 +383,7 @@ private final class NewConversationButton: UIImageView {
contentMode = .center
layer.cornerRadius = (NewConversationButtonSet.collapsedButtonSize / 2)
themeBackgroundColor = (isMainButton ? .menuButton_background : .backgroundTertiary)
themeBackgroundColor = .menuButton_background
themeTintColor = .menuButton_icon
if isMainButton {

View File

@ -55,12 +55,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Note: Intentionally dispatching sync as we want to wait for these to complete before
// continuing
DispatchQueue.main.sync {
OWSScreenLockUI.sharedManager().setup(withRootWindow: mainWindow)
ScreenLockUI.shared.setupWithRootWindow(rootWindow: mainWindow)
OWSWindowManager.shared().setup(
withRootWindow: mainWindow,
screenBlockingWindow: OWSScreenLockUI.sharedManager().screenBlockingWindow
screenBlockingWindow: ScreenLockUI.shared.screenBlockingWindow
)
OWSScreenLockUI.sharedManager().startObserving()
ScreenLockUI.shared.startObserving()
}
},
migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in

View File

@ -15,9 +15,7 @@
#import "OWSMessageTimerView.h"
#import "OWSNavigationController.h"
#import "OWSProgressView.h"
#import "OWSScreenLockUI.h"
#import "OWSWindowManager.h"
#import "PrivacySettingsTableViewController.h"
#import "OWSQRCodeScanningViewController.h"
#import "MainAppContext.h"
#import "UIViewController+Permissions.h"

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Benachrichtigungsstrategie";
"modal_seed_title" = "Ihr Wiederherstellungssatz";
"modal_seed_explanation" = "Das ist Ihr Wiederherstellungssatz. Damit können Sie Ihre Session ID wiederherstellen oder auf ein neues Gerät migrieren.";
"modal_clear_all_data_title" = "Alle Daten löschen";
"modal_clear_all_data_explanation" = "Dadurch werden Ihre Nachrichten, Sessions und Kontakte dauerhaft gelöscht.";
"modal_clear_all_data_explanation_2" = "Möchtest du nur dieses Gerät löschen oder dein gesamtes Konto löschen?";
"dialog_clear_all_data_deletion_failed_1" = "Daten nicht gelöscht von Service Node 1. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Daten nicht gelöscht von %@ Service Noten. Service Noten IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Nur Geräte";
"modal_clear_all_data_entire_account_button_title" = "Gesamtes Konto";
"vc_qr_code_title" = "QR-Code";
"vc_qr_code_view_my_qr_code_tab_title" = "Meinen QR-Code anzeigen";
"vc_qr_code_view_scan_qr_code_tab_title" = "QR-Code scannen";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Alle Daten löschen";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Daten nicht gelöscht von Service Node 1. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Daten nicht gelöscht von %@ Service Noten. Service Noten IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Notification Strategy";
"modal_seed_title" = "Your Recovery Phrase";
"modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device.";
"modal_clear_all_data_title" = "Clear All Data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts.";
"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Device Only";
"modal_clear_all_data_entire_account_button_title" = "Entire Account";
"vc_qr_code_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code";
"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Clear All Data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Estrategia de notificación";
"modal_seed_title" = "Tu frase de recuperación";
"modal_seed_explanation" = "Esta es tu frase de recuperación. Con ella, puedes restaurar o migrar tu ID de Session a un nuevo dispositivo.";
"modal_clear_all_data_title" = "Borrar todos los datos";
"modal_clear_all_data_explanation" = "Esto eliminará permanentemente tu ID de Session, incluyendo todos los mensajes, sesiones y contactos.";
"modal_clear_all_data_explanation_2" = "¿Quieres borrar sólo este dispositivo o eliminar toda tu cuenta?";
"dialog_clear_all_data_deletion_failed_1" = "Datos no borrados por 1 nodo de servicio. ID del nodo de servicio: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Datos no borrados por %@ nodos de servicio. ID del nodo de servicio: %@.";
"modal_clear_all_data_device_only_button_title" = "Sólo el dispositivo";
"modal_clear_all_data_entire_account_button_title" = "Toda la cuenta";
"vc_qr_code_title" = "Código QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Ver mi código QR";
"vc_qr_code_view_scan_qr_code_tab_title" = "Escanear código QR";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Borrar todos los datos";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Datos no borrados por 1 nodo de servicio. ID del nodo de servicio: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Datos no borrados por %@ nodos de servicio. ID del nodo de servicio: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "استراتژی اعلان";
"modal_seed_title" = "عبارت بازیابی شما";
"modal_seed_explanation" = "این عبارت بازیابی شماست. با استفاده از آن می‌توانید شناسه‌ی Session خود را به دستگاه جدید بازیابی یا انتقال دهید.";
"modal_clear_all_data_title" = "پاک کردن همه داده‌ها";
"modal_clear_all_data_explanation" = "این به طور دائم پیام‌ها، جلسات و مخاطبین شما را حذف می‌کند.";
"modal_clear_all_data_explanation_2" = "آیا فقط می‌خواهید این دستگاه را پاک کنید یا می‌خواهید این اکانت را پاک کنید؟";
"dialog_clear_all_data_deletion_failed_1" = "داده ها توسط ۱ گره سرویس حذف نشده است. شناسه گره سرویس: %@.";
"dialog_clear_all_data_deletion_failed_2" = "داده ها توسط گره سرویس %@ حذف نشدند. شناسه گره سرویس: %@.";
"modal_clear_all_data_device_only_button_title" = "فقط دستگاه";
"modal_clear_all_data_entire_account_button_title" = "تمام حساب";
"vc_qr_code_title" = "کد QR";
"vc_qr_code_view_my_qr_code_tab_title" = "مشاهده کد QR من";
"vc_qr_code_view_scan_qr_code_tab_title" = "اسکن کد QR";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "پاک کردن همه داده‌ها";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "داده ها توسط ۱ گره سرویس حذف نشده است. شناسه گره سرویس: %@.";
"dialog_clear_all_data_deletion_failed_2" = "داده ها توسط گره سرویس %@ حذف نشدند. شناسه گره سرویس: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Ilmoitustyyli";
"modal_seed_title" = "Palatusvirkkeesi";
"modal_seed_explanation" = "Tämä on palautusvirkkeesi. Sillä voit palauttaa tai siirtää Session ID:si uuteen laitteeseen.";
"modal_clear_all_data_title" = "Tyhjennä kaikki data";
"modal_clear_all_data_explanation" = "Tämä poistaa kaikki viestisi sekä yhteydet lopullisesti.";
"modal_clear_all_data_explanation_2" = "Haluatko tyhjentää datan vain tästä laitteesta vai poistaa koko tilin?";
"dialog_clear_all_data_deletion_failed_1" = "Dataa ei poistettu yhdestä palvelusolmusta. Palvelusolmun tunnus: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dataa ei poistettu %@ palvelusolmusta. Palvelusolmujen tunnukset: %@.";
"modal_clear_all_data_device_only_button_title" = "Vain tämä laite";
"modal_clear_all_data_entire_account_button_title" = "Koko tili";
"vc_qr_code_title" = "QR-koodi";
"vc_qr_code_view_my_qr_code_tab_title" = "Näytä QR-koodini";
"vc_qr_code_view_scan_qr_code_tab_title" = "Skannaa QR-koodi";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Tyhjennä kaikki data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Dataa ei poistettu yhdestä palvelusolmusta. Palvelusolmun tunnus: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dataa ei poistettu %@ palvelusolmusta. Palvelusolmujen tunnukset: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Stratégie de notification";
"modal_seed_title" = "Votre phrase de récupération";
"modal_seed_explanation" = "Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil.";
"modal_clear_all_data_title" = "Effacer toutes les données";
"modal_clear_all_data_explanation" = "Cela supprimera définitivement vos messages, vos sessions et vos contacts.";
"modal_clear_all_data_explanation_2" = "Souhaitez-vous effacer seulement cet appareil ou supprimer lensemble de votre compte ?";
"dialog_clear_all_data_deletion_failed_1" = "Les données nont pas été supprimées sur un nœud de service. ID du nœud de service : %@.";
"dialog_clear_all_data_deletion_failed_2" = "Les données nont pas été supprimées sur %@ nœuds de service. ID des nœuds de service : %@.";
"modal_clear_all_data_device_only_button_title" = "Lappareil uniquement";
"modal_clear_all_data_entire_account_button_title" = "Lensemble du compte";
"vc_qr_code_title" = "Code QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Afficher mon code QR";
"vc_qr_code_view_scan_qr_code_tab_title" = "Scanner le QR Code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Effacer toutes les données";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Les données nont pas été supprimées sur un nœud de service. ID du nœud de service : %@.";
"dialog_clear_all_data_deletion_failed_2" = "Les données nont pas été supprimées sur %@ nœuds de service. ID des nœuds de service : %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Notification Strategy";
"modal_seed_title" = "Your Recovery Phrase";
"modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device.";
"modal_clear_all_data_title" = "Clear All Data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts.";
"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Device Only";
"modal_clear_all_data_entire_account_button_title" = "Entire Account";
"vc_qr_code_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code";
"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Clear All Data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Strategija obavijesti";
"modal_seed_title" = "Fraza za oporavak";
"modal_seed_explanation" = "Ovo je vaša fraza za oporavak. Pomoću nje možete vratiti ili migrirati svoj Session ID na novi uređaj.";
"modal_clear_all_data_title" = "Obriši sve podatke";
"modal_clear_all_data_explanation" = "Ovo će trajno izbrisati vaše poruke, sesije razgovora i kontakte.";
"modal_clear_all_data_explanation_2" = "Želite li izbrisati samo ovaj uređaj ili želite izbrisati cijeli račun?";
"dialog_clear_all_data_deletion_failed_1" = "Podatke nije izbrisao 1 uslužni čvor. ID uslužnog čvora: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Podatke nije izbrisao %@ uslužni čvor. ID uslužnog čvora: %@.";
"modal_clear_all_data_device_only_button_title" = "Samo uređaj";
"modal_clear_all_data_entire_account_button_title" = "Cijeli račun";
"vc_qr_code_title" = "QR kôd";
"vc_qr_code_view_my_qr_code_tab_title" = "Pogledaj moj QR kôd";
"vc_qr_code_view_scan_qr_code_tab_title" = "Skeniraj QR kôd";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Obriši sve podatke";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Podatke nije izbrisao 1 uslužni čvor. ID uslužnog čvora: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Podatke nije izbrisao %@ uslužni čvor. ID uslužnog čvora: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Strategi notofikasi";
"modal_seed_title" = "Kata pemulihan anda";
"modal_seed_explanation" = "Ini adalah kata pemulihan anda. Gunakan untuk mengembalikan atau memindahkan Session ID anda ke perangkat lain";
"modal_clear_all_data_title" = "Hapus semua data";
"modal_clear_all_data_explanation" = "Pesan, Session, dan kontak anda akan dihapus secara permanen";
"modal_clear_all_data_explanation_2" = "Apakah anda ingin menghapus di perangkat ini, atau menghapus akun anda seutuhnya?";
"dialog_clear_all_data_deletion_failed_1" = "Data tidak dihapus oleh 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data tidak dihapus oleh %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Perangkat saja";
"modal_clear_all_data_entire_account_button_title" = "Seluruh akun";
"vc_qr_code_title" = "Kode QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Lihat kode QR saya";
"vc_qr_code_view_scan_qr_code_tab_title" = "Pindai kode QR";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Hapus semua data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data tidak dihapus oleh 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data tidak dihapus oleh %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Strategia di notifica";
"modal_seed_title" = "Frase di recupero";
"modal_seed_explanation" = "Questa è la tua frase di recupero. Usala per ripristinare o migrare la Sessione ID a un nuovo dispositivo.";
"modal_clear_all_data_title" = "Elimina tutti i dati";
"modal_clear_all_data_explanation" = "Ciò eliminerà permanentemente i tuoi messaggi, sessioni e contatti.";
"modal_clear_all_data_explanation_2" = "Vuoi cancellare solo su questo dispositivo o eliminare l'intero account?";
"dialog_clear_all_data_deletion_failed_1" = "Dati non eliminati da 1 nodi di servizio. ID Nodo di servizio: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dati non eliminati da %@ nodi di servizio. ID Nodo di servizio: %@.";
"modal_clear_all_data_device_only_button_title" = "Solo dispositivo";
"modal_clear_all_data_entire_account_button_title" = "Tutto l'account";
"vc_qr_code_title" = "Codice QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Visualizza il mio codice QR";
"vc_qr_code_view_scan_qr_code_tab_title" = "Scansiona il codice QR";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Elimina tutti i dati";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Dati non eliminati da 1 nodi di servizio. ID Nodo di servizio: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dati non eliminati da %@ nodi di servizio. ID Nodo di servizio: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "通知戦略";
"modal_seed_title" = "あなたのリカバリーフレーズ";
"modal_seed_explanation" = "これはあなたのリカバリーフレーズです。これにより、Session ID を新しい端末に復元または移行できます。";
"modal_clear_all_data_title" = "すべてのデータを消去する";
"modal_clear_all_data_explanation" = "これにより、メッセージ、Session、連絡先が完全に削除されます。";
"modal_clear_all_data_explanation_2" = "この端末のみから消去するか、すべての端末からアカウント全体を削除しますか?";
"dialog_clear_all_data_deletion_failed_1" = "このサービスードからデータが削除されませんでした。ID: %@";
"dialog_clear_all_data_deletion_failed_2" = "%@ つのサービスードからデータが削除されませんでした。ID %@";
"modal_clear_all_data_device_only_button_title" = "この端末のみ";
"modal_clear_all_data_entire_account_button_title" = "アカウント全体";
"vc_qr_code_title" = "QR コード";
"vc_qr_code_view_my_qr_code_tab_title" = "私の QR コードを表示する";
"vc_qr_code_view_scan_qr_code_tab_title" = "QR コードをスキャンする";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "すべてのデータを消去する";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "このサービスードからデータが削除されませんでした。ID: %@";
"dialog_clear_all_data_deletion_failed_2" = "%@ つのサービスードからデータが削除されませんでした。ID %@";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Notificatie Inhoud";
"modal_seed_title" = "Uw Herstel Zin";
"modal_seed_explanation" = "Dit is uw herstel zin, Hiermee kun je je sessie-ID herstellen of migreren naar een nieuw apparaat.";
"modal_clear_all_data_title" = "Wis alle gegevens";
"modal_clear_all_data_explanation" = "Hiermee worden uw berichten, sessies en contacten permanent verwijderd.";
"modal_clear_all_data_explanation_2" = "Wilt u alleen de data op dit apparaat verwijderen, of wilt u uw hele account verwijderen?";
"dialog_clear_all_data_deletion_failed_1" = "Gegevens niet verwijderd door 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Gegevens niet verwijderd door %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Alleen apparaat";
"modal_clear_all_data_entire_account_button_title" = "Gehele account";
"vc_qr_code_title" = "QR-code";
"vc_qr_code_view_my_qr_code_tab_title" = "Bekijk mijn QR-code";
"vc_qr_code_view_scan_qr_code_tab_title" = "QR-code scannen";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Wis alle gegevens";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Gegevens niet verwijderd door 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Gegevens niet verwijderd door %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Strategia powiadomień";
"modal_seed_title" = "Twoja fraza odzyskiwania";
"modal_seed_explanation" = "To jest twoja fraza odzyskiwania. Dzięki niej możesz przywrócić lub przenieść identyfikator Session na nowe urządzenie.";
"modal_clear_all_data_title" = "Wyczyść wszystkie dane";
"modal_clear_all_data_explanation" = "Spowoduje to trwałe usunięcie wiadomości, Session i kontaktów.";
"modal_clear_all_data_explanation_2" = "Czy chcesz wyczyścić tylko to urządzenie, czy usunąć całe swoje konto?";
"dialog_clear_all_data_deletion_failed_1" = "Dane nie zostały usunięte przez 1 węzeł usługowy. Identyfikator węzła: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dane nie zostały usunięte przez %@ węzły usługowe. Identyfikatory węzłów: %@.";
"modal_clear_all_data_device_only_button_title" = "Tylko urządzenie";
"modal_clear_all_data_entire_account_button_title" = "Całe konto";
"vc_qr_code_title" = "Kod QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Wyświetl mój kod QR";
"vc_qr_code_view_scan_qr_code_tab_title" = "Skanowania QR code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Wyczyść wszystkie dane";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Dane nie zostały usunięte przez 1 węzeł usługowy. Identyfikator węzła: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dane nie zostały usunięte przez %@ węzły usługowe. Identyfikatory węzłów: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Estratégia de notificação";
"modal_seed_title" = "Sua frase de recuperação";
"modal_seed_explanation" = "Esta é sua frase de recuperação. Com ela, você pode restaurar ou migrar seu ID Session para um novo dispositivo.";
"modal_clear_all_data_title" = "Limpar todos os dados";
"modal_clear_all_data_explanation" = "Isso excluirá permanentemente suas mensagens, sessões e contatos.";
"modal_clear_all_data_explanation_2" = "Você gostaria de limpar apenas esse dispositivo, ou deletar sua conta?";
"dialog_clear_all_data_deletion_failed_1" = "Dados não excluídos por 1 Service Node. ID do Service Node: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dados não excluídos por %@ Service Nodes. IDs dos Service Nodes: %@.";
"modal_clear_all_data_device_only_button_title" = "Somente esse dispositivo";
"modal_clear_all_data_entire_account_button_title" = "Conta inteira";
"vc_qr_code_title" = "Código QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Ver meu código QR";
"vc_qr_code_view_scan_qr_code_tab_title" = "Escanear código QR";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Limpar todos os dados";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Dados não excluídos por 1 Service Node. ID do Service Node: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Dados não excluídos por %@ Service Nodes. IDs dos Service Nodes: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Метод уведомлений";
"modal_seed_title" = "Ваша секретная фраза";
"modal_seed_explanation" = "Это ваша секретная фраза. С ее помощью вы можете восстановить или перенести свой Session ID на новое устройство.";
"modal_clear_all_data_title" = "Очистить все данные";
"modal_clear_all_data_explanation" = "Это навсегда удалит ваши сообщения, сессии и контакты.";
"modal_clear_all_data_explanation_2" = "Вы хотите очистить только это устройство или полностью удалить ваш аккаунт?";
"dialog_clear_all_data_deletion_failed_1" = "Данные не удалены 1 узлом сервиса. Номер узла: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Данные не удалены %@ узлами сервиса. Номера узлов: %@.";
"modal_clear_all_data_device_only_button_title" = "Только устройство";
"modal_clear_all_data_entire_account_button_title" = "Полностью аккаунт";
"vc_qr_code_title" = "QR-код";
"vc_qr_code_view_my_qr_code_tab_title" = "Посмотреть мой QR-код";
"vc_qr_code_view_scan_qr_code_tab_title" = "Сканировать QR-код";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Очистить все данные";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Данные не удалены 1 узлом сервиса. Номер узла: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Данные не удалены %@ узлами сервиса. Номера узлов: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Notification Strategy";
"modal_seed_title" = "Your Recovery Phrase";
"modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device.";
"modal_clear_all_data_title" = "Clear All Data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts.";
"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Device Only";
"modal_clear_all_data_entire_account_button_title" = "Entire Account";
"vc_qr_code_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code";
"vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Clear All Data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Stratégia upozornení";
"modal_seed_title" = "Vaša fráza pre obnovenie";
"modal_seed_explanation" = "Toto je vaša fráza pre obnovenie. S jej pomocou môžete obnoviť alebo presunúť svoje Session ID na nové zariadenie.";
"modal_clear_all_data_title" = "Odstrániť všetky dáta";
"modal_clear_all_data_explanation" = "Toto navždy vymaže vaše správy, stretnutia a kontakty.";
"modal_clear_all_data_explanation_2" = "Chcete vymazať iba toto zariadenie alebo celý účet?";
"dialog_clear_all_data_deletion_failed_1" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@.";
"dialog_clear_all_data_deletion_failed_2" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@.";
"modal_clear_all_data_device_only_button_title" = "Iba zariadenie";
"modal_clear_all_data_entire_account_button_title" = "Celý Účet";
"vc_qr_code_title" = "QR kód";
"vc_qr_code_view_my_qr_code_tab_title" = "Zobraziť môj QR kód";
"vc_qr_code_view_scan_qr_code_tab_title" = "Skenovať QR kód";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Odstrániť všetky dáta";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@.";
"dialog_clear_all_data_deletion_failed_2" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Strategi för aviseringar";
"modal_seed_title" = "Din Återställningsfras";
"modal_seed_explanation" = "Detta är din återställningsfras. Med den kan du återställa eller migrera ditt Sessions-ID till en ny enhet.";
"modal_clear_all_data_title" = "Rensa all data";
"modal_clear_all_data_explanation" = "Detta kommer att radera dina meddelanden, sessioner och kontakter permanent.";
"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Device Only";
"modal_clear_all_data_entire_account_button_title" = "Entire Account";
"vc_qr_code_title" = "QR-kod";
"vc_qr_code_view_my_qr_code_tab_title" = "Visa min QR-kod";
"vc_qr_code_view_scan_qr_code_tab_title" = "Skanna QR-kod";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Rensa all data";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "กลยุทธ์สำคัญแจ้งเตือน";
"modal_seed_title" = "วลีกู้คืนของคุณ";
"modal_seed_explanation" = "นี่คือวลีกู้คืนของคุณ ด้วยวิธีนี้ คุณสามารถกู้คืนหรือย้ายไอดีเซสชันSessionของคุณไปยังอุปกรณ์ใหม่ได้";
"modal_clear_all_data_title" = "ลบข้อมูลไปหมด";
"modal_clear_all_data_explanation" = "การดำเนินการนี้จะลบข้อความแชตและผู้ติดต่อของคุณ";
"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Device Only";
"modal_clear_all_data_entire_account_button_title" = "Entire Account";
"vc_qr_code_title" = "QR โค้ด";
"vc_qr_code_view_my_qr_code_tab_title" = "แสดง QR โค้ดของคุน";
"vc_qr_code_view_scan_qr_code_tab_title" = "สแกน QR โค้ด";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "ลบข้อมูลไปหมด";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Chiến lược thông báo";
"modal_seed_title" = "Cụm từ khôi phục của bạn";
"modal_seed_explanation" = "Đây là cụm từ khôi phục của bạn. Bạn có thể dùng nó để khôi phục hoặc chuyển Session ID của mình sang một thiết bị mới.";
"modal_clear_all_data_title" = "Xóa tất cả dữ liệu";
"modal_clear_all_data_explanation" = "Thao tác này sẽ xóa vĩnh viễn các tin nhắn, sessions, và danh bạ của bạn.";
"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_device_only_button_title" = "Device Only";
"modal_clear_all_data_entire_account_button_title" = "Entire Account";
"vc_qr_code_title" = "Mã QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Xem mã QR của tôi";
"vc_qr_code_view_scan_qr_code_tab_title" = "Quét mã QR";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "Xóa tất cả dữ liệu";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@.";
"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@.";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "通知類型";
"modal_seed_title" = "您的回復用字句";
"modal_seed_explanation" = "這是您的回復用字句,您可以利用此字句來回復或轉移您的帳號至新的裝置上。";
"modal_clear_all_data_title" = "清除所有資料";
"modal_clear_all_data_explanation" = "這樣做將永久清除您的訊息,帳號與聯絡人。";
"modal_clear_all_data_explanation_2" = "你要清附這部裝置的資料,還是你整個賬戶的資料?";
"dialog_clear_all_data_deletion_failed_1" = "數據已被1個服務節點刪除。節點ID: %@";
"dialog_clear_all_data_deletion_failed_2" = "數據沒有被%@個服務節點刪除。節點ID: %@";
"modal_clear_all_data_device_only_button_title" = "僅限裝置";
"modal_clear_all_data_entire_account_button_title" = "整個賬戶";
"vc_qr_code_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "查看我的 QR Code";
"vc_qr_code_view_scan_qr_code_tab_title" = "掃描 QR Code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "清除所有資料";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "數據已被1個服務節點刪除。節點ID: %@";
"dialog_clear_all_data_deletion_failed_2" = "數據沒有被%@個服務節點刪除。節點ID: %@";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "通知选项";
"modal_seed_title" = "您的恢复口令";
"modal_seed_explanation" = "这是您的恢复口令。您可以通过该口令将Session ID还原或迁移到新设备上。";
"modal_clear_all_data_title" = "清除所有数据";
"modal_clear_all_data_explanation" = "这将永久删除您的消息、对话和联系人。";
"modal_clear_all_data_explanation_2" = "你想只清除这个设备,还是删除你的整个账户?";
"dialog_clear_all_data_deletion_failed_1" = "数据未被一个服务节点删除。服务节点ID %@";
"dialog_clear_all_data_deletion_failed_2" = "数据未被 %@ 服务节点删除。服务节点ID %@";
"modal_clear_all_data_device_only_button_title" = "仅设备";
"modal_clear_all_data_entire_account_button_title" = "整个账户";
"vc_qr_code_title" = "二维码";
"vc_qr_code_view_my_qr_code_tab_title" = "查看我的二维码";
"vc_qr_code_view_scan_qr_code_tab_title" = "扫描二维码";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again.";
"PRIVACY_TITLE" = "Privacy";
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications";
"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message.";
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats.";
"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators";
"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats.";
"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews";
"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews";
"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs.";
"PRIVACY_SECTION_CALLS" = "Calls (Beta)";
"PRIVACY_CALLS_TITLE" = "Voice and Video Calls";
"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users.";
"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)";
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?";
"NOTIFICATIONS_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support";
"modal_clear_all_data_title" = "清除所有数据";
"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?";
"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.";
"modal_clear_all_data_device_only_button_title" = "Clear Device Only";
"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network";
"dialog_clear_all_data_deletion_failed_1" = "数据未被一个服务节点删除。服务节点ID %@";
"dialog_clear_all_data_deletion_failed_2" = "数据未被 %@ 服务节点删除。服务节点ID %@";
"modal_clear_all_data_confirm" = "Clear";

View File

@ -112,29 +112,9 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
super.init()
AppReadiness.runNowOrWhenAppDidBecomeReady {
NotificationCenter.default.addObserver(self, selector: #selector(self.handleMessageRead), name: .incomingMessageMarkedAsRead, object: nil)
}
SwiftSingletons.register(self)
}
// MARK: -
@objc
func handleMessageRead(notification: Notification) {
AssertIsOnMainThread()
switch notification.object {
case let interaction as Interaction:
guard interaction.variant == .standardIncoming else { return }
Logger.debug("canceled notification for message: \(interaction)")
cancelNotifications(identifiers: interaction.notificationIdentifiers)
default: break
}
}
// MARK: - Presenting Notifications
func registerNotificationSettings() -> Promise<Void> {
@ -165,7 +145,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
let senderName = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview)
.defaulting(to: .defaultPreviewType)
switch previewType {
case .noNameNoPreview:
@ -305,7 +285,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
public func notifyForFailedSend(_ db: Database, in thread: SessionThread) {
let notificationTitle: String?
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview)
.defaulting(to: .defaultPreviewType)
switch previewType {
case .noNameNoPreview: notificationTitle = nil

View File

@ -1,26 +1,46 @@
import UIKit
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
final class PathStatusView : UIView {
import UIKit
import SessionUIKit
final class PathStatusView: UIView {
enum Status {
case unknown
case connecting
case connected
case error
var themeColor: ThemeValue {
switch self {
case .unknown: return .path_unknown
case .connecting: return .path_connecting
case .connected: return .path_connected
case .error: return .path_error
}
}
}
static let size = CGFloat(8)
static let size: CGFloat = 8
override init(frame: CGRect) {
super.init(frame: frame)
setUpViewHierarchy()
registerObservers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
registerObservers()
}
private func setUpViewHierarchy() {
layer.cornerRadius = PathStatusView.size / 2
layer.cornerRadius = (PathStatusView.size / 2)
layer.masksToBounds = false
let color = (!OnionRequestAPI.paths.isEmpty ? Colors.accent : Colors.pathsBuilding)
setColor(to: color, isAnimated: false)
setStatus(to: (!OnionRequestAPI.paths.isEmpty ? .connected : .connecting))
}
private func registerObservers() {
@ -33,18 +53,28 @@ final class PathStatusView : UIView {
NotificationCenter.default.removeObserver(self)
}
private func setColor(to color: UIColor, isAnimated: Bool) {
backgroundColor = color
let size = PathStatusView.size
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: color, isAnimated: isAnimated, radius: isLightMode ? 6 : 8)
setCircularGlow(with: glowConfiguration)
private func setStatus(to status: Status) {
themeBackgroundColor = status.themeColor
layer.themeShadowColor = status.themeColor
layer.shadowOffset = CGSize(width: 0, height: 0.8)
layer.shadowPath = UIBezierPath(
ovalIn: CGRect(
origin: CGPoint.zero,
size: CGSize(width: PathStatusView.size, height: PathStatusView.size)
)
).cgPath
ThemeManager.onThemeChange(observer: self) { [weak self] theme, _ in
self?.layer.shadowOpacity = (theme.interfaceStyle == .light ? 0.4 : 1)
self?.layer.shadowRadius = (theme.interfaceStyle == .light ? 6 : 8)
}
}
@objc private func handleBuildingPathsNotification() {
setColor(to: Colors.pathsBuilding, isAnimated: true)
setStatus(to: .connecting)
}
@objc private func handlePathsBuiltNotification() {
setColor(to: Colors.accent, isAnimated: true)
setStatus(to: .connected)
}
}

View File

@ -154,28 +154,37 @@ final class PathVC: BaseVC {
// MARK: General
private func getPathRow(title: String, subtitle: String?, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) -> UIStackView {
let lineView = LineView(location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
let lineView = LineView(
location: location,
dotAnimationStartDelay: dotAnimationStartDelay,
dotAnimationRepeatInterval: dotAnimationRepeatInterval
)
lineView.set(.width, to: PathVC.expandedDotSize)
lineView.set(.height, to: PathVC.rowHeight)
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
let titleLabel: UILabel = UILabel()
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
titleLabel.text = title
titleLabel.themeTextColor = .textPrimary
titleLabel.lineBreakMode = .byTruncatingTail
let titleStackView = UIStackView(arrangedSubviews: [ titleLabel ])
titleStackView.axis = .vertical
if let subtitle = subtitle {
let subtitleLabel = UILabel()
subtitleLabel.textColor = Colors.text
subtitleLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
subtitleLabel.text = subtitle
subtitleLabel.themeTextColor = .textPrimary
subtitleLabel.lineBreakMode = .byTruncatingTail
titleStackView.addArrangedSubview(subtitleLabel)
}
let stackView = UIStackView(arrangedSubviews: [ lineView, titleStackView ])
stackView.axis = .horizontal
stackView.spacing = Values.largeSpacing
stackView.alignment = .center
return stackView
}
@ -221,7 +230,9 @@ private final class LineView : UIView {
self.location = location
self.dotAnimationStartDelay = dotAnimationStartDelay
self.dotAnimationRepeatInterval = dotAnimationRepeatInterval
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}

View File

@ -102,6 +102,22 @@ class HelpViewModel: SettingsTableViewModel<HelpViewModel.Section, HelpViewModel
return
}
UIApplication.shared.open(url)
})
)
]
),
SectionModel(
model: .support,
elements: [
SettingInfo(
id: .support,
title: "HELP_SUPPORT_TITLE".localized(),
action: .trigger(action: {
guard let url: URL = URL(string: "https://sessionapp.zendesk.com/hc/en-us") else {
return
}
UIApplication.shared.open(url)
})
)
@ -111,7 +127,6 @@ class HelpViewModel: SettingsTableViewModel<HelpViewModel.Section, HelpViewModel
}
.removeDuplicates()
// MARK: - Functions
public override func updateSettings(_ updatedSettings: [SectionModel]) {

View File

@ -35,6 +35,7 @@ class NotificationContentViewModel: SettingsTableViewModel<NotificationSettingsV
private lazy var _observableSettingsData: ObservableData = ValueObservation
.trackingConstantRegion { [weak self] db -> [SectionModel] in
let currentSelection: Preferences.NotificationPreviewType? = db[.preferencesNotificationPreviewType]
.defaulting(to: .defaultPreviewType)
return [
SectionModel(

View File

@ -65,7 +65,7 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
SyncPushTokensJob.run(uploadOnlyIfStale: false)
}
),
extraActionTitle: { _, primaryColor in
extraActionTitle: { theme, primaryColor in
NSMutableAttributedString()
.appending(
NSAttributedString(
@ -77,7 +77,10 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
)
.appending(
NSAttributedString(
string: "NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION_2".localized()
string: "NOTIFICATIONS_STRATEGY_FAST_MODE_ACTION_2".localized(),
attributes: [
.foregroundColor: (theme.colors[.textPrimary] ?? .white)
]
)
)
},
@ -95,7 +98,7 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
db,
type: Preferences.Sound.self,
key: .defaultNotificationSound,
titleGenerator: { $0?.displayName },
titleGenerator: { $0.defaulting(to: .defaultNotificationSound).displayName },
createUpdateScreen: {
SettingsTableViewController(viewModel: NotificationSoundViewModel())
}
@ -119,7 +122,7 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
db,
type: Preferences.NotificationPreviewType.self,
key: .preferencesNotificationPreviewType,
titleGenerator: { $0?.name },
titleGenerator: { $0.defaulting(to: .defaultPreviewType).name },
createUpdateScreen: {
SettingsTableViewController(viewModel: NotificationContentViewModel())
}

View File

@ -43,6 +43,7 @@ class NotificationSoundViewModel: SettingsTableViewModel<NotificationSettingsVie
private lazy var _observableSettingsData: ObservableData = ValueObservation
.trackingConstantRegion { [weak self] db -> [SectionModel] in
self?.currentSelection = (self?.currentSelection ?? db[.defaultNotificationSound])
.defaulting(to: .defaultNotificationSound)
return [
SectionModel(
@ -63,7 +64,10 @@ class NotificationSoundViewModel: SettingsTableViewModel<NotificationSettingsVie
}(),
action: .listSelection(
isSelected: { (self?.currentSelection == sound) },
storedSelection: (db[.defaultNotificationSound] == sound),
storedSelection: (
sound == db[.defaultNotificationSound]
.defaulting(to: .defaultNotificationSound)
),
shouldAutoSave: false,
selectValue: {
self?.currentSelection = sound

View File

@ -6,117 +6,98 @@ import SessionSnodeKit
import SessionMessagingKit
import SignalUtilitiesKit
@objc(LKNukeDataModal)
final class NukeDataModal: Modal {
// MARK: - Components
private lazy var titleLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.text = "modal_clear_all_data_title".localized()
result.numberOfLines = 0
result.lineBreakMode = .byWordWrapping
result.themeTextColor = .textPrimary
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result
}()
private lazy var explanationLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
result.font = .systemFont(ofSize: Values.smallFontSize)
result.text = "modal_clear_all_data_explanation".localized()
result.numberOfLines = 0
result.themeTextColor = .textPrimary
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result
}()
private lazy var clearDeviceRadio: RadioButton = {
let result: RadioButton = RadioButton(size: .small) { [weak self] radio in
self?.clearNetworkRadio.update(isSelected: false)
radio.update(isSelected: true)
}
result.font = .systemFont(ofSize: Values.smallFontSize)
result.text = "modal_clear_all_data_device_only_button_title".localized()
result.update(isSelected: true)
return result
}()
private lazy var clearNetworkRadio: RadioButton = {
let result: RadioButton = RadioButton(size: .small) { [weak self] radio in
self?.clearDeviceRadio.update(isSelected: false)
radio.update(isSelected: true)
}
result.font = .systemFont(ofSize: Values.smallFontSize)
result.text = "modal_clear_all_data_entire_account_button_title".localized()
return result
}()
private lazy var clearDataButton: UIButton = {
let result = UIButton()
result.set(.height, to: Values.mediumButtonHeight)
result.layer.cornerRadius = Modal.buttonCornerRadius
if isDarkMode {
result.backgroundColor = Colors.destructive
}
result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
result.setTitleColor(isLightMode ? Colors.destructive : Colors.text, for: UIControl.State.normal)
result.setTitle("TXT_DELETE_TITLE".localized(), for: UIControl.State.normal)
let result: UIButton = Modal.createButton(
title: "modal_clear_all_data_confirm".localized(),
titleColor: .danger
)
result.addTarget(self, action: #selector(clearAllData), for: UIControl.Event.touchUpInside)
return result
}()
private lazy var buttonStackView1: UIStackView = {
let result = UIStackView(arrangedSubviews: [ cancelButton, clearDataButton ])
private lazy var buttonStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ clearDataButton, cancelButton ])
result.axis = .horizontal
result.spacing = Values.mediumSpacing
result.distribution = .fillEqually
return result
}()
private lazy var deviceOnlyButton: UIButton = {
let result = UIButton()
result.set(.height, to: Values.mediumButtonHeight)
result.layer.cornerRadius = Modal.buttonCornerRadius
result.backgroundColor = Colors.buttonBackground
result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
result.setTitleColor(Colors.text, for: UIControl.State.normal)
result.setTitle("modal_clear_all_data_device_only_button_title".localized(), for: UIControl.State.normal)
result.addTarget(self, action: #selector(clearDeviceOnly), for: UIControl.Event.touchUpInside)
return result
}()
private lazy var entireAccountButton: UIButton = {
let result = UIButton()
result.set(.height, to: Values.mediumButtonHeight)
result.layer.cornerRadius = Modal.buttonCornerRadius
if isDarkMode {
result.backgroundColor = Colors.destructive
}
result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
result.setTitleColor(isLightMode ? Colors.destructive : Colors.text, for: UIControl.State.normal)
result.setTitle("modal_clear_all_data_entire_account_button_title".localized(), for: UIControl.State.normal)
result.addTarget(self, action: #selector(clearEntireAccount), for: UIControl.Event.touchUpInside)
return result
}()
private lazy var buttonStackView2: UIStackView = {
let result = UIStackView(arrangedSubviews: [ deviceOnlyButton, entireAccountButton ])
result.axis = .horizontal
result.spacing = Values.mediumSpacing
result.distribution = .fillEqually
result.alpha = 0
return result
}()
private lazy var buttonStackViewContainer: UIView = {
let result = UIView()
result.addSubview(buttonStackView2)
buttonStackView2.pin(to: result)
result.addSubview(buttonStackView1)
buttonStackView1.pin(to: result)
return result
}()
private lazy var contentStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ])
let result = UIStackView(arrangedSubviews: [
titleLabel,
explanationLabel,
clearDeviceRadio,
UIView.separator(),
clearNetworkRadio
])
result.axis = .vertical
result.spacing = Values.largeSpacing
result.spacing = Values.smallSpacing
result.isLayoutMarginsRelativeArrangement = true
result.layoutMargins = UIEdgeInsets(
top: Values.largeSpacing,
leading: Values.largeSpacing,
bottom: Values.verySmallSpacing,
trailing: Values.largeSpacing
)
return result
}()
private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackViewContainer ])
let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackView ])
result.axis = .vertical
result.spacing = Values.largeSpacing - Values.smallFontSize / 2
@ -127,32 +108,33 @@ final class NukeDataModal: Modal {
override func populateContentView() {
contentView.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: mainStackView.spacing)
mainStackView.pin(to: contentView)
}
// MARK: - Interaction
@objc private func clearAllData() {
UIView.animate(withDuration: 0.25) {
self.buttonStackView1.alpha = 0
self.buttonStackView2.alpha = 1
guard clearNetworkRadio.isSelected else {
clearDeviceOnly()
return
}
UIView.transition(
with: explanationLabel,
duration: 0.25,
options: .transitionCrossDissolve,
animations: {
self.explanationLabel.text = "modal_clear_all_data_explanation_2".localized()
},
completion: nil
)
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_clear_all_data_title".localized(),
explanation: "modal_clear_all_data_explanation_2".localized(),
confirmTitle: "modal_clear_all_data_confirm".localized(),
confirmStyle: .danger,
cancelStyle: .textPrimary
)
) { [weak self] confirmationModal in
self?.clearEntireAccount(presentedViewController: confirmationModal)
}
present(confirmationModal, animated: true, completion: nil)
}
@objc private func clearDeviceOnly() {
private func clearDeviceOnly() {
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in
Storage.shared
.writeAsync { db in try MessageSender.syncConfiguration(db, forceSyncNow: true) }
@ -164,9 +146,9 @@ final class NukeDataModal: Modal {
}
}
@objc private func clearEntireAccount() {
private func clearEntireAccount(presentedViewController: UIViewController) {
ModalActivityIndicatorViewController
.present(fromViewController: self, canCancel: false) { [weak self] _ in
.present(fromViewController: presentedViewController, canCancel: false) { [weak self] _ in
SnodeAPI.clearAllData()
.done(on: DispatchQueue.main) { confirmations in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface PrivacySettingsTableViewController : OWSTableViewController
@property (nonatomic) BOOL shouldShowCloseButton;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,351 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "PrivacySettingsTableViewController.h"
#import "Session-Swift.h"
#import <SignalCoreKit/NSString+OWS.h>
#import <SignalUtilitiesKit/UIColor+OWS.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/NSString+SSK.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-sender/";
@implementation PrivacySettingsTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self observeNotifications];
[self updateTableContents];
[LKViewControllerUtilities setUpDefaultSessionStyleForVC:self withTitle:NSLocalizedString(@"vc_privacy_settings_title", @"") customBackButton:NO];
self.tableView.backgroundColor = UIColor.clearColor;
if (self.shouldShowCloseButton) {
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"X"]
style:UIBarButtonItemStylePlain
target:self
action:@selector(close:)];
[[self navigationItem] setLeftBarButtonItem:closeButton];
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self updateTableContents];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(screenLockDidChange:)
name:OWSScreenLock.ScreenLockDidChange
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak PrivacySettingsTableViewController *weakSelf = self;
OWSTableSection *readReceiptsSection = [OWSTableSection new];
readReceiptsSection.headerTitle
= NSLocalizedString(@"SETTINGS_READ_RECEIPT", @"Label for the 'read receipts' setting.");
readReceiptsSection.footerTitle = NSLocalizedString(
@"SETTINGS_READ_RECEIPTS_SECTION_FOOTER", @"An explanation of the 'read receipts' setting.");
[readReceiptsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_READ_RECEIPT",
@"Label for the 'read receipts' setting.")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"read_receipts"]
isOnBlock:^{
return [SMKPreferences areReadReceiptsEnabled];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleReadReceiptsSwitch:)]];
[contents addSection:readReceiptsSection];
OWSTableSection *typingIndicatorsSection = [OWSTableSection new];
typingIndicatorsSection.headerTitle
= NSLocalizedString(@"SETTINGS_TYPING_INDICATORS", @"Label for the 'typing indicators' setting.");
typingIndicatorsSection.footerTitle = NSLocalizedString(@"See and share when messages are being typed (applies to all sessions).", @"");
[typingIndicatorsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_TYPING_INDICATORS",
@"Label for the 'typing indicators' setting.")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"typing_indicators"]
isOnBlock:^{
return [SMKPreferences areTypingIndicatorsEnabled];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleTypingIndicatorsSwitch:)]];
[contents addSection:typingIndicatorsSection];
OWSTableSection *screenLockSection = [OWSTableSection new];
screenLockSection.headerTitle = NSLocalizedString(
@"SETTINGS_SCREEN_LOCK_SECTION_TITLE", @"Title for the 'screen lock' section of the privacy settings.");
screenLockSection.footerTitle = NSLocalizedString(@"Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions notification settings to customise the information displayed in notifications.", @"");
[screenLockSection
addItem:[OWSTableItem
switchItemWithText:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_SWITCH_LABEL",
@"Label for the 'enable screen lock' switch of the privacy settings.")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"screenlock"]
isOnBlock:^{
return [OWSScreenLock.sharedManager isScreenLockEnabled];
}
isEnabledBlock:^{
return YES;
}
target:self
selector:@selector(isScreenLockEnabledDidChange:)]];
[contents addSection:screenLockSection];
if (OWSScreenLock.sharedManager.isScreenLockEnabled) {
OWSTableSection *screenLockTimeoutSection = [OWSTableSection new];
uint32_t screenLockTimeout = (uint32_t)round(OWSScreenLock.sharedManager.screenLockTimeout);
NSString *screenLockTimeoutString = [self formatScreenLockTimeout:screenLockTimeout useShortFormat:YES];
[screenLockTimeoutSection
addItem:[OWSTableItem
disclosureItemWithText:
NSLocalizedString(@"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT",
@"Label for the 'screen lock activity timeout' setting of the privacy settings.")
detailText:screenLockTimeoutString
accessibilityIdentifier:[NSString
stringWithFormat:@"settings.privacy.%@", @"screen_lock_timeout"]
actionBlock:^{
[weakSelf showScreenLockTimeoutUI];
}]];
[contents addSection:screenLockTimeoutSection];
}
OWSTableSection *screenSecuritySection = [OWSTableSection new];
screenSecuritySection.headerTitle = NSLocalizedString(@"SETTINGS_SECURITY_TITLE", @"Section header");
screenSecuritySection.footerTitle = NSLocalizedString(@"Prevent Session previews from appearing in the app switcher.", nil);
[screenSecuritySection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"Disable Preview in App Switcher", @"")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"screen_security"]
isOnBlock:^{
return [SMKPreferences isScreenSecurityEnabled];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleScreenSecuritySwitch:)]];
[contents addSection:screenSecuritySection];
OWSTableSection *historyLogsSection = [OWSTableSection new];
historyLogsSection.headerTitle = NSLocalizedString(@"SETTINGS_HISTORYLOG_TITLE", @"Section header");
[historyLogsSection
addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"SETTINGS_CLEAR_HISTORY", @"")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"clear_logs"]
actionBlock:^{
[weakSelf clearHistoryLogs];
}]];
[contents addSection:historyLogsSection];
OWSTableSection *linkPreviewsSection = [OWSTableSection new];
[linkPreviewsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_LINK_PREVIEWS",
@"Setting for enabling & disabling link previews.")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"link_previews"]
isOnBlock:^{
return [SMKPreferences areLinkPreviewsEnabled];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleLinkPreviewsEnabled:)]];
linkPreviewsSection.headerTitle = NSLocalizedString(
@"SETTINGS_LINK_PREVIEWS_HEADER", @"Header for setting for enabling & disabling link previews.");
linkPreviewsSection.footerTitle = NSLocalizedString(
@"SETTINGS_LINK_PREVIEWS_FOOTER", @"Footer for setting for enabling & disabling link previews.");
[contents addSection:linkPreviewsSection];
OWSTableSection *callsSection = [OWSTableSection new];
[callsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_CALLS",
@"Setting for enabling & disabling voice & video calls.")
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"calls"]
isOnBlock:^{
return [SMKPreferences areCallsEnabled];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleCallsEnabled:)]];
callsSection.headerTitle = [NSString stringWithFormat:@"%@ (BETA)", NSLocalizedString( @"SETTINGS_CALLS_HEADER", @"Header for setting for enabling & disabling voice & video calls.")];
callsSection.footerTitle = NSLocalizedString(
@"SETTINGS_CALLS_FOOTER", @"Footer for setting for enabling & disabling voice & video calls.");
[contents addSection:callsSection];
self.contents = contents;
}
#pragma mark - Events
- (void)clearHistoryLogs
{
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:nil
message:NSLocalizedString(@"Are you sure? This cannot be undone.",
@"Alert message before user confirms clearing history")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[OWSAlerts cancelAction]];
UIAlertAction *deleteAction =
[UIAlertAction actionWithTitle:
NSLocalizedString(@"SETTINGS_DELETE_HISTORYLOG_CONFIRMATION_BUTTON",
@"Confirmation text for button which deletes all message, calling, attachments, etc.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"delete")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[self deleteThreadsAndMessages];
}];
[alert addAction:deleteAction];
[self presentAlert:alert];
}
- (void)deleteThreadsAndMessages
{
[SMKThread deleteAll];
}
- (void)didToggleScreenSecuritySwitch:(UISwitch *)sender
{
BOOL enabled = sender.isOn;
OWSLogInfo(@"toggled screen security: %@", enabled ? @"ON" : @"OFF");
[SMKPreferences setScreenSecurity:enabled];
}
- (void)didToggleReadReceiptsSwitch:(UISwitch *)sender
{
BOOL enabled = sender.isOn;
OWSLogInfo(@"toggled areReadReceiptsEnabled: %@", enabled ? @"ON" : @"OFF");
[SMKPreferences setAreReadReceiptsEnabled:enabled];
}
- (void)didToggleTypingIndicatorsSwitch:(UISwitch *)sender
{
BOOL enabled = sender.isOn;
OWSLogInfo(@"toggled areTypingIndicatorsEnabled: %@", enabled ? @"ON" : @"OFF");
[SMKPreferences setTypingIndicatorsEnabled:enabled];
}
- (void)didToggleLinkPreviewsEnabled:(UISwitch *)sender
{
BOOL enabled = sender.isOn;
OWSLogInfo(@"toggled to: %@", (enabled ? @"ON" : @"OFF"));
[SMKPreferences setLinkPreviewsEnabled:enabled];
}
- (void)didToggleCallsEnabled:(UISwitch *)sender
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL enabled = sender.isOn;
if (enabled && ![userDefaults boolForKey:@"hasSeenCallIPExposureWarning"]) {
[userDefaults setBool:YES forKey:@"hasSeenCallIPExposureWarning"];
CallModal *modal = [[CallModal alloc] initOnCallEnabled:^{
OWSLogInfo(@"toggled to: %@", (enabled ? @"ON" : @"OFF"));
[self objc_requestMicrophonePermissionIfNeeded];
}];
[self presentViewController:modal animated:YES completion:nil];
}
else {
OWSLogInfo(@"toggled to: %@", (enabled ? @"ON" : @"OFF"));
[SMKPreferences setCallsEnabled:enabled];
}
}
- (void)isScreenLockEnabledDidChange:(UISwitch *)sender
{
BOOL shouldBeEnabled = sender.isOn;
if (shouldBeEnabled == OWSScreenLock.sharedManager.isScreenLockEnabled) {
OWSLogError(@"ignoring redundant screen lock.");
return;
}
OWSLogInfo(@"trying to set is screen lock enabled: %@", @(shouldBeEnabled));
[OWSScreenLock.sharedManager setIsScreenLockEnabled:shouldBeEnabled];
}
- (void)screenLockDidChange:(NSNotification *)notification
{
OWSLogInfo(@"");
[self updateTableContents];
}
- (void)showScreenLockTimeoutUI
{
OWSLogInfo(@"");
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT",
@"Label for the 'screen lock activity timeout' setting of the privacy settings.")
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
for (NSNumber *timeoutValue in OWSScreenLock.sharedManager.screenLockTimeouts) {
uint32_t screenLockTimeout = (uint32_t)round(timeoutValue.doubleValue);
NSString *screenLockTimeoutString = [self formatScreenLockTimeout:screenLockTimeout useShortFormat:NO];
UIAlertAction *action =
[UIAlertAction actionWithTitle:screenLockTimeoutString
accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.timeout.%@", timeoutValue]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *ignore) {
[OWSScreenLock.sharedManager setScreenLockTimeout:screenLockTimeout];
}];
[alert addAction:action];
}
[alert addAction:[OWSAlerts cancelAction]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentAlert:alert];
}
- (NSString *)formatScreenLockTimeout:(NSInteger)value useShortFormat:(BOOL)useShortFormat
{
if (value <= 1) {
return NSLocalizedString(@"SCREEN_LOCK_ACTIVITY_TIMEOUT_NONE",
@"Indicates a delay of zero seconds, and that 'screen lock activity' will timeout immediately.");
}
return [NSString formatDurationSeconds:(uint32_t)value useShortFormat:useShortFormat];
}
- (void)close: (UIBarButtonItem *)sender
{
[[self navigationController] dismissViewControllerAnimated:YES completion:nil];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,6 +0,0 @@
extension PrivacySettingsTableViewController {
@objc func objc_requestMicrophonePermissionIfNeeded() {
requestMicrophonePermissionIfNeeded { }
}
}

View File

@ -0,0 +1,137 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import DifferenceKit
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
class PrivacySettingsViewModel: SettingsTableViewModel<PrivacySettingsViewModel.Section, PrivacySettingsViewModel.Section> {
// MARK: - Section
public enum Section: SettingSection {
case screenLock
case screenshotNotifications
case readReceipts
case typingIndicators
case linkPreviews
case calls
var title: String {
switch self {
case .screenLock: return "PRIVACY_SECTION_SCREEN_SECURITY".localized()
case .screenshotNotifications: return "" // No title
case .readReceipts: return "PRIVACY_SECTION_READ_RECEIPTS".localized()
case .typingIndicators: return "PRIVACY_SECTION_TYPING_INDICATORS".localized()
case .linkPreviews: return "PRIVACY_SECTION_LINK_PREVIEWS".localized()
case .calls: return "PRIVACY_SECTION_CALLS".localized()
}
}
}
// MARK: - Content
override var title: String { "PRIVACY_TITLE".localized() }
private var _settingsData: [SectionModel] = []
public override var settingsData: [SectionModel] { _settingsData }
public override var observableSettingsData: ObservableData { _observableSettingsData }
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
///
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
private lazy var _observableSettingsData: ObservableData = ValueObservation
.trackingConstantRegion { db -> [SectionModel] in
return [
SectionModel(
model: .screenLock,
elements: [
SettingInfo(
id: .screenLock,
title: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE".localized(),
subtitle: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION".localized(),
action: .settingBool(key: .isScreenLockEnabled)
)
]
),
SectionModel(
model: .screenshotNotifications,
elements: [
SettingInfo(
id: .screenshotNotifications,
title: "PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE".localized(),
subtitle: "PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION".localized(),
action: .settingBool(key: .showScreenshotNotifications)
)
]
),
SectionModel(
model: .readReceipts,
elements: [
SettingInfo(
id: .readReceipts,
title: "PRIVACY_READ_RECEIPTS_TITLE".localized(),
subtitle: "PRIVACY_READ_RECEIPTS_DESCRIPTION".localized(),
action: .settingBool(key: .areReadReceiptsEnabled)
)
]
),
SectionModel(
model: .typingIndicators,
elements: [
SettingInfo(
id: .typingIndicators,
title: "PRIVACY_TYPING_INDICATORS_TITLE".localized(),
subtitle: "PRIVACY_TYPING_INDICATORS_DESCRIPTION".localized(),
action: .settingBool(key: .typingIndicatorsEnabled)
)
]
),
SectionModel(
model: .linkPreviews,
elements: [
SettingInfo(
id: .linkPreviews,
title: "PRIVACY_LINK_PREVIEWS_TITLE".localized(),
subtitle: "PRIVACY_LINK_PREVIEWS_DESCRIPTION".localized(),
action: .settingBool(key: .areLinkPreviewsEnabled)
)
]
),
SectionModel(
model: .calls,
elements: [
SettingInfo(
id: .calls,
title: "PRIVACY_CALLS_TITLE".localized(),
subtitle: "PRIVACY_CALLS_DESCRIPTION".localized(),
action: .settingBool(
key: .areCallsEnabled,
confirmationInfo: ConfirmationModal.Info(
title: "PRIVACY_CALLS_WARNING_TITLE".localized(),
explanation: "PRIVACY_CALLS_WARNING_DESCRIPTION".localized(),
stateToShow: .whenDisabled,
confirmStyle: .textPrimary
) { requestMicrophonePermissionIfNeeded() }
)
)
]
)
]
}
.removeDuplicates()
// MARK: - Functions
public override func updateSettings(_ updatedSettings: [SectionModel]) {
self._settingsData = updatedSettings
}
public override func saveChanges() {}
}

View File

@ -162,39 +162,71 @@ private final class ViewMyQRCodeVC : UIViewController {
override func viewDidLoad() {
// Remove background color
view.backgroundColor = .clear
view.themeBackgroundColor = .clear
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
titleLabel.text = "Scan Me"
titleLabel.numberOfLines = 1
titleLabel.themeTextColor = .textPrimary
titleLabel.textAlignment = .center
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.numberOfLines = 1
titleLabel.set(.height, to: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
// Set up QR code image view
let qrCodeImageView = UIImageView()
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
qrCodeImageView.image = qrCode
qrCodeImageView.contentMode = .scaleAspectFit
let qrCodeImageView = UIImageView(
image: QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: false)
.withRenderingMode(.alwaysTemplate)
)
qrCodeImageView.set(.height, to: isIPhone5OrSmaller ? 180 : 240)
qrCodeImageView.set(.width, to: isIPhone5OrSmaller ? 180 : 240)
#if targetEnvironment(simulator)
#else
// Note: For some reason setting this seems to stop the QRCode from rendering on the
// simulator so only doing it on device
qrCodeImageView.contentMode = .scaleAspectFit
#endif
let qrCodeImageViewBackgroundView = UIView()
qrCodeImageViewBackgroundView.layer.cornerRadius = 8
qrCodeImageViewBackgroundView.addSubview(qrCodeImageView)
qrCodeImageView.pin(
to: qrCodeImageViewBackgroundView,
withInset: 5 // The QRCode image has about 6pt of padding and we want 11 in total
)
ThemeManager.onThemeChange(observer: qrCodeImageView) { theme, _ in
switch theme.interfaceStyle {
case .light:
qrCodeImageView.tintColor = theme.colors[.textPrimary]
qrCodeImageViewBackgroundView.backgroundColor = nil
default:
qrCodeImageView.tintColor = theme.colors[.backgroundPrimary]
qrCodeImageViewBackgroundView.backgroundColor = .white
}
}
// Set up QR code image view container
let qrCodeImageViewContainer = UIView()
qrCodeImageViewContainer.accessibilityLabel = "Your QR code"
qrCodeImageViewContainer.isAccessibilityElement = true
qrCodeImageViewContainer.addSubview(qrCodeImageView)
qrCodeImageView.center(.horizontal, in: qrCodeImageViewContainer)
qrCodeImageView.pin(.top, to: .top, of: qrCodeImageViewContainer)
qrCodeImageView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer)
qrCodeImageViewContainer.addSubview(qrCodeImageViewBackgroundView)
qrCodeImageViewBackgroundView.center(.horizontal, in: qrCodeImageViewContainer)
qrCodeImageViewBackgroundView.pin(.top, to: .top, of: qrCodeImageViewContainer)
qrCodeImageViewBackgroundView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer)
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.mediumFontSize)
explanationLabel.text = NSLocalizedString("vc_view_my_qr_code_explanation", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.text = "vc_view_my_qr_code_explanation".localized()
explanationLabel.themeTextColor = .textPrimary
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.numberOfLines = 0
// Set up share button
let shareButton = OutlineButton(style: .regular, size: .large)
@ -222,16 +254,19 @@ private final class ViewMyQRCodeVC : UIViewController {
stackView.pin(.top, to: .top, of: view)
view.pin(.trailing, to: .trailing, of: stackView)
bottomConstraint = view.pin(.bottom, to: .bottom, of: stackView)
// Set up width constraint
view.set(.width, to: UIScreen.main.bounds.width)
}
// MARK: General
// MARK: - General
func constrainHeight(to height: CGFloat) {
view.set(.height, to: height)
}
// MARK: Interaction
// MARK: - Interaction
@objc private func shareQRCode() {
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
let shareVC = UIActivityViewController(activityItems: [ qrCode ], applicationActivities: nil)
@ -250,26 +285,30 @@ private final class ScanQRCodePlaceholderVC : UIViewController {
override func viewDidLoad() {
// Remove background color
view.backgroundColor = .clear
view.themeBackgroundColor = .clear
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("vc_scan_qr_code_camera_access_explanation", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.text = "vc_scan_qr_code_camera_access_explanation".localized()
explanationLabel.themeTextColor = .textPrimary
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.numberOfLines = 0
// Set up call to action button
let callToActionButton = UIButton()
callToActionButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
callToActionButton.setTitleColor(Colors.accent, for: UIControl.State.normal)
callToActionButton.setTitle(NSLocalizedString("vc_scan_qr_code_grant_camera_access_button_title", comment: ""), for: UIControl.State.normal)
callToActionButton.titleLabel?.font = .boldSystemFont(ofSize: Values.mediumFontSize)
callToActionButton.setTitle("vc_scan_qr_code_grant_camera_access_button_title".localized(), for: UIControl.State.normal)
callToActionButton.setThemeTitleColor(.primary, for: .normal)
callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside)
// Set up stack view
let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ])
stackView.axis = .vertical
stackView.spacing = Values.mediumSpacing
stackView.alignment = .center
// Set up constraints
view.set(.width, to: UIScreen.main.bounds.width)
view.addSubview(stackView)

View File

@ -11,6 +11,7 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
typealias SectionModel = SettingsTableViewModel<Section, SettingItem>.SectionModel
private let viewModel: SettingsTableViewModel<Section, SettingItem>
private let shouldShowCloseButton: Bool
private var dataChangeObservable: DatabaseCancellable?
private var hasLoadedInitialSettingsData: Bool = false
@ -37,8 +38,9 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
// MARK: - Initialization
init(viewModel: SettingsTableViewModel<Section, SettingItem>) {
init(viewModel: SettingsTableViewModel<Section, SettingItem>, shouldShowCloseButton: Bool = false) {
self.viewModel = viewModel
self.shouldShowCloseButton = shouldShowCloseButton
super.init(nibName: nil, bundle: nil)
}
@ -163,7 +165,16 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
case .listSelection(_, _, let shouldAutoSave, _) = data.first?.elements.first?.action,
!shouldAutoSave
else {
navigationItem.leftBarButtonItem = nil
navigationItem.leftBarButtonItem = {
guard shouldShowCloseButton else { return nil }
return UIBarButtonItem(
image: UIImage(named: "X")?.withRenderingMode(.alwaysTemplate),
style: .plain,
target: self,
action: #selector(closePressed)
)
}()
navigationItem.rightBarButtonItem = nil
return
}
@ -243,9 +254,23 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo)
onChange?()
case .settingBool(let key):
Storage.shared.write { db in db[key] = !db[key] }
manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo)
case .settingBool(let key, let confirmationInfo):
guard
let confirmationInfo: ConfirmationModal.Info = confirmationInfo,
confirmationInfo.stateToShow.shouldShow(for: Storage.shared[key])
else {
Storage.shared.write { db in db[key] = !db[key] }
manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo)
return
}
// Show a confirmation modal before continuing
let confirmationModal: ConfirmationModal = ConfirmationModal(info: confirmationInfo) { [weak self] _ in
Storage.shared.write { db in db[key] = !db[key] }
self?.manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo)
self?.dismiss(animated: true)
}
present(confirmationModal, animated: true, completion: nil)
case .push(let createDestination), .dangerPush(let createDestination),
.settingEnum(_, _, let createDestination):
@ -308,6 +333,10 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
// MARK: - NavigationActions
@objc private func closePressed() {
navigationController?.dismiss(animated: true)
}
@objc private func cancelButtonPressed() {
navigationController?.popViewController(animated: true)
}
@ -322,6 +351,11 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
// MARK: - SettingHeaderView
class SettingHeaderView: UITableViewHeaderFooterView {
private lazy var emptyHeightConstraint: NSLayoutConstraint = self.heightAnchor
.constraint(equalToConstant: (Values.verySmallSpacing * 2))
private lazy var filledHeightConstraint: NSLayoutConstraint = self.heightAnchor
.constraint(greaterThanOrEqualToConstant: Values.mediumSpacing)
// MARK: - UI
private let stackView: UIStackView = {
@ -331,12 +365,6 @@ class SettingHeaderView: UITableViewHeaderFooterView {
result.distribution = .fill
result.alignment = .fill
result.isLayoutMarginsRelativeArrangement = true
result.layoutMargins = UIEdgeInsets(
top: Values.mediumSpacing,
left: Values.largeSpacing,
bottom: Values.smallSpacing,
right: Values.largeSpacing
)
return result
}()
@ -373,8 +401,6 @@ class SettingHeaderView: UITableViewHeaderFooterView {
}
private func setupLayout() {
self.heightAnchor.constraint(greaterThanOrEqualToConstant: Values.mediumSpacing).isActive = true
stackView.pin(to: self)
separator.pin(.left, to: .left, of: self)
@ -387,5 +413,14 @@ class SettingHeaderView: UITableViewHeaderFooterView {
fileprivate func update(with title: String) {
titleLabel.text = title
titleLabel.isHidden = title.isEmpty
stackView.layoutMargins = UIEdgeInsets(
top: (title.isEmpty ? Values.verySmallSpacing : Values.mediumSpacing),
left: Values.largeSpacing,
bottom: (title.isEmpty ? Values.verySmallSpacing : Values.mediumSpacing),
right: Values.largeSpacing
)
emptyHeightConstraint.isActive = title.isEmpty
filledHeightConstraint.isActive = !title.isEmpty
self.layoutIfNeeded()
}
}

View File

@ -88,7 +88,10 @@ public enum SettingsAction: Hashable, Equatable {
key: String,
onChange: (() -> Void)?
)
case settingBool(key: Setting.BoolKey)
case settingBool(
key: Setting.BoolKey,
confirmationInfo: ConfirmationModal.Info?
)
case settingEnum(
key: String,
title: String?,
@ -152,6 +155,10 @@ public enum SettingsAction: Hashable, Equatable {
createUpdateScreen: createUpdateScreen
)
}
public static func settingBool(key: Setting.BoolKey) -> SettingsAction {
return .settingBool(key: key, confirmationInfo: nil)
}
// MARK: - Conformance
@ -160,7 +167,10 @@ public enum SettingsAction: Hashable, Equatable {
switch self {
case .userDefaultsBool(_, let key, _): key.hash(into: &hasher)
case .settingBool(let key): key.hash(into: &hasher)
case .settingBool(let key, let confirmationInfo):
key.hash(into: &hasher)
confirmationInfo.hash(into: &hasher)
case .settingEnum(let key, let title, _):
key.hash(into: &hasher)
title.hash(into: &hasher)
@ -179,7 +189,11 @@ public enum SettingsAction: Hashable, Equatable {
case (.userDefaultsBool(_, let lhsKey, _), .userDefaultsBool(_, let rhsKey, _)):
return (lhsKey == rhsKey)
case (.settingBool(let lhsKey), .settingBool(let rhsKey)): return (lhsKey == rhsKey)
case (.settingBool(let lhsKey, let lhsConfirmationInfo), .settingBool(let rhsKey, let rhsConfirmationInfo)):
return (
lhsKey == rhsKey &&
lhsConfirmationInfo == rhsConfirmationInfo
)
case (.settingEnum(let lhsKey, let lhsTitle, _), .settingEnum(let rhsKey, let rhsTitle, _)):
return (

View File

@ -322,7 +322,7 @@ class SettingsCell: UITableViewCell {
toggleSwitch.setOn(newValue, animated: true)
}
case .settingBool(let key):
case .settingBool(let key, _):
actionContainerView.isHidden = false
toggleSwitch.isHidden = false
@ -373,19 +373,23 @@ class SettingsCell: UITableViewCell {
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
// Note: Only setting the highlighted state is done here, the unhighlight is done
// in 'setSelected'
guard highlighted else { return }
rightActionButtonContainerView.themeBackgroundColor = .solidButton_highlight
rightActionButtonContainerView.themeBackgroundColor = (highlighted ?
.solidButton_highlight :
.solidButton_background
)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Note: Only un-setting the unhighlighted state is done here, the highlighted state is done
// in 'setHighlighted'
guard !selected else { return }
// Note: When initially triggering a selection we will be coming from the highlighted
// state but will have already set highlighted to false at this stage, as a result we
// need to swap back into the "highlighted" state so we can properly unhighlight within
// the "deselect" animation
guard !selected else {
rightActionButtonContainerView.themeBackgroundColor = .solidButton_highlight
return
}
guard animated else {
rightActionButtonContainerView.themeBackgroundColor = .solidButton_background
return

View File

@ -3,8 +3,10 @@
import UIKit
import SessionUIKit
class BaseVC: UIViewController {
override var preferredStatusBarStyle: UIStatusBarStyle { return ThemeManager.currentTheme.statusBarStyle }
public class BaseVC: UIViewController {
public override var preferredStatusBarStyle: UIStatusBarStyle {
return ThemeManager.currentTheme.statusBarStyle
}
lazy var navBarTitleLabel: UILabel = {
let result = UILabel()
@ -26,7 +28,7 @@ class BaseVC: UIViewController {
return result
}()
override func viewDidLoad() {
public override func viewDidLoad() {
super.viewDidLoad()
navigationItem.backButtonTitle = ""

View File

@ -1,21 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface OWSScreenLockUI : NSObject
@property (nonatomic, readonly) UIWindow *screenBlockingWindow;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedManager;
- (void)setupWithRootWindow:(UIWindow *)rootWindow;
- (void)startObserving;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,511 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSScreenLockUI.h"
#import "OWSWindowManager.h"
#import "Session-Swift.h"
#import <SignalUtilitiesKit/ScreenLockViewController.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/UIView+OWS.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSScreenLockUI () <ScreenLockViewDelegate>
@property (nonatomic) UIWindow *screenBlockingWindow;
@property (nonatomic) ScreenLockViewController *screenBlockingViewController;
// Unlike UIApplication.applicationState, this state reflects the
// notifications, i.e. "did become active", "will resign active",
// "will enter foreground", "did enter background".
//
// We want to update our state to reflect these transitions and have
// the "update" logic be consistent with "last reported" state. i.e.
// when you're responding to "will resign active", we need to behave
// as though we're already inactive.
//
// Secondly, we need to show the screen protection _before_ we become
// inactive in order for it to be reflected in the app switcher.
@property (nonatomic) BOOL appIsInactiveOrBackground;
@property (nonatomic) BOOL appIsInBackground;
@property (nonatomic) BOOL isShowingScreenLockUI;
@property (nonatomic) BOOL didLastUnlockAttemptFail;
// We want to remain in "screen lock" mode while "local auth"
// UI is dismissing. So we lazily clear isShowingScreenLockUI
// using this property.
@property (nonatomic) BOOL shouldClearAuthUIWhenActive;
// Indicates whether or not the user is currently locked out of
// the app. Should only be set if OWSScreenLock.isScreenLockEnabled.
//
// * The user is locked out by default on app launch.
// * The user is also locked out if they spend more than
// "timeout" seconds outside the app. When the user leaves
// the app, a "countdown" begins.
@property (nonatomic) BOOL isScreenLockLocked;
// The "countdown" until screen lock takes effect.
@property (nonatomic, nullable) NSDate *screenLockCountdownDate;
@end
#pragma mark -
@implementation OWSScreenLockUI
+ (instancetype)sharedManager
{
static OWSScreenLockUI *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] initDefault];
});
return instance;
}
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
OWSAssertIsOnMainThread();
OWSSingletonAssert();
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:OWSApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:OWSApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:OWSApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:OWSApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(screenLockDidChange:)
name:OWSScreenLock.ScreenLockDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clockDidChange:)
name:NSSystemClockDidChangeNotification
object:nil];
}
- (void)setupWithRootWindow:(UIWindow *)rootWindow
{
OWSAssertIsOnMainThread();
OWSAssertDebug(rootWindow);
[self createScreenBlockingWindowWithRootWindow:rootWindow];
OWSAssertDebug(self.screenBlockingWindow);
}
- (void)startObserving
{
_appIsInactiveOrBackground = [UIApplication sharedApplication].applicationState != UIApplicationStateActive;
[self observeNotifications];
// Hide the screen blocking window until "app is ready" to
// avoid blocking the loading view.
[self updateScreenBlockingWindow:ScreenLockUIStateNone animated:NO];
// Initialize the screen lock state.
//
// It's not safe to access OWSScreenLock.isScreenLockEnabled
// until the app is ready.
[AppReadiness runNowOrWhenAppWillBecomeReady:^{
self.isScreenLockLocked = OWSScreenLock.sharedManager.isScreenLockEnabled;
[self ensureUI];
}];
}
#pragma mark - Methods
- (void)tryToActivateScreenLockBasedOnCountdown
{
OWSAssertDebug(!self.appIsInBackground);
OWSAssertIsOnMainThread();
if (!AppReadiness.isAppReady) {
// It's not safe to access OWSScreenLock.isScreenLockEnabled
// until the app is ready.
//
// We don't need to try to lock the screen lock;
// It will be initialized by `setupWithRootWindow`.
OWSLogVerbose(@"tryToActivateScreenLockUponBecomingActive NO 0");
return;
}
if (!OWSScreenLock.sharedManager.isScreenLockEnabled) {
// Screen lock is not enabled.
OWSLogVerbose(@"tryToActivateScreenLockUponBecomingActive NO 1");
return;
}
if (self.isScreenLockLocked) {
// Screen lock is already activated.
OWSLogVerbose(@"tryToActivateScreenLockUponBecomingActive NO 2");
return;
}
if (!self.screenLockCountdownDate) {
// We became inactive, but never started a countdown.
OWSLogVerbose(@"tryToActivateScreenLockUponBecomingActive NO 3");
return;
}
NSTimeInterval countdownInterval = fabs([self.screenLockCountdownDate timeIntervalSinceNow]);
OWSAssertDebug(countdownInterval >= 0);
NSTimeInterval screenLockTimeout = OWSScreenLock.sharedManager.screenLockTimeout;
OWSAssertDebug(screenLockTimeout >= 0);
if (countdownInterval >= screenLockTimeout) {
self.isScreenLockLocked = YES;
OWSLogVerbose(
@"tryToActivateScreenLockUponBecomingActive YES 4 (%0.3f >= %0.3f)", countdownInterval, screenLockTimeout);
} else {
OWSLogVerbose(
@"tryToActivateScreenLockUponBecomingActive NO 5 (%0.3f < %0.3f)", countdownInterval, screenLockTimeout);
}
}
// Setter for property indicating that the app is either
// inactive or in the background, e.g. not "foreground and active."
- (void)setAppIsInactiveOrBackground:(BOOL)appIsInactiveOrBackground
{
OWSAssertIsOnMainThread();
_appIsInactiveOrBackground = appIsInactiveOrBackground;
if (appIsInactiveOrBackground) {
if (!self.isShowingScreenLockUI) {
[self startScreenLockCountdownIfNecessary];
}
} else {
[self tryToActivateScreenLockBasedOnCountdown];
OWSLogInfo(@"setAppIsInactiveOrBackground clear screenLockCountdownDate.");
self.screenLockCountdownDate = nil;
}
[self ensureUI];
}
// Setter for property indicating that the app is in the background.
// If true, by definition the app is not active.
- (void)setAppIsInBackground:(BOOL)appIsInBackground
{
OWSAssertIsOnMainThread();
_appIsInBackground = appIsInBackground;
if (self.appIsInBackground) {
[self startScreenLockCountdownIfNecessary];
} else {
[self tryToActivateScreenLockBasedOnCountdown];
}
[self ensureUI];
}
- (void)startScreenLockCountdownIfNecessary
{
OWSLogVerbose(@"startScreenLockCountdownIfNecessary: %d", self.screenLockCountdownDate != nil);
if (!self.screenLockCountdownDate) {
OWSLogInfo(@"startScreenLockCountdown.");
self.screenLockCountdownDate = [NSDate new];
}
self.didLastUnlockAttemptFail = NO;
}
// Ensure that:
//
// * The blocking window has the correct state.
// * That we show the "iOS auth UI to unlock" if necessary.
- (void)ensureUI
{
OWSAssertIsOnMainThread();
if (!AppReadiness.isAppReady) {
[AppReadiness runNowOrWhenAppWillBecomeReady:^{
[self ensureUI];
}];
return;
}
ScreenLockUIState desiredUIState = self.desiredUIState;
OWSLogVerbose(@"ensureUI: %@", NSStringForScreenLockUIState(desiredUIState));
[self updateScreenBlockingWindow:desiredUIState animated:YES];
// Show the "iOS auth UI to unlock" if necessary.
if (desiredUIState == ScreenLockUIStateScreenLock && !self.didLastUnlockAttemptFail) {
[self tryToPresentAuthUIToUnlockScreenLock];
}
}
- (void)tryToPresentAuthUIToUnlockScreenLock
{
OWSAssertIsOnMainThread();
if (self.isShowingScreenLockUI) {
// We're already showing the auth UI; abort.
return;
}
if (self.appIsInactiveOrBackground) {
// Never show the auth UI unless active.
return;
}
OWSLogInfo(@"try to unlock screen lock");
self.isShowingScreenLockUI = YES;
[OWSScreenLock.sharedManager
tryToUnlockScreenLockWithSuccess:^{
OWSLogInfo(@"unlock screen lock succeeded.");
self.isShowingScreenLockUI = NO;
self.isScreenLockLocked = NO;
[self ensureUI];
}
failure:^(NSError *error) {
OWSLogInfo(@"unlock screen lock failed.");
[self clearAuthUIWhenActive];
self.didLastUnlockAttemptFail = YES;
[self showScreenLockFailureAlertWithMessage:error.localizedDescription];
}
unexpectedFailure:^(NSError *error) {
OWSLogInfo(@"unlock screen lock unexpectedly failed.");
// Local Authentication isn't working properly.
// This isn't covered by the docs or the forums but in practice
// it appears to be effective to retry again after waiting a bit.
dispatch_async(dispatch_get_main_queue(), ^{
[self clearAuthUIWhenActive];
});
}
cancel:^{
OWSLogInfo(@"unlock screen lock cancelled.");
[self clearAuthUIWhenActive];
self.didLastUnlockAttemptFail = YES;
// Re-show the unlock UI.
[self ensureUI];
}];
[self ensureUI];
}
// Determines what the state of the app should be.
- (ScreenLockUIState)desiredUIState
{
if (self.isScreenLockLocked) {
if (self.appIsInactiveOrBackground) {
OWSLogVerbose(@"desiredUIState: screen protection 1.");
return ScreenLockUIStateScreenProtection;
} else {
OWSLogVerbose(@"desiredUIState: screen lock 2.");
return ScreenLockUIStateScreenLock;
}
}
if (!self.appIsInactiveOrBackground) {
// App is inactive or background.
OWSLogVerbose(@"desiredUIState: none 3.");
return ScreenLockUIStateNone;
}
if (SMKEnvironment.shared.isRequestingPermission) {
return ScreenLockUIStateNone;
}
if ([SMKPreferences isScreenSecurityEnabled]) {
OWSLogVerbose(@"desiredUIState: screen protection 4.");
return ScreenLockUIStateScreenProtection;
} else {
OWSLogVerbose(@"desiredUIState: none 5.");
return ScreenLockUIStateNone;
}
}
- (void)showScreenLockFailureAlertWithMessage:(NSString *)message
{
OWSAssertIsOnMainThread();
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_UNLOCK_FAILED",
@"Title for alert indicating that screen lock could not be unlocked.")
message:message
buttonTitle:nil
buttonAction:^(UIAlertAction *action) {
// After the alert, update the UI.
[self ensureUI];
}
fromViewController:self.screenBlockingWindow.rootViewController];
}
// 'Screen Blocking' window obscures the app screen:
//
// * In the app switcher.
// * During 'Screen Lock' unlock process.
- (void)createScreenBlockingWindowWithRootWindow:(UIWindow *)rootWindow
{
OWSAssertIsOnMainThread();
OWSAssertDebug(rootWindow);
UIWindow *window = [[UIWindow alloc] initWithFrame:rootWindow.bounds];
window.hidden = NO;
window.windowLevel = UIWindowLevel_Background;
window.opaque = YES;
window.backgroundColor = UIColor.ows_materialBlueColor;
ScreenLockViewController *viewController = [ScreenLockViewController new];
viewController.delegate = self;
window.rootViewController = viewController;
self.screenBlockingWindow = window;
self.screenBlockingViewController = viewController;
}
// The "screen blocking" window has three possible states:
//
// * "Just a logo". Used when app is launching and in app switcher. Must match the "Launch Screen"
// storyboard pixel-for-pixel.
// * "Screen Lock, local auth UI presented". Move the Signal logo so that it is visible.
// * "Screen Lock, local auth UI not presented". Move the Signal logo so that it is visible,
// show "unlock" button.
- (void)updateScreenBlockingWindow:(ScreenLockUIState)desiredUIState animated:(BOOL)animated
{
OWSAssertIsOnMainThread();
BOOL shouldShowBlockWindow = desiredUIState != ScreenLockUIStateNone;
[OWSWindowManager.sharedManager setIsScreenBlockActive:shouldShowBlockWindow];
[self.screenBlockingViewController updateUIWithState:desiredUIState
isLogoAtTop:self.isShowingScreenLockUI
animated:animated];
}
#pragma mark - Events
- (void)screenLockDidChange:(NSNotification *)notification
{
[self ensureUI];
}
- (void)clearAuthUIWhenActive
{
// For continuity, continue to present blocking screen in "screen lock" mode while
// dismissing the "local auth UI".
if (self.appIsInactiveOrBackground) {
self.shouldClearAuthUIWhenActive = YES;
} else {
self.isShowingScreenLockUI = NO;
[self ensureUI];
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
if (self.shouldClearAuthUIWhenActive) {
self.shouldClearAuthUIWhenActive = NO;
self.isShowingScreenLockUI = NO;
}
self.appIsInactiveOrBackground = NO;
}
- (void)applicationWillResignActive:(NSNotification *)notification
{
self.appIsInactiveOrBackground = YES;
}
- (void)applicationWillEnterForeground:(NSNotification *)notification
{
self.appIsInBackground = NO;
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
self.appIsInBackground = YES;
}
// Whenever the device date/time is edited by the user,
// trigger screen lock immediately if enabled.
- (void)clockDidChange:(NSNotification *)notification
{
OWSLogInfo(@"clock did change");
if (!AppReadiness.isAppReady) {
// It's not safe to access OWSScreenLock.isScreenLockEnabled
// until the app is ready.
//
// We don't need to try to lock the screen lock;
// It will be initialized by `setupWithRootWindow`.
OWSLogVerbose(@"clockDidChange 0");
return;
}
self.isScreenLockLocked = OWSScreenLock.sharedManager.isScreenLockEnabled;
// NOTE: this notifications fires _before_ applicationDidBecomeActive,
// which is desirable. Don't assume that though; call ensureUI
// just in case it's necessary.
[self ensureUI];
}
#pragma mark - ScreenLockViewDelegate
- (void)unlockButtonWasTapped
{
OWSAssertIsOnMainThread();
if (self.appIsInactiveOrBackground) {
// This button can be pressed while the app is inactive
// for a brief window while the iOS auth UI is dismissing.
return;
}
OWSLogInfo(@"unlockButtonWasTapped");
self.didLastUnlockAttemptFail = NO;
[self ensureUI];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,381 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionMessagingKit
import SessionUtilitiesKit
import SignalUtilitiesKit
class ScreenLockUI {
public static let shared: ScreenLockUI = ScreenLockUI()
public lazy var screenBlockingWindow: UIWindow = {
let result: UIWindow = UIWindow()
result.isHidden = false
result.windowLevel = ._Background
result.isOpaque = true
result.themeBackgroundColor = .backgroundPrimary
result.rootViewController = self.screenBlockingViewController
return result
}()
private lazy var screenBlockingViewController: ScreenLockViewController = {
let result: ScreenLockViewController = ScreenLockViewController { [weak self] in
guard self?.appIsInactiveOrBackground == false else {
// This button can be pressed while the app is inactive
// for a brief window while the iOS auth UI is dismissing.
return
}
Logger.info("unlockButtonWasTapped")
self?.didLastUnlockAttemptFail = false
self?.ensureUI()
}
return result
}()
/// Unlike UIApplication.applicationState, this state reflects the notifications, i.e. "did become active", "will resign active",
/// "will enter foreground", "did enter background".
///
///We want to update our state to reflect these transitions and have the "update" logic be consistent with "last reported"
///state. i.e. when you're responding to "will resign active", we need to behave as though we're already inactive.
///
///Secondly, we need to show the screen protection _before_ we become inactive in order for it to be reflected in the
///app switcher.
private var appIsInactiveOrBackground: Bool = false {
didSet {
if self.appIsInactiveOrBackground {
if !self.isShowingScreenLockUI {
self.didLastUnlockAttemptFail = false
self.tryToActivateScreenLockBasedOnCountdown()
}
}
else if !self.didUnlockJustSucceed {
self.tryToActivateScreenLockBasedOnCountdown()
}
self.didUnlockJustSucceed = false
self.ensureUI()
}
}
private var appIsInBackground: Bool = false {
didSet {
self.didUnlockJustSucceed = false
self.tryToActivateScreenLockBasedOnCountdown()
self.ensureUI()
}
}
private var isShowingScreenLockUI: Bool = false
private var didUnlockJustSucceed: Bool = false
private var didLastUnlockAttemptFail: Bool = false
/// We want to remain in "screen lock" mode while "local auth" UI is dismissing. So we lazily clear isShowingScreenLockUI
/// using this property.
private var shouldClearAuthUIWhenActive: Bool = false
/// Indicates whether or not the user is currently locked out of the app. Should only be set if db[.isScreenLockEnabled].
///
/// * The user is locked out by default on app launch.
/// * The user is also locked out if the app is sent to the background
private var isScreenLockLocked: Bool = false
// Determines what the state of the app should be.
private var desiredUIState: ScreenLockViewController.State {
if isScreenLockLocked {
if appIsInactiveOrBackground {
Logger.verbose("desiredUIState: screen protection 1.")
return .protection
}
Logger.verbose("desiredUIState: screen lock 2.")
return (isShowingScreenLockUI ? .protection : .lock)
}
if !self.appIsInactiveOrBackground {
// App is inactive or background.
Logger.verbose("desiredUIState: none 3.");
return .none;
}
if Environment.shared?.isRequestingPermission == true {
return .none;
}
if Storage.shared[.appSwitcherPreviewEnabled] {
Logger.verbose("desiredUIState: screen protection 4.")
return .protection;
}
Logger.verbose("desiredUIState: none 5.")
return .none
}
// MARK: - Lifecycle
deinit {
NotificationCenter.default.removeObserver(self)
}
private func observeNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidBecomeActive),
name: .OWSApplicationDidBecomeActive,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationWillResignActive),
name: .OWSApplicationWillResignActive,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationWillEnterForeground),
name: .OWSApplicationWillEnterForeground,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidEnterBackground),
name: .OWSApplicationDidEnterBackground,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(clockDidChange),
name: .NSSystemClockDidChange,
object: nil
)
}
public func setupWithRootWindow(rootWindow: UIWindow) {
self.screenBlockingWindow.frame = rootWindow.bounds
}
public func startObserving() {
self.appIsInactiveOrBackground = (UIApplication.shared.applicationState != .active)
self.observeNotifications()
// Hide the screen blocking window until "app is ready" to
// avoid blocking the loading view.
updateScreenBlockingWindow(state: .none, animated: false)
// Initialize the screen lock state.
//
// It's not safe to access OWSScreenLock.isScreenLockEnabled
// until the app is ready.
AppReadiness.runNowOrWhenAppWillBecomeReady { [weak self] in
self?.isScreenLockLocked = Storage.shared[.isScreenLockEnabled]
self?.ensureUI()
}
}
// MARK: - Functions
private func tryToActivateScreenLockBasedOnCountdown() {
guard AppReadiness.isAppReady() else {
// It's not safe to access OWSScreenLock.isScreenLockEnabled
// until the app is ready.
//
// We don't need to try to lock the screen lock;
// It will be initialized by `setupWithRootWindow`.
Logger.verbose("tryToActivateScreenLockUponBecomingActive NO 0")
return
}
guard Storage.shared[.isScreenLockEnabled] else {
// Screen lock is not enabled.
Logger.verbose("tryToActivateScreenLockUponBecomingActive NO 1")
return;
}
guard !isScreenLockLocked else {
// Screen lock is already activated.
Logger.verbose("tryToActivateScreenLockUponBecomingActive NO 2")
return;
}
self.isScreenLockLocked = true
}
/// Ensure that:
///
/// * The blocking window has the correct state.
/// * That we show the "iOS auth UI to unlock" if necessary.
private func ensureUI() {
guard AppReadiness.isAppReady() else {
AppReadiness.runNowOrWhenAppWillBecomeReady { [weak self] in
self?.ensureUI()
}
return
}
let desiredUIState: ScreenLockViewController.State = self.desiredUIState
Logger.verbose("ensureUI: \(desiredUIState)")
// Show the "iOS auth UI to unlock" if necessary.
if desiredUIState == .lock && !didLastUnlockAttemptFail {
tryToPresentAuthUIToUnlockScreenLock()
}
// Note: We want to regenerate the 'desiredUIState' as if we are about to show the
// 'unlock screen' UI then we shouldn't show the "unlock" button
updateScreenBlockingWindow(state: self.desiredUIState, animated: true)
}
private func tryToPresentAuthUIToUnlockScreenLock() {
guard !isShowingScreenLockUI else { return } // We're already showing the auth UI; abort
guard !appIsInactiveOrBackground else { return } // Never show the auth UI unless active
Logger.info("try to unlock screen lock")
isShowingScreenLockUI = true
ScreenLock.shared.tryToUnlockScreenLock(
success: { [weak self] in
Logger.info("unlock screen lock succeeded.")
self?.isShowingScreenLockUI = false
self?.isScreenLockLocked = false
self?.didUnlockJustSucceed = true
self?.ensureUI()
},
failure: { [weak self] error in
Logger.info("unlock screen lock failed.")
self?.clearAuthUIWhenActive()
self?.didLastUnlockAttemptFail = true
self?.showScreenLockFailureAlert(message: error.localizedDescription)
},
unexpectedFailure: { [weak self] error in
Logger.info("unlock screen lock unexpectedly failed.")
// Local Authentication isn't working properly.
// This isn't covered by the docs or the forums but in practice
// it appears to be effective to retry again after waiting a bit.
DispatchQueue.main.async {
self?.clearAuthUIWhenActive()
}
},
cancel: { [weak self] in
Logger.info("unlock screen lock cancelled.")
self?.clearAuthUIWhenActive()
self?.didLastUnlockAttemptFail = true
// Re-show the unlock UI
self?.ensureUI()
}
)
self.ensureUI()
}
private func showScreenLockFailureAlert(message: String) {
OWSAlerts.showAlert(
title: "SCREEN_LOCK_UNLOCK_FAILED".localized(),
message: message,
buttonTitle: nil,
buttonAction: { [weak self] _ in self?.ensureUI() }, // After the alert, update the UI
fromViewController: screenBlockingWindow.rootViewController
)
}
/// 'Screen Blocking' window obscures the app screen:
///
/// * In the app switcher.
/// * During 'Screen Lock' unlock process.
private func createScreenBlockingWindow(rootWindow: UIWindow) {
let window: UIWindow = UIWindow(frame: rootWindow.bounds)
window.isHidden = false
window.windowLevel = ._Background
window.isOpaque = true
window.themeBackgroundColor = .backgroundPrimary
let viewController: ScreenLockViewController = ScreenLockViewController { [weak self] in
guard self?.appIsInactiveOrBackground == false else {
// This button can be pressed while the app is inactive
// for a brief window while the iOS auth UI is dismissing.
return
}
Logger.info("unlockButtonWasTapped")
self?.didLastUnlockAttemptFail = false
self?.ensureUI()
}
window.rootViewController = viewController
self.screenBlockingWindow = window
self.screenBlockingViewController = viewController
}
/// The "screen blocking" window has three possible states:
///
/// * "Just a logo". Used when app is launching and in app switcher. Must match the "Launch Screen" storyboard pixel-for-pixel.
/// * "Screen Lock, local auth UI presented". Move the Signal logo so that it is visible.
/// * "Screen Lock, local auth UI not presented". Move the Signal logo so that it is visible, show "unlock" button.
private func updateScreenBlockingWindow(state: ScreenLockViewController.State, animated: Bool) {
let shouldShowBlockWindow: Bool = (state != .none)
OWSWindowManager.shared().isScreenBlockActive = shouldShowBlockWindow
self.screenBlockingViewController.updateUI(state: state, animated: animated)
}
// MARK: - Events
private func clearAuthUIWhenActive() {
// For continuity, continue to present blocking screen in "screen lock" mode while
// dismissing the "local auth UI".
if self.appIsInactiveOrBackground {
self.shouldClearAuthUIWhenActive = true
}
else {
self.isShowingScreenLockUI = false
self.ensureUI()
}
}
@objc private func applicationDidBecomeActive() {
if self.shouldClearAuthUIWhenActive {
self.shouldClearAuthUIWhenActive = false
self.isShowingScreenLockUI = false
}
self.appIsInactiveOrBackground = false
}
@objc private func applicationWillResignActive() {
self.appIsInactiveOrBackground = true
}
@objc private func applicationWillEnterForeground() {
self.appIsInBackground = false
}
@objc private func applicationDidEnterBackground() {
self.appIsInBackground = true
}
/// Whenever the device date/time is edited by the user, trigger screen lock immediately if enabled.
@objc private func clockDidChange() {
Logger.info("clock did change")
guard AppReadiness.isAppReady() else {
// It's not safe to access OWSScreenLock.isScreenLockEnabled
// until the app is ready.
//
// We don't need to try to lock the screen lock;
// It will be initialized by `setupWithRootWindow`.
Logger.verbose("clockDidChange 0")
return;
}
self.isScreenLockLocked = Storage.shared[.isScreenLockEnabled]
// NOTE: this notifications fires _before_ applicationDidBecomeActive,
// which is desirable. Don't assume that though; call ensureUI
// just in case it's necessary.
self.ensureUI()
}
}

View File

@ -0,0 +1,179 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUIKit
public class ConfirmationModal: Modal {
public struct Info: Equatable, Hashable {
enum State {
case whenEnabled
case whenDisabled
case always
func shouldShow(for value: Bool) -> Bool {
switch self {
case .whenEnabled: return (value == true)
case .whenDisabled: return (value == false)
case .always: return true
}
}
}
let title: String
let explanation: String?
let stateToShow: State
let confirmTitle: String
let confirmStyle: ThemeValue
let cancelTitle: String
let cancelStyle: ThemeValue
let onConfirm: (() -> ())?
init(
title: String,
explanation: String? = nil,
stateToShow: State = .always,
confirmTitle: String = "continue_2".localized(),
confirmStyle: ThemeValue = .textPrimary,
cancelTitle: String = "TXT_CANCEL_TITLE".localized(),
cancelStyle: ThemeValue = .danger,
onConfirm: (() -> ())? = nil
) {
self.title = title
self.explanation = explanation
self.stateToShow = stateToShow
self.confirmTitle = confirmTitle
self.confirmStyle = confirmStyle
self.cancelTitle = cancelTitle
self.cancelStyle = cancelStyle
self.onConfirm = onConfirm
}
public static func == (lhs: ConfirmationModal.Info, rhs: ConfirmationModal.Info) -> Bool {
return (
lhs.title == rhs.title &&
lhs.explanation == rhs.explanation &&
lhs.stateToShow == rhs.stateToShow &&
lhs.confirmTitle == rhs.confirmTitle &&
lhs.confirmStyle == rhs.confirmStyle &&
lhs.cancelTitle == rhs.cancelTitle &&
lhs.cancelStyle == rhs.cancelStyle
)
}
public func hash(into hasher: inout Hasher) {
title.hash(into: &hasher)
explanation.hash(into: &hasher)
stateToShow.hash(into: &hasher)
confirmTitle.hash(into: &hasher)
confirmStyle.hash(into: &hasher)
cancelTitle.hash(into: &hasher)
cancelStyle.hash(into: &hasher)
}
}
private let onConfirm: (UIViewController) -> ()
// MARK: - Components
private lazy var titleLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.themeTextColor = .textPrimary
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result
}()
private lazy var explanationLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.smallFontSize)
result.themeTextColor = .textPrimary
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result
}()
private lazy var confirmButton: UIButton = {
let result: UIButton = Modal.createButton(
title: "",
titleColor: .danger
)
result.addTarget(self, action: #selector(confirmationPressed), for: .touchUpInside)
return result
}()
private lazy var buttonStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ confirmButton, cancelButton ])
result.axis = .horizontal
result.distribution = .fillEqually
return result
}()
private lazy var contentStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ])
result.axis = .vertical
result.spacing = Values.smallSpacing
result.isLayoutMarginsRelativeArrangement = true
result.layoutMargins = UIEdgeInsets(
top: Values.largeSpacing,
leading: Values.largeSpacing,
bottom: Values.verySmallSpacing,
trailing: Values.largeSpacing
)
return result
}()
private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackView ])
result.axis = .vertical
result.spacing = Values.largeSpacing - Values.smallFontSize / 2
return result
}()
// MARK: - Lifecycle
init(info: Info, onConfirm: @escaping (UIViewController) -> ()) {
self.onConfirm = { viewController in
onConfirm(viewController)
info.onConfirm?()
}
super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
// Set the content based on the provided info
titleLabel.text = info.title
explanationLabel.text = info.explanation
explanationLabel.isHidden = (info.explanation == nil)
confirmButton.setTitle(info.confirmTitle, for: .normal)
confirmButton.setThemeTitleColor(info.confirmStyle, for: .normal)
cancelButton.setTitle(info.cancelTitle, for: .normal)
cancelButton.setThemeTitleColor(info.cancelStyle, for: .normal)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func populateContentView() {
contentView.addSubview(mainStackView)
mainStackView.pin(to: contentView)
}
// MARK: - Interaction
@objc private func confirmationPressed() {
onConfirm(self)
}
}

View File

@ -3,66 +3,92 @@
import UIKit
import SessionUIKit
@objc(LKModal)
class Modal: BaseVC, UIGestureRecognizerDelegate {
public class Modal: BaseVC, UIGestureRecognizerDelegate {
private static let cornerRadius: CGFloat = 11
// MARK: Components
lazy var contentView: UIView = {
let result = UIView()
result.backgroundColor = Colors.modalBackground
// MARK: - Components
lazy var dimmingView: UIView = {
let result = UIVisualEffectView()
ThemeManager.onThemeChange(observer: result) { [weak result] theme, _ in
result?.effect = UIBlurEffect(
style: (theme.interfaceStyle == .light ?
UIBlurEffect.Style.systemUltraThinMaterialLight :
UIBlurEffect.Style.systemUltraThinMaterial
)
)
}
return result
}()
lazy var containerView: UIView = {
let result: UIView = UIView()
result.clipsToBounds = false
result.themeBackgroundColor = .alert_background
result.themeShadowColor = .black
result.layer.cornerRadius = Modal.cornerRadius
result.layer.masksToBounds = false
result.layer.borderColor = isLightMode ? UIColor.white.cgColor : Colors.modalBorder.cgColor
result.layer.borderWidth = 1
result.layer.shadowColor = UIColor.black.cgColor
result.layer.shadowRadius = isLightMode ? 2 : 8
result.layer.shadowOpacity = isLightMode ? 0.1 : 0.64
result.layer.shadowRadius = 10
result.layer.shadowOpacity = 0.4
return result
}()
lazy var contentView: UIView = {
let result: UIView = UIView()
result.clipsToBounds = true
result.layer.cornerRadius = Modal.cornerRadius
return result
}()
lazy var cancelButton: UIButton = {
let result = UIButton()
result.set(.height, to: Values.mediumButtonHeight)
result.layer.cornerRadius = Modal.buttonCornerRadius
result.backgroundColor = Colors.buttonBackground
result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
result.setTitleColor(Colors.text, for: UIControl.State.normal)
result.setTitle(NSLocalizedString("cancel", comment: ""), for: UIControl.State.normal)
let result: UIButton = Modal.createButton(title: "cancel".localized(), titleColor: .textPrimary)
result.addTarget(self, action: #selector(close), for: .touchUpInside)
return result
}()
// MARK: Settings
private static let cornerRadius: CGFloat = 10
static let buttonCornerRadius = CGFloat(5)
// MARK: - Lifecycle
// MARK: Lifecycle
override func viewDidLoad() {
public override func viewDidLoad() {
super.viewDidLoad()
let alpha = isLightMode ? CGFloat(0.1) : Values.highOpacity
view.backgroundColor = UIColor(hex: 0x000000).withAlphaComponent(alpha)
cancelButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside)
// Need to remove the background color which is added by the BaseVC
view.themeBackgroundColor = .clear
view.addSubview(dimmingView)
view.addSubview(containerView)
containerView.addSubview(contentView)
dimmingView.pin(to: view)
contentView.pin(to: containerView)
if UIDevice.current.isIPad {
containerView.set(.width, to: Values.iPadModalWidth)
containerView.center(in: view)
}
else {
containerView.leadingAnchor
.constraint(equalTo: view.leadingAnchor, constant: Values.veryLargeSpacing)
.isActive = true
view.trailingAnchor
.constraint(equalTo: containerView.trailingAnchor, constant: Values.veryLargeSpacing)
.isActive = true
containerView.center(.vertical, in: view)
}
// Gestures
let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(close))
swipeGestureRecognizer.direction = .down
view.addGestureRecognizer(swipeGestureRecognizer)
dimmingView.addGestureRecognizer(swipeGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(close))
tapGestureRecognizer.delegate = self
view.addGestureRecognizer(tapGestureRecognizer)
dimmingView.addGestureRecognizer(tapGestureRecognizer)
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
view.addSubview(contentView)
if UIDevice.current.isIPad {
contentView.set(.width, to: Values.iPadModalWidth)
contentView.center(in: view)
} else {
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Values.veryLargeSpacing).isActive = true
view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: Values.veryLargeSpacing).isActive = true
contentView.center(.vertical, in: view)
}
populateContentView()
}
@ -71,6 +97,18 @@ class Modal: BaseVC, UIGestureRecognizerDelegate {
preconditionFailure("populateContentView() is abstract and must be overridden.")
}
static func createButton(title: String, titleColor: ThemeValue) -> UIButton {
let result: UIButton = UIButton()
result.titleLabel?.font = .systemFont(ofSize: Values.mediumFontSize, weight: UIFont.Weight(600))
result.setTitle(title, for: .normal)
result.setThemeTitleColor(titleColor, for: .normal)
result.setThemeBackgroundColor(.alert_buttonBackground, for: .normal)
result.setThemeBackgroundColor(.alert_buttonHighlight, for: .highlighted)
result.set(.height, to: Values.alertButtonHeight)
return result
}
// MARK: - Interaction
@objc func close() {
@ -79,7 +117,7 @@ class Modal: BaseVC, UIGestureRecognizerDelegate {
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let location: CGPoint = touch.location(in: contentView)
return !contentView.point(inside: location, with: nil)

View File

@ -1,39 +1,47 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Photos
import PhotosUI
import SessionUtilitiesKit
public func requestCameraPermissionIfNeeded() -> Bool {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: return true
case .denied, .restricted:
let modal = PermissionMissingModal(permission: "camera") { }
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil)
return false
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in })
return false
default: return false
case .authorized: return true
case .denied, .restricted:
let modal = PermissionMissingModal(permission: "camera") { }
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil)
return false
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in })
return false
default: return false
}
}
public func requestMicrophonePermissionIfNeeded(onNotGranted: @escaping () -> Void) {
public func requestMicrophonePermissionIfNeeded(onNotGranted: (() -> Void)? = nil) {
switch AVAudioSession.sharedInstance().recordPermission {
case .granted: break
case .denied:
onNotGranted()
let modal = PermissionMissingModal(permission: "microphone") {
onNotGranted()
}
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil)
case .undetermined:
onNotGranted()
AVAudioSession.sharedInstance().requestRecordPermission { _ in }
default: break
case .granted: break
case .denied:
onNotGranted?()
let modal = PermissionMissingModal(permission: "microphone") {
onNotGranted?()
}
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil)
case .undetermined:
onNotGranted?()
AVAudioSession.sharedInstance().requestRecordPermission { _ in }
default: break
}
}
@ -66,7 +74,8 @@ public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void)
}
}
}
} else {
}
else {
authorizationStatus = PHPhotoLibrary.authorizationStatus()
if authorizationStatus == .notDetermined {
PHPhotoLibrary.requestAuthorization { status in
@ -76,15 +85,16 @@ public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void)
}
}
}
switch authorizationStatus {
case .authorized, .limited:
onAuthorized()
case .denied, .restricted:
let modal = PermissionMissingModal(permission: "library") { }
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil)
default: return
case .authorized, .limited: onAuthorized()
case .denied, .restricted:
let modal = PermissionMissingModal(permission: "library") { }
modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil)
default: return
}
}

View File

@ -1438,7 +1438,7 @@ enum _003_YDBToGRDBMigration: Migration {
.defaulting(to: Preferences.Sound.defaultNotificationSound)
db[.playNotificationSoundInForeground] = (legacyPreferences[SMKLegacy.preferencesKeyNotificationSoundInForeground] as? Bool == true)
db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType(rawValue: legacyPreferences[SMKLegacy.preferencesKeyNotificationPreviewType] as? Int ?? -1)
.defaulting(to: .nameAndPreview)
.defaulting(to: .defaultPreviewType)
if let lastPushToken: String = legacyPreferences[SMKLegacy.preferencesKeyLastRecordedPushToken] as? String {
db[.lastRecordedPushToken] = lastPushToken
@ -1454,8 +1454,13 @@ enum _003_YDBToGRDBMigration: Migration {
db[.areReadReceiptsEnabled] = (legacyPreferences[SMKLegacy.readReceiptManagerAreReadReceiptsEnabled] as? Bool == true)
db[.typingIndicatorsEnabled] = (legacyPreferences[SMKLegacy.typingIndicatorsEnabledKey] as? Bool == true)
db[.isScreenLockEnabled] = (legacyPreferences[SMKLegacy.screenLockIsScreenLockEnabledKey] as? Bool == true)
db[.screenLockTimeoutSeconds] = (legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] as? Double)
.defaulting(to: (15 * 60))
// Note: 'screenLockTimeoutSeconds' has been removed, but we want to avoid changing the behaviour
// of old migrations when possible
db.unsafeSet(
key: "screenLockTimeoutSeconds",
value: (legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] as? Double)
.defaulting(to: (15 * 60))
)
db[.appSwitcherPreviewEnabled] = (legacyPreferences[SMKLegacy.preferencesKeyScreenSecurityDisabled] as? Bool == false)
db[.areLinkPreviewsEnabled] = (legacyPreferences[SMKLegacy.preferencesKeyAreLinkPreviewsEnabled] as? Bool == true)
db[.areCallsEnabled] = (legacyPreferences[SMKLegacy.preferencesKeyAreCallsEnabled] as? Bool == true)

View File

@ -5,7 +5,6 @@ import Foundation
public extension Notification.Name {
static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived")
static let incomingMessageMarkedAsRead = Notification.Name("incomingMessageMarkedAsRead")
static let missedCall = Notification.Name("missedCall")
}
@ -16,5 +15,4 @@ public extension Notification.Key {
@objc public extension NSNotification {
@objc static let initialConfigurationMessageReceived = Notification.Name.initialConfigurationMessageReceived.rawValue as NSString
@objc static let incomingMessageMarkedAsRead = Notification.Name.incomingMessageMarkedAsRead.rawValue as NSString
}

View File

@ -62,6 +62,10 @@ public extension Setting.BoolKey {
/// A flag indicating whether the app is ready for app extensions to run
static let isReadyForAppExtensions: Setting.BoolKey = "isReadyForAppExtensions"
/// Controls whether the device should show screenshot notifications in one-to-one conversations (will always
/// send screenshot notifications, this just controls whether they get filtered out or not)
static let showScreenshotNotifications: Setting.BoolKey = "showScreenshotNotifications"
}
public extension Setting.StringKey {
@ -74,11 +78,14 @@ public extension Setting.StringKey {
public extension Setting.DoubleKey {
/// The duration of the timeout for screen lock in seconds
@available(*, unavailable, message: "Screen Lock should always be instant now")
static let screenLockTimeoutSeconds: Setting.DoubleKey = "screenLockTimeoutSeconds"
}
public enum Preferences {
public enum NotificationPreviewType: Int, CaseIterable, EnumIntSetting, Differentiable {
public static var defaultPreviewType: NotificationPreviewType = .nameAndPreview
/// Notifications should include both the sender name and a preview of the message content
case nameAndPreview
@ -303,61 +310,7 @@ public enum Preferences {
// MARK: - Objective C Support
// FIXME: Remove the below the 'NotificationSettingsViewController' and 'OWSSoundSettingsViewController' have been refactored to Swift
@objc(SMKPreferences)
public class SMKPreferences: NSObject {
@objc(setScreenSecurity:)
static func objc_setScreenSecurity(_ enabled: Bool) {
Storage.shared.write { db in db[.appSwitcherPreviewEnabled] = enabled }
}
@objc(isScreenSecurityEnabled)
static func objc_isScreenSecurityEnabled() -> Bool {
return Storage.shared[.appSwitcherPreviewEnabled]
}
@objc(setAreReadReceiptsEnabled:)
static func objc_setAreReadReceiptsEnabled(_ enabled: Bool) {
Storage.shared.write { db in db[.areReadReceiptsEnabled] = enabled }
}
@objc(areReadReceiptsEnabled)
static func objc_areReadReceiptsEnabled() -> Bool {
return Storage.shared[.areReadReceiptsEnabled]
}
@objc(setTypingIndicatorsEnabled:)
static func objc_setTypingIndicatorsEnabled(_ enabled: Bool) {
Storage.shared.write { db in db[.typingIndicatorsEnabled] = enabled }
}
@objc(areTypingIndicatorsEnabled)
static func objc_areTypingIndicatorsEnabled() -> Bool {
return Storage.shared[.typingIndicatorsEnabled]
}
@objc(setLinkPreviewsEnabled:)
static func objc_setLinkPreviewsEnabled(_ enabled: Bool) {
Storage.shared.write { db in db[.areLinkPreviewsEnabled] = enabled }
}
@objc(areLinkPreviewsEnabled)
static func objc_areLinkPreviewsEnabled() -> Bool {
return Storage.shared[.areLinkPreviewsEnabled]
}
@objc(setCallsEnabled:)
static func objc_setCallsEnabled(_ enabled: Bool) {
Storage.shared.write { db in db[.areCallsEnabled] = enabled }
}
@objc(areCallsEnabled)
static func objc_areCallsEnabled() -> Bool {
return Storage.shared[.areCallsEnabled]
}
}
// FIXME: Remove the below when OWSConversationSettingsViewController no longer nees SMKSound
@objc(SMKSound)
public class SMKSound: NSObject {
@objc public static var notificationSounds: [Int] = Preferences.Sound.notificationSounds.map { $0.rawValue }

View File

@ -65,7 +65,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
// Title & body
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview)
.defaulting(to: .defaultPreviewType)
switch previewType {
case .nameAndPreview:

View File

@ -7,7 +7,7 @@ import SignalUtilitiesKit
import SessionUIKit
import SessionUtilitiesKit
final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockViewDelegate {
final class SAEScreenLockViewController: ScreenLockViewController {
private var hasShownAuthUIOnce: Bool = false
private var isShowingAuthUI: Bool = false
@ -16,10 +16,10 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
// MARK: - Initialization
init(shareViewDelegate: ShareViewDelegate) {
super.init(nibName: nil, bundle: nil)
super.init()
self.onUnlockPressed = { [weak self] in self?.unlockButtonWasTapped() }
self.shareViewDelegate = shareViewDelegate
self.delegate = self
}
required init?(coder aDecoder: NSCoder) {
@ -32,57 +32,52 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
// MARK: - UI
private lazy var gradientBackground: CAGradientLayer = {
let layer: CAGradientLayer = CAGradientLayer()
let gradientStartColor: UIColor = (LKAppModeUtilities.isLightMode ?
UIColor(rgbHex: 0xFCFCFC) :
UIColor(rgbHex: 0x171717)
)
let gradientEndColor: UIColor = (LKAppModeUtilities.isLightMode ?
UIColor(rgbHex: 0xFFFFFF) :
UIColor(rgbHex: 0x121212)
)
layer.colors = [gradientStartColor.cgColor, gradientEndColor.cgColor]
return layer
}()
private lazy var titleLabel: UILabel = {
let titleLabel: UILabel = UILabel()
titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = "vc_share_title".localized()
titleLabel.textColor = Colors.text
titleLabel.themeTextColor = .textPrimary
return titleLabel
}()
private lazy var closeButton: UIBarButtonItem = {
let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(dismissPressed))
closeButton.tintColor = Colors.text
closeButton.themeTintColor = .textPrimary
return closeButton
}()
// MARK: - Lifecycle
override func loadView() {
public override func loadView() {
super.loadView()
UIView.appearance().tintColor = Colors.text
self.view.backgroundColor = UIColor.clear
self.view.layer.insertSublayer(gradientBackground, at: 0)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.tintColor = Colors.navigationBarBackground
UIView.appearance().themeTintColor = .textPrimary
self.view.themeBackgroundColor = .backgroundPrimary
self.navigationItem.titleView = titleLabel
self.navigationItem.leftBarButtonItem = closeButton
setupLayout()
ThemeManager.onThemeChange(observer: self.unlockButton) { [weak self] theme, _ in
switch theme.interfaceStyle {
case .light:
self?.unlockButton.setTitleColor(theme.colors[.textPrimary], for: .normal)
self?.unlockButton.setBackgroundImage(
theme.colors[.textPrimary]?.withAlphaComponent(0.3).toImage(),
for: .highlighted
)
self?.unlockButton.layer.borderColor = theme.colors[.textPrimary]?.cgColor
default:
self?.unlockButton.setTitleColor(Theme.PrimaryColor.green.color, for: .normal)
self?.unlockButton.setBackgroundImage(
Theme.PrimaryColor.green.color.withAlphaComponent(0.3).toImage(),
for: .highlighted
)
self?.unlockButton.layer.borderColor = Theme.PrimaryColor.green.color.cgColor
}
}
}
override func viewWillAppear(_ animated: Bool) {
@ -104,12 +99,6 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
}
}
// MARK: - Layout
private func setupLayout() {
gradientBackground.frame = UIScreen.main.bounds
}
// MARK: - Functions
private func tryToPresentAuthUIToUnlockScreenLock() {
@ -122,7 +111,7 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
isShowingAuthUI = true
OWSScreenLock.shared.tryToUnlockScreenLock(
ScreenLock.shared.tryToUnlockScreenLock(
success: { [weak self] in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock succeeded.")
@ -164,7 +153,7 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
}
private func ensureUI() {
self.updateUI(with: .screenLock, isLogoAtTop: false, animated: false)
self.updateUI(state: .lock, animated: false)
}
private func showScreenLockFailureAlert(message: String) {
@ -183,6 +172,13 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
)
}
func unlockButtonWasTapped() {
AssertIsOnMainThread()
OWSLogger.info("unlockButtonWasTapped")
self.tryToPresentAuthUIToUnlockScreenLock()
}
// MARK: - Transitions
@objc private func dismissPressed() {
@ -194,13 +190,4 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
private func cancelShareExperience() {
self.shareViewDelegate?.shareViewWasCancelled()
}
// MARK: - ScreenLockViewDelegate
func unlockButtonWasTapped() {
AssertIsOnMainThread()
OWSLogger.info("unlockButtonWasTapped")
self.tryToPresentAuthUIToUnlockScreenLock()
}
}

View File

@ -24,14 +24,17 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe
override func loadView() {
super.loadView()
// This should be the first thing we do.
let appContext = ShareAppExtensionContext(rootViewController: self)
SetCurrentAppContext(appContext)
AppModeManager.configure(delegate: self)
// Need to manually trigger these since we don't have a "mainWindow" here
ThemeManager.applyNavigationStyling()
ThemeManager.applyWindowStyling()
// This should be the first thing we do (Note: If you leave the share context and return to it
// the context will already exist, trying to override it results in the share context crashing
// so ensure it doesn't exist first)
if !HasAppContext() {
let appContext = ShareAppExtensionContext(rootViewController: self)
SetCurrentAppContext(appContext)
}
// Need to manually trigger these since we don't have a "mainWindow" here and the current theme
// might have been changed since the share extension was last opened
ThemeManager.applySavedTheme()
Logger.info("")
@ -144,7 +147,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe
Logger.info("")
if OWSScreenLock.shared.isScreenLockEnabled() {
if Storage.shared[.isScreenLockEnabled] {
self.dismiss(animated: false) { [weak self] in
AssertIsOnMainThread()
self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
@ -173,7 +176,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe
// MARK: Updating
private func showLockScreenOrMainContent() {
if OWSScreenLock.shared.isScreenLockEnabled() {
if Storage.shared[.isScreenLockEnabled] {
showLockScreen()
}
else {

View File

@ -32,6 +32,12 @@ public final class OutlineButton: UIButton {
private func setUpStyle(style: Style, size: Size) {
clipsToBounds = true
contentEdgeInsets = UIEdgeInsets(
top: 0,
left: Values.smallSpacing,
bottom: 0,
right: Values.smallSpacing
)
titleLabel?.font = .boldSystemFont(ofSize: (size == .small ?
Values.smallFontSize :
Values.mediumFontSize

View File

@ -0,0 +1,152 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public class RadioButton: UIView {
private static let selectionBorderSize: CGFloat = 26
private static let selectionSize: CGFloat = 20
public enum Size {
case small
case medium
var borderSize: CGFloat {
switch self {
case .small: return 20
case .medium: return 26
}
}
var selectionSize: CGFloat {
switch self {
case .small: return 15
case .medium: return 20
}
}
}
public var font: UIFont {
get { titleLabel.font }
set { titleLabel.font = newValue }
}
public var text: String? {
get { titleLabel.text }
set { titleLabel.text = newValue }
}
public private(set) var isSelected: Bool = false
private let onSelected: ((RadioButton) -> ())?
// MARK: - UI
private lazy var selectionButton: UIButton = {
let result: UIButton = UIButton()
result.translatesAutoresizingMaskIntoConstraints = false
result.addTarget(self, action: #selector(itemSelected), for: .touchUpInside)
return result
}()
private let titleLabel: UILabel = {
let result: UILabel = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.isUserInteractionEnabled = false
result.font = .systemFont(ofSize: Values.smallFontSize)
result.themeTextColor = .textPrimary
result.numberOfLines = 0
return result
}()
private let selectionBorderView: UIView = {
let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.isUserInteractionEnabled = false
result.layer.borderWidth = 1
result.themeBorderColor = .radioButton_unselectedBorder
return result
}()
private let selectionView: UIView = {
let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.isUserInteractionEnabled = false
result.themeBackgroundColor = .radioButton_unselectedBackground
return result
}()
// MARK: - Initialization
public init(size: Size, onSelected: ((RadioButton) -> ())? = nil) {
self.onSelected = onSelected
super.init(frame: .zero)
setupViewHierarchy(size: size)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Layout
private func setupViewHierarchy(size: Size) {
addSubview(selectionButton)
addSubview(titleLabel)
addSubview(selectionBorderView)
addSubview(selectionView)
self.heightAnchor.constraint(
greaterThanOrEqualTo: titleLabel.heightAnchor,
constant: Values.mediumSpacing
).isActive = true
self.heightAnchor.constraint(
greaterThanOrEqualTo: selectionBorderView.heightAnchor,
constant: Values.mediumSpacing
).isActive = true
selectionButton.pin(to: self)
titleLabel.center(.vertical, in: self)
titleLabel.pin(.left, to: .left, of: self)
selectionBorderView.center(.vertical, in: self)
selectionBorderView.pin(.right, to: .right, of: self)
selectionBorderView.set(.width, to: size.borderSize)
selectionBorderView.set(.height, to: size.borderSize)
selectionView.center(in: selectionBorderView)
selectionView.set(.width, to: size.selectionSize)
selectionView.set(.height, to: size.selectionSize)
selectionBorderView.layer.cornerRadius = (size.borderSize / 2)
selectionView.layer.cornerRadius = (size.selectionSize / 2)
}
// MARK: - Content
public func setThemeBackgroundColor(_ value: ThemeValue, for state: UIControl.State) {
selectionButton.setThemeBackgroundColor(value, for: state)
}
public func update(isSelected: Bool) {
self.isSelected = isSelected
selectionBorderView.themeBorderColor = (isSelected ?
.radioButton_selectedBorder :
.radioButton_unselectedBorder
)
selectionView.themeBackgroundColor = (isSelected ?
.radioButton_selectedBackground :
.radioButton_unselectedBackground
)
}
@objc func itemSelected() {
onSelected?(self)
}
}

View File

@ -1,28 +1,34 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public final class TabBar : UIView {
public final class TabBar: UIView {
private let tabs: [Tab]
private var accentLineViewHorizontalCenteringConstraint: NSLayoutConstraint!
private var accentLineViewWidthConstraint: NSLayoutConstraint!
// MARK: Components
// MARK: - Components
private lazy var tabLabels: [UILabel] = tabs.map { tab in
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
result.textAlignment = .center
result.text = tab.title
result.themeTextColor = .textPrimary
result.textAlignment = .center
result.alpha = Values.mediumOpacity
result.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness)
return result
}
private lazy var accentLineView: UIView = {
let result = UIView()
result.backgroundColor = Colors.accent
result.themeBackgroundColor = .primary
return result
}()
// MARK: Types
// MARK: - Types
public struct Tab {
let title: String
let onTap: () -> Void
@ -33,10 +39,12 @@ public final class TabBar : UIView {
}
}
// MARK: Settings
// MARK: - Settings
public static let snHeight = isIPhone5OrSmaller ? CGFloat(32) : CGFloat(48)
// MARK: Lifecycle
// MARK: - Lifecycle
public init(tabs: [Tab]) {
self.tabs = tabs
super.init(frame: CGRect.zero)
@ -53,37 +61,48 @@ public final class TabBar : UIView {
private func setUpViewHierarchy() {
set(.height, to: TabBar.snHeight)
tabLabels.forEach { tabLabel in
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:)))
tabLabel.addGestureRecognizer(tapGestureRecognizer)
}
let tabLabelStackView = UIStackView(arrangedSubviews: tabLabels)
tabLabelStackView.axis = .horizontal
tabLabelStackView.distribution = .fillEqually
tabLabelStackView.spacing = Values.mediumSpacing
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:)))
tabLabelStackView.addGestureRecognizer(tapGestureRecognizer)
tabLabelStackView.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness)
addSubview(tabLabelStackView)
let separator = UIView()
separator.backgroundColor = Colors.separator
separator.themeBackgroundColor = .borderSeparator
separator.set(.height, to: Values.separatorThickness)
addSubview(separator)
accentLineView.set(.height, to: Values.accentLineThickness)
addSubview(accentLineView)
tabLabelStackView.pin(.leading, to: .leading, of: self)
tabLabelStackView.pin(.top, to: .top, of: self)
pin(.trailing, to: .trailing, of: tabLabelStackView)
separator.pin(.leading, to: .leading, of: self)
separator.pin(.top, to: .bottom, of: tabLabelStackView)
pin(.trailing, to: .trailing, of: separator)
accentLineView.translatesAutoresizingMaskIntoConstraints = false
selectTab(at: 0, withAnimatedTransition: false)
accentLineView.pin(.top, to: .bottom, of: separator)
pin(.bottom, to: .bottom, of: accentLineView)
}
// MARK: Updating
// MARK: - Updating
public func selectTab(at index: Int, withAnimatedTransition isAnimated: Bool = true) {
let tabLabel = tabLabels[index]
accentLineViewHorizontalCenteringConstraint?.isActive = false
@ -92,16 +111,20 @@ public final class TabBar : UIView {
accentLineViewWidthConstraint?.isActive = false
accentLineViewWidthConstraint = accentLineView.widthAnchor.constraint(equalTo: tabLabel.widthAnchor)
accentLineViewWidthConstraint.isActive = true
var tabLabelsCopy = tabLabels
tabLabelsCopy.remove(at: index)
UIView.animate(withDuration: isAnimated ? 0.25 : 0) {
tabLabel.textColor = Colors.text
tabLabelsCopy.forEach { $0.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) }
tabLabel.alpha = 1
tabLabelsCopy.forEach { $0.alpha = Values.mediumOpacity }
self.layoutIfNeeded()
}
}
// MARK: Interaction
// MARK: - Interaction
@objc private func handleTabLabelTapped(_ sender: UITapGestureRecognizer) {
guard let tabLabel = tabLabels.first(where: { $0.bounds.contains(sender.location(in: $0)) }), let index = tabLabels.firstIndex(of: tabLabel) else { return }
selectTab(at: index)

View File

@ -1,47 +0,0 @@
import UIKit
@objc(LKGradient)
public final class Gradient : NSObject {
public let start: UIColor
public let end: UIColor
private override init() { preconditionFailure("Use init(start:end:) instead.") }
@objc public init(start: UIColor, end: UIColor) {
self.start = start
self.end = end
super.init()
}
}
@objc public extension UIView {
@objc func setGradient(_ gradient: Gradient) {
let layer = CAGradientLayer()
layer.frame = UIScreen.main.bounds
layer.colors = [ gradient.start.cgColor, gradient.end.cgColor ]
if let existingSublayer = self.layer.sublayers?[0], existingSublayer is CAGradientLayer {
self.layer.replaceSublayer(existingSublayer, with: layer)
} else {
self.layer.insertSublayer(layer, at: 0)
}
}
}
@objc(LKGradients)
final public class Gradients : NSObject {
@objc public static var defaultBackground: Gradient {
switch AppModeManager.shared.currentAppMode {
case .light: return Gradient(start: UIColor(hex: 0xFCFCFC), end: UIColor(hex: 0xFFFFFF))
case .dark: return Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex: 0x121212))
}
}
@objc public static var homeVCFade: Gradient {
switch AppModeManager.shared.currentAppMode {
case .light: return Gradient(start: UIColor(hex: 0xFFFFFF).withAlphaComponent(0), end: UIColor(hex: 0xFFFFFF))
case .dark: return Gradient(start: UIColor(hex: 0x000000).withAlphaComponent(0), end: UIColor(hex: 0x000000))
}
}
}

View File

@ -22,6 +22,8 @@ public extension Setting.BoolKey {
// MARK: - ThemeManager
public enum ThemeManager {
private static var hasSetInitialSystemTrait: Bool = false
/// **Note:** Using `weakToStrongObjects` means that the value types will continue to be maintained until the map table resizes
/// itself (ie. until a new UI element is registered to the table)
///
@ -111,6 +113,11 @@ public enum ThemeManager {
}
}
public static func applySavedTheme() {
ThemeManager.primaryColor = Storage.shared[.themePrimaryColor].defaulting(to: Theme.PrimaryColor.green)
ThemeManager.currentTheme = Storage.shared[.theme].defaulting(to: Theme.classicDark)
}
public static func applyNavigationStyling() {
let textPrimary: UIColor = (ThemeManager.currentTheme.colors[.textPrimary] ?? .white)
@ -224,6 +231,11 @@ public enum ThemeManager {
applyNavigationStyling()
applyWindowStyling()
if !hasSetInitialSystemTrait {
traitCollectionDidChange(nil)
hasSetInitialSystemTrait = true
}
}
internal static func set<T: AnyObject>(

View File

@ -5,74 +5,87 @@ import UIKit.UIColor
internal enum Theme_ClassicDark: ThemeColors {
static let theme: [ThemeValue: UIColor] = [
// General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary,
.defaultPrimary: Theme.PrimaryColor.green.color,
.danger: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1),
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0),
.backgroundPrimary: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.backgroundSecondary: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1),
.backgroundTertiary: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1),
.textPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.textSecondary: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1),
.borderSeparator: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.danger: .dangerDark,
.backgroundPrimary: .classicDark0,
.backgroundSecondary: .classicDark1,
.textPrimary: .classicDark6,
.textSecondary: .classicDark5,
.borderSeparator: .classicDark3,
// Path
.path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .classicDark4,
// TextBox
.textBox_background: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.textBox_border: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.textBox_background: .classicDark1,
.textBox_border: .classicDark3,
// MessageBubble
.messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1),
.messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.messageBubble_incomingText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.messageBubble_incomingBackground: .classicDark2,
.messageBubble_outgoingText: .classicDark0,
.messageBubble_incomingText: .classicDark6,
// MenuButton
.menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_icon: .classicDark6,
.menuButton_outerShadow: .primary,
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_innerShadow: .classicDark6,
// RadioButton
.radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.radioButton_unselectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.radioButton_selectedBorder: .classicDark6,
.radioButton_unselectedBorder: .classicDark6,
// OutlineButton
.outlineButton_text: .primary,
.outlineButton_background: .clear,
.outlineButton_highlight: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3),
.outlineButton_highlight: .classicDark6.withAlphaComponent(0.3),
.outlineButton_border: .primary,
.outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.outlineButton_filledBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1),
.outlineButton_filledHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.outlineButton_destructiveText: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1),
.outlineButton_filledText: .classicDark6,
.outlineButton_filledBackground: .classicDark1,
.outlineButton_filledHighlight: .classicDark3,
.outlineButton_destructiveText: .dangerDark,
.outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1),
.outlineButton_destructiveHighlight: .dangerDark.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: .dangerDark,
// SolidButton
.solidButton_background: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1),
.solidButton_highlight: #colorLiteral(red: 0.3254901961, green: 0.3254901961, blue: 0.3254901961, alpha: 1),
.solidButton_background: .classicDark3,
.solidButton_highlight: .classicDark4,
// Settings
.settings_tabBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1),
.settings_tabHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.settings_tabBackground: .classicDark1,
.settings_tabHighlight: .classicDark3,
// Appearance
.appearance_sectionBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1),
.appearance_buttonBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1),
.appearance_buttonHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.appearance_sectionBackground: .classicDark1,
.appearance_buttonBackground: .classicDark1,
.appearance_buttonHighlight: .classicDark3,
// Alert
.alert_background: .classicDark1,
.alert_buttonBackground: .classicDark1,
.alert_buttonHighlight: .classicDark3,
// ConversationButton
.conversationButton_background: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1),
.conversationButton_highlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.conversationButton_unreadBackground: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1),
.conversationButton_unreadHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.conversationButton_background: .classicDark1,
.conversationButton_highlight: .classicDark3,
.conversationButton_unreadBackground: .classicDark2,
.conversationButton_unreadHighlight: .classicDark3,
.conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1),
.conversationButton_unreadBubbleText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color
.conversationButton_unreadBubbleBackground: .classicDark3,
.conversationButton_unreadBubbleText: .classicDark6,
.conversationButton_swipeDestructive: .dangerDark,
.conversationButton_swipeSecondary: .classicDark2,
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color
]
}

View File

@ -5,74 +5,87 @@ import UIKit.UIColor
internal enum Theme_ClassicLight: ThemeColors {
static let theme: [ThemeValue: UIColor] = [
// General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary,
.defaultPrimary: Theme.PrimaryColor.green.color,
.danger: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1),
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0),
.backgroundPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.backgroundSecondary: #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1),
.backgroundTertiary: #colorLiteral(red: 0.9058823529, green: 0.9058823529, blue: 0.9058823529, alpha: 1),
.textPrimary: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.textSecondary: #colorLiteral(red: 0.4274509804, green: 0.4274509804, blue: 0.4274509804, alpha: 1),
.borderSeparator: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1),
.danger: .dangerLight,
.backgroundPrimary: .classicLight6,
.backgroundSecondary: .classicLight5,
.textPrimary: .classicLight0,
.textSecondary: .classicLight1,
.borderSeparator: .classicLight2,
// Path
.path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .classicLight4,
// TextBox
.textBox_background: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.textBox_border: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1),
.textBox_background: .classicLight6,
.textBox_border: .classicLight2,
// MessageBubble
.messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1),
.messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.messageBubble_incomingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.messageBubble_incomingBackground: .classicLight4,
.messageBubble_outgoingText: .classicLight0,
.messageBubble_incomingText: .classicLight0,
// MenuButton
.menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_outerShadow: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_icon: .classicLight6,
.menuButton_outerShadow: .classicLight0,
.menuButton_innerShadow: .classicLight6,
// RadioButton
.radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.radioButton_unselectedBorder: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.radioButton_selectedBorder: .classicLight0,
.radioButton_unselectedBorder: .classicLight0,
// OutlineButton
.outlineButton_text: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.outlineButton_text: .classicLight0,
.outlineButton_background: .clear,
.outlineButton_highlight: #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 0.1),
.outlineButton_border: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.outlineButton_filledBackground: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.outlineButton_filledHighlight: #colorLiteral(red: 0.4274509804, green: 0.4274509804, blue: 0.4274509804, alpha: 1),
.outlineButton_destructiveText: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1),
.outlineButton_highlight: .classicLight0.withAlphaComponent(0.1),
.outlineButton_border: .classicLight0,
.outlineButton_filledText: .classicLight6,
.outlineButton_filledBackground: .classicLight0,
.outlineButton_filledHighlight: .classicLight1,
.outlineButton_destructiveText: .dangerLight,
.outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1),
.outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: .dangerLight,
// SolidButton
.solidButton_background: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1),
.solidButton_highlight: #colorLiteral(red: 0.8156862745, green: 0.8156862745, blue: 0.8156862745, alpha: 1),
.solidButton_background: .classicLight3,
.solidButton_highlight: .classicLight4,
// Settings
.settings_tabBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.settings_tabHighlight: #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1),
.settings_tabBackground: .classicLight6,
.settings_tabHighlight: .classicLight3,
// AppearanceButton
.appearance_sectionBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.appearance_buttonBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.appearance_buttonHighlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1),
.appearance_sectionBackground: .classicLight6,
.appearance_buttonBackground: .classicLight6,
.appearance_buttonHighlight: .classicLight4,
// Alert
.alert_background: .classicLight6,
.alert_buttonBackground: .classicLight6,
.alert_buttonHighlight: .classicLight4,
// ConversationButton
.conversationButton_background: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.conversationButton_highlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1),
.conversationButton_unreadBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.conversationButton_unreadHighlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1),
.conversationButton_background: .classicLight6,
.conversationButton_highlight: .classicLight4,
.conversationButton_unreadBackground: .classicLight6,
.conversationButton_unreadHighlight: .classicLight4,
.conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1),
.conversationButton_unreadBubbleText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color
.conversationButton_unreadBubbleBackground: .classicLight3,
.conversationButton_unreadBubbleText: .classicLight0,
.conversationButton_swipeDestructive: .dangerLight,
.conversationButton_swipeSecondary: .classicLight1,
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color
]
}

View File

@ -0,0 +1,88 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit.UIColor
import SessionUtilitiesKit
// MARK: - Primary Colors
public extension Theme {
enum PrimaryColor: String, Codable, CaseIterable, EnumStringSetting {
case green
case blue
case purple
case pink
case red
case orange
case yellow
internal init?(color: UIColor?) {
guard
let color: UIColor = color,
let primaryColor: PrimaryColor = PrimaryColor.allCases.first(where: { $0.color == color })
else { return nil }
self = primaryColor
}
public var color: UIColor {
switch self {
case .green: return #colorLiteral(red: 0.1921568627, green: 0.9450980392, blue: 0.5882352941, alpha: 1) // #31F196
case .blue: return #colorLiteral(red: 0.3411764706, green: 0.7882352941, blue: 0.9803921569, alpha: 1) // #57C9FA
case .purple: return #colorLiteral(red: 0.7882352941, green: 0.5764705882, blue: 1, alpha: 1) // #C993FF
case .pink: return #colorLiteral(red: 1, green: 0.5843137255, blue: 0.937254902, alpha: 1) // #FF95EF
case .red: return #colorLiteral(red: 1, green: 0.6117647059, blue: 0.5568627451, alpha: 1) // #FF9C8E
case .orange: return #colorLiteral(red: 0.9882352941, green: 0.6941176471, blue: 0.3490196078, alpha: 1) // #FCB159
case .yellow: return #colorLiteral(red: 0.9803921569, green: 0.8392156863, blue: 0.3411764706, alpha: 1) // #FAD657
}
}
}
}
// MARK: - Standard Theme Colors
internal extension UIColor {
static let dangerDark: UIColor = #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1) // #FF3A3A
static let dangerLight: UIColor = #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1) // #E12D19
static let pathConnected: UIColor = #colorLiteral(red: 0.1921568627, green: 0.9450980392, blue: 0.5882352941, alpha: 1) // #31F196
static let pathConnecting: UIColor = #colorLiteral(red: 0.9882352941, green: 0.6941176471, blue: 0.3490196078, alpha: 1) // #FCB159
static let pathError: UIColor = #colorLiteral(red: 0.9176470588, green: 0.3333333333, blue: 0.2705882353, alpha: 1) // #EA5545
static let classicDark0: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) // #000000
static let classicDark1: UIColor = #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1) // #1B1B1B
static let classicDark2: UIColor = #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1) // #2D2D2D
static let classicDark3: UIColor = #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1) // #414141
static let classicDark4: UIColor = #colorLiteral(red: 0.462745098, green: 0.462745098, blue: 0.462745098, alpha: 1) // #767676
static let classicDark5: UIColor = #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1) // #A1A2A1
static let classicDark6: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) // #FFFFFF
static let classicLight0: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) // #000000
static let classicLight1: UIColor = #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1) // #6D6D6D
static let classicLight2: UIColor = #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1) // #A1A2A1
static let classicLight3: UIColor = #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1) // #DFDFDF
static let classicLight4: UIColor = #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1) // #F0F0F0
static let classicLight5: UIColor = #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1) // #F9F9F9
static let classicLight6: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) // #FFFFFF
static let oceanDark0: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) // #000000
static let oceanDark1: UIColor = #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1) // #1A1C28
static let oceanDark2: UIColor = #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1) // #252735
static let oceanDark3: UIColor = #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1) // #2B2D40
static let oceanDark4: UIColor = #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1) // #3D4A5D
static let oceanDark5: UIColor = #colorLiteral(red: 0.6509803922, green: 0.662745098, blue: 0.8078431373, alpha: 1) // #A6A9CE
static let oceanDark6: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) // #FFFFFF
static let oceanLight0: UIColor = #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1) // #19345D
static let oceanLight1: UIColor = #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1) // #6A6E90
static let oceanLight2: UIColor = #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1) // #5CAACC
static let oceanLight3: UIColor = #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.9490196078, alpha: 1) // #B3EDF2
static let oceanLight4: UIColor = #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1) // #E7F3F4
static let oceanLight5: UIColor = #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1) // #ECFAFB
static let oceanLight6: UIColor = #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1) // #FCFFFF
}
public extension UIColor {
static let primary: UIColor = UIColor(dynamicProvider: { _ in
return ThemeManager.primaryColor.color
})
}

View File

@ -5,74 +5,87 @@ import UIKit.UIColor
internal enum Theme_OceanDark: ThemeColors {
static let theme: [ThemeValue: UIColor] = [
// General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary,
.defaultPrimary: Theme.PrimaryColor.blue.color,
.danger: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1),
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0),
.backgroundPrimary: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1),
.backgroundSecondary: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1),
.backgroundTertiary: #colorLiteral(red: 0.1725490196, green: 0.1803921569, blue: 0.2274509804, alpha: 1),
.textPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.textSecondary: #colorLiteral(red: 0.6509803922, green: 0.662745098, blue: 0.8078431373, alpha: 1),
.borderSeparator: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1),
.danger: .dangerDark,
.backgroundPrimary: .oceanDark2,
.backgroundSecondary: .oceanDark1,
.textPrimary: .oceanDark6,
.textSecondary: .oceanDark5,
.borderSeparator: .oceanDark4,
// Path
.path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .oceanDark4,
// TextBox
.textBox_background: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1),
.textBox_border: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1),
.textBox_background: .oceanDark1,
.textBox_border: .oceanDark4,
// MessageBubble
.messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1),
.messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.messageBubble_incomingText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.messageBubble_incomingBackground: .oceanDark4,
.messageBubble_outgoingText: .oceanDark0,
.messageBubble_incomingText: .oceanDark6,
// MenuButton
.menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_icon: .oceanDark6,
.menuButton_outerShadow: .primary,
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_innerShadow: .oceanDark6,
// RadioButton
.radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.radioButton_unselectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.radioButton_selectedBorder: .oceanDark6,
.radioButton_unselectedBorder: .oceanDark6,
// OutlineButton
.outlineButton_text: .primary,
.outlineButton_background: .clear,
.outlineButton_highlight: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3),
.outlineButton_highlight: .oceanDark6.withAlphaComponent(0.3),
.outlineButton_border: .primary,
.outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.outlineButton_filledBackground: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1),
.outlineButton_filledHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.outlineButton_destructiveText: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1),
.outlineButton_filledText: .oceanDark6,
.outlineButton_filledBackground: .oceanDark1,
.outlineButton_filledHighlight: .oceanDark3,
.outlineButton_destructiveText: .dangerDark,
.outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1),
.outlineButton_destructiveHighlight: .dangerDark.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: .dangerDark,
// SolidButton
.solidButton_background: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1),
.solidButton_highlight: #colorLiteral(red: 0.2117647059, green: 0.2196078431, blue: 0.3019607844, alpha: 1),
.solidButton_background: .oceanDark2,
.solidButton_highlight: .oceanDark4,
// Settings
.settings_tabBackground: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1),
.settings_tabHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.settings_tabBackground: .oceanDark1,
.settings_tabHighlight: .oceanDark3,
// Appearance
.appearance_sectionBackground: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.appearance_buttonBackground: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.appearance_buttonHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.appearance_sectionBackground: .oceanDark3,
.appearance_buttonBackground: .oceanDark3,
.appearance_buttonHighlight: .oceanDark4,
// Alert
.alert_background: .oceanDark3,
.alert_buttonBackground: .oceanDark3,
.alert_buttonHighlight: .oceanDark4,
// ConversationButton
.conversationButton_background: #colorLiteral(red: 0.168627451, green: 0.168627451, blue: 0.2509803922, alpha: 1),
.conversationButton_highlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.conversationButton_unreadBackground: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1),
.conversationButton_unreadHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1),
.conversationButton_background: .oceanDark3,
.conversationButton_highlight: .oceanDark3,
.conversationButton_unreadBackground: .oceanDark2,
.conversationButton_unreadHighlight: .oceanDark3,
.conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: .primary,
.conversationButton_unreadBubbleText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color
.conversationButton_unreadBubbleText: .oceanDark0,
.conversationButton_swipeDestructive: .dangerDark,
.conversationButton_swipeSecondary: .oceanDark2,
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color
]
}

View File

@ -5,74 +5,87 @@ import UIKit.UIColor
internal enum Theme_OceanLight: ThemeColors {
static let theme: [ThemeValue: UIColor] = [
// General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary,
.defaultPrimary: Theme.PrimaryColor.blue.color,
.danger: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1),
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0),
.backgroundPrimary: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.backgroundSecondary: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1),
.backgroundTertiary: #colorLiteral(red: 0.8549019608, green: 0.9098039216, blue: 0.9137254902, alpha: 1),
.textPrimary: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.textSecondary: #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1),
.borderSeparator: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1),
.danger: .dangerLight,
.backgroundPrimary: .oceanLight6,
.backgroundSecondary: .oceanLight5,
.textPrimary: .oceanLight0,
.textSecondary: .oceanLight1,
.borderSeparator: .oceanLight2,
// Path
.path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .oceanLight4,
// TextBox
.textBox_background: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.textBox_border: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1),
.textBox_background: .oceanLight6,
.textBox_border: .oceanLight2,
// MessageBubble
.messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.9490196078, alpha: 1),
.messageBubble_outgoingText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.messageBubble_incomingText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.messageBubble_incomingBackground: .oceanLight3,
.messageBubble_outgoingText: .oceanLight0,
.messageBubble_incomingText: .oceanLight0,
// MenuButton
.menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_outerShadow: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.menuButton_icon: .white,
.menuButton_outerShadow: .black,
.menuButton_innerShadow: .white,
// RadioButton
.radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.radioButton_unselectedBorder: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1),
.radioButton_selectedBorder: .oceanLight0,
.radioButton_unselectedBorder: .oceanLight2,
// OutlineButton
.outlineButton_text: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.outlineButton_text: .oceanLight0,
.outlineButton_background: .clear,
.outlineButton_highlight: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 0.1),
.outlineButton_border: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.outlineButton_filledText: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.outlineButton_filledBackground: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.outlineButton_filledHighlight: #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1),
.outlineButton_destructiveText: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1),
.outlineButton_highlight: .oceanLight0.withAlphaComponent(0.1),
.outlineButton_border: .oceanLight0,
.outlineButton_filledText: .oceanLight6,
.outlineButton_filledBackground: .oceanLight0,
.outlineButton_filledHighlight: .oceanLight1,
.outlineButton_destructiveText: .dangerLight,
.outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1),
.outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: .dangerLight,
// SolidButton
.solidButton_background: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1),
.solidButton_highlight: #colorLiteral(red: 0.8431372549, green: 0.9333333334, blue: 0.9411764706, alpha: 1),
.solidButton_background: .oceanLight4,
.solidButton_highlight: .oceanLight5,
// Settings
.settings_tabBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.settings_tabHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1),
.settings_tabBackground: .oceanLight6,
.settings_tabHighlight: .oceanLight4,
// Appearance
.appearance_sectionBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.appearance_buttonBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.appearance_buttonHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1),
.appearance_sectionBackground: .oceanLight6,
.appearance_buttonBackground: .oceanLight6,
.appearance_buttonHighlight: .oceanLight4,
// Alert
.alert_background: .oceanLight6,
.alert_buttonBackground: .oceanLight6,
.alert_buttonHighlight: .oceanLight4,
// ConversationButton
.conversationButton_background: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1),
.conversationButton_highlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1),
.conversationButton_unreadBackground: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1),
.conversationButton_unreadHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1),
.conversationButton_background: .oceanLight6,
.conversationButton_highlight: .oceanLight4,
.conversationButton_unreadBackground: .oceanLight5,
.conversationButton_unreadHighlight: .oceanLight4,
.conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: .primary,
.conversationButton_unreadBubbleText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1),
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color
.conversationButton_unreadBubbleText: .oceanLight0,
.conversationButton_swipeDestructive: .dangerLight,
.conversationButton_swipeSecondary: .oceanLight1,
.conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color
]
}

View File

@ -1,43 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit.UIColor
import SessionUtilitiesKit
public extension Theme {
enum PrimaryColor: String, Codable, CaseIterable, EnumStringSetting {
case green
case blue
case purple
case pink
case red
case orange
case yellow
internal init?(color: UIColor?) {
guard
let color: UIColor = color,
let primaryColor: PrimaryColor = PrimaryColor.allCases.first(where: { $0.color == color })
else { return nil }
self = primaryColor
}
public var color: UIColor {
switch self {
case .green: return #colorLiteral(red: 0.1921568627, green: 0.9450980392, blue: 0.5882352941, alpha: 1)
case .blue: return #colorLiteral(red: 0.3411764706, green: 0.7882352941, blue: 0.9803921569, alpha: 1)
case .purple: return #colorLiteral(red: 0.7882352941, green: 0.5764705882, blue: 1, alpha: 1)
case .pink: return #colorLiteral(red: 1, green: 0.5843137255, blue: 0.937254902, alpha: 1)
case .red: return #colorLiteral(red: 1, green: 0.6117647059, blue: 0.5568627451, alpha: 1)
case .orange: return #colorLiteral(red: 0.9882352941, green: 0.6941176471, blue: 0.3490196078, alpha: 1)
case .yellow: return #colorLiteral(red: 0.9803921569, green: 0.8392156863, blue: 0.3411764706, alpha: 1)
}
}
}
}
public extension UIColor {
static let primary: UIColor = UIColor(dynamicProvider: { _ in
return ThemeManager.primaryColor.color
})
}

View File

@ -56,18 +56,24 @@ public protocol ThemeColors {
public enum ThemeValue {
// General
case white
case black
case clear
case primary
case defaultPrimary
case danger
case white
case clear
case backgroundPrimary
case backgroundSecondary
case backgroundTertiary
case textPrimary
case textSecondary
case borderSeparator
// Path
case path_connected
case path_connecting
case path_error
case path_unknown
// TextBox
case textBox_background
case textBox_border
@ -116,6 +122,11 @@ public enum ThemeValue {
case appearance_buttonBackground
case appearance_buttonHighlight
// Alert
case alert_background
case alert_buttonBackground
case alert_buttonHighlight
// ConversationButton
case conversationButton_background
case conversationButton_highlight
@ -124,5 +135,7 @@ public enum ThemeValue {
case conversationButton_unreadStripBackground
case conversationButton_unreadBubbleBackground
case conversationButton_unreadBubbleText
case conversationButton_pinBackground
case conversationButton_swipeDestructive
case conversationButton_swipeSecondary
case conversationButton_swipeTertiary
}

View File

@ -21,6 +21,7 @@ public final class Values : NSObject {
@objc public static let smallButtonHeight = isIPhone5OrSmaller ? CGFloat(24) : CGFloat(27)
@objc public static let mediumButtonHeight = isIPhone5OrSmaller ? CGFloat(30) : CGFloat(34)
@objc public static let largeButtonHeight = isIPhone5OrSmaller ? CGFloat(40) : CGFloat(45)
@objc public static let alertButtonHeight: CGFloat = 50
@objc public static let accentLineThickness = CGFloat(4)

View File

@ -2,7 +2,7 @@
import UIKit.UIColor
internal extension UIColor {
public extension UIColor {
func toImage() -> UIImage {
let bounds: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(bounds: bounds)

View File

@ -66,12 +66,12 @@ public extension UIView {
}
@discardableResult
func center(_ direction: Direction, in view: UIView) -> NSLayoutConstraint {
func center(_ direction: Direction, in view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch direction {
case .horizontal: return centerXAnchor.constraint(equalTo: view.centerXAnchor)
case .vertical: return centerYAnchor.constraint(equalTo: view.centerYAnchor)
case .horizontal: return centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: inset)
case .vertical: return centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: inset)
}
}()
constraint.isActive = true

View File

@ -165,6 +165,15 @@ public extension Storage {
}
public extension Database {
@discardableResult func unsafeSet<T: Numeric>(key: String, value: T?) -> Setting? {
guard let value: T = value else {
_ = try? Setting.filter(id: key).deleteAll(self)
return nil
}
return try? Setting(key: key, value: value)?.saved(self)
}
private subscript(key: String) -> Setting? {
get { try? Setting.filter(id: key).fetchOne(self) }
set {

View File

@ -113,6 +113,7 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
@end
id<AppContext> CurrentAppContext(void);
BOOL HasAppContext(void);
void SetCurrentAppContext(id<AppContext> appContext);
void ExitShareExtension(void);

View File

@ -26,6 +26,11 @@ id<AppContext> CurrentAppContext(void)
return currentAppContext;
}
BOOL HasAppContext(void)
{
return (currentAppContext != nil);
}
void SetCurrentAppContext(id<AppContext> appContext)
{
// The main app context should only be set once.

View File

@ -21,7 +21,6 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/OWSTextField.h>
#import <SignalUtilitiesKit/OWSTextView.h>
#import <SignalUtilitiesKit/OWSViewController.h>
#import <SignalUtilitiesKit/ScreenLockViewController.h>
#import <SignalUtilitiesKit/SSKAsserts.h>
#import <SignalUtilitiesKit/TSConstants.h>
#import <SignalUtilitiesKit/UIFont+OWS.h>

View File

@ -22,7 +22,7 @@ public final class ProfilePictureView: UIView {
private lazy var imageView: YYAnimatedImageView = getImageView()
private lazy var additionalImageView: YYAnimatedImageView = {
let result: YYAnimatedImageView = getImageView()
result.themeBackgroundColor = .backgroundTertiary
result.themeBackgroundColor = .primary
result.themeBorderColor = .backgroundPrimary
result.layer.borderWidth = Values.separatorThickness

View File

@ -5,18 +5,16 @@ import GRDB
import LocalAuthentication
import SessionMessagingKit
// FIXME: Refactor this once the 'PrivacySettingsTableViewController' and 'OWSScreenLockUI' have been refactored
@objc public class OWSScreenLock: NSObject {
public enum OWSScreenLockOutcome {
public class ScreenLock {
public enum Outcome {
case success
case cancel
case failure(error: String)
case unexpectedFailure(error: String)
}
@objc public let screenLockTimeoutDefault = (15 * kMinuteInterval)
@objc public let screenLockTimeouts = [
public let screenLockTimeoutDefault = (15 * kMinuteInterval)
public let screenLockTimeouts = [
1 * kMinuteInterval,
5 * kMinuteInterval,
15 * kMinuteInterval,
@ -24,104 +22,72 @@ import SessionMessagingKit
1 * kHourInterval,
0
]
@objc public static let ScreenLockDidChange = Notification.Name("ScreenLockDidChange")
// MARK: - Singleton class
@objc(sharedManager)
public static let shared = OWSScreenLock()
private override init() {
super.init()
SwiftSingletons.register(self)
}
// MARK: - Properties
@objc public func isScreenLockEnabled() -> Bool {
return Storage.shared[.isScreenLockEnabled]
}
@objc
public func setIsScreenLockEnabled(_ value: Bool) {
Storage.shared.writeAsync(
updates: { db in db[.isScreenLockEnabled] = value },
completion: { _, _ in
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil)
}
)
}
@objc public func screenLockTimeout() -> TimeInterval {
return Storage.shared[.screenLockTimeoutSeconds]
.defaulting(to: screenLockTimeoutDefault)
}
@objc public func setScreenLockTimeout(_ value: TimeInterval) {
Storage.shared.writeAsync(
updates: { db in db[.screenLockTimeoutSeconds] = value },
completion: { _, _ in
NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil)
}
)
}
public static let shared: ScreenLock = ScreenLock()
// MARK: - Methods
// This method should only be called:
//
// * On the main thread.
//
// Exactly one of these completions will be performed:
//
// * Asynchronously.
// * On the main thread.
@objc public func tryToUnlockScreenLock(success: @escaping (() -> Void),
failure: @escaping ((Error) -> Void),
unexpectedFailure: @escaping ((Error) -> Void),
cancel: @escaping (() -> Void)) {
/// This method should only be called:
///
/// * On the main thread.
///
/// Exactly one of these completions will be performed:
///
/// * Asynchronously.
/// * On the main thread.
public func tryToUnlockScreenLock(
success: @escaping (() -> Void),
failure: @escaping ((Error) -> Void),
unexpectedFailure: @escaping ((Error) -> Void),
cancel: @escaping (() -> Void)
) {
AssertIsOnMainThread()
tryToVerifyLocalAuthentication(localizedReason: NSLocalizedString("SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK",
comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'."),
completion: { (outcome: OWSScreenLockOutcome) in
AssertIsOnMainThread()
switch outcome {
case .failure(let error):
Logger.error("local authentication failed with error: \(error)")
failure(self.authenticationError(errorDescription: error))
case .unexpectedFailure(let error):
Logger.error("local authentication failed with unexpected error: \(error)")
unexpectedFailure(self.authenticationError(errorDescription: error))
case .success:
Logger.verbose("local authentication succeeded.")
success()
case .cancel:
Logger.verbose("local authentication cancelled.")
cancel()
}
})
tryToVerifyLocalAuthentication(
// Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to
// unlock 'screen lock'.
localizedReason: "SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK".localized()
) { outcome in
AssertIsOnMainThread()
switch outcome {
case .failure(let error):
Logger.error("local authentication failed with error: \(error)")
failure(self.authenticationError(errorDescription: error))
case .unexpectedFailure(let error):
Logger.error("local authentication failed with unexpected error: \(error)")
unexpectedFailure(self.authenticationError(errorDescription: error))
case .success:
Logger.verbose("local authentication succeeded.")
success()
case .cancel:
Logger.verbose("local authentication cancelled.")
cancel()
}
}
}
// This method should only be called:
//
// * On the main thread.
//
// completionParam will be performed:
//
// * Asynchronously.
// * On the main thread.
private func tryToVerifyLocalAuthentication(localizedReason: String,
completion completionParam: @escaping ((OWSScreenLockOutcome) -> Void)) {
/// This method should only be called:
///
/// * On the main thread.
///
/// completionParam will be performed:
///
/// * Asynchronously.
/// * On the main thread.
private func tryToVerifyLocalAuthentication(
localizedReason: String,
completion completionParam: @escaping ((Outcome) -> Void)
) {
AssertIsOnMainThread()
let defaultErrorDescription = "SCREEN_LOCK_ENABLE_UNKNOWN_ERROR".localized()
// Ensure completion is always called on the main thread.
let completion = { (outcome: OWSScreenLockOutcome) in
let completion = { outcome in
DispatchQueue.main.async {
completionParam(outcome)
}
@ -131,18 +97,20 @@ import SessionMessagingKit
var authError: NSError?
let canEvaluatePolicy = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authError)
if !canEvaluatePolicy || authError != nil {
Logger.error("could not determine if local authentication is supported: \(String(describing: authError))")
let outcome = self.outcomeForLAError(errorParam: authError,
defaultErrorDescription: defaultErrorDescription)
switch outcome {
case .success:
owsFailDebug("local authentication unexpected success")
completion(.failure(error: defaultErrorDescription))
case .cancel, .failure, .unexpectedFailure:
completion(outcome)
}
case .success:
owsFailDebug("local authentication unexpected success")
completion(.failure(error: defaultErrorDescription))
case .cancel, .failure, .unexpectedFailure:
completion(outcome)
}
return
}
@ -151,38 +119,46 @@ import SessionMessagingKit
if success {
Logger.info("local authentication succeeded.")
completion(.success)
} else {
let outcome = self.outcomeForLAError(errorParam: evaluateError,
defaultErrorDescription: defaultErrorDescription)
switch outcome {
return
}
let outcome = self.outcomeForLAError(
errorParam: evaluateError,
defaultErrorDescription: defaultErrorDescription
)
switch outcome {
case .success:
owsFailDebug("local authentication unexpected success")
completion(.failure(error:defaultErrorDescription))
case .cancel, .failure, .unexpectedFailure:
completion(outcome)
}
}
}
}
// MARK: - Outcome
private func outcomeForLAError(errorParam: Error?, defaultErrorDescription: String) -> OWSScreenLockOutcome {
private func outcomeForLAError(errorParam: Error?, defaultErrorDescription: String) -> Outcome {
if let error = errorParam {
guard let laError = error as? LAError else {
return .failure(error:defaultErrorDescription)
return .failure(error: defaultErrorDescription)
}
switch laError.code {
case .biometryNotAvailable:
Logger.error("local authentication error: biometryNotAvailable.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE".localized())
case .biometryNotEnrolled:
Logger.error("local authentication error: biometryNotEnrolled.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED".localized())
case .biometryLockout:
Logger.error("local authentication error: biometryLockout.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT".localized())
default:
// Fall through to second switch
break
@ -192,27 +168,37 @@ import SessionMessagingKit
case .authenticationFailed:
Logger.error("local authentication error: authenticationFailed.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED".localized())
case .userCancel, .userFallback, .systemCancel, .appCancel:
Logger.info("local authentication cancelled.")
return .cancel
case .passcodeNotSet:
Logger.error("local authentication error: passcodeNotSet.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET".localized())
case .touchIDNotAvailable:
Logger.error("local authentication error: touchIDNotAvailable.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE".localized())
case .touchIDNotEnrolled:
Logger.error("local authentication error: touchIDNotEnrolled.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED".localized())
case .touchIDLockout:
Logger.error("local authentication error: touchIDLockout.")
return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT".localized())
case .invalidContext:
owsFailDebug("context not valid.")
return .unexpectedFailure(error:defaultErrorDescription)
return .unexpectedFailure(error: defaultErrorDescription)
case .notInteractive:
owsFailDebug("context not interactive.")
return .unexpectedFailure(error:defaultErrorDescription)
return .unexpectedFailure(error: defaultErrorDescription)
@unknown default:
return .failure(error: defaultErrorDescription)
}
}
@ -220,8 +206,7 @@ import SessionMessagingKit
}
private func authenticationError(errorDescription: String) -> Error {
return OWSErrorWithCodeDescription(.localAuthenticationError,
errorDescription)
return OWSErrorWithCodeDescription(.localAuthenticationError, errorDescription)
}
// MARK: - Context

View File

@ -1,29 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
typedef NS_ENUM(NSUInteger, ScreenLockUIState) {
ScreenLockUIStateNone,
// Shown while app is inactive or background, if enabled.
ScreenLockUIStateScreenProtection,
// Shown while app is active, if enabled.
ScreenLockUIStateScreenLock,
};
NSString *NSStringForScreenLockUIState(ScreenLockUIState value);
@protocol ScreenLockViewDelegate <NSObject>
- (void)unlockButtonWasTapped;
@end
#pragma mark -
@interface ScreenLockViewController : UIViewController
@property (nonatomic, weak) id<ScreenLockViewDelegate> delegate;
- (void)updateUIWithState:(ScreenLockUIState)uiState isLogoAtTop:(BOOL)isLogoAtTop animated:(BOOL)animated;
@end

View File

@ -1,162 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "ScreenLockViewController.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUIKit/SessionUIKit.h>
NSString *NSStringForScreenLockUIState(ScreenLockUIState value)
{
switch (value) {
case ScreenLockUIStateNone:
return @"ScreenLockUIStateNone";
case ScreenLockUIStateScreenProtection:
return @"ScreenLockUIStateScreenProtection";
case ScreenLockUIStateScreenLock:
return @"ScreenLockUIStateScreenLock";
}
}
@interface ScreenLockViewController ()
@property (nonatomic) UIView *screenBlockingImageView;
@property (nonatomic) UIView *screenBlockingButton;
@property (nonatomic) NSArray<NSLayoutConstraint *> *screenBlockingConstraints;
@property (nonatomic) NSString *screenBlockingSignature;
@end
#pragma mark -
@implementation ScreenLockViewController
- (void)loadView
{
[super loadView];
// Loki: Set gradient background
self.view.backgroundColor = UIColor.clearColor;
CAGradientLayer *layer = [CAGradientLayer new];
layer.frame = UIScreen.mainScreen.bounds;
UIColor *gradientStartColor = LKAppModeUtilities.isLightMode ? [UIColor colorWithRGBHex:0xFCFCFC] : [UIColor colorWithRGBHex:0x171717];
UIColor *gradientEndColor = LKAppModeUtilities.isLightMode ? [UIColor colorWithRGBHex:0xFFFFFF] : [UIColor colorWithRGBHex:0x121212];
layer.colors = @[ (id)gradientStartColor.CGColor, (id)gradientEndColor.CGColor ];
[self.view.layer insertSublayer:layer atIndex:0];
UIView *edgesView = [UIView containerView];
[self.view addSubview:edgesView];
[edgesView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[edgesView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[edgesView autoPinWidthToSuperview];
UIImage *image = [UIImage imageNamed:@"SessionGreen64"];
UIImageView *imageView = [UIImageView new];
imageView.image = image;
imageView.contentMode = UIViewContentModeScaleAspectFit;
[edgesView addSubview:imageView];
[imageView autoHCenterInSuperview];
[imageView autoSetDimension:ALDimensionWidth toSize:64];
[imageView autoSetDimension:ALDimensionHeight toSize:64];
const CGFloat kButtonHeight = 40.f;
OWSFlatButton *button =
[OWSFlatButton buttonWithTitle:NSLocalizedString(@"Unlock Session", @"")
font:[UIFont boldSystemFontOfSize:LKValues.mediumFontSize]
titleColor:LKAppModeUtilities.isLightMode ? UIColor.blackColor : UIColor.whiteColor
backgroundColor:UIColor.clearColor
target:self
selector:@selector(showUnlockUI)];
[edgesView addSubview:button];
[button autoSetDimension:ALDimensionHeight toSize:kButtonHeight];
[button autoPinLeadingToSuperviewMarginWithInset:50.f];
[button autoPinTrailingToSuperviewMarginWithInset:50.f];
const CGFloat kVMargin = 65.f;
[button autoPinBottomToSuperviewMarginWithInset:kVMargin];
self.screenBlockingImageView = imageView;
self.screenBlockingButton = button;
[self updateUIWithState:ScreenLockUIStateScreenProtection isLogoAtTop:NO animated:NO];
}
// The "screen blocking" window has three possible states:
//
// * "Just a logo". Used when app is launching and in app switcher. Must match the "Launch Screen"
// storyboard pixel-for-pixel.
// * "Screen Lock, local auth UI presented". Move the Signal logo so that it is visible.
// * "Screen Lock, local auth UI not presented". Move the Signal logo so that it is visible,
// show "unlock" button.
- (void)updateUIWithState:(ScreenLockUIState)uiState isLogoAtTop:(BOOL)isLogoAtTop animated:(BOOL)animated
{
OWSAssertIsOnMainThread();
if (!self.isViewLoaded) {
return;
}
BOOL shouldShowBlockWindow = uiState != ScreenLockUIStateNone;
BOOL shouldHaveScreenLock = uiState == ScreenLockUIStateScreenLock;
self.screenBlockingImageView.hidden = !shouldShowBlockWindow;
NSString *signature = [NSString stringWithFormat:@"%d %d", shouldHaveScreenLock, isLogoAtTop];
if ([NSObject isNullableObject:self.screenBlockingSignature equalTo:signature]) {
// Skip redundant work to avoid interfering with ongoing animations.
return;
}
[NSLayoutConstraint deactivateConstraints:self.screenBlockingConstraints];
NSMutableArray<NSLayoutConstraint *> *screenBlockingConstraints = [NSMutableArray new];
self.screenBlockingButton.hidden = !shouldHaveScreenLock;
if (isLogoAtTop) {
const CGFloat kVMargin = 60.f;
[screenBlockingConstraints addObject:[self.screenBlockingImageView autoPinEdge:ALEdgeTop
toEdge:ALEdgeTop
ofView:self.view
withOffset:kVMargin]];
} else {
[screenBlockingConstraints addObject:[self.screenBlockingImageView autoVCenterInSuperview]];
}
self.screenBlockingConstraints = screenBlockingConstraints;
self.screenBlockingSignature = signature;
if (animated) {
[UIView animateWithDuration:0.35f
animations:^{
[self.view layoutIfNeeded];
}];
} else {
[self.view layoutIfNeeded];
}
}
- (void)showUnlockUI
{
OWSAssertIsOnMainThread();
[self.delegate unlockButtonWasTapped];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
@end

View File

@ -0,0 +1,121 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUIKit
open class ScreenLockViewController: UIViewController {
public enum State {
case none
/// Shown while app is inactive or background, if enabled.
case protection
/// Shown while app is active, if enabled.
case lock
}
public override var preferredStatusBarStyle: UIStatusBarStyle {
return ThemeManager.currentTheme.statusBarStyle
}
public override var canBecomeFirstResponder: Bool { true }
public var onUnlockPressed: (() -> ())?
private var screenBlockingSignature: String?
// MARK: - UI
private let logoView: UIImageView = {
let result: UIImageView = UIImageView(image: #imageLiteral(resourceName: "SessionGreen64"))
result.contentMode = .scaleAspectFit
result.isHidden = true
return result
}()
public lazy var unlockButton: OutlineButton = {
let result: OutlineButton = OutlineButton(style: .regular, size: .medium)
result.translatesAutoresizingMaskIntoConstraints = false
result.setTitle("Unlock Session", for: .normal)
result.addTarget(self, action: #selector(showUnlockUI), for: .touchUpInside)
result.isHidden = true
// Need to match the launch screen so force the styling to be the primary green
result.setTitleColor(Theme.PrimaryColor.green.color, for: .normal)
result.setBackgroundImage(
Theme.PrimaryColor.green.color.withAlphaComponent(0.3).toImage(),
for: .highlighted
)
result.layer.borderColor = Theme.PrimaryColor.green.color.cgColor
return result
}()
// MARK: - Lifecycle
public init(onUnlockPressed: (() -> ())? = nil) {
self.onUnlockPressed = onUnlockPressed
super.init(nibName: nil, bundle: nil)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open override func loadView() {
super.loadView()
view.themeBackgroundColor = .black // Need to match the Launch screen
let edgesView: UIView = UIView.container()
self.view.addSubview(edgesView)
edgesView.pin(to: view)
edgesView.addSubview(logoView)
logoView.center(in: edgesView)
logoView.set(.width, to: 64)
logoView.set(.height, to: 64)
edgesView.addSubview(unlockButton)
unlockButton.pin(.top, to: .bottom, of: logoView, withInset: Values.mediumSpacing)
unlockButton.center(.horizontal, in: view)
updateUI(state: .protection, animated: false)
}
/// The "screen blocking" window has three possible states:
///
/// * "Just a logo". Used when app is launching and in app switcher. Must match the "Launch Screen" storyboard pixel-for-pixel.
/// * "Screen Lock, local auth UI presented". Must match the "Launch Screen" storyboard pixel-for-pixel.
/// * "Screen Lock, local auth UI not presented". Show "unlock" button.
public func updateUI(state: State, animated: Bool) {
guard isViewLoaded else { return }
let shouldShowBlockWindow: Bool = (state != .none)
let shouldHaveScreenLock: Bool = (state == .lock)
self.logoView.isHidden = !shouldShowBlockWindow
let signature: String = String(format: "%d", shouldHaveScreenLock)
// Skip redundant work to avoid interfering with ongoing animations
guard signature != self.screenBlockingSignature else { return }
self.unlockButton.isHidden = !shouldHaveScreenLock
self.screenBlockingSignature = signature
guard animated else {
self.view.setNeedsLayout()
return
}
UIView.animate(withDuration: 0.3) { [weak self] in
self?.view.layoutIfNeeded()
}
}
@objc private func showUnlockUI() {
self.onUnlockPressed?()
}
}

View File

@ -24,22 +24,6 @@ public extension UIColor {
// MARK: - Functions
func toImage(isDarkMode: Bool) -> UIImage {
let bounds: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
rendererContext.cgContext
.setFillColor(
self.resolvedColor(
// Note: This is needed for '.cgColor' to support dark mode
with: UITraitCollection(userInterfaceStyle: isDarkMode ? .dark : .light)
).cgColor
)
rendererContext.cgContext.fill(bounds)
}
}
func darken(by percentage: CGFloat) -> UIColor {
guard percentage != 0 else { return self }
guard let hsba: HSBA = self.hsba else { return self }