mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
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:
parent
20d63d106c
commit
823006a892
85 changed files with 2634 additions and 2206 deletions
|
@ -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 */,
|
||||||
|
|
|
@ -55,8 +55,25 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 ]
|
||||||
|
|
||||||
|
|
|
@ -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 ]
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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 l’ensemble de votre compte ?";
|
|
||||||
"dialog_clear_all_data_deletion_failed_1" = "Les données n’ont 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 n’ont pas été supprimées sur %@ nœuds de service. ID des nœuds de service : %@.";
|
|
||||||
"modal_clear_all_data_device_only_button_title" = "L’appareil uniquement";
|
|
||||||
"modal_clear_all_data_entire_account_button_title" = "L’ensemble 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 n’ont 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 n’ont pas été supprimées sur %@ nœuds de service. ID des nœuds de service : %@.";
|
||||||
|
"modal_clear_all_data_confirm" = "Clear";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,26 +1,46 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SessionUIKit
|
||||||
|
|
||||||
final class PathStatusView: UIView {
|
final class PathStatusView: UIView {
|
||||||
|
enum Status {
|
||||||
|
case unknown
|
||||||
|
case connecting
|
||||||
|
case connected
|
||||||
|
case error
|
||||||
|
|
||||||
static let size = CGFloat(8)
|
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
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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 Session’s screen. You can still receive notifications when Screen Lock is enabled. Use Session’s 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
|
|
|
@ -1,6 +0,0 @@
|
||||||
extension PrivacySettingsTableViewController {
|
|
||||||
|
|
||||||
@objc func objc_requestMicrophonePermissionIfNeeded() {
|
|
||||||
requestMicrophonePermissionIfNeeded { }
|
|
||||||
}
|
|
||||||
}
|
|
137
Session/Settings/PrivacySettingsViewModel.swift
Normal file
137
Session/Settings/PrivacySettingsViewModel.swift
Normal 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() {}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
guard
|
||||||
|
let confirmationInfo: ConfirmationModal.Info = confirmationInfo,
|
||||||
|
confirmationInfo.stateToShow.shouldShow(for: Storage.shared[key])
|
||||||
|
else {
|
||||||
Storage.shared.write { db in db[key] = !db[key] }
|
Storage.shared.write { db in db[key] = !db[key] }
|
||||||
manuallyReload(indexPath: indexPath, section: section, settingInfo: settingInfo)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?,
|
||||||
|
@ -153,6 +156,10 @@ public enum SettingsAction: Hashable, Equatable {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func settingBool(key: Setting.BoolKey) -> SettingsAction {
|
||||||
|
return .settingBool(key: key, confirmationInfo: nil)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Conformance
|
// MARK: - Conformance
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
381
Session/Shared/ScreenLockUI.swift
Normal file
381
Session/Shared/ScreenLockUI.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
179
Session/Sheets & Modals/ConfirmationModal.swift
Normal file
179
Session/Sheets & Modals/ConfirmationModal.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
// 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) {
|
||||||
|
@ -11,28 +15,32 @@ public func requestCameraPermissionIfNeeded() -> Bool {
|
||||||
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:
|
case .notDetermined:
|
||||||
AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in })
|
AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in })
|
||||||
return false
|
return false
|
||||||
|
|
||||||
default: 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:
|
case .undetermined:
|
||||||
onNotGranted()
|
onNotGranted?()
|
||||||
AVAudioSession.sharedInstance().requestRecordPermission { _ in }
|
AVAudioSession.sharedInstance().requestRecordPermission { _ in }
|
||||||
|
|
||||||
default: break
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
// of old migrations when possible
|
||||||
|
db.unsafeSet(
|
||||||
|
key: "screenLockTimeoutSeconds",
|
||||||
|
value: (legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] as? Double)
|
||||||
.defaulting(to: (15 * 60))
|
.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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
// the context will already exist, trying to override it results in the share context crashing
|
||||||
|
// so ensure it doesn't exist first)
|
||||||
|
if !HasAppContext() {
|
||||||
let appContext = ShareAppExtensionContext(rootViewController: self)
|
let appContext = ShareAppExtensionContext(rootViewController: self)
|
||||||
SetCurrentAppContext(appContext)
|
SetCurrentAppContext(appContext)
|
||||||
|
}
|
||||||
|
|
||||||
AppModeManager.configure(delegate: self)
|
// Need to manually trigger these since we don't have a "mainWindow" here and the current theme
|
||||||
// Need to manually trigger these since we don't have a "mainWindow" here
|
// might have been changed since the share extension was last opened
|
||||||
ThemeManager.applyNavigationStyling()
|
ThemeManager.applySavedTheme()
|
||||||
ThemeManager.applyWindowStyling()
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
152
SessionUIKit/Components/RadioButton.swift
Normal file
152
SessionUIKit/Components/RadioButton.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public final class TabBar: UIView {
|
public final class TabBar: UIView {
|
||||||
|
@ -5,24 +7,28 @@ public final class TabBar : UIView {
|
||||||
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)
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
88
SessionUIKit/Style Guide/Themes/Theme+Colors.swift
Normal file
88
SessionUIKit/Style Guide/Themes/Theme+Colors.swift
Normal 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
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -25,103 +23,71 @@ import SessionMessagingKit
|
||||||
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(
|
||||||
|
success: @escaping (() -> Void),
|
||||||
failure: @escaping ((Error) -> Void),
|
failure: @escaping ((Error) -> Void),
|
||||||
unexpectedFailure: @escaping ((Error) -> Void),
|
unexpectedFailure: @escaping ((Error) -> Void),
|
||||||
cancel: @escaping (() -> 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'.
|
||||||
|
localizedReason: "SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK".localized()
|
||||||
|
) { outcome in
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
switch outcome {
|
switch outcome {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
Logger.error("local authentication failed with error: \(error)")
|
Logger.error("local authentication failed with error: \(error)")
|
||||||
failure(self.authenticationError(errorDescription: error))
|
failure(self.authenticationError(errorDescription: error))
|
||||||
|
|
||||||
case .unexpectedFailure(let error):
|
case .unexpectedFailure(let error):
|
||||||
Logger.error("local authentication failed with unexpected error: \(error)")
|
Logger.error("local authentication failed with unexpected error: \(error)")
|
||||||
unexpectedFailure(self.authenticationError(errorDescription: error))
|
unexpectedFailure(self.authenticationError(errorDescription: error))
|
||||||
|
|
||||||
case .success:
|
case .success:
|
||||||
Logger.verbose("local authentication succeeded.")
|
Logger.verbose("local authentication succeeded.")
|
||||||
success()
|
success()
|
||||||
|
|
||||||
case .cancel:
|
case .cancel:
|
||||||
Logger.verbose("local authentication cancelled.")
|
Logger.verbose("local authentication cancelled.")
|
||||||
cancel()
|
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,6 +97,7 @@ 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))")
|
||||||
|
|
||||||
|
@ -140,6 +107,7 @@ import SessionMessagingKit
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -151,23 +119,28 @@ 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)
|
|
||||||
|
let outcome = self.outcomeForLAError(
|
||||||
|
errorParam: evaluateError,
|
||||||
|
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:
|
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)
|
||||||
|
@ -177,12 +150,15 @@ import SessionMessagingKit
|
||||||
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
|
|
@ -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
|
|
|
@ -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
|
|
121
SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift
Normal file
121
SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift
Normal 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?()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
|
Loading…
Reference in a new issue