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 */ /* Begin PBXBuildFile section */
1FFD68A448D5A1439F2F02FD /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; 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 */; }; 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 */; }; 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */; };
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */; }; 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */; };
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.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 */; }; 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 */; }; 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */; };
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.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 */; }; 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; }; 34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; };
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.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 */; }; 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */; };
7B0EFDF62755CC5400FFAAE7 /* CallMissedTipsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */; }; 7B0EFDF62755CC5400FFAAE7 /* CallMissedTipsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */; };
7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.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 */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; };
7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; };
7B1581E827210ECC00848B49 /* RenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E727210ECC00848B49 /* RenderView.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 */; }; 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; };
7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; C331FF982558FA6B00070591 /* AppMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9689023FA1401005F64E0 /* AppMode.swift */; };
C331FF992558FA6B00070591 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; C331FF992558FA6B00070591 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; };
C331FF9A2558FA6B00070591 /* Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A1238F356100BA5194 /* Values.swift */; }; 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 */; }; C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8544E3023D16CA500299F14 /* DeviceUtilities.swift */; };
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.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 */; }; 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 */; }; C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; };
C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.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 */; }; 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 */; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; };
C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F3255B6DBC007E1867 /* UIImage+OWS.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 */; }; 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, ); }; }; 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 */; }; C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */; };
C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF34B255B6DC8007E1867 /* OWSTableViewController.m */; }; 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, ); }; }; 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 */; }; C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF355255B6DCB007E1867 /* OWSViewController.m */; };
C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF356255B6DCB007E1867 /* OWSNavigationController.m */; }; C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF356255B6DCB007E1867 /* OWSNavigationController.m */; };
C38EF372255B6DCC007E1867 /* MediaMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF358255B6DCC007E1867 /* MediaMessageView.swift */; }; 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 */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; };
FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.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 */; }; 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 */; }; FD37E9CA28A1E4BD003AE748 /* Theme+ClassicLight.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */; };
FD37E9CC28A1E578003AE748 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */; }; FD37E9CC28A1E578003AE748 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */; };
FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CE28A1EB1B003AE748 /* Theme.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 */; }; FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; };
FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.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 */; }; 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 */; }; 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 */; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
@ -2297,7 +2291,6 @@
B848A4C4269EAAA200617031 /* UserDetailsSheet.swift */, B848A4C4269EAAA200617031 /* UserDetailsSheet.swift */,
FD4B200D283492210034334B /* InsetLockableTableView.swift */, FD4B200D283492210034334B /* InsetLockableTableView.swift */,
7B1581E3271FC59C00848B49 /* CallModal.swift */, 7B1581E3271FC59C00848B49 /* CallModal.swift */,
7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */,
); );
path = "Views & Modals"; path = "Views & Modals";
sourceTree = "<group>"; sourceTree = "<group>";
@ -2504,8 +2497,7 @@
B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */, B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */,
C31D1DDC25217014005D4DA8 /* UserCell.swift */, C31D1DDC25217014005D4DA8 /* UserCell.swift */,
C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */, C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */,
34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */, FD52090828B59411006098F6 /* ScreenLockUI.swift */,
34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2729,7 +2721,6 @@
B8BB829F238F322400BA5194 /* Colors.swift */, B8BB829F238F322400BA5194 /* Colors.swift */,
C39DD28724F3318C008590FC /* Colors.xcassets */, C39DD28724F3318C008590FC /* Colors.xcassets */,
B8BB82BD2394D4CE00BA5194 /* Fonts.swift */, B8BB82BD2394D4CE00BA5194 /* Fonts.swift */,
B8BB82A8238F62FB00BA5194 /* Gradients.swift */,
B8BB82A1238F356100BA5194 /* Values.swift */, B8BB82A1238F356100BA5194 /* Values.swift */,
); );
path = "Style Guide"; path = "Style Guide";
@ -2751,6 +2742,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B8B5BCEB2394D869003823C9 /* OutlineButton.swift */, B8B5BCEB2394D869003823C9 /* OutlineButton.swift */,
FD52090228B4680F006098F6 /* RadioButton.swift */,
B8BB82B02390C37000BA5194 /* SearchBar.swift */, B8BB82B02390C37000BA5194 /* SearchBar.swift */,
B8BB82B82394911B00BA5194 /* Separator.swift */, B8BB82B82394911B00BA5194 /* Separator.swift */,
B8CCF638239721E20091D419 /* TabBar.swift */, B8CCF638239721E20091D419 /* TabBar.swift */,
@ -2828,13 +2820,11 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FD37E9CD28A1E682003AE748 /* Views */, FD37E9CD28A1E682003AE748 /* Views */,
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */,
340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */,
7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */,
B8CCF6422397711F0091D419 /* SettingsVC.swift */, B8CCF6422397711F0091D419 /* SettingsVC.swift */,
B886B4A62398B23E00211ABE /* QRCodeVC.swift */, B886B4A62398B23E00211ABE /* QRCodeVC.swift */,
FD37EA0828AA2D27003AE748 /* SettingsTableViewModel.swift */, FD37EA0828AA2D27003AE748 /* SettingsTableViewModel.swift */,
FD37EA0628AA2CCA003AE748 /* SettingsTableViewController.swift */, FD37EA0628AA2CCA003AE748 /* SettingsTableViewController.swift */,
FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */,
FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */, FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */,
FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */, FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */,
FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */, FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */,
@ -2888,6 +2878,7 @@
children = ( children = (
B86BD08323399ACF000F5AE3 /* Modal.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */,
C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */,
FD52090628B49738006098F6 /* ConfirmationModal.swift */,
); );
path = "Sheets & Modals"; path = "Sheets & Modals";
sourceTree = "<group>"; sourceTree = "<group>";
@ -2955,9 +2946,8 @@
C36096EE25AD21BC008B62B2 /* Screen Lock */ = { C36096EE25AD21BC008B62B2 /* Screen Lock */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */, C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */,
C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */, FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */,
C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */,
); );
path = "Screen Lock"; path = "Screen Lock";
sourceTree = "<group>"; sourceTree = "<group>";
@ -3626,7 +3616,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FD37E9CE28A1EB1B003AE748 /* Theme.swift */, FD37E9CE28A1EB1B003AE748 /* Theme.swift */,
FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */, FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */,
FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */, FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */,
FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */, FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */,
FD37E9D228A1FCDB003AE748 /* Theme+OceanDark.swift */, FD37E9D228A1FCDB003AE748 /* Theme+OceanDark.swift */,
@ -4030,7 +4020,6 @@
C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */, C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */,
C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */, C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */,
C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */, C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */,
C38EF366255B6DCC007E1867 /* ScreenLockViewController.h in Headers */,
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */, C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */, C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */,
C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */, C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */,
@ -4910,13 +4899,12 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C331FF972558FA6B00070591 /* Fonts.swift in Sources */, C331FF972558FA6B00070591 /* Fonts.swift in Sources */,
C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */,
FD37E9D328A1FCDB003AE748 /* Theme+OceanDark.swift in Sources */, FD37E9D328A1FCDB003AE748 /* Theme+OceanDark.swift in Sources */,
C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */, C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */,
C331FFE72558FB0000070591 /* TextField.swift in Sources */, C331FFE72558FB0000070591 /* TextField.swift in Sources */,
C331FFE32558FB0000070591 /* TabBar.swift in Sources */, C331FFE32558FB0000070591 /* TabBar.swift in Sources */,
FD37E9D528A1FCE8003AE748 /* Theme+OceanLight.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 */, FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */,
FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */, FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */,
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */,
@ -4924,6 +4912,7 @@
FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */,
C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */,
C331FF982558FA6B00070591 /* AppMode.swift in Sources */, C331FF982558FA6B00070591 /* AppMode.swift in Sources */,
FD52090328B4680F006098F6 /* RadioButton.swift in Sources */,
C331FFE82558FB0000070591 /* TextView.swift in Sources */, C331FFE82558FB0000070591 /* TextView.swift in Sources */,
FD37E9D728A20B5D003AE748 /* UIColor+Utilities.swift in Sources */, FD37E9D728A20B5D003AE748 /* UIColor+Utilities.swift in Sources */,
FD37E9F928A5F14A003AE748 /* _001_ThemePreferences.swift in Sources */, FD37E9F928A5F14A003AE748 /* _001_ThemePreferences.swift in Sources */,
@ -4961,7 +4950,7 @@
C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */, C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */,
C38EF3FB255B6DF7007E1867 /* UIAlertController+OWS.swift in Sources */, C38EF3FB255B6DF7007E1867 /* UIAlertController+OWS.swift in Sources */,
C33FDC53255A582000E217F9 /* OutageDetection.swift in Sources */, C33FDC53255A582000E217F9 /* OutageDetection.swift in Sources */,
C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */, C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */,
C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */, C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */,
C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */, C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */,
C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */, C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */,
@ -5003,11 +4992,11 @@
C38EF22B255B6D5D007E1867 /* ShareViewDelegate.swift in Sources */, C38EF22B255B6D5D007E1867 /* ShareViewDelegate.swift in Sources */,
C38EF3BF255B6DE7007E1867 /* ImageEditorView.swift in Sources */, C38EF3BF255B6DE7007E1867 /* ImageEditorView.swift in Sources */,
C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */, C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */,
C38EF36B255B6DCC007E1867 /* ScreenLockViewController.m in Sources */,
C38EF40C255B6DF7007E1867 /* GradientView.swift in Sources */, C38EF40C255B6DF7007E1867 /* GradientView.swift in Sources */,
C38EF3FA255B6DF7007E1867 /* DirectionalPanGestureRecognizer.swift in Sources */, C38EF3FA255B6DF7007E1867 /* DirectionalPanGestureRecognizer.swift in Sources */,
C38EF3BB255B6DE7007E1867 /* ImageEditorStrokeItem.swift in Sources */, C38EF3BB255B6DE7007E1867 /* ImageEditorStrokeItem.swift in Sources */,
C38EF3C0255B6DE7007E1867 /* ImageEditorCropViewController.swift in Sources */, C38EF3C0255B6DE7007E1867 /* ImageEditorCropViewController.swift in Sources */,
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */,
C38EF401255B6DF7007E1867 /* VideoPlayerView.swift in Sources */, C38EF401255B6DF7007E1867 /* VideoPlayerView.swift in Sources */,
B8856D60256F129B001CE70E /* OWSAlerts.swift in Sources */, B8856D60256F129B001CE70E /* OWSAlerts.swift in Sources */,
C38EF3FE255B6DF7007E1867 /* OWSTextField.m in Sources */, C38EF3FE255B6DF7007E1867 /* OWSTextField.m in Sources */,
@ -5350,14 +5339,15 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */,
FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */, FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */,
B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */, B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */,
B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */, B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */,
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */,
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */,
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */,
7B13E1EB2811138200BD4F64 /* PrivacySettingsTableViewController.swift in Sources */,
C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */, C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */,
FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */,
FD37EA0928AA2D27003AE748 /* SettingsTableViewModel.swift in Sources */, FD37EA0928AA2D27003AE748 /* SettingsTableViewModel.swift in Sources */,
7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */, 7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */,
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */, B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */,
@ -5422,6 +5412,7 @@
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */, 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */,
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */, C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */,
FD52090728B49738006098F6 /* ConfirmationModal.swift in Sources */,
B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */, B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */,
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */, C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */,
B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */, B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */,
@ -5446,7 +5437,6 @@
B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */, B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */,
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */, 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
C331FFF32558FF0300070591 /* PathStatusView.swift in Sources */, C331FFF32558FF0300070591 /* PathStatusView.swift in Sources */,
7BFFB33C27D02F5800BEA04E /* CallPermissionRequestModal.swift in Sources */,
B848A4C5269EAAA200617031 /* UserDetailsSheet.swift in Sources */, B848A4C5269EAAA200617031 /* UserDetailsSheet.swift in Sources */,
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */, 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */, 7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */,
@ -5478,7 +5468,6 @@
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */, 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */, B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */,
45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */,
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */,
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */,
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
@ -5508,7 +5497,6 @@
C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */, C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */,
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
FD37E9D128A1F2EB003AE748 /* ThemeSelectionView.swift in Sources */, FD37E9D128A1F2EB003AE748 /* ThemeSelectionView.swift in Sources */,
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */, 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */,
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */, B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */,
7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */, 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */,

View File

@ -55,12 +55,29 @@ extension ConversationVC:
@objc func startCall(_ sender: Any?) { @objc func startCall(_ sender: Any?) {
guard SessionCall.isEnabled else { return } guard SessionCall.isEnabled else { return }
guard Storage.shared[.areCallsEnabled] else { guard Storage.shared[.areCallsEnabled] else {
let callPermissionRequestModal = CallPermissionRequestModal() let confirmationModal: ConfirmationModal = ConfirmationModal(
self.navigationController?.present(callPermissionRequestModal, animated: true, completion: nil) 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 return
} }
requestMicrophonePermissionIfNeeded { } requestMicrophonePermissionIfNeeded {}
let threadId: String = self.viewModel.threadData.threadId let threadId: String = self.viewModel.threadData.threadId

View File

@ -10,7 +10,6 @@ import SignalUtilitiesKit
final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate { final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate {
private static let loadingHeaderHeight: CGFloat = 20 private static let loadingHeaderHeight: CGFloat = 20
private static let messageRequestButtonHeight: CGFloat = 34
internal let viewModel: ConversationViewModel internal let viewModel: ConversationViewModel
private var dataChangeObservable: DatabaseCancellable? private var dataChangeObservable: DatabaseCancellable?
@ -209,11 +208,11 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
lazy var messageRequestView: UIView = { lazy var messageRequestView: UIView = {
let result: UIView = UIView() let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false result.translatesAutoresizingMaskIntoConstraints = false
result.themeBackgroundColor = .backgroundPrimary
result.isHidden = ( result.isHidden = (
self.viewModel.threadData.threadIsMessageRequest == false || self.viewModel.threadData.threadIsMessageRequest == false ||
self.viewModel.threadData.threadRequiresApproval == true self.viewModel.threadData.threadRequiresApproval == true
) )
result.setGradient(Gradients.defaultBackground)
return result return result
}() }()
@ -222,8 +221,8 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
let result: UILabel = UILabel() let result: UILabel = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false result.translatesAutoresizingMaskIntoConstraints = false
result.font = UIFont.systemFont(ofSize: 12) result.font = UIFont.systemFont(ofSize: 12)
result.text = NSLocalizedString("MESSAGE_REQUESTS_INFO", comment: "") result.text = "MESSAGE_REQUESTS_INFO".localized()
result.textColor = Colors.sessionMessageRequestsInfoText result.themeTextColor = .textSecondary
result.textAlignment = .center result.textAlignment = .center
result.numberOfLines = 2 result.numberOfLines = 2
@ -231,50 +230,18 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
}() }()
private lazy var messageRequestAcceptButton: UIButton = { private lazy var messageRequestAcceptButton: UIButton = {
let result: UIButton = UIButton() let result: OutlineButton = OutlineButton(style: .regular, size: .medium)
result.translatesAutoresizingMaskIntoConstraints = false result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true result.setTitle("TXT_DELETE_ACCEPT".localized(), for: .normal)
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.addTarget(self, action: #selector(acceptMessageRequest), for: .touchUpInside) result.addTarget(self, action: #selector(acceptMessageRequest), for: .touchUpInside)
return result return result
}() }()
private lazy var messageRequestDeleteButton: UIButton = { private lazy var messageRequestDeleteButton: UIButton = {
let result: UIButton = UIButton() let result: OutlineButton = OutlineButton(style: .destructive, size: .medium)
result.translatesAutoresizingMaskIntoConstraints = false result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true result.setTitle("TXT_DELETE_TITLE".localized(), for: .normal)
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.addTarget(self, action: #selector(deleteMessageRequest), for: .touchUpInside) result.addTarget(self, action: #selector(deleteMessageRequest), for: .touchUpInside)
return result return result
@ -353,14 +320,12 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers
messageRequestAcceptButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20) messageRequestAcceptButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20)
messageRequestAcceptButton.pin(.left, to: .left, of: messageRequestView, withInset: 20) messageRequestAcceptButton.pin(.left, to: .left, of: messageRequestView, withInset: 20)
messageRequestAcceptButton.pin(.bottom, to: .bottom, of: messageRequestView) messageRequestAcceptButton.pin(.bottom, to: .bottom, of: messageRequestView)
messageRequestAcceptButton.set(.height, to: ConversationVC.messageRequestButtonHeight)
messageRequestDeleteButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20) 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(.left, to: .right, of: messageRequestAcceptButton, withInset: UIDevice.current.isIPad ? Values.iPadButtonSpacing : 20)
messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20) messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20)
messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView) messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView)
messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton) messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton)
messageRequestDeleteButton.set(.height, to: ConversationVC.messageRequestButtonHeight)
// Unread count view // Unread count view
view.addSubview(unreadCountView) 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 let hide = UITableViewRowAction(style: .destructive, title: "TXT_HIDE_TITLE".localized()) { _, _ in
Storage.shared.write { db in db[.hasHiddenMessageRequests] = true } Storage.shared.write { db in db[.hasHiddenMessageRequests] = true }
} }
hide.themeBackgroundColor = .danger hide.themeBackgroundColor = .conversationButton_swipeDestructive
return [hide] return [hide]
@ -586,7 +586,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
self?.present(alert, animated: true, completion: nil) self?.present(alert, animated: true, completion: nil)
} }
delete.themeBackgroundColor = .danger delete.themeBackgroundColor = .conversationButton_swipeDestructive
let pin: UITableViewRowAction = UITableViewRowAction( let pin: UITableViewRowAction = UITableViewRowAction(
style: .normal, style: .normal,
@ -601,7 +601,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
.updateAll(db, SessionThread.Columns.isPinned.set(to: !threadViewModel.threadIsPinned)) .updateAll(db, SessionThread.Columns.isPinned.set(to: !threadViewModel.threadIsPinned))
} }
} }
pin.themeBackgroundColor = .conversationButton_pinBackground pin.themeBackgroundColor = .conversationButton_swipeTertiary
guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else { guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else {
return [ delete, pin ] return [ delete, pin ]
@ -631,7 +631,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve
.retainUntilComplete() .retainUntilComplete()
} }
} }
block.themeBackgroundColor = .backgroundTertiary block.themeBackgroundColor = .conversationButton_swipeSecondary
return [ delete, block, pin ] return [ delete, block, pin ]

View File

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

View File

@ -383,7 +383,7 @@ private final class NewConversationButton: UIImageView {
contentMode = .center contentMode = .center
layer.cornerRadius = (NewConversationButtonSet.collapsedButtonSize / 2) layer.cornerRadius = (NewConversationButtonSet.collapsedButtonSize / 2)
themeBackgroundColor = (isMainButton ? .menuButton_background : .backgroundTertiary) themeBackgroundColor = .menuButton_background
themeTintColor = .menuButton_icon themeTintColor = .menuButton_icon
if isMainButton { 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 // Note: Intentionally dispatching sync as we want to wait for these to complete before
// continuing // continuing
DispatchQueue.main.sync { DispatchQueue.main.sync {
OWSScreenLockUI.sharedManager().setup(withRootWindow: mainWindow) ScreenLockUI.shared.setupWithRootWindow(rootWindow: mainWindow)
OWSWindowManager.shared().setup( OWSWindowManager.shared().setup(
withRootWindow: mainWindow, withRootWindow: mainWindow,
screenBlockingWindow: OWSScreenLockUI.sharedManager().screenBlockingWindow screenBlockingWindow: ScreenLockUI.shared.screenBlockingWindow
) )
OWSScreenLockUI.sharedManager().startObserving() ScreenLockUI.shared.startObserving()
} }
}, },
migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in

View File

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

View File

@ -522,13 +522,6 @@
"preferences_notifications_strategy_category_title" = "Benachrichtigungsstrategie"; "preferences_notifications_strategy_category_title" = "Benachrichtigungsstrategie";
"modal_seed_title" = "Ihr Wiederherstellungssatz"; "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_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_title" = "QR-Code";
"vc_qr_code_view_my_qr_code_tab_title" = "Meinen QR-Code anzeigen"; "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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Notification Strategy";
"modal_seed_title" = "Your Recovery Phrase"; "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_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_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "View My 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Estrategia de notificación";
"modal_seed_title" = "Tu frase de recuperació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_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_title" = "Código QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Ver mi 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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" = "استراتژی اعلان"; "preferences_notifications_strategy_category_title" = "استراتژی اعلان";
"modal_seed_title" = "عبارت بازیابی شما"; "modal_seed_title" = "عبارت بازیابی شما";
"modal_seed_explanation" = "این عبارت بازیابی شماست. با استفاده از آن می‌توانید شناسه‌ی Session خود را به دستگاه جدید بازیابی یا انتقال دهید."; "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_title" = "کد QR";
"vc_qr_code_view_my_qr_code_tab_title" = "مشاهده کد QR من"; "vc_qr_code_view_my_qr_code_tab_title" = "مشاهده کد QR من";
"vc_qr_code_view_scan_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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Ilmoitustyyli";
"modal_seed_title" = "Palatusvirkkeesi"; "modal_seed_title" = "Palatusvirkkeesi";
"modal_seed_explanation" = "Tämä on palautusvirkkeesi. Sillä voit palauttaa tai siirtää Session ID:si uuteen laitteeseen."; "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_title" = "QR-koodi";
"vc_qr_code_view_my_qr_code_tab_title" = "Näytä QR-koodini"; "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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Stratégie de notification";
"modal_seed_title" = "Votre phrase de récupération"; "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_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_title" = "Code QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Afficher mon 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Notification Strategy";
"modal_seed_title" = "Your Recovery Phrase"; "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_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_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "View My 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Strategija obavijesti";
"modal_seed_title" = "Fraza za oporavak"; "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_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_title" = "QR kôd";
"vc_qr_code_view_my_qr_code_tab_title" = "Pogledaj moj 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Strategi notofikasi";
"modal_seed_title" = "Kata pemulihan anda"; "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_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_title" = "Kode QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Lihat kode QR saya"; "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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Strategia di notifica";
"modal_seed_title" = "Frase di recupero"; "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_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_title" = "Codice QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Visualizza il mio 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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" = "通知戦略"; "preferences_notifications_strategy_category_title" = "通知戦略";
"modal_seed_title" = "あなたのリカバリーフレーズ"; "modal_seed_title" = "あなたのリカバリーフレーズ";
"modal_seed_explanation" = "これはあなたのリカバリーフレーズです。これにより、Session ID を新しい端末に復元または移行できます。"; "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_title" = "QR コード";
"vc_qr_code_view_my_qr_code_tab_title" = "私の QR コードを表示する"; "vc_qr_code_view_my_qr_code_tab_title" = "私の QR コードを表示する";
"vc_qr_code_view_scan_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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Notificatie Inhoud";
"modal_seed_title" = "Uw Herstel Zin"; "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_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_title" = "QR-code";
"vc_qr_code_view_my_qr_code_tab_title" = "Bekijk mijn 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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ń"; "preferences_notifications_strategy_category_title" = "Strategia powiadomień";
"modal_seed_title" = "Twoja fraza odzyskiwania"; "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_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_title" = "Kod QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Wyświetl mój 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Estratégia de notificação";
"modal_seed_title" = "Sua frase de recuperaçã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_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_title" = "Código QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Ver meu 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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" = "Метод уведомлений"; "preferences_notifications_strategy_category_title" = "Метод уведомлений";
"modal_seed_title" = "Ваша секретная фраза"; "modal_seed_title" = "Ваша секретная фраза";
"modal_seed_explanation" = "Это ваша секретная фраза. С ее помощью вы можете восстановить или перенести свой Session ID на новое устройство."; "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_title" = "QR-код";
"vc_qr_code_view_my_qr_code_tab_title" = "Посмотреть мой QR-код"; "vc_qr_code_view_my_qr_code_tab_title" = "Посмотреть мой QR-код";
"vc_qr_code_view_scan_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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Notification Strategy";
"modal_seed_title" = "Your Recovery Phrase"; "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_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_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_title" = "View My 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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í"; "preferences_notifications_strategy_category_title" = "Stratégia upozornení";
"modal_seed_title" = "Vaša fráza pre obnovenie"; "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_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_title" = "QR kód";
"vc_qr_code_view_my_qr_code_tab_title" = "Zobraziť môj 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "preferences_notifications_strategy_category_title" = "Strategi för aviseringar";
"modal_seed_title" = "Din Återställningsfras"; "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_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_title" = "QR-kod";
"vc_qr_code_view_my_qr_code_tab_title" = "Visa min 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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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" = "กลยุทธ์สำคัญแจ้งเตือน"; "preferences_notifications_strategy_category_title" = "กลยุทธ์สำคัญแจ้งเตือน";
"modal_seed_title" = "วลีกู้คืนของคุณ"; "modal_seed_title" = "วลีกู้คืนของคุณ";
"modal_seed_explanation" = "นี่คือวลีกู้คืนของคุณ ด้วยวิธีนี้ คุณสามารถกู้คืนหรือย้ายไอดีเซสชันSessionของคุณไปยังอุปกรณ์ใหม่ได้"; "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_title" = "QR โค้ด";
"vc_qr_code_view_my_qr_code_tab_title" = "แสดง QR โค้ดของคุน"; "vc_qr_code_view_my_qr_code_tab_title" = "แสดง QR โค้ดของคุน";
"vc_qr_code_view_scan_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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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"; "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_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_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_title" = "Mã QR";
"vc_qr_code_view_my_qr_code_tab_title" = "Xem mã QR của tôi"; "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"; "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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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" = "通知類型"; "preferences_notifications_strategy_category_title" = "通知類型";
"modal_seed_title" = "您的回復用字句"; "modal_seed_title" = "您的回復用字句";
"modal_seed_explanation" = "這是您的回復用字句,您可以利用此字句來回復或轉移您的帳號至新的裝置上。"; "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_title" = "QR Code";
"vc_qr_code_view_my_qr_code_tab_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"; "vc_qr_code_view_scan_qr_code_tab_title" = "掃描 QR Code";
@ -684,6 +677,26 @@
"SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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" = "通知选项"; "preferences_notifications_strategy_category_title" = "通知选项";
"modal_seed_title" = "您的恢复口令"; "modal_seed_title" = "您的恢复口令";
"modal_seed_explanation" = "这是您的恢复口令。您可以通过该口令将Session ID还原或迁移到新设备上。"; "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_title" = "二维码";
"vc_qr_code_view_my_qr_code_tab_title" = "查看我的二维码"; "vc_qr_code_view_my_qr_code_tab_title" = "查看我的二维码";
"vc_qr_code_view_scan_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."; "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send.";
"INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again.";
"INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase 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_TITLE" = "Notifications";
"NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy";
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode";
@ -714,3 +727,11 @@
"HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FEEDBACK_TITLE" = "We'd love your Feedback";
"HELP_FAQ_TITLE" = "FAQ"; "HELP_FAQ_TITLE" = "FAQ";
"HELP_SUPPORT_TITLE" = "Support"; "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() super.init()
AppReadiness.runNowOrWhenAppDidBecomeReady {
NotificationCenter.default.addObserver(self, selector: #selector(self.handleMessageRead), name: .incomingMessageMarkedAsRead, object: nil)
}
SwiftSingletons.register(self) 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 // MARK: - Presenting Notifications
func registerNotificationSettings() -> Promise<Void> { 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 senderName = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant)
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview) .defaulting(to: .defaultPreviewType)
switch previewType { switch previewType {
case .noNameNoPreview: case .noNameNoPreview:
@ -305,7 +285,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
public func notifyForFailedSend(_ db: Database, in thread: SessionThread) { public func notifyForFailedSend(_ db: Database, in thread: SessionThread) {
let notificationTitle: String? let notificationTitle: String?
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview) .defaulting(to: .defaultPreviewType)
switch previewType { switch previewType {
case .noNameNoPreview: notificationTitle = nil 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) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
setUpViewHierarchy() setUpViewHierarchy()
registerObservers() registerObservers()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
setUpViewHierarchy() setUpViewHierarchy()
registerObservers() registerObservers()
} }
private func setUpViewHierarchy() { private func setUpViewHierarchy() {
layer.cornerRadius = PathStatusView.size / 2 layer.cornerRadius = (PathStatusView.size / 2)
layer.masksToBounds = false 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() { private func registerObservers() {
@ -33,18 +53,28 @@ final class PathStatusView : UIView {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
private func setColor(to color: UIColor, isAnimated: Bool) { private func setStatus(to status: Status) {
backgroundColor = color themeBackgroundColor = status.themeColor
let size = PathStatusView.size layer.themeShadowColor = status.themeColor
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: color, isAnimated: isAnimated, radius: isLightMode ? 6 : 8) layer.shadowOffset = CGSize(width: 0, height: 0.8)
setCircularGlow(with: glowConfiguration) 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() { @objc private func handleBuildingPathsNotification() {
setColor(to: Colors.pathsBuilding, isAnimated: true) setStatus(to: .connecting)
} }
@objc private func handlePathsBuiltNotification() { @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 // MARK: General
private func getPathRow(title: String, subtitle: String?, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) -> UIStackView { 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(.width, to: PathVC.expandedDotSize)
lineView.set(.height, to: PathVC.rowHeight) lineView.set(.height, to: PathVC.rowHeight)
let titleLabel = UILabel()
titleLabel.textColor = Colors.text let titleLabel: UILabel = UILabel()
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize) titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
titleLabel.text = title titleLabel.text = title
titleLabel.themeTextColor = .textPrimary
titleLabel.lineBreakMode = .byTruncatingTail titleLabel.lineBreakMode = .byTruncatingTail
let titleStackView = UIStackView(arrangedSubviews: [ titleLabel ]) let titleStackView = UIStackView(arrangedSubviews: [ titleLabel ])
titleStackView.axis = .vertical titleStackView.axis = .vertical
if let subtitle = subtitle { if let subtitle = subtitle {
let subtitleLabel = UILabel() let subtitleLabel = UILabel()
subtitleLabel.textColor = Colors.text
subtitleLabel.font = .systemFont(ofSize: Values.verySmallFontSize) subtitleLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
subtitleLabel.text = subtitle subtitleLabel.text = subtitle
subtitleLabel.themeTextColor = .textPrimary
subtitleLabel.lineBreakMode = .byTruncatingTail subtitleLabel.lineBreakMode = .byTruncatingTail
titleStackView.addArrangedSubview(subtitleLabel) titleStackView.addArrangedSubview(subtitleLabel)
} }
let stackView = UIStackView(arrangedSubviews: [ lineView, titleStackView ]) let stackView = UIStackView(arrangedSubviews: [ lineView, titleStackView ])
stackView.axis = .horizontal stackView.axis = .horizontal
stackView.spacing = Values.largeSpacing stackView.spacing = Values.largeSpacing
stackView.alignment = .center stackView.alignment = .center
return stackView return stackView
} }
@ -221,7 +230,9 @@ private final class LineView : UIView {
self.location = location self.location = location
self.dotAnimationStartDelay = dotAnimationStartDelay self.dotAnimationStartDelay = dotAnimationStartDelay
self.dotAnimationRepeatInterval = dotAnimationRepeatInterval self.dotAnimationRepeatInterval = dotAnimationRepeatInterval
super.init(frame: CGRect.zero) super.init(frame: CGRect.zero)
setUpViewHierarchy() setUpViewHierarchy()
} }

View File

@ -102,6 +102,22 @@ class HelpViewModel: SettingsTableViewModel<HelpViewModel.Section, HelpViewModel
return 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) UIApplication.shared.open(url)
}) })
) )
@ -111,7 +127,6 @@ class HelpViewModel: SettingsTableViewModel<HelpViewModel.Section, HelpViewModel
} }
.removeDuplicates() .removeDuplicates()
// MARK: - Functions // MARK: - Functions
public override func updateSettings(_ updatedSettings: [SectionModel]) { public override func updateSettings(_ updatedSettings: [SectionModel]) {

View File

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

View File

@ -65,7 +65,7 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
SyncPushTokensJob.run(uploadOnlyIfStale: false) SyncPushTokensJob.run(uploadOnlyIfStale: false)
} }
), ),
extraActionTitle: { _, primaryColor in extraActionTitle: { theme, primaryColor in
NSMutableAttributedString() NSMutableAttributedString()
.appending( .appending(
NSAttributedString( NSAttributedString(
@ -77,7 +77,10 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
) )
.appending( .appending(
NSAttributedString( 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, db,
type: Preferences.Sound.self, type: Preferences.Sound.self,
key: .defaultNotificationSound, key: .defaultNotificationSound,
titleGenerator: { $0?.displayName }, titleGenerator: { $0.defaulting(to: .defaultNotificationSound).displayName },
createUpdateScreen: { createUpdateScreen: {
SettingsTableViewController(viewModel: NotificationSoundViewModel()) SettingsTableViewController(viewModel: NotificationSoundViewModel())
} }
@ -119,7 +122,7 @@ class NotificationSettingsViewModel: SettingsTableViewModel<NotificationSettings
db, db,
type: Preferences.NotificationPreviewType.self, type: Preferences.NotificationPreviewType.self,
key: .preferencesNotificationPreviewType, key: .preferencesNotificationPreviewType,
titleGenerator: { $0?.name }, titleGenerator: { $0.defaulting(to: .defaultPreviewType).name },
createUpdateScreen: { createUpdateScreen: {
SettingsTableViewController(viewModel: NotificationContentViewModel()) SettingsTableViewController(viewModel: NotificationContentViewModel())
} }

View File

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

View File

@ -6,117 +6,98 @@ import SessionSnodeKit
import SessionMessagingKit import SessionMessagingKit
import SignalUtilitiesKit import SignalUtilitiesKit
@objc(LKNukeDataModal)
final class NukeDataModal: Modal { final class NukeDataModal: Modal {
// MARK: - Components // MARK: - Components
private lazy var titleLabel: UILabel = { private lazy var titleLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.textColor = Colors.text
result.font = .boldSystemFont(ofSize: Values.mediumFontSize) result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.text = "modal_clear_all_data_title".localized() result.text = "modal_clear_all_data_title".localized()
result.numberOfLines = 0 result.themeTextColor = .textPrimary
result.lineBreakMode = .byWordWrapping
result.textAlignment = .center result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
return result return result
}() }()
private lazy var explanationLabel: UILabel = { private lazy var explanationLabel: UILabel = {
let result = UILabel() let result = UILabel()
result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
result.font = .systemFont(ofSize: Values.smallFontSize) result.font = .systemFont(ofSize: Values.smallFontSize)
result.text = "modal_clear_all_data_explanation".localized() result.text = "modal_clear_all_data_explanation".localized()
result.numberOfLines = 0 result.themeTextColor = .textPrimary
result.textAlignment = .center result.textAlignment = .center
result.lineBreakMode = .byWordWrapping 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 return result
}() }()
private lazy var clearDataButton: UIButton = { private lazy var clearDataButton: UIButton = {
let result = UIButton() let result: UIButton = Modal.createButton(
result.set(.height, to: Values.mediumButtonHeight) title: "modal_clear_all_data_confirm".localized(),
result.layer.cornerRadius = Modal.buttonCornerRadius titleColor: .danger
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)
result.addTarget(self, action: #selector(clearAllData), for: UIControl.Event.touchUpInside) result.addTarget(self, action: #selector(clearAllData), for: UIControl.Event.touchUpInside)
return result return result
}() }()
private lazy var buttonStackView1: UIStackView = { private lazy var buttonStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ cancelButton, clearDataButton ]) let result = UIStackView(arrangedSubviews: [ clearDataButton, cancelButton ])
result.axis = .horizontal result.axis = .horizontal
result.spacing = Values.mediumSpacing
result.distribution = .fillEqually result.distribution = .fillEqually
return result 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 = { 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.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 return result
}() }()
private lazy var mainStackView: UIStackView = { private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackViewContainer ]) let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackView ])
result.axis = .vertical result.axis = .vertical
result.spacing = Values.largeSpacing - Values.smallFontSize / 2 result.spacing = Values.largeSpacing - Values.smallFontSize / 2
@ -127,32 +108,33 @@ final class NukeDataModal: Modal {
override func populateContentView() { override func populateContentView() {
contentView.addSubview(mainStackView) contentView.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) mainStackView.pin(to: contentView)
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: mainStackView.spacing)
} }
// MARK: - Interaction // MARK: - Interaction
@objc private func clearAllData() { @objc private func clearAllData() {
UIView.animate(withDuration: 0.25) { guard clearNetworkRadio.isSelected else {
self.buttonStackView1.alpha = 0 clearDeviceOnly()
self.buttonStackView2.alpha = 1 return
} }
UIView.transition( let confirmationModal: ConfirmationModal = ConfirmationModal(
with: explanationLabel, info: ConfirmationModal.Info(
duration: 0.25, title: "modal_clear_all_data_title".localized(),
options: .transitionCrossDissolve, explanation: "modal_clear_all_data_explanation_2".localized(),
animations: { confirmTitle: "modal_clear_all_data_confirm".localized(),
self.explanationLabel.text = "modal_clear_all_data_explanation_2".localized() confirmStyle: .danger,
}, cancelStyle: .textPrimary
completion: nil )
) ) { [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 ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in
Storage.shared Storage.shared
.writeAsync { db in try MessageSender.syncConfiguration(db, forceSyncNow: true) } .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 ModalActivityIndicatorViewController
.present(fromViewController: self, canCancel: false) { [weak self] _ in .present(fromViewController: presentedViewController, canCancel: false) { [weak self] _ in
SnodeAPI.clearAllData() SnodeAPI.clearAllData()
.done(on: DispatchQueue.main) { confirmations in .done(on: DispatchQueue.main) { confirmations in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader 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() { override func viewDidLoad() {
// Remove background color // Remove background color
view.backgroundColor = .clear view.themeBackgroundColor = .clear
// Set up title label // Set up title label
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize) titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
titleLabel.text = "Scan Me" titleLabel.text = "Scan Me"
titleLabel.numberOfLines = 1 titleLabel.themeTextColor = .textPrimary
titleLabel.textAlignment = .center titleLabel.textAlignment = .center
titleLabel.lineBreakMode = .byWordWrapping titleLabel.lineBreakMode = .byWordWrapping
titleLabel.numberOfLines = 1
titleLabel.set(.height, to: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize) titleLabel.set(.height, to: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
// Set up QR code image view // Set up QR code image view
let qrCodeImageView = UIImageView() let qrCodeImageView = UIImageView(
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true) image: QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: false)
qrCodeImageView.image = qrCode .withRenderingMode(.alwaysTemplate)
qrCodeImageView.contentMode = .scaleAspectFit )
qrCodeImageView.set(.height, to: isIPhone5OrSmaller ? 180 : 240) qrCodeImageView.set(.height, to: isIPhone5OrSmaller ? 180 : 240)
qrCodeImageView.set(.width, 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 // Set up QR code image view container
let qrCodeImageViewContainer = UIView() let qrCodeImageViewContainer = UIView()
qrCodeImageViewContainer.accessibilityLabel = "Your QR code" qrCodeImageViewContainer.accessibilityLabel = "Your QR code"
qrCodeImageViewContainer.isAccessibilityElement = true qrCodeImageViewContainer.isAccessibilityElement = true
qrCodeImageViewContainer.addSubview(qrCodeImageView) qrCodeImageViewContainer.addSubview(qrCodeImageViewBackgroundView)
qrCodeImageView.center(.horizontal, in: qrCodeImageViewContainer) qrCodeImageViewBackgroundView.center(.horizontal, in: qrCodeImageViewContainer)
qrCodeImageView.pin(.top, to: .top, of: qrCodeImageViewContainer) qrCodeImageViewBackgroundView.pin(.top, to: .top, of: qrCodeImageViewContainer)
qrCodeImageView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer) qrCodeImageViewBackgroundView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer)
// Set up explanation label // Set up explanation label
let explanationLabel = UILabel() let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.mediumFontSize) explanationLabel.font = .systemFont(ofSize: Values.mediumFontSize)
explanationLabel.text = NSLocalizedString("vc_view_my_qr_code_explanation", comment: "") explanationLabel.text = "vc_view_my_qr_code_explanation".localized()
explanationLabel.numberOfLines = 0 explanationLabel.themeTextColor = .textPrimary
explanationLabel.textAlignment = .center explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.numberOfLines = 0
// Set up share button // Set up share button
let shareButton = OutlineButton(style: .regular, size: .large) let shareButton = OutlineButton(style: .regular, size: .large)
@ -222,16 +254,19 @@ private final class ViewMyQRCodeVC : UIViewController {
stackView.pin(.top, to: .top, of: view) stackView.pin(.top, to: .top, of: view)
view.pin(.trailing, to: .trailing, of: stackView) view.pin(.trailing, to: .trailing, of: stackView)
bottomConstraint = view.pin(.bottom, to: .bottom, of: stackView) bottomConstraint = view.pin(.bottom, to: .bottom, of: stackView)
// Set up width constraint // Set up width constraint
view.set(.width, to: UIScreen.main.bounds.width) view.set(.width, to: UIScreen.main.bounds.width)
} }
// MARK: General // MARK: - General
func constrainHeight(to height: CGFloat) { func constrainHeight(to height: CGFloat) {
view.set(.height, to: height) view.set(.height, to: height)
} }
// MARK: Interaction // MARK: - Interaction
@objc private func shareQRCode() { @objc private func shareQRCode() {
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true) let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
let shareVC = UIActivityViewController(activityItems: [ qrCode ], applicationActivities: nil) let shareVC = UIActivityViewController(activityItems: [ qrCode ], applicationActivities: nil)
@ -250,26 +285,30 @@ private final class ScanQRCodePlaceholderVC : UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
// Remove background color // Remove background color
view.backgroundColor = .clear view.themeBackgroundColor = .clear
// Set up explanation label // Set up explanation label
let explanationLabel = UILabel() let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("vc_scan_qr_code_camera_access_explanation", comment: "") explanationLabel.text = "vc_scan_qr_code_camera_access_explanation".localized()
explanationLabel.numberOfLines = 0 explanationLabel.themeTextColor = .textPrimary
explanationLabel.textAlignment = .center explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.numberOfLines = 0
// Set up call to action button // Set up call to action button
let callToActionButton = UIButton() let callToActionButton = UIButton()
callToActionButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize) callToActionButton.titleLabel?.font = .boldSystemFont(ofSize: Values.mediumFontSize)
callToActionButton.setTitleColor(Colors.accent, for: UIControl.State.normal) callToActionButton.setTitle("vc_scan_qr_code_grant_camera_access_button_title".localized(), for: UIControl.State.normal)
callToActionButton.setTitle(NSLocalizedString("vc_scan_qr_code_grant_camera_access_button_title", comment: ""), for: UIControl.State.normal) callToActionButton.setThemeTitleColor(.primary, for: .normal)
callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside) callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside)
// Set up stack view // Set up stack view
let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ]) let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ])
stackView.axis = .vertical stackView.axis = .vertical
stackView.spacing = Values.mediumSpacing stackView.spacing = Values.mediumSpacing
stackView.alignment = .center stackView.alignment = .center
// Set up constraints // Set up constraints
view.set(.width, to: UIScreen.main.bounds.width) view.set(.width, to: UIScreen.main.bounds.width)
view.addSubview(stackView) view.addSubview(stackView)

View File

@ -11,6 +11,7 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
typealias SectionModel = SettingsTableViewModel<Section, SettingItem>.SectionModel typealias SectionModel = SettingsTableViewModel<Section, SettingItem>.SectionModel
private let viewModel: SettingsTableViewModel<Section, SettingItem> private let viewModel: SettingsTableViewModel<Section, SettingItem>
private let shouldShowCloseButton: Bool
private var dataChangeObservable: DatabaseCancellable? private var dataChangeObservable: DatabaseCancellable?
private var hasLoadedInitialSettingsData: Bool = false private var hasLoadedInitialSettingsData: Bool = false
@ -37,8 +38,9 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
// MARK: - Initialization // MARK: - Initialization
init(viewModel: SettingsTableViewModel<Section, SettingItem>) { init(viewModel: SettingsTableViewModel<Section, SettingItem>, shouldShowCloseButton: Bool = false) {
self.viewModel = viewModel self.viewModel = viewModel
self.shouldShowCloseButton = shouldShowCloseButton
super.init(nibName: nil, bundle: nil) 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, case .listSelection(_, _, let shouldAutoSave, _) = data.first?.elements.first?.action,
!shouldAutoSave !shouldAutoSave
else { 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 navigationItem.rightBarButtonItem = nil
return return
} }
@ -243,9 +254,23 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo) manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo)
onChange?() onChange?()
case .settingBool(let key): case .settingBool(let key, let confirmationInfo):
Storage.shared.write { db in db[key] = !db[key] } guard
manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo) 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), case .push(let createDestination), .dangerPush(let createDestination),
.settingEnum(_, _, let createDestination): .settingEnum(_, _, let createDestination):
@ -308,6 +333,10 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
// MARK: - NavigationActions // MARK: - NavigationActions
@objc private func closePressed() {
navigationController?.dismiss(animated: true)
}
@objc private func cancelButtonPressed() { @objc private func cancelButtonPressed() {
navigationController?.popViewController(animated: true) navigationController?.popViewController(animated: true)
} }
@ -322,6 +351,11 @@ class SettingsTableViewController<Section: SettingSection, SettingItem: Hashable
// MARK: - SettingHeaderView // MARK: - SettingHeaderView
class SettingHeaderView: UITableViewHeaderFooterView { 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 // MARK: - UI
private let stackView: UIStackView = { private let stackView: UIStackView = {
@ -331,12 +365,6 @@ class SettingHeaderView: UITableViewHeaderFooterView {
result.distribution = .fill result.distribution = .fill
result.alignment = .fill result.alignment = .fill
result.isLayoutMarginsRelativeArrangement = true result.isLayoutMarginsRelativeArrangement = true
result.layoutMargins = UIEdgeInsets(
top: Values.mediumSpacing,
left: Values.largeSpacing,
bottom: Values.smallSpacing,
right: Values.largeSpacing
)
return result return result
}() }()
@ -373,8 +401,6 @@ class SettingHeaderView: UITableViewHeaderFooterView {
} }
private func setupLayout() { private func setupLayout() {
self.heightAnchor.constraint(greaterThanOrEqualToConstant: Values.mediumSpacing).isActive = true
stackView.pin(to: self) stackView.pin(to: self)
separator.pin(.left, to: .left, of: self) separator.pin(.left, to: .left, of: self)
@ -387,5 +413,14 @@ class SettingHeaderView: UITableViewHeaderFooterView {
fileprivate func update(with title: String) { fileprivate func update(with title: String) {
titleLabel.text = title titleLabel.text = title
titleLabel.isHidden = title.isEmpty 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, key: String,
onChange: (() -> Void)? onChange: (() -> Void)?
) )
case settingBool(key: Setting.BoolKey) case settingBool(
key: Setting.BoolKey,
confirmationInfo: ConfirmationModal.Info?
)
case settingEnum( case settingEnum(
key: String, key: String,
title: String?, title: String?,
@ -152,6 +155,10 @@ public enum SettingsAction: Hashable, Equatable {
createUpdateScreen: createUpdateScreen createUpdateScreen: createUpdateScreen
) )
} }
public static func settingBool(key: Setting.BoolKey) -> SettingsAction {
return .settingBool(key: key, confirmationInfo: nil)
}
// MARK: - Conformance // MARK: - Conformance
@ -160,7 +167,10 @@ public enum SettingsAction: Hashable, Equatable {
switch self { switch self {
case .userDefaultsBool(_, let key, _): key.hash(into: &hasher) 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, _): case .settingEnum(let key, let title, _):
key.hash(into: &hasher) key.hash(into: &hasher)
title.hash(into: &hasher) title.hash(into: &hasher)
@ -179,7 +189,11 @@ public enum SettingsAction: Hashable, Equatable {
case (.userDefaultsBool(_, let lhsKey, _), .userDefaultsBool(_, let rhsKey, _)): case (.userDefaultsBool(_, let lhsKey, _), .userDefaultsBool(_, let rhsKey, _)):
return (lhsKey == 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, _)): case (.settingEnum(let lhsKey, let lhsTitle, _), .settingEnum(let rhsKey, let rhsTitle, _)):
return ( return (

View File

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

View File

@ -3,8 +3,10 @@
import UIKit import UIKit
import SessionUIKit import SessionUIKit
class BaseVC: UIViewController { public class BaseVC: UIViewController {
override var preferredStatusBarStyle: UIStatusBarStyle { return ThemeManager.currentTheme.statusBarStyle } public override var preferredStatusBarStyle: UIStatusBarStyle {
return ThemeManager.currentTheme.statusBarStyle
}
lazy var navBarTitleLabel: UILabel = { lazy var navBarTitleLabel: UILabel = {
let result = UILabel() let result = UILabel()
@ -26,7 +28,7 @@ class BaseVC: UIViewController {
return result return result
}() }()
override func viewDidLoad() { public override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
navigationItem.backButtonTitle = "" 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 UIKit
import SessionUIKit import SessionUIKit
@objc(LKModal) public class Modal: BaseVC, UIGestureRecognizerDelegate {
class Modal: BaseVC, UIGestureRecognizerDelegate { private static let cornerRadius: CGFloat = 11
// MARK: Components // MARK: - Components
lazy var contentView: UIView = {
let result = UIView() lazy var dimmingView: UIView = {
result.backgroundColor = Colors.modalBackground 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.cornerRadius = Modal.cornerRadius
result.layer.masksToBounds = false result.layer.shadowRadius = 10
result.layer.borderColor = isLightMode ? UIColor.white.cgColor : Colors.modalBorder.cgColor result.layer.shadowOpacity = 0.4
result.layer.borderWidth = 1
result.layer.shadowColor = UIColor.black.cgColor return result
result.layer.shadowRadius = isLightMode ? 2 : 8 }()
result.layer.shadowOpacity = isLightMode ? 0.1 : 0.64
lazy var contentView: UIView = {
let result: UIView = UIView()
result.clipsToBounds = true
result.layer.cornerRadius = Modal.cornerRadius
return result return result
}() }()
lazy var cancelButton: UIButton = { lazy var cancelButton: UIButton = {
let result = UIButton() let result: UIButton = Modal.createButton(title: "cancel".localized(), titleColor: .textPrimary)
result.set(.height, to: Values.mediumButtonHeight) result.addTarget(self, action: #selector(close), for: .touchUpInside)
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)
return result return result
}() }()
// MARK: Settings // MARK: - Lifecycle
private static let cornerRadius: CGFloat = 10
static let buttonCornerRadius = CGFloat(5)
// MARK: Lifecycle public override func viewDidLoad() {
override func viewDidLoad() {
super.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)) let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(close))
swipeGestureRecognizer.direction = .down swipeGestureRecognizer.direction = .down
view.addGestureRecognizer(swipeGestureRecognizer) dimmingView.addGestureRecognizer(swipeGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(close)) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(close))
tapGestureRecognizer.delegate = self 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() populateContentView()
} }
@ -71,6 +97,18 @@ class Modal: BaseVC, UIGestureRecognizerDelegate {
preconditionFailure("populateContentView() is abstract and must be overridden.") 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 // MARK: - Interaction
@objc func close() { @objc func close() {
@ -79,7 +117,7 @@ class Modal: BaseVC, UIGestureRecognizerDelegate {
// MARK: - 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) let location: CGPoint = touch.location(in: contentView)
return !contentView.point(inside: location, with: nil) 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 Photos
import PhotosUI import PhotosUI
import SessionUtilitiesKit
public func requestCameraPermissionIfNeeded() -> Bool { public func requestCameraPermissionIfNeeded() -> Bool {
switch AVCaptureDevice.authorizationStatus(for: .video) { switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: return true case .authorized: return true
case .denied, .restricted: case .denied, .restricted:
let modal = PermissionMissingModal(permission: "camera") { } let modal = PermissionMissingModal(permission: "camera") { }
modal.modalPresentationStyle = .overFullScreen modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil) presentingVC.present(modal, animated: true, completion: nil)
return false return false
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in }) case .notDetermined:
return false AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in })
default: return false return false
default: return false
} }
} }
public func requestMicrophonePermissionIfNeeded(onNotGranted: @escaping () -> Void) { public func requestMicrophonePermissionIfNeeded(onNotGranted: (() -> Void)? = nil) {
switch AVAudioSession.sharedInstance().recordPermission { switch AVAudioSession.sharedInstance().recordPermission {
case .granted: break case .granted: break
case .denied: case .denied:
onNotGranted() onNotGranted?()
let modal = PermissionMissingModal(permission: "microphone") { let modal = PermissionMissingModal(permission: "microphone") {
onNotGranted() onNotGranted?()
} }
modal.modalPresentationStyle = .overFullScreen modal.modalPresentationStyle = .overFullScreen
modal.modalTransitionStyle = .crossDissolve modal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
presentingVC.present(modal, animated: true, completion: nil) presentingVC.present(modal, animated: true, completion: nil)
case .undetermined:
onNotGranted() case .undetermined:
AVAudioSession.sharedInstance().requestRecordPermission { _ in } onNotGranted?()
default: break AVAudioSession.sharedInstance().requestRecordPermission { _ in }
default: break
} }
} }
@ -66,7 +74,8 @@ public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void)
} }
} }
} }
} else { }
else {
authorizationStatus = PHPhotoLibrary.authorizationStatus() authorizationStatus = PHPhotoLibrary.authorizationStatus()
if authorizationStatus == .notDetermined { if authorizationStatus == .notDetermined {
PHPhotoLibrary.requestAuthorization { status in PHPhotoLibrary.requestAuthorization { status in
@ -76,15 +85,16 @@ public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void)
} }
} }
} }
switch authorizationStatus { switch authorizationStatus {
case .authorized, .limited: case .authorized, .limited: onAuthorized()
onAuthorized() case .denied, .restricted:
case .denied, .restricted: let modal = PermissionMissingModal(permission: "library") { }
let modal = PermissionMissingModal(permission: "library") { } modal.modalPresentationStyle = .overFullScreen
modal.modalPresentationStyle = .overFullScreen modal.modalTransitionStyle = .crossDissolve
modal.modalTransitionStyle = .crossDissolve guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() }
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } presentingVC.present(modal, animated: true, completion: nil)
presentingVC.present(modal, animated: true, completion: nil)
default: return default: return
} }
} }

View File

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

View File

@ -5,7 +5,6 @@ import Foundation
public extension Notification.Name { public extension Notification.Name {
static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived") static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived")
static let incomingMessageMarkedAsRead = Notification.Name("incomingMessageMarkedAsRead")
static let missedCall = Notification.Name("missedCall") static let missedCall = Notification.Name("missedCall")
} }
@ -16,5 +15,4 @@ public extension Notification.Key {
@objc public extension NSNotification { @objc public extension NSNotification {
@objc static let initialConfigurationMessageReceived = Notification.Name.initialConfigurationMessageReceived.rawValue as NSString @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 /// A flag indicating whether the app is ready for app extensions to run
static let isReadyForAppExtensions: Setting.BoolKey = "isReadyForAppExtensions" 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 { public extension Setting.StringKey {
@ -74,11 +78,14 @@ public extension Setting.StringKey {
public extension Setting.DoubleKey { public extension Setting.DoubleKey {
/// The duration of the timeout for screen lock in seconds /// 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" static let screenLockTimeoutSeconds: Setting.DoubleKey = "screenLockTimeoutSeconds"
} }
public enum Preferences { public enum Preferences {
public enum NotificationPreviewType: Int, CaseIterable, EnumIntSetting, Differentiable { 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 /// Notifications should include both the sender name and a preview of the message content
case nameAndPreview case nameAndPreview
@ -303,61 +310,7 @@ public enum Preferences {
// MARK: - Objective C Support // MARK: - Objective C Support
// FIXME: Remove the below the 'NotificationSettingsViewController' and 'OWSSoundSettingsViewController' have been refactored to Swift // FIXME: Remove the below when OWSConversationSettingsViewController no longer nees SMKSound
@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]
}
}
@objc(SMKSound) @objc(SMKSound)
public class SMKSound: NSObject { public class SMKSound: NSObject {
@objc public static var notificationSounds: [Int] = Preferences.Sound.notificationSounds.map { $0.rawValue } @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 // Title & body
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
.defaulting(to: .nameAndPreview) .defaulting(to: .defaultPreviewType)
switch previewType { switch previewType {
case .nameAndPreview: case .nameAndPreview:

View File

@ -7,7 +7,7 @@ import SignalUtilitiesKit
import SessionUIKit import SessionUIKit
import SessionUtilitiesKit import SessionUtilitiesKit
final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockViewDelegate { final class SAEScreenLockViewController: ScreenLockViewController {
private var hasShownAuthUIOnce: Bool = false private var hasShownAuthUIOnce: Bool = false
private var isShowingAuthUI: Bool = false private var isShowingAuthUI: Bool = false
@ -16,10 +16,10 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
// MARK: - Initialization // MARK: - Initialization
init(shareViewDelegate: ShareViewDelegate) { init(shareViewDelegate: ShareViewDelegate) {
super.init(nibName: nil, bundle: nil) super.init()
self.onUnlockPressed = { [weak self] in self?.unlockButtonWasTapped() }
self.shareViewDelegate = shareViewDelegate self.shareViewDelegate = shareViewDelegate
self.delegate = self
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@ -32,57 +32,52 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
// MARK: - UI // 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 = { private lazy var titleLabel: UILabel = {
let titleLabel: UILabel = UILabel() let titleLabel: UILabel = UILabel()
titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = "vc_share_title".localized() titleLabel.text = "vc_share_title".localized()
titleLabel.textColor = Colors.text titleLabel.themeTextColor = .textPrimary
return titleLabel return titleLabel
}() }()
private lazy var closeButton: UIBarButtonItem = { private lazy var closeButton: UIBarButtonItem = {
let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(dismissPressed)) let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(dismissPressed))
closeButton.tintColor = Colors.text closeButton.themeTintColor = .textPrimary
return closeButton return closeButton
}() }()
// MARK: - Lifecycle // MARK: - Lifecycle
override func loadView() { public override func loadView() {
super.loadView() super.loadView()
UIView.appearance().tintColor = Colors.text UIView.appearance().themeTintColor = .textPrimary
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
self.view.themeBackgroundColor = .backgroundPrimary
self.navigationItem.titleView = titleLabel self.navigationItem.titleView = titleLabel
self.navigationItem.leftBarButtonItem = closeButton 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) { 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 // MARK: - Functions
private func tryToPresentAuthUIToUnlockScreenLock() { private func tryToPresentAuthUIToUnlockScreenLock() {
@ -122,7 +111,7 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
isShowingAuthUI = true isShowingAuthUI = true
OWSScreenLock.shared.tryToUnlockScreenLock( ScreenLock.shared.tryToUnlockScreenLock(
success: { [weak self] in success: { [weak self] in
AssertIsOnMainThread() AssertIsOnMainThread()
OWSLogger.info("unlock screen lock succeeded.") OWSLogger.info("unlock screen lock succeeded.")
@ -164,7 +153,7 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
} }
private func ensureUI() { private func ensureUI() {
self.updateUI(with: .screenLock, isLogoAtTop: false, animated: false) self.updateUI(state: .lock, animated: false)
} }
private func showScreenLockFailureAlert(message: String) { private func showScreenLockFailureAlert(message: String) {
@ -183,6 +172,13 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
) )
} }
func unlockButtonWasTapped() {
AssertIsOnMainThread()
OWSLogger.info("unlockButtonWasTapped")
self.tryToPresentAuthUIToUnlockScreenLock()
}
// MARK: - Transitions // MARK: - Transitions
@objc private func dismissPressed() { @objc private func dismissPressed() {
@ -194,13 +190,4 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie
private func cancelShareExperience() { private func cancelShareExperience() {
self.shareViewDelegate?.shareViewWasCancelled() 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() { override func loadView() {
super.loadView() super.loadView()
// This should be the first thing we do. // This should be the first thing we do (Note: If you leave the share context and return to it
let appContext = ShareAppExtensionContext(rootViewController: self) // the context will already exist, trying to override it results in the share context crashing
SetCurrentAppContext(appContext) // so ensure it doesn't exist first)
if !HasAppContext() {
AppModeManager.configure(delegate: self) let appContext = ShareAppExtensionContext(rootViewController: self)
// Need to manually trigger these since we don't have a "mainWindow" here SetCurrentAppContext(appContext)
ThemeManager.applyNavigationStyling() }
ThemeManager.applyWindowStyling()
// 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("") Logger.info("")
@ -144,7 +147,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe
Logger.info("") Logger.info("")
if OWSScreenLock.shared.isScreenLockEnabled() { if Storage.shared[.isScreenLockEnabled] {
self.dismiss(animated: false) { [weak self] in self.dismiss(animated: false) { [weak self] in
AssertIsOnMainThread() AssertIsOnMainThread()
self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
@ -173,7 +176,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe
// MARK: Updating // MARK: Updating
private func showLockScreenOrMainContent() { private func showLockScreenOrMainContent() {
if OWSScreenLock.shared.isScreenLockEnabled() { if Storage.shared[.isScreenLockEnabled] {
showLockScreen() showLockScreen()
} }
else { else {

View File

@ -32,6 +32,12 @@ public final class OutlineButton: UIButton {
private func setUpStyle(style: Style, size: Size) { private func setUpStyle(style: Style, size: Size) {
clipsToBounds = true clipsToBounds = true
contentEdgeInsets = UIEdgeInsets(
top: 0,
left: Values.smallSpacing,
bottom: 0,
right: Values.smallSpacing
)
titleLabel?.font = .boldSystemFont(ofSize: (size == .small ? titleLabel?.font = .boldSystemFont(ofSize: (size == .small ?
Values.smallFontSize : Values.smallFontSize :
Values.mediumFontSize 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 import UIKit
public final class TabBar : UIView { public final class TabBar: UIView {
private let tabs: [Tab] private let tabs: [Tab]
private var accentLineViewHorizontalCenteringConstraint: NSLayoutConstraint! private var accentLineViewHorizontalCenteringConstraint: NSLayoutConstraint!
private var accentLineViewWidthConstraint: NSLayoutConstraint! private var accentLineViewWidthConstraint: NSLayoutConstraint!
// MARK: Components // MARK: - Components
private lazy var tabLabels: [UILabel] = tabs.map { tab in private lazy var tabLabels: [UILabel] = tabs.map { tab in
let result = UILabel() let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize) result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
result.textAlignment = .center
result.text = tab.title result.text = tab.title
result.themeTextColor = .textPrimary
result.textAlignment = .center
result.alpha = Values.mediumOpacity
result.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness) result.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness)
return result return result
} }
private lazy var accentLineView: UIView = { private lazy var accentLineView: UIView = {
let result = UIView() let result = UIView()
result.backgroundColor = Colors.accent result.themeBackgroundColor = .primary
return result return result
}() }()
// MARK: Types // MARK: - Types
public struct Tab { public struct Tab {
let title: String let title: String
let onTap: () -> Void 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) public static let snHeight = isIPhone5OrSmaller ? CGFloat(32) : CGFloat(48)
// MARK: Lifecycle // MARK: - Lifecycle
public init(tabs: [Tab]) { public init(tabs: [Tab]) {
self.tabs = tabs self.tabs = tabs
super.init(frame: CGRect.zero) super.init(frame: CGRect.zero)
@ -53,37 +61,48 @@ public final class TabBar : UIView {
private func setUpViewHierarchy() { private func setUpViewHierarchy() {
set(.height, to: TabBar.snHeight) set(.height, to: TabBar.snHeight)
tabLabels.forEach { tabLabel in tabLabels.forEach { tabLabel in
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:))) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:)))
tabLabel.addGestureRecognizer(tapGestureRecognizer) tabLabel.addGestureRecognizer(tapGestureRecognizer)
} }
let tabLabelStackView = UIStackView(arrangedSubviews: tabLabels) let tabLabelStackView = UIStackView(arrangedSubviews: tabLabels)
tabLabelStackView.axis = .horizontal tabLabelStackView.axis = .horizontal
tabLabelStackView.distribution = .fillEqually tabLabelStackView.distribution = .fillEqually
tabLabelStackView.spacing = Values.mediumSpacing tabLabelStackView.spacing = Values.mediumSpacing
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:))) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:)))
tabLabelStackView.addGestureRecognizer(tapGestureRecognizer) tabLabelStackView.addGestureRecognizer(tapGestureRecognizer)
tabLabelStackView.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness) tabLabelStackView.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness)
addSubview(tabLabelStackView) addSubview(tabLabelStackView)
let separator = UIView() let separator = UIView()
separator.backgroundColor = Colors.separator separator.themeBackgroundColor = .borderSeparator
separator.set(.height, to: Values.separatorThickness) separator.set(.height, to: Values.separatorThickness)
addSubview(separator) addSubview(separator)
accentLineView.set(.height, to: Values.accentLineThickness) accentLineView.set(.height, to: Values.accentLineThickness)
addSubview(accentLineView) addSubview(accentLineView)
tabLabelStackView.pin(.leading, to: .leading, of: self) tabLabelStackView.pin(.leading, to: .leading, of: self)
tabLabelStackView.pin(.top, to: .top, of: self) tabLabelStackView.pin(.top, to: .top, of: self)
pin(.trailing, to: .trailing, of: tabLabelStackView) pin(.trailing, to: .trailing, of: tabLabelStackView)
separator.pin(.leading, to: .leading, of: self) separator.pin(.leading, to: .leading, of: self)
separator.pin(.top, to: .bottom, of: tabLabelStackView) separator.pin(.top, to: .bottom, of: tabLabelStackView)
pin(.trailing, to: .trailing, of: separator) pin(.trailing, to: .trailing, of: separator)
accentLineView.translatesAutoresizingMaskIntoConstraints = false accentLineView.translatesAutoresizingMaskIntoConstraints = false
selectTab(at: 0, withAnimatedTransition: false) selectTab(at: 0, withAnimatedTransition: false)
accentLineView.pin(.top, to: .bottom, of: separator) accentLineView.pin(.top, to: .bottom, of: separator)
pin(.bottom, to: .bottom, of: accentLineView) pin(.bottom, to: .bottom, of: accentLineView)
} }
// MARK: Updating // MARK: - Updating
public func selectTab(at index: Int, withAnimatedTransition isAnimated: Bool = true) { public func selectTab(at index: Int, withAnimatedTransition isAnimated: Bool = true) {
let tabLabel = tabLabels[index] let tabLabel = tabLabels[index]
accentLineViewHorizontalCenteringConstraint?.isActive = false accentLineViewHorizontalCenteringConstraint?.isActive = false
@ -92,16 +111,20 @@ public final class TabBar : UIView {
accentLineViewWidthConstraint?.isActive = false accentLineViewWidthConstraint?.isActive = false
accentLineViewWidthConstraint = accentLineView.widthAnchor.constraint(equalTo: tabLabel.widthAnchor) accentLineViewWidthConstraint = accentLineView.widthAnchor.constraint(equalTo: tabLabel.widthAnchor)
accentLineViewWidthConstraint.isActive = true accentLineViewWidthConstraint.isActive = true
var tabLabelsCopy = tabLabels var tabLabelsCopy = tabLabels
tabLabelsCopy.remove(at: index) tabLabelsCopy.remove(at: index)
UIView.animate(withDuration: isAnimated ? 0.25 : 0) { UIView.animate(withDuration: isAnimated ? 0.25 : 0) {
tabLabel.textColor = Colors.text tabLabel.alpha = 1
tabLabelsCopy.forEach { $0.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) } tabLabelsCopy.forEach { $0.alpha = Values.mediumOpacity }
self.layoutIfNeeded() self.layoutIfNeeded()
} }
} }
// MARK: Interaction // MARK: - Interaction
@objc private func handleTabLabelTapped(_ sender: UITapGestureRecognizer) { @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 } guard let tabLabel = tabLabels.first(where: { $0.bounds.contains(sender.location(in: $0)) }), let index = tabLabels.firstIndex(of: tabLabel) else { return }
selectTab(at: index) 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 // MARK: - ThemeManager
public enum 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 /// **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) /// 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() { public static func applyNavigationStyling() {
let textPrimary: UIColor = (ThemeManager.currentTheme.colors[.textPrimary] ?? .white) let textPrimary: UIColor = (ThemeManager.currentTheme.colors[.textPrimary] ?? .white)
@ -224,6 +231,11 @@ public enum ThemeManager {
applyNavigationStyling() applyNavigationStyling()
applyWindowStyling() applyWindowStyling()
if !hasSetInitialSystemTrait {
traitCollectionDidChange(nil)
hasSetInitialSystemTrait = true
}
} }
internal static func set<T: AnyObject>( internal static func set<T: AnyObject>(

View File

@ -5,74 +5,87 @@ import UIKit.UIColor
internal enum Theme_ClassicDark: ThemeColors { internal enum Theme_ClassicDark: ThemeColors {
static let theme: [ThemeValue: UIColor] = [ static let theme: [ThemeValue: UIColor] = [
// General // General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary, .primary: .primary,
.defaultPrimary: Theme.PrimaryColor.green.color, .defaultPrimary: Theme.PrimaryColor.green.color,
.danger: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), .danger: .dangerDark,
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .backgroundPrimary: .classicDark0,
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), .backgroundSecondary: .classicDark1,
.backgroundPrimary: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .textPrimary: .classicDark6,
.backgroundSecondary: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), .textSecondary: .classicDark5,
.backgroundTertiary: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), .borderSeparator: .classicDark3,
.textPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.textSecondary: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1), // Path
.borderSeparator: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .classicDark4,
// TextBox // TextBox
.textBox_background: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .textBox_background: .classicDark1,
.textBox_border: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .textBox_border: .classicDark3,
// MessageBubble // MessageBubble
.messageBubble_outgoingBackground: .primary, .messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), .messageBubble_incomingBackground: .classicDark2,
.messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .messageBubble_outgoingText: .classicDark0,
.messageBubble_incomingText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .messageBubble_incomingText: .classicDark6,
// MenuButton // MenuButton
.menuButton_background: .primary, .menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_icon: .classicDark6,
.menuButton_outerShadow: .primary, .menuButton_outerShadow: .primary,
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_innerShadow: .classicDark6,
// RadioButton // RadioButton
.radioButton_selectedBackground: .primary, .radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear, .radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .radioButton_selectedBorder: .classicDark6,
.radioButton_unselectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .radioButton_unselectedBorder: .classicDark6,
// OutlineButton // OutlineButton
.outlineButton_text: .primary, .outlineButton_text: .primary,
.outlineButton_background: .clear, .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_border: .primary,
.outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .outlineButton_filledText: .classicDark6,
.outlineButton_filledBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), .outlineButton_filledBackground: .classicDark1,
.outlineButton_filledHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .outlineButton_filledHighlight: .classicDark3,
.outlineButton_destructiveText: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), .outlineButton_destructiveText: .dangerDark,
.outlineButton_destructiveBackground: .clear, .outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 0.3), .outlineButton_destructiveHighlight: .dangerDark.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), .outlineButton_destructiveBorder: .dangerDark,
// SolidButton // SolidButton
.solidButton_background: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), .solidButton_background: .classicDark3,
.solidButton_highlight: #colorLiteral(red: 0.3254901961, green: 0.3254901961, blue: 0.3254901961, alpha: 1), .solidButton_highlight: .classicDark4,
// Settings // Settings
.settings_tabBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), .settings_tabBackground: .classicDark1,
.settings_tabHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .settings_tabHighlight: .classicDark3,
// Appearance // Appearance
.appearance_sectionBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), .appearance_sectionBackground: .classicDark1,
.appearance_buttonBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), .appearance_buttonBackground: .classicDark1,
.appearance_buttonHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .appearance_buttonHighlight: .classicDark3,
// Alert
.alert_background: .classicDark1,
.alert_buttonBackground: .classicDark1,
.alert_buttonHighlight: .classicDark3,
// ConversationButton // ConversationButton
.conversationButton_background: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), .conversationButton_background: .classicDark1,
.conversationButton_highlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .conversationButton_highlight: .classicDark3,
.conversationButton_unreadBackground: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), .conversationButton_unreadBackground: .classicDark2,
.conversationButton_unreadHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .conversationButton_unreadHighlight: .classicDark3,
.conversationButton_unreadStripBackground: .primary, .conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), .conversationButton_unreadBubbleBackground: .classicDark3,
.conversationButton_unreadBubbleText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .conversationButton_unreadBubbleText: .classicDark6,
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color .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 { internal enum Theme_ClassicLight: ThemeColors {
static let theme: [ThemeValue: UIColor] = [ static let theme: [ThemeValue: UIColor] = [
// General // General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary, .primary: .primary,
.defaultPrimary: Theme.PrimaryColor.green.color, .defaultPrimary: Theme.PrimaryColor.green.color,
.danger: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), .danger: .dangerLight,
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .backgroundPrimary: .classicLight6,
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), .backgroundSecondary: .classicLight5,
.backgroundPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .textPrimary: .classicLight0,
.backgroundSecondary: #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1), .textSecondary: .classicLight1,
.backgroundTertiary: #colorLiteral(red: 0.9058823529, green: 0.9058823529, blue: 0.9058823529, alpha: 1), .borderSeparator: .classicLight2,
.textPrimary: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1),
.textSecondary: #colorLiteral(red: 0.4274509804, green: 0.4274509804, blue: 0.4274509804, alpha: 1), // Path
.borderSeparator: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1), .path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .classicLight4,
// TextBox // TextBox
.textBox_background: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .textBox_background: .classicLight6,
.textBox_border: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1), .textBox_border: .classicLight2,
// MessageBubble // MessageBubble
.messageBubble_outgoingBackground: .primary, .messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), .messageBubble_incomingBackground: .classicLight4,
.messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .messageBubble_outgoingText: .classicLight0,
.messageBubble_incomingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .messageBubble_incomingText: .classicLight0,
// MenuButton // MenuButton
.menuButton_background: .primary, .menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_icon: .classicLight6,
.menuButton_outerShadow: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .menuButton_outerShadow: .classicLight0,
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_innerShadow: .classicLight6,
// RadioButton // RadioButton
.radioButton_selectedBackground: .primary, .radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear, .radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .radioButton_selectedBorder: .classicLight0,
.radioButton_unselectedBorder: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .radioButton_unselectedBorder: .classicLight0,
// OutlineButton // OutlineButton
.outlineButton_text: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .outlineButton_text: .classicLight0,
.outlineButton_background: .clear, .outlineButton_background: .clear,
.outlineButton_highlight: #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 0.1), .outlineButton_highlight: .classicLight0.withAlphaComponent(0.1),
.outlineButton_border: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .outlineButton_border: .classicLight0,
.outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .outlineButton_filledText: .classicLight6,
.outlineButton_filledBackground: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .outlineButton_filledBackground: .classicLight0,
.outlineButton_filledHighlight: #colorLiteral(red: 0.4274509804, green: 0.4274509804, blue: 0.4274509804, alpha: 1), .outlineButton_filledHighlight: .classicLight1,
.outlineButton_destructiveText: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), .outlineButton_destructiveText: .dangerLight,
.outlineButton_destructiveBackground: .clear, .outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 0.3), .outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), .outlineButton_destructiveBorder: .dangerLight,
// SolidButton // SolidButton
.solidButton_background: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), .solidButton_background: .classicLight3,
.solidButton_highlight: #colorLiteral(red: 0.8156862745, green: 0.8156862745, blue: 0.8156862745, alpha: 1), .solidButton_highlight: .classicLight4,
// Settings // Settings
.settings_tabBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .settings_tabBackground: .classicLight6,
.settings_tabHighlight: #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1), .settings_tabHighlight: .classicLight3,
// AppearanceButton // AppearanceButton
.appearance_sectionBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .appearance_sectionBackground: .classicLight6,
.appearance_buttonBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .appearance_buttonBackground: .classicLight6,
.appearance_buttonHighlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), .appearance_buttonHighlight: .classicLight4,
// Alert
.alert_background: .classicLight6,
.alert_buttonBackground: .classicLight6,
.alert_buttonHighlight: .classicLight4,
// ConversationButton // ConversationButton
.conversationButton_background: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .conversationButton_background: .classicLight6,
.conversationButton_highlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), .conversationButton_highlight: .classicLight4,
.conversationButton_unreadBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .conversationButton_unreadBackground: .classicLight6,
.conversationButton_unreadHighlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), .conversationButton_unreadHighlight: .classicLight4,
.conversationButton_unreadStripBackground: .primary, .conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1), .conversationButton_unreadBubbleBackground: .classicLight3,
.conversationButton_unreadBubbleText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .conversationButton_unreadBubbleText: .classicLight0,
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color .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 { internal enum Theme_OceanDark: ThemeColors {
static let theme: [ThemeValue: UIColor] = [ static let theme: [ThemeValue: UIColor] = [
// General // General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary, .primary: .primary,
.defaultPrimary: Theme.PrimaryColor.blue.color, .defaultPrimary: Theme.PrimaryColor.blue.color,
.danger: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), .danger: .dangerDark,
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .backgroundPrimary: .oceanDark2,
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), .backgroundSecondary: .oceanDark1,
.backgroundPrimary: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1), .textPrimary: .oceanDark6,
.backgroundSecondary: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), .textSecondary: .oceanDark5,
.backgroundTertiary: #colorLiteral(red: 0.1725490196, green: 0.1803921569, blue: 0.2274509804, alpha: 1), .borderSeparator: .oceanDark4,
.textPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1),
.textSecondary: #colorLiteral(red: 0.6509803922, green: 0.662745098, blue: 0.8078431373, alpha: 1), // Path
.borderSeparator: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1), .path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .oceanDark4,
// TextBox // TextBox
.textBox_background: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), .textBox_background: .oceanDark1,
.textBox_border: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1), .textBox_border: .oceanDark4,
// MessageBubble // MessageBubble
.messageBubble_outgoingBackground: .primary, .messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1), .messageBubble_incomingBackground: .oceanDark4,
.messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .messageBubble_outgoingText: .oceanDark0,
.messageBubble_incomingText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .messageBubble_incomingText: .oceanDark6,
// MenuButton // MenuButton
.menuButton_background: .primary, .menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_icon: .oceanDark6,
.menuButton_outerShadow: .primary, .menuButton_outerShadow: .primary,
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_innerShadow: .oceanDark6,
// RadioButton // RadioButton
.radioButton_selectedBackground: .primary, .radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear, .radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .radioButton_selectedBorder: .oceanDark6,
.radioButton_unselectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .radioButton_unselectedBorder: .oceanDark6,
// OutlineButton // OutlineButton
.outlineButton_text: .primary, .outlineButton_text: .primary,
.outlineButton_background: .clear, .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_border: .primary,
.outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .outlineButton_filledText: .oceanDark6,
.outlineButton_filledBackground: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), .outlineButton_filledBackground: .oceanDark1,
.outlineButton_filledHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .outlineButton_filledHighlight: .oceanDark3,
.outlineButton_destructiveText: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), .outlineButton_destructiveText: .dangerDark,
.outlineButton_destructiveBackground: .clear, .outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 0.3), .outlineButton_destructiveHighlight: .dangerDark.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), .outlineButton_destructiveBorder: .dangerDark,
// SolidButton // SolidButton
.solidButton_background: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1), .solidButton_background: .oceanDark2,
.solidButton_highlight: #colorLiteral(red: 0.2117647059, green: 0.2196078431, blue: 0.3019607844, alpha: 1), .solidButton_highlight: .oceanDark4,
// Settings // Settings
.settings_tabBackground: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), .settings_tabBackground: .oceanDark1,
.settings_tabHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .settings_tabHighlight: .oceanDark3,
// Appearance // Appearance
.appearance_sectionBackground: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .appearance_sectionBackground: .oceanDark3,
.appearance_buttonBackground: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .appearance_buttonBackground: .oceanDark3,
.appearance_buttonHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .appearance_buttonHighlight: .oceanDark4,
// Alert
.alert_background: .oceanDark3,
.alert_buttonBackground: .oceanDark3,
.alert_buttonHighlight: .oceanDark4,
// ConversationButton // ConversationButton
.conversationButton_background: #colorLiteral(red: 0.168627451, green: 0.168627451, blue: 0.2509803922, alpha: 1), .conversationButton_background: .oceanDark3,
.conversationButton_highlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .conversationButton_highlight: .oceanDark3,
.conversationButton_unreadBackground: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1), .conversationButton_unreadBackground: .oceanDark2,
.conversationButton_unreadHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), .conversationButton_unreadHighlight: .oceanDark3,
.conversationButton_unreadStripBackground: .primary, .conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: .primary, .conversationButton_unreadBubbleBackground: .primary,
.conversationButton_unreadBubbleText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .conversationButton_unreadBubbleText: .oceanDark0,
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color .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 { internal enum Theme_OceanLight: ThemeColors {
static let theme: [ThemeValue: UIColor] = [ static let theme: [ThemeValue: UIColor] = [
// General // General
.white: .white,
.black: .black,
.clear: .clear,
.primary: .primary, .primary: .primary,
.defaultPrimary: Theme.PrimaryColor.blue.color, .defaultPrimary: Theme.PrimaryColor.blue.color,
.danger: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), .danger: .dangerLight,
.white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .backgroundPrimary: .oceanLight6,
.clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), .backgroundSecondary: .oceanLight5,
.backgroundPrimary: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .textPrimary: .oceanLight0,
.backgroundSecondary: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1), .textSecondary: .oceanLight1,
.backgroundTertiary: #colorLiteral(red: 0.8549019608, green: 0.9098039216, blue: 0.9137254902, alpha: 1), .borderSeparator: .oceanLight2,
.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), // Path
.borderSeparator: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1), .path_connected: .pathConnected,
.path_connecting: .pathConnecting,
.path_error: .pathError,
.path_unknown: .oceanLight4,
// TextBox // TextBox
.textBox_background: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .textBox_background: .oceanLight6,
.textBox_border: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1), .textBox_border: .oceanLight2,
// MessageBubble // MessageBubble
.messageBubble_outgoingBackground: .primary, .messageBubble_outgoingBackground: .primary,
.messageBubble_incomingBackground: #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.9490196078, alpha: 1), .messageBubble_incomingBackground: .oceanLight3,
.messageBubble_outgoingText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .messageBubble_outgoingText: .oceanLight0,
.messageBubble_incomingText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .messageBubble_incomingText: .oceanLight0,
// MenuButton // MenuButton
.menuButton_background: .primary, .menuButton_background: .primary,
.menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_icon: .white,
.menuButton_outerShadow: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), .menuButton_outerShadow: .black,
.menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), .menuButton_innerShadow: .white,
// RadioButton // RadioButton
.radioButton_selectedBackground: .primary, .radioButton_selectedBackground: .primary,
.radioButton_unselectedBackground: .clear, .radioButton_unselectedBackground: .clear,
.radioButton_selectedBorder: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .radioButton_selectedBorder: .oceanLight0,
.radioButton_unselectedBorder: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1), .radioButton_unselectedBorder: .oceanLight2,
// OutlineButton // OutlineButton
.outlineButton_text: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .outlineButton_text: .oceanLight0,
.outlineButton_background: .clear, .outlineButton_background: .clear,
.outlineButton_highlight: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 0.1), .outlineButton_highlight: .oceanLight0.withAlphaComponent(0.1),
.outlineButton_border: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .outlineButton_border: .oceanLight0,
.outlineButton_filledText: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .outlineButton_filledText: .oceanLight6,
.outlineButton_filledBackground: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .outlineButton_filledBackground: .oceanLight0,
.outlineButton_filledHighlight: #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1), .outlineButton_filledHighlight: .oceanLight1,
.outlineButton_destructiveText: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), .outlineButton_destructiveText: .dangerLight,
.outlineButton_destructiveBackground: .clear, .outlineButton_destructiveBackground: .clear,
.outlineButton_destructiveHighlight: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 0.3), .outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3),
.outlineButton_destructiveBorder: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), .outlineButton_destructiveBorder: .dangerLight,
// SolidButton // SolidButton
.solidButton_background: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1), .solidButton_background: .oceanLight4,
.solidButton_highlight: #colorLiteral(red: 0.8431372549, green: 0.9333333334, blue: 0.9411764706, alpha: 1), .solidButton_highlight: .oceanLight5,
// Settings // Settings
.settings_tabBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .settings_tabBackground: .oceanLight6,
.settings_tabHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), .settings_tabHighlight: .oceanLight4,
// Appearance // Appearance
.appearance_sectionBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .appearance_sectionBackground: .oceanLight6,
.appearance_buttonBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .appearance_buttonBackground: .oceanLight6,
.appearance_buttonHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), .appearance_buttonHighlight: .oceanLight4,
// Alert
.alert_background: .oceanLight6,
.alert_buttonBackground: .oceanLight6,
.alert_buttonHighlight: .oceanLight4,
// ConversationButton // ConversationButton
.conversationButton_background: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), .conversationButton_background: .oceanLight6,
.conversationButton_highlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), .conversationButton_highlight: .oceanLight4,
.conversationButton_unreadBackground: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1), .conversationButton_unreadBackground: .oceanLight5,
.conversationButton_unreadHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), .conversationButton_unreadHighlight: .oceanLight4,
.conversationButton_unreadStripBackground: .primary, .conversationButton_unreadStripBackground: .primary,
.conversationButton_unreadBubbleBackground: .primary, .conversationButton_unreadBubbleBackground: .primary,
.conversationButton_unreadBubbleText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), .conversationButton_unreadBubbleText: .oceanLight0,
.conversationButton_pinBackground: Theme.PrimaryColor.yellow.color .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 { public enum ThemeValue {
// General // General
case white
case black
case clear
case primary case primary
case defaultPrimary case defaultPrimary
case danger case danger
case white
case clear
case backgroundPrimary case backgroundPrimary
case backgroundSecondary case backgroundSecondary
case backgroundTertiary
case textPrimary case textPrimary
case textSecondary case textSecondary
case borderSeparator case borderSeparator
// Path
case path_connected
case path_connecting
case path_error
case path_unknown
// TextBox // TextBox
case textBox_background case textBox_background
case textBox_border case textBox_border
@ -116,6 +122,11 @@ public enum ThemeValue {
case appearance_buttonBackground case appearance_buttonBackground
case appearance_buttonHighlight case appearance_buttonHighlight
// Alert
case alert_background
case alert_buttonBackground
case alert_buttonHighlight
// ConversationButton // ConversationButton
case conversationButton_background case conversationButton_background
case conversationButton_highlight case conversationButton_highlight
@ -124,5 +135,7 @@ public enum ThemeValue {
case conversationButton_unreadStripBackground case conversationButton_unreadStripBackground
case conversationButton_unreadBubbleBackground case conversationButton_unreadBubbleBackground
case conversationButton_unreadBubbleText 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 smallButtonHeight = isIPhone5OrSmaller ? CGFloat(24) : CGFloat(27)
@objc public static let mediumButtonHeight = isIPhone5OrSmaller ? CGFloat(30) : CGFloat(34) @objc public static let mediumButtonHeight = isIPhone5OrSmaller ? CGFloat(30) : CGFloat(34)
@objc public static let largeButtonHeight = isIPhone5OrSmaller ? CGFloat(40) : CGFloat(45) @objc public static let largeButtonHeight = isIPhone5OrSmaller ? CGFloat(40) : CGFloat(45)
@objc public static let alertButtonHeight: CGFloat = 50
@objc public static let accentLineThickness = CGFloat(4) @objc public static let accentLineThickness = CGFloat(4)

View File

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

View File

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

View File

@ -165,6 +165,15 @@ public extension Storage {
} }
public extension Database { 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? { private subscript(key: String) -> Setting? {
get { try? Setting.filter(id: key).fetchOne(self) } get { try? Setting.filter(id: key).fetchOne(self) }
set { set {

View File

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

View File

@ -26,6 +26,11 @@ id<AppContext> CurrentAppContext(void)
return currentAppContext; return currentAppContext;
} }
BOOL HasAppContext(void)
{
return (currentAppContext != nil);
}
void SetCurrentAppContext(id<AppContext> appContext) void SetCurrentAppContext(id<AppContext> appContext)
{ {
// The main app context should only be set once. // 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/OWSTextField.h>
#import <SignalUtilitiesKit/OWSTextView.h> #import <SignalUtilitiesKit/OWSTextView.h>
#import <SignalUtilitiesKit/OWSViewController.h> #import <SignalUtilitiesKit/OWSViewController.h>
#import <SignalUtilitiesKit/ScreenLockViewController.h>
#import <SignalUtilitiesKit/SSKAsserts.h> #import <SignalUtilitiesKit/SSKAsserts.h>
#import <SignalUtilitiesKit/TSConstants.h> #import <SignalUtilitiesKit/TSConstants.h>
#import <SignalUtilitiesKit/UIFont+OWS.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 imageView: YYAnimatedImageView = getImageView()
private lazy var additionalImageView: YYAnimatedImageView = { private lazy var additionalImageView: YYAnimatedImageView = {
let result: YYAnimatedImageView = getImageView() let result: YYAnimatedImageView = getImageView()
result.themeBackgroundColor = .backgroundTertiary result.themeBackgroundColor = .primary
result.themeBorderColor = .backgroundPrimary result.themeBorderColor = .backgroundPrimary
result.layer.borderWidth = Values.separatorThickness result.layer.borderWidth = Values.separatorThickness

View File

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