From 823006a8921ed3658f5c68ef5d3d70c8eaff4d79 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 24 Aug 2022 17:33:10 +1000 Subject: [PATCH] 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 --- Session.xcodeproj/project.pbxproj | 68 +-- .../ConversationVC+Interaction.swift | 23 +- Session/Conversations/ConversationVC.swift | 49 +- .../CallPermissionRequestModal.swift | 79 --- Session/Home/HomeVC.swift | 8 +- .../MessageRequestsViewController.swift | 2 +- Session/Home/NewConversationButtonSet.swift | 2 +- Session/Meta/AppDelegate.swift | 6 +- Session/Meta/Signal-Bridging-Header.h | 2 - .../Translations/de.lproj/Localizable.strings | 35 +- .../Translations/en.lproj/Localizable.strings | 35 +- .../Translations/es.lproj/Localizable.strings | 35 +- .../Translations/fa.lproj/Localizable.strings | 35 +- .../Translations/fi.lproj/Localizable.strings | 35 +- .../Translations/fr.lproj/Localizable.strings | 35 +- .../Translations/hi.lproj/Localizable.strings | 35 +- .../Translations/hr.lproj/Localizable.strings | 35 +- .../id-ID.lproj/Localizable.strings | 35 +- .../Translations/it.lproj/Localizable.strings | 35 +- .../Translations/ja.lproj/Localizable.strings | 35 +- .../Translations/nl.lproj/Localizable.strings | 35 +- .../Translations/pl.lproj/Localizable.strings | 35 +- .../pt_BR.lproj/Localizable.strings | 35 +- .../Translations/ru.lproj/Localizable.strings | 35 +- .../Translations/si.lproj/Localizable.strings | 35 +- .../Translations/sk.lproj/Localizable.strings | 35 +- .../Translations/sv.lproj/Localizable.strings | 35 +- .../Translations/th.lproj/Localizable.strings | 35 +- .../vi-VN.lproj/Localizable.strings | 35 +- .../zh-Hant.lproj/Localizable.strings | 35 +- .../zh_CN.lproj/Localizable.strings | 35 +- Session/Notifications/AppNotifications.swift | 24 +- Session/Path/PathStatusView.swift | 56 +- Session/Path/PathVC.swift | 19 +- Session/Settings/HelpViewModel.swift | 17 +- .../NotificationContentViewModel.swift | 1 + .../NotificationSettingsViewModel.swift | 11 +- .../Settings/NotificationSoundViewModel.swift | 6 +- Session/Settings/NukeDataModal.swift | 158 +++--- .../PrivacySettingsTableViewController.h | 15 - .../PrivacySettingsTableViewController.m | 351 ------------ .../PrivacySettingsTableViewController.swift | 6 - .../Settings/PrivacySettingsViewModel.swift | 137 +++++ Session/Settings/QRCodeVC.swift | 85 ++- .../SettingsTableViewController.swift | 61 ++- Session/Settings/SettingsTableViewModel.swift | 20 +- Session/Settings/Views/SettingsCell.swift | 22 +- Session/Shared/BaseVC.swift | 8 +- Session/Shared/OWSScreenLockUI.h | 21 - Session/Shared/OWSScreenLockUI.m | 511 ------------------ Session/Shared/ScreenLockUI.swift | 381 +++++++++++++ .../Sheets & Modals/ConfirmationModal.swift | 179 ++++++ Session/Sheets & Modals/Modal.swift | 124 +++-- Session/Utilities/Permissions.swift | 84 +-- .../Migrations/_003_YDBToGRDBMigration.swift | 11 +- .../Notification+MessageReceiver.swift | 2 - .../Utilities/Preferences.swift | 63 +-- .../NSENotificationPresenter.swift | 2 +- .../SAEScreenLockViewController.swift | 85 ++- SessionShareExtension/ShareVC.swift | 23 +- SessionUIKit/Components/OutlineButton.swift | 6 + SessionUIKit/Components/RadioButton.swift | 152 ++++++ SessionUIKit/Components/TabBar.swift | 49 +- SessionUIKit/Style Guide/Gradients.swift | 47 -- SessionUIKit/Style Guide/ThemeManager.swift | 12 + .../Themes/Theme+ClassicDark.swift | 91 ++-- .../Themes/Theme+ClassicLight.swift | 97 ++-- .../Style Guide/Themes/Theme+Colors.swift | 88 +++ .../Style Guide/Themes/Theme+OceanDark.swift | 89 +-- .../Style Guide/Themes/Theme+OceanLight.swift | 95 ++-- .../Themes/Theme+PrimaryColors.swift | 43 -- SessionUIKit/Style Guide/Themes/Theme.swift | 21 +- SessionUIKit/Style Guide/Values.swift | 1 + .../Utilities/UIColor+Utilities.swift | 2 +- .../Utilities/UIView+Constraints.swift | 6 +- .../Database/Models/Setting.swift | 9 + SessionUtilitiesKit/General/AppContext.h | 1 + SessionUtilitiesKit/General/AppContext.m | 5 + SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 1 - .../Profile Pictures/ProfilePictureView.swift | 2 +- .../{OWSScreenLock.swift => ScreenLock.swift} | 203 ++++--- .../Screen Lock/ScreenLockViewController.h | 29 - .../Screen Lock/ScreenLockViewController.m | 162 ------ .../ScreenLockViewController.swift | 121 +++++ .../Utilities/UIColor+Extensions.swift | 16 - 85 files changed, 2634 insertions(+), 2206 deletions(-) delete mode 100644 Session/Conversations/Views & Modals/CallPermissionRequestModal.swift delete mode 100644 Session/Settings/PrivacySettingsTableViewController.h delete mode 100644 Session/Settings/PrivacySettingsTableViewController.m delete mode 100644 Session/Settings/PrivacySettingsTableViewController.swift create mode 100644 Session/Settings/PrivacySettingsViewModel.swift delete mode 100644 Session/Shared/OWSScreenLockUI.h delete mode 100644 Session/Shared/OWSScreenLockUI.m create mode 100644 Session/Shared/ScreenLockUI.swift create mode 100644 Session/Sheets & Modals/ConfirmationModal.swift create mode 100644 SessionUIKit/Components/RadioButton.swift delete mode 100644 SessionUIKit/Style Guide/Gradients.swift create mode 100644 SessionUIKit/Style Guide/Themes/Theme+Colors.swift delete mode 100644 SessionUIKit/Style Guide/Themes/Theme+PrimaryColors.swift rename SignalUtilitiesKit/Screen Lock/{OWSScreenLock.swift => ScreenLock.swift} (50%) delete mode 100644 SignalUtilitiesKit/Screen Lock/ScreenLockViewController.h delete mode 100644 SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m create mode 100644 SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 941818721..f48f4dd15 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 1FFD68A448D5A1439F2F02FD /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; 3289CA2E9E89DA9D4D52A90C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF4561630A52BE96F164CF6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; }; - 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */; }; 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */; }; 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */; }; 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; }; @@ -38,7 +37,6 @@ 34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */ = {isa = PBXBuildFile; fileRef = 34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */; }; 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */; }; 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */; }; - 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; }; 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; }; 34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; }; 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; }; @@ -113,7 +111,6 @@ 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */; }; 7B0EFDF62755CC5400FFAAE7 /* CallMissedTipsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */; }; 7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */; }; - 7B13E1EB2811138200BD4F64 /* PrivacySettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; 7B1581E827210ECC00848B49 /* RenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E727210ECC00848B49 /* RenderView.swift */; }; @@ -153,7 +150,6 @@ 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; }; 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */ = {isa = PBXBuildFile; fileRef = 7BFD1A962747689000FB91B9 /* Session-Turn-Server */; }; - 7BFFB33C27D02F5800BEA04E /* CallPermissionRequestModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */; }; 821EFD1644285AC2D3733D27 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C58C3ADF46C718488458C2 /* Pods_GlobalDependencies_SessionUIKit.framework */; }; 92EB2776D36B22D2E0552A05 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; 98547545DAF8E7916DF9F0BF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F84A214B9A1C0CCF6DB09C8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; @@ -315,7 +311,6 @@ C331FF982558FA6B00070591 /* AppMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C9689023FA1401005F64E0 /* AppMode.swift */; }; C331FF992558FA6B00070591 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; C331FF9A2558FA6B00070591 /* Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A1238F356100BA5194 /* Values.swift */; }; - C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A8238F62FB00BA5194 /* Gradients.swift */; }; C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8544E3023D16CA500299F14 /* DeviceUtilities.swift */; }; C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; }; C331FFE02558FB0000070591 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B02390C37000BA5194 /* SearchBar.swift */; }; @@ -401,7 +396,7 @@ C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; }; C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; }; C38EF2B4255B6D9C007E1867 /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */; }; - C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */; }; + C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; }; C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */; }; C38EF324255B6DBF007E1867 /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FA255B6DBD007E1867 /* Bench.swift */; }; @@ -416,9 +411,7 @@ C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF344255B6DC5007E1867 /* OWSViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */; }; C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF34B255B6DC8007E1867 /* OWSTableViewController.m */; }; - C38EF366255B6DCC007E1867 /* ScreenLockViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF34D255B6DC8007E1867 /* OWSTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C38EF36B255B6DCC007E1867 /* ScreenLockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */; }; C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF355255B6DCB007E1867 /* OWSViewController.m */; }; C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF356255B6DCB007E1867 /* OWSNavigationController.m */; }; C38EF372255B6DCC007E1867 /* MediaMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF358255B6DCC007E1867 /* MediaMessageView.swift */; }; @@ -640,7 +633,7 @@ FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; }; - FD37E9C828A1D73F003AE748 /* Theme+PrimaryColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */; }; + FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; }; FD37E9CA28A1E4BD003AE748 /* Theme+ClassicLight.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */; }; FD37E9CC28A1E578003AE748 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */; }; FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9CE28A1EB1B003AE748 /* Theme.swift */; }; @@ -677,6 +670,11 @@ FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; }; FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; }; + FD52090328B4680F006098F6 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090228B4680F006098F6 /* RadioButton.swift */; }; + FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; }; + FD52090728B49738006098F6 /* ConfirmationModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090628B49738006098F6 /* ConfirmationModal.swift */; }; + FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; }; + FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; }; FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; }; FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; }; FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; }; @@ -1026,9 +1024,7 @@ 2581AFACDDDC1404866D7B8C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 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 = ""; }; - 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivacySettingsTableViewController.m; sourceTree = ""; }; 340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = ""; }; - 340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = ""; }; 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = ""; }; 340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = ""; }; 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSConversationSettingsViewController.m; sourceTree = ""; }; @@ -1063,8 +1059,6 @@ 34CF0786203E6B78005C4D61 /* end_call_tone_cept.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = end_call_tone_cept.caf; path = Session/Meta/AudioFiles/end_call_tone_cept.caf; sourceTree = SOURCE_ROOT; }; 34D1F04F1F7D45A60066283D /* GifPickerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerCell.swift; sourceTree = ""; }; 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyDownloader.swift; sourceTree = ""; }; - 34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScreenLockUI.h; sourceTree = ""; }; - 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSScreenLockUI.m; sourceTree = ""; }; 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = ""; }; 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = ""; }; 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; }; @@ -1156,7 +1150,6 @@ 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringing.mp3; sourceTree = ""; }; 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMissedTipsModal.swift; sourceTree = ""; }; 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+Action.swift"; sourceTree = ""; }; - 7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsTableViewController.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; 7B1581E727210ECC00848B49 /* RenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderView.swift; sourceTree = ""; }; @@ -1199,7 +1192,6 @@ 7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; 7BFD1A962747689000FB91B9 /* Session-Turn-Server */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Session-Turn-Server"; sourceTree = ""; }; - 7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallPermissionRequestModal.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 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 = ""; }; @@ -1292,7 +1284,6 @@ B8BB829F238F322400BA5194 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; B8BB82A1238F356100BA5194 /* Values.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Values.swift; sourceTree = ""; }; B8BB82A4238F627000BA5194 /* HomeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeVC.swift; sourceTree = ""; }; - B8BB82A8238F62FB00BA5194 /* Gradients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gradients.swift; sourceTree = ""; }; B8BB82AA238F669C00BA5194 /* FullConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullConversationCell.swift; sourceTree = ""; }; B8BB82B02390C37000BA5194 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = ""; }; B8BB82B423947F2D00BA5194 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; @@ -1469,7 +1460,7 @@ C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = "SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift"; sourceTree = SOURCE_ROOT; }; C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIViewController+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift"; sourceTree = SOURCE_ROOT; }; C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIView+Utilities.swift"; sourceTree = SOURCE_ROOT; }; - C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift"; sourceTree = SOURCE_ROOT; }; + C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/ScreenLock.swift"; sourceTree = SOURCE_ROOT; }; C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; }; C38EF2EF255B6DBB007E1867 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Weak.swift; path = SessionUtilitiesKit/General/Weak.swift; sourceTree = SOURCE_ROOT; }; C38EF2F2255B6DBC007E1867 /* Searcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Searcher.swift; path = SignalUtilitiesKit/Utilities/Searcher.swift; sourceTree = SOURCE_ROOT; }; @@ -1491,9 +1482,7 @@ C38EF344255B6DC5007E1867 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSViewController.h; path = "SignalUtilitiesKit/Shared View Controllers/OWSViewController.h"; sourceTree = SOURCE_ROOT; }; C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ModalActivityIndicatorViewController.swift; path = "SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift"; sourceTree = SOURCE_ROOT; }; C38EF34B255B6DC8007E1867 /* OWSTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSTableViewController.m; path = "SignalUtilitiesKit/Shared View Controllers/OWSTableViewController.m"; sourceTree = SOURCE_ROOT; }; - C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScreenLockViewController.h; path = "SignalUtilitiesKit/Screen Lock/ScreenLockViewController.h"; sourceTree = SOURCE_ROOT; }; C38EF34D255B6DC8007E1867 /* OWSTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSTableViewController.h; path = "SignalUtilitiesKit/Shared View Controllers/OWSTableViewController.h"; sourceTree = SOURCE_ROOT; }; - C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ScreenLockViewController.m; path = "SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m"; sourceTree = SOURCE_ROOT; }; C38EF355255B6DCB007E1867 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSViewController.m; path = "SignalUtilitiesKit/Shared View Controllers/OWSViewController.m"; sourceTree = SOURCE_ROOT; }; C38EF356255B6DCB007E1867 /* OWSNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSNavigationController.m; path = "SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.m"; sourceTree = SOURCE_ROOT; }; C38EF358255B6DCC007E1867 /* MediaMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaMessageView.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift"; sourceTree = SOURCE_ROOT; }; @@ -1706,7 +1695,7 @@ FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; - FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+PrimaryColors.swift"; sourceTree = ""; }; + FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = ""; }; FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicLight.swift"; sourceTree = ""; }; FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceViewController.swift; sourceTree = ""; }; FD37E9CE28A1EB1B003AE748 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; @@ -1741,6 +1730,11 @@ FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = ""; }; FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = ""; }; FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = ""; }; + FD52090228B4680F006098F6 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; + FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewModel.swift; sourceTree = ""; }; + FD52090628B49738006098F6 /* ConfirmationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationModal.swift; sourceTree = ""; }; + FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = ""; }; + FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = ""; }; FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = ""; }; FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = ""; }; FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = ""; }; @@ -2297,7 +2291,6 @@ B848A4C4269EAAA200617031 /* UserDetailsSheet.swift */, FD4B200D283492210034334B /* InsetLockableTableView.swift */, 7B1581E3271FC59C00848B49 /* CallModal.swift */, - 7BFFB33B27D02F5800BEA04E /* CallPermissionRequestModal.swift */, ); path = "Views & Modals"; sourceTree = ""; @@ -2504,8 +2497,7 @@ B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */, C31D1DDC25217014005D4DA8 /* UserCell.swift */, C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */, - 34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */, - 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */, + FD52090828B59411006098F6 /* ScreenLockUI.swift */, ); path = Shared; sourceTree = ""; @@ -2729,7 +2721,6 @@ B8BB829F238F322400BA5194 /* Colors.swift */, C39DD28724F3318C008590FC /* Colors.xcassets */, B8BB82BD2394D4CE00BA5194 /* Fonts.swift */, - B8BB82A8238F62FB00BA5194 /* Gradients.swift */, B8BB82A1238F356100BA5194 /* Values.swift */, ); path = "Style Guide"; @@ -2751,6 +2742,7 @@ isa = PBXGroup; children = ( B8B5BCEB2394D869003823C9 /* OutlineButton.swift */, + FD52090228B4680F006098F6 /* RadioButton.swift */, B8BB82B02390C37000BA5194 /* SearchBar.swift */, B8BB82B82394911B00BA5194 /* Separator.swift */, B8CCF638239721E20091D419 /* TabBar.swift */, @@ -2828,13 +2820,11 @@ isa = PBXGroup; children = ( FD37E9CD28A1E682003AE748 /* Views */, - 340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */, - 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */, - 7B13E1EA2811138200BD4F64 /* PrivacySettingsTableViewController.swift */, B8CCF6422397711F0091D419 /* SettingsVC.swift */, B886B4A62398B23E00211ABE /* QRCodeVC.swift */, FD37EA0828AA2D27003AE748 /* SettingsTableViewModel.swift */, FD37EA0628AA2CCA003AE748 /* SettingsTableViewController.swift */, + FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */, FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */, FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */, FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */, @@ -2888,6 +2878,7 @@ children = ( B86BD08323399ACF000F5AE3 /* Modal.swift */, C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, + FD52090628B49738006098F6 /* ConfirmationModal.swift */, ); path = "Sheets & Modals"; sourceTree = ""; @@ -2955,9 +2946,8 @@ C36096EE25AD21BC008B62B2 /* Screen Lock */ = { isa = PBXGroup; children = ( - C38EF2E2255B6DB9007E1867 /* OWSScreenLock.swift */, - C38EF34C255B6DC8007E1867 /* ScreenLockViewController.h */, - C38EF351255B6DC9007E1867 /* ScreenLockViewController.m */, + C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */, + FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */, ); path = "Screen Lock"; sourceTree = ""; @@ -3626,7 +3616,7 @@ isa = PBXGroup; children = ( FD37E9CE28A1EB1B003AE748 /* Theme.swift */, - FD37E9C728A1D73F003AE748 /* Theme+PrimaryColors.swift */, + FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */, FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */, FD37E9C928A1E4BD003AE748 /* Theme+ClassicLight.swift */, FD37E9D228A1FCDB003AE748 /* Theme+OceanDark.swift */, @@ -4030,7 +4020,6 @@ C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */, C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */, C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */, - C38EF366255B6DCC007E1867 /* ScreenLockViewController.h in Headers */, C33FDDB3255A582000E217F9 /* OWSError.h in Headers */, C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */, C38EF367255B6DCC007E1867 /* OWSTableViewController.h in Headers */, @@ -4910,13 +4899,12 @@ buildActionMask = 2147483647; files = ( C331FF972558FA6B00070591 /* Fonts.swift in Sources */, - C331FF9B2558FA6B00070591 /* Gradients.swift in Sources */, FD37E9D328A1FCDB003AE748 /* Theme+OceanDark.swift in Sources */, C331FFB82558FA8D00070591 /* DeviceUtilities.swift in Sources */, C331FFE72558FB0000070591 /* TextField.swift in Sources */, C331FFE32558FB0000070591 /* TabBar.swift in Sources */, FD37E9D528A1FCE8003AE748 /* Theme+OceanLight.swift in Sources */, - FD37E9C828A1D73F003AE748 /* Theme+PrimaryColors.swift in Sources */, + FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */, FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */, FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */, C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, @@ -4924,6 +4912,7 @@ FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, C331FF982558FA6B00070591 /* AppMode.swift in Sources */, + FD52090328B4680F006098F6 /* RadioButton.swift in Sources */, C331FFE82558FB0000070591 /* TextView.swift in Sources */, FD37E9D728A20B5D003AE748 /* UIColor+Utilities.swift in Sources */, FD37E9F928A5F14A003AE748 /* _001_ThemePreferences.swift in Sources */, @@ -4961,7 +4950,7 @@ C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */, C38EF3FB255B6DF7007E1867 /* UIAlertController+OWS.swift in Sources */, C33FDC53255A582000E217F9 /* OutageDetection.swift in Sources */, - C38EF30C255B6DBF007E1867 /* OWSScreenLock.swift in Sources */, + C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */, C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */, C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */, C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */, @@ -5003,11 +4992,11 @@ C38EF22B255B6D5D007E1867 /* ShareViewDelegate.swift in Sources */, C38EF3BF255B6DE7007E1867 /* ImageEditorView.swift in Sources */, C38EF365255B6DCC007E1867 /* OWSTableViewController.m in Sources */, - C38EF36B255B6DCC007E1867 /* ScreenLockViewController.m in Sources */, C38EF40C255B6DF7007E1867 /* GradientView.swift in Sources */, C38EF3FA255B6DF7007E1867 /* DirectionalPanGestureRecognizer.swift in Sources */, C38EF3BB255B6DE7007E1867 /* ImageEditorStrokeItem.swift in Sources */, C38EF3C0255B6DE7007E1867 /* ImageEditorCropViewController.swift in Sources */, + FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */, C38EF401255B6DF7007E1867 /* VideoPlayerView.swift in Sources */, B8856D60256F129B001CE70E /* OWSAlerts.swift in Sources */, C38EF3FE255B6DF7007E1867 /* OWSTextField.m in Sources */, @@ -5350,14 +5339,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */, FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */, B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */, B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, - 7B13E1EB2811138200BD4F64 /* PrivacySettingsTableViewController.swift in Sources */, C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */, + FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */, FD37EA0928AA2D27003AE748 /* SettingsTableViewModel.swift in Sources */, 7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */, B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */, @@ -5422,6 +5412,7 @@ 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */, + FD52090728B49738006098F6 /* ConfirmationModal.swift in Sources */, B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */, C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */, B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */, @@ -5446,7 +5437,6 @@ B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */, 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */, C331FFF32558FF0300070591 /* PathStatusView.swift in Sources */, - 7BFFB33C27D02F5800BEA04E /* CallPermissionRequestModal.swift in Sources */, B848A4C5269EAAA200617031 /* UserDetailsSheet.swift in Sources */, 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */, 7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */, @@ -5478,7 +5468,6 @@ 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */, B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */, 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, - 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, @@ -5508,7 +5497,6 @@ C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */, 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, FD37E9D128A1F2EB003AE748 /* ThemeSelectionView.swift in Sources */, - 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */, 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */, B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */, 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index e0f112deb..85fb35059 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -55,12 +55,29 @@ extension ConversationVC: @objc func startCall(_ sender: Any?) { guard SessionCall.isEnabled else { return } guard Storage.shared[.areCallsEnabled] else { - let callPermissionRequestModal = CallPermissionRequestModal() - self.navigationController?.present(callPermissionRequestModal, animated: true, completion: nil) + let confirmationModal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "modal_call_permission_request_title".localized(), + explanation: "modal_call_permission_request_explanation".localized(), + confirmTitle: "vc_settings_title".localized() + ) + ) { [weak self] _ in + self?.dismiss(animated: true) { + let navController: OWSNavigationController = OWSNavigationController( + rootViewController: SettingsTableViewController( + viewModel: PrivacySettingsViewModel(), + shouldShowCloseButton: true + ) + ) + navController.modalPresentationStyle = .fullScreen + self?.present(navController, animated: true, completion: nil) + } + } + self.navigationController?.present(confirmationModal, animated: true, completion: nil) return } - requestMicrophonePermissionIfNeeded { } + requestMicrophonePermissionIfNeeded {} let threadId: String = self.viewModel.threadData.threadId diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index ec3be96f4..68036970a 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -10,7 +10,6 @@ import SignalUtilitiesKit final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate { private static let loadingHeaderHeight: CGFloat = 20 - private static let messageRequestButtonHeight: CGFloat = 34 internal let viewModel: ConversationViewModel private var dataChangeObservable: DatabaseCancellable? @@ -209,11 +208,11 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers lazy var messageRequestView: UIView = { let result: UIView = UIView() result.translatesAutoresizingMaskIntoConstraints = false + result.themeBackgroundColor = .backgroundPrimary result.isHidden = ( self.viewModel.threadData.threadIsMessageRequest == false || self.viewModel.threadData.threadRequiresApproval == true ) - result.setGradient(Gradients.defaultBackground) return result }() @@ -222,8 +221,8 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers let result: UILabel = UILabel() result.translatesAutoresizingMaskIntoConstraints = false result.font = UIFont.systemFont(ofSize: 12) - result.text = NSLocalizedString("MESSAGE_REQUESTS_INFO", comment: "") - result.textColor = Colors.sessionMessageRequestsInfoText + result.text = "MESSAGE_REQUESTS_INFO".localized() + result.themeTextColor = .textSecondary result.textAlignment = .center result.numberOfLines = 2 @@ -231,50 +230,18 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers }() private lazy var messageRequestAcceptButton: UIButton = { - let result: UIButton = UIButton() + let result: OutlineButton = OutlineButton(style: .regular, size: .medium) result.translatesAutoresizingMaskIntoConstraints = false - result.clipsToBounds = true - result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18) - result.setTitle(NSLocalizedString("TXT_DELETE_ACCEPT", comment: ""), for: .normal) - result.setTitleColor(Colors.sessionHeading, for: .normal) - result.setBackgroundImage( - Colors.sessionHeading - .withAlphaComponent(isDarkMode ? 0.2 : 0.06) - .toImage(isDarkMode: isDarkMode), - for: .highlighted - ) - result.layer.cornerRadius = (ConversationVC.messageRequestButtonHeight / 2) - result.layer.borderColor = Colors.sessionHeading - .resolvedColor( - // Note: This is needed for '.cgColor' to support dark mode - with: UITraitCollection(userInterfaceStyle: isDarkMode ? .dark : .light) - ).cgColor - result.layer.borderWidth = 1 + result.setTitle("TXT_DELETE_ACCEPT".localized(), for: .normal) result.addTarget(self, action: #selector(acceptMessageRequest), for: .touchUpInside) return result }() private lazy var messageRequestDeleteButton: UIButton = { - let result: UIButton = UIButton() + let result: OutlineButton = OutlineButton(style: .destructive, size: .medium) result.translatesAutoresizingMaskIntoConstraints = false - result.clipsToBounds = true - result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18) - result.setTitle(NSLocalizedString("TXT_DELETE_TITLE", comment: ""), for: .normal) - result.setTitleColor(Colors.destructive, for: .normal) - result.setBackgroundImage( - Colors.destructive - .withAlphaComponent(isDarkMode ? 0.2 : 0.06) - .toImage(isDarkMode: isDarkMode), - for: .highlighted - ) - result.layer.cornerRadius = (ConversationVC.messageRequestButtonHeight / 2) - result.layer.borderColor = Colors.destructive - .resolvedColor( - // Note: This is needed for '.cgColor' to support dark mode - with: UITraitCollection(userInterfaceStyle: isDarkMode ? .dark : .light) - ).cgColor - result.layer.borderWidth = 1 + result.setTitle("TXT_DELETE_TITLE".localized(), for: .normal) result.addTarget(self, action: #selector(deleteMessageRequest), for: .touchUpInside) return result @@ -353,14 +320,12 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers messageRequestAcceptButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20) messageRequestAcceptButton.pin(.left, to: .left, of: messageRequestView, withInset: 20) messageRequestAcceptButton.pin(.bottom, to: .bottom, of: messageRequestView) - messageRequestAcceptButton.set(.height, to: ConversationVC.messageRequestButtonHeight) messageRequestDeleteButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20) messageRequestDeleteButton.pin(.left, to: .right, of: messageRequestAcceptButton, withInset: UIDevice.current.isIPad ? Values.iPadButtonSpacing : 20) messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20) messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView) messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton) - messageRequestDeleteButton.set(.height, to: ConversationVC.messageRequestButtonHeight) // Unread count view view.addSubview(unreadCountView) diff --git a/Session/Conversations/Views & Modals/CallPermissionRequestModal.swift b/Session/Conversations/Views & Modals/CallPermissionRequestModal.swift deleted file mode 100644 index 0d54fc262..000000000 --- a/Session/Conversations/Views & Modals/CallPermissionRequestModal.swift +++ /dev/null @@ -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) - } - }) - } -} diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 1568dda2f..370edd453 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -537,7 +537,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve let hide = UITableViewRowAction(style: .destructive, title: "TXT_HIDE_TITLE".localized()) { _, _ in Storage.shared.write { db in db[.hasHiddenMessageRequests] = true } } - hide.themeBackgroundColor = .danger + hide.themeBackgroundColor = .conversationButton_swipeDestructive return [hide] @@ -586,7 +586,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve self?.present(alert, animated: true, completion: nil) } - delete.themeBackgroundColor = .danger + delete.themeBackgroundColor = .conversationButton_swipeDestructive let pin: UITableViewRowAction = UITableViewRowAction( style: .normal, @@ -601,7 +601,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve .updateAll(db, SessionThread.Columns.isPinned.set(to: !threadViewModel.threadIsPinned)) } } - pin.themeBackgroundColor = .conversationButton_pinBackground + pin.themeBackgroundColor = .conversationButton_swipeTertiary guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else { return [ delete, pin ] @@ -631,7 +631,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve .retainUntilComplete() } } - block.themeBackgroundColor = .backgroundTertiary + block.themeBackgroundColor = .conversationButton_swipeSecondary return [ delete, block, pin ] diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 83af902dd..a859cdbcc 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -353,7 +353,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat ) { [weak self] _, _ in self?.delete(threadId) } - delete.backgroundColor = Colors.destructive + delete.themeBackgroundColor = .conversationButton_swipeDestructive return [ delete ] diff --git a/Session/Home/NewConversationButtonSet.swift b/Session/Home/NewConversationButtonSet.swift index eeae96f56..5c5f18f5f 100644 --- a/Session/Home/NewConversationButtonSet.swift +++ b/Session/Home/NewConversationButtonSet.swift @@ -383,7 +383,7 @@ private final class NewConversationButton: UIImageView { contentMode = .center layer.cornerRadius = (NewConversationButtonSet.collapsedButtonSize / 2) - themeBackgroundColor = (isMainButton ? .menuButton_background : .backgroundTertiary) + themeBackgroundColor = .menuButton_background themeTintColor = .menuButton_icon if isMainButton { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index c723660e0..64df23e4e 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -55,12 +55,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // Note: Intentionally dispatching sync as we want to wait for these to complete before // continuing DispatchQueue.main.sync { - OWSScreenLockUI.sharedManager().setup(withRootWindow: mainWindow) + ScreenLockUI.shared.setupWithRootWindow(rootWindow: mainWindow) OWSWindowManager.shared().setup( withRootWindow: mainWindow, - screenBlockingWindow: OWSScreenLockUI.sharedManager().screenBlockingWindow + screenBlockingWindow: ScreenLockUI.shared.screenBlockingWindow ) - OWSScreenLockUI.sharedManager().startObserving() + ScreenLockUI.shared.startObserving() } }, migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in diff --git a/Session/Meta/Signal-Bridging-Header.h b/Session/Meta/Signal-Bridging-Header.h index 48020340f..8c9e3032f 100644 --- a/Session/Meta/Signal-Bridging-Header.h +++ b/Session/Meta/Signal-Bridging-Header.h @@ -15,9 +15,7 @@ #import "OWSMessageTimerView.h" #import "OWSNavigationController.h" #import "OWSProgressView.h" -#import "OWSScreenLockUI.h" #import "OWSWindowManager.h" -#import "PrivacySettingsTableViewController.h" #import "OWSQRCodeScanningViewController.h" #import "MainAppContext.h" #import "UIViewController+Permissions.h" diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 05823e1cd..8175162a3 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Benachrichtigungsstrategie"; "modal_seed_title" = "Ihr Wiederherstellungssatz"; "modal_seed_explanation" = "Das ist Ihr Wiederherstellungssatz. Damit können Sie Ihre Session ID wiederherstellen oder auf ein neues Gerät migrieren."; -"modal_clear_all_data_title" = "Alle Daten löschen"; -"modal_clear_all_data_explanation" = "Dadurch werden Ihre Nachrichten, Sessions und Kontakte dauerhaft gelöscht."; -"modal_clear_all_data_explanation_2" = "Möchtest du nur dieses Gerät löschen oder dein gesamtes Konto löschen?"; -"dialog_clear_all_data_deletion_failed_1" = "Daten nicht gelöscht von Service Node 1. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Daten nicht gelöscht von %@ Service Noten. Service Noten IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Nur Geräte"; -"modal_clear_all_data_entire_account_button_title" = "Gesamtes Konto"; "vc_qr_code_title" = "QR-Code"; "vc_qr_code_view_my_qr_code_tab_title" = "Meinen QR-Code anzeigen"; "vc_qr_code_view_scan_qr_code_tab_title" = "QR-Code scannen"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Alle Daten löschen"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Daten nicht gelöscht von Service Node 1. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Daten nicht gelöscht von %@ Service Noten. Service Noten IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 3a9392053..f0a04655c 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Notification Strategy"; "modal_seed_title" = "Your Recovery Phrase"; "modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; -"modal_clear_all_data_title" = "Clear All Data"; -"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; -"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; -"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Device Only"; -"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "QR Code"; "vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; "vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index b4540ad7d..442a93c1d 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Estrategia de notificación"; "modal_seed_title" = "Tu frase de recuperación"; "modal_seed_explanation" = "Esta es tu frase de recuperación. Con ella, puedes restaurar o migrar tu ID de Session a un nuevo dispositivo."; -"modal_clear_all_data_title" = "Borrar todos los datos"; -"modal_clear_all_data_explanation" = "Esto eliminará permanentemente tu ID de Session, incluyendo todos los mensajes, sesiones y contactos."; -"modal_clear_all_data_explanation_2" = "¿Quieres borrar sólo este dispositivo o eliminar toda tu cuenta?"; -"dialog_clear_all_data_deletion_failed_1" = "Datos no borrados por 1 nodo de servicio. ID del nodo de servicio: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Datos no borrados por %@ nodos de servicio. ID del nodo de servicio: %@."; -"modal_clear_all_data_device_only_button_title" = "Sólo el dispositivo"; -"modal_clear_all_data_entire_account_button_title" = "Toda la cuenta"; "vc_qr_code_title" = "Código QR"; "vc_qr_code_view_my_qr_code_tab_title" = "Ver mi código QR"; "vc_qr_code_view_scan_qr_code_tab_title" = "Escanear código QR"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Borrar todos los datos"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Datos no borrados por 1 nodo de servicio. ID del nodo de servicio: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Datos no borrados por %@ nodos de servicio. ID del nodo de servicio: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 44e053327..6641663d2 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "استراتژی اعلان"; "modal_seed_title" = "عبارت بازیابی شما"; "modal_seed_explanation" = "این عبارت بازیابی شماست. با استفاده از آن می‌توانید شناسه‌ی Session خود را به دستگاه جدید بازیابی یا انتقال دهید."; -"modal_clear_all_data_title" = "پاک کردن همه داده‌ها"; -"modal_clear_all_data_explanation" = "این به طور دائم پیام‌ها، جلسات و مخاطبین شما را حذف می‌کند."; -"modal_clear_all_data_explanation_2" = "آیا فقط می‌خواهید این دستگاه را پاک کنید یا می‌خواهید این اکانت را پاک کنید؟"; -"dialog_clear_all_data_deletion_failed_1" = "داده ها توسط ۱ گره سرویس حذف نشده است. شناسه گره سرویس: %@."; -"dialog_clear_all_data_deletion_failed_2" = "داده ها توسط گره سرویس %@ حذف نشدند. شناسه گره سرویس: %@."; -"modal_clear_all_data_device_only_button_title" = "فقط دستگاه"; -"modal_clear_all_data_entire_account_button_title" = "تمام حساب"; "vc_qr_code_title" = "کد QR"; "vc_qr_code_view_my_qr_code_tab_title" = "مشاهده کد QR من"; "vc_qr_code_view_scan_qr_code_tab_title" = "اسکن کد QR"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "پاک کردن همه داده‌ها"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "داده ها توسط ۱ گره سرویس حذف نشده است. شناسه گره سرویس: %@."; +"dialog_clear_all_data_deletion_failed_2" = "داده ها توسط گره سرویس %@ حذف نشدند. شناسه گره سرویس: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 5dcf6d776..49689e409 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Ilmoitustyyli"; "modal_seed_title" = "Palatusvirkkeesi"; "modal_seed_explanation" = "Tämä on palautusvirkkeesi. Sillä voit palauttaa tai siirtää Session ID:si uuteen laitteeseen."; -"modal_clear_all_data_title" = "Tyhjennä kaikki data"; -"modal_clear_all_data_explanation" = "Tämä poistaa kaikki viestisi sekä yhteydet lopullisesti."; -"modal_clear_all_data_explanation_2" = "Haluatko tyhjentää datan vain tästä laitteesta vai poistaa koko tilin?"; -"dialog_clear_all_data_deletion_failed_1" = "Dataa ei poistettu yhdestä palvelusolmusta. Palvelusolmun tunnus: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Dataa ei poistettu %@ palvelusolmusta. Palvelusolmujen tunnukset: %@."; -"modal_clear_all_data_device_only_button_title" = "Vain tämä laite"; -"modal_clear_all_data_entire_account_button_title" = "Koko tili"; "vc_qr_code_title" = "QR-koodi"; "vc_qr_code_view_my_qr_code_tab_title" = "Näytä QR-koodini"; "vc_qr_code_view_scan_qr_code_tab_title" = "Skannaa QR-koodi"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Tyhjennä kaikki data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Dataa ei poistettu yhdestä palvelusolmusta. Palvelusolmun tunnus: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Dataa ei poistettu %@ palvelusolmusta. Palvelusolmujen tunnukset: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index ca83608e2..57e529c0e 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Stratégie de notification"; "modal_seed_title" = "Votre phrase de récupération"; "modal_seed_explanation" = "Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil."; -"modal_clear_all_data_title" = "Effacer toutes les données"; -"modal_clear_all_data_explanation" = "Cela supprimera définitivement vos messages, vos sessions et vos contacts."; -"modal_clear_all_data_explanation_2" = "Souhaitez-vous effacer seulement cet appareil ou supprimer 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_view_my_qr_code_tab_title" = "Afficher mon code QR"; "vc_qr_code_view_scan_qr_code_tab_title" = "Scanner le QR Code"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Effacer toutes les données"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Les données 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"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index de1e94561..adf250b4a 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Notification Strategy"; "modal_seed_title" = "Your Recovery Phrase"; "modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; -"modal_clear_all_data_title" = "Clear All Data"; -"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; -"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; -"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Device Only"; -"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "QR Code"; "vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; "vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 9b93bc66b..3b2c60448 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Strategija obavijesti"; "modal_seed_title" = "Fraza za oporavak"; "modal_seed_explanation" = "Ovo je vaša fraza za oporavak. Pomoću nje možete vratiti ili migrirati svoj Session ID na novi uređaj."; -"modal_clear_all_data_title" = "Obriši sve podatke"; -"modal_clear_all_data_explanation" = "Ovo će trajno izbrisati vaše poruke, sesije razgovora i kontakte."; -"modal_clear_all_data_explanation_2" = "Želite li izbrisati samo ovaj uređaj ili želite izbrisati cijeli račun?"; -"dialog_clear_all_data_deletion_failed_1" = "Podatke nije izbrisao 1 uslužni čvor. ID uslužnog čvora: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Podatke nije izbrisao %@ uslužni čvor. ID uslužnog čvora: %@."; -"modal_clear_all_data_device_only_button_title" = "Samo uređaj"; -"modal_clear_all_data_entire_account_button_title" = "Cijeli račun"; "vc_qr_code_title" = "QR kôd"; "vc_qr_code_view_my_qr_code_tab_title" = "Pogledaj moj QR kôd"; "vc_qr_code_view_scan_qr_code_tab_title" = "Skeniraj QR kôd"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Obriši sve podatke"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Podatke nije izbrisao 1 uslužni čvor. ID uslužnog čvora: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Podatke nije izbrisao %@ uslužni čvor. ID uslužnog čvora: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index a3c93ec66..d834e8bcb 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Strategi notofikasi"; "modal_seed_title" = "Kata pemulihan anda"; "modal_seed_explanation" = "Ini adalah kata pemulihan anda. Gunakan untuk mengembalikan atau memindahkan Session ID anda ke perangkat lain"; -"modal_clear_all_data_title" = "Hapus semua data"; -"modal_clear_all_data_explanation" = "Pesan, Session, dan kontak anda akan dihapus secara permanen"; -"modal_clear_all_data_explanation_2" = "Apakah anda ingin menghapus di perangkat ini, atau menghapus akun anda seutuhnya?"; -"dialog_clear_all_data_deletion_failed_1" = "Data tidak dihapus oleh 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data tidak dihapus oleh %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Perangkat saja"; -"modal_clear_all_data_entire_account_button_title" = "Seluruh akun"; "vc_qr_code_title" = "Kode QR"; "vc_qr_code_view_my_qr_code_tab_title" = "Lihat kode QR saya"; "vc_qr_code_view_scan_qr_code_tab_title" = "Pindai kode QR"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Hapus semua data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data tidak dihapus oleh 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data tidak dihapus oleh %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index c5bd0d294..2aaf6c9c7 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Strategia di notifica"; "modal_seed_title" = "Frase di recupero"; "modal_seed_explanation" = "Questa è la tua frase di recupero. Usala per ripristinare o migrare la Sessione ID a un nuovo dispositivo."; -"modal_clear_all_data_title" = "Elimina tutti i dati"; -"modal_clear_all_data_explanation" = "Ciò eliminerà permanentemente i tuoi messaggi, sessioni e contatti."; -"modal_clear_all_data_explanation_2" = "Vuoi cancellare solo su questo dispositivo o eliminare l'intero account?"; -"dialog_clear_all_data_deletion_failed_1" = "Dati non eliminati da 1 nodi di servizio. ID Nodo di servizio: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Dati non eliminati da %@ nodi di servizio. ID Nodo di servizio: %@."; -"modal_clear_all_data_device_only_button_title" = "Solo dispositivo"; -"modal_clear_all_data_entire_account_button_title" = "Tutto l'account"; "vc_qr_code_title" = "Codice QR"; "vc_qr_code_view_my_qr_code_tab_title" = "Visualizza il mio codice QR"; "vc_qr_code_view_scan_qr_code_tab_title" = "Scansiona il codice QR"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Elimina tutti i dati"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Dati non eliminati da 1 nodi di servizio. ID Nodo di servizio: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Dati non eliminati da %@ nodi di servizio. ID Nodo di servizio: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index f9e6ce439..84623bb59 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "通知戦略"; "modal_seed_title" = "あなたのリカバリーフレーズ"; "modal_seed_explanation" = "これはあなたのリカバリーフレーズです。これにより、Session ID を新しい端末に復元または移行できます。"; -"modal_clear_all_data_title" = "すべてのデータを消去する"; -"modal_clear_all_data_explanation" = "これにより、メッセージ、Session、連絡先が完全に削除されます。"; -"modal_clear_all_data_explanation_2" = "この端末のみから消去するか、すべての端末からアカウント全体を削除しますか?"; -"dialog_clear_all_data_deletion_failed_1" = "このサービスノードからデータが削除されませんでした。ID: %@"; -"dialog_clear_all_data_deletion_failed_2" = "%@ つのサービスノードからデータが削除されませんでした。ID %@"; -"modal_clear_all_data_device_only_button_title" = "この端末のみ"; -"modal_clear_all_data_entire_account_button_title" = "アカウント全体"; "vc_qr_code_title" = "QR コード"; "vc_qr_code_view_my_qr_code_tab_title" = "私の QR コードを表示する"; "vc_qr_code_view_scan_qr_code_tab_title" = "QR コードをスキャンする"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "すべてのデータを消去する"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "このサービスノードからデータが削除されませんでした。ID: %@"; +"dialog_clear_all_data_deletion_failed_2" = "%@ つのサービスノードからデータが削除されませんでした。ID %@"; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index e038be7c3..9efaada40 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Notificatie Inhoud"; "modal_seed_title" = "Uw Herstel Zin"; "modal_seed_explanation" = "Dit is uw herstel zin, Hiermee kun je je sessie-ID herstellen of migreren naar een nieuw apparaat."; -"modal_clear_all_data_title" = "Wis alle gegevens"; -"modal_clear_all_data_explanation" = "Hiermee worden uw berichten, sessies en contacten permanent verwijderd."; -"modal_clear_all_data_explanation_2" = "Wilt u alleen de data op dit apparaat verwijderen, of wilt u uw hele account verwijderen?"; -"dialog_clear_all_data_deletion_failed_1" = "Gegevens niet verwijderd door 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Gegevens niet verwijderd door %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Alleen apparaat"; -"modal_clear_all_data_entire_account_button_title" = "Gehele account"; "vc_qr_code_title" = "QR-code"; "vc_qr_code_view_my_qr_code_tab_title" = "Bekijk mijn QR-code"; "vc_qr_code_view_scan_qr_code_tab_title" = "QR-code scannen"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Wis alle gegevens"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Gegevens niet verwijderd door 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Gegevens niet verwijderd door %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index b391bb9dd..f647502a1 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Strategia powiadomień"; "modal_seed_title" = "Twoja fraza odzyskiwania"; "modal_seed_explanation" = "To jest twoja fraza odzyskiwania. Dzięki niej możesz przywrócić lub przenieść identyfikator Session na nowe urządzenie."; -"modal_clear_all_data_title" = "Wyczyść wszystkie dane"; -"modal_clear_all_data_explanation" = "Spowoduje to trwałe usunięcie wiadomości, Session i kontaktów."; -"modal_clear_all_data_explanation_2" = "Czy chcesz wyczyścić tylko to urządzenie, czy usunąć całe swoje konto?"; -"dialog_clear_all_data_deletion_failed_1" = "Dane nie zostały usunięte przez 1 węzeł usługowy. Identyfikator węzła: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Dane nie zostały usunięte przez %@ węzły usługowe. Identyfikatory węzłów: %@."; -"modal_clear_all_data_device_only_button_title" = "Tylko urządzenie"; -"modal_clear_all_data_entire_account_button_title" = "Całe konto"; "vc_qr_code_title" = "Kod QR"; "vc_qr_code_view_my_qr_code_tab_title" = "Wyświetl mój kod QR"; "vc_qr_code_view_scan_qr_code_tab_title" = "Skanowania QR code"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Wyczyść wszystkie dane"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Dane nie zostały usunięte przez 1 węzeł usługowy. Identyfikator węzła: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Dane nie zostały usunięte przez %@ węzły usługowe. Identyfikatory węzłów: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 37a71255d..b6a9dcc2d 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Estratégia de notificação"; "modal_seed_title" = "Sua frase de recuperação"; "modal_seed_explanation" = "Esta é sua frase de recuperação. Com ela, você pode restaurar ou migrar seu ID Session para um novo dispositivo."; -"modal_clear_all_data_title" = "Limpar todos os dados"; -"modal_clear_all_data_explanation" = "Isso excluirá permanentemente suas mensagens, sessões e contatos."; -"modal_clear_all_data_explanation_2" = "Você gostaria de limpar apenas esse dispositivo, ou deletar sua conta?"; -"dialog_clear_all_data_deletion_failed_1" = "Dados não excluídos por 1 Service Node. ID do Service Node: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Dados não excluídos por %@ Service Nodes. IDs dos Service Nodes: %@."; -"modal_clear_all_data_device_only_button_title" = "Somente esse dispositivo"; -"modal_clear_all_data_entire_account_button_title" = "Conta inteira"; "vc_qr_code_title" = "Código QR"; "vc_qr_code_view_my_qr_code_tab_title" = "Ver meu código QR"; "vc_qr_code_view_scan_qr_code_tab_title" = "Escanear código QR"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Limpar todos os dados"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Dados não excluídos por 1 Service Node. ID do Service Node: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Dados não excluídos por %@ Service Nodes. IDs dos Service Nodes: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 2ac2210c1..e828e48a4 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Метод уведомлений"; "modal_seed_title" = "Ваша секретная фраза"; "modal_seed_explanation" = "Это ваша секретная фраза. С ее помощью вы можете восстановить или перенести свой Session ID на новое устройство."; -"modal_clear_all_data_title" = "Очистить все данные"; -"modal_clear_all_data_explanation" = "Это навсегда удалит ваши сообщения, сессии и контакты."; -"modal_clear_all_data_explanation_2" = "Вы хотите очистить только это устройство или полностью удалить ваш аккаунт?"; -"dialog_clear_all_data_deletion_failed_1" = "Данные не удалены 1 узлом сервиса. Номер узла: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Данные не удалены %@ узлами сервиса. Номера узлов: %@."; -"modal_clear_all_data_device_only_button_title" = "Только устройство"; -"modal_clear_all_data_entire_account_button_title" = "Полностью аккаунт"; "vc_qr_code_title" = "QR-код"; "vc_qr_code_view_my_qr_code_tab_title" = "Посмотреть мой QR-код"; "vc_qr_code_view_scan_qr_code_tab_title" = "Сканировать QR-код"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Очистить все данные"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Данные не удалены 1 узлом сервиса. Номер узла: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Данные не удалены %@ узлами сервиса. Номера узлов: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 90f043cb5..530f7127d 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Notification Strategy"; "modal_seed_title" = "Your Recovery Phrase"; "modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; -"modal_clear_all_data_title" = "Clear All Data"; -"modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; -"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; -"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Device Only"; -"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "QR Code"; "vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; "vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Clear All Data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 626e80ce7..12deb2ab1 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Stratégia upozornení"; "modal_seed_title" = "Vaša fráza pre obnovenie"; "modal_seed_explanation" = "Toto je vaša fráza pre obnovenie. S jej pomocou môžete obnoviť alebo presunúť svoje Session ID na nové zariadenie."; -"modal_clear_all_data_title" = "Odstrániť všetky dáta"; -"modal_clear_all_data_explanation" = "Toto navždy vymaže vaše správy, stretnutia a kontakty."; -"modal_clear_all_data_explanation_2" = "Chcete vymazať iba toto zariadenie alebo celý účet?"; -"dialog_clear_all_data_deletion_failed_1" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@."; -"dialog_clear_all_data_deletion_failed_2" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@."; -"modal_clear_all_data_device_only_button_title" = "Iba zariadenie"; -"modal_clear_all_data_entire_account_button_title" = "Celý Účet"; "vc_qr_code_title" = "QR kód"; "vc_qr_code_view_my_qr_code_tab_title" = "Zobraziť môj QR kód"; "vc_qr_code_view_scan_qr_code_tab_title" = "Skenovať QR kód"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Odstrániť všetky dáta"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@."; +"dialog_clear_all_data_deletion_failed_2" = "Data neboli odstránené 1 Servisným Uzlom. Servisný Uzol ID:%@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 5160ee73f..e602e2cf0 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Strategi för aviseringar"; "modal_seed_title" = "Din Återställningsfras"; "modal_seed_explanation" = "Detta är din återställningsfras. Med den kan du återställa eller migrera ditt Sessions-ID till en ny enhet."; -"modal_clear_all_data_title" = "Rensa all data"; -"modal_clear_all_data_explanation" = "Detta kommer att radera dina meddelanden, sessioner och kontakter permanent."; -"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; -"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Device Only"; -"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "QR-kod"; "vc_qr_code_view_my_qr_code_tab_title" = "Visa min QR-kod"; "vc_qr_code_view_scan_qr_code_tab_title" = "Skanna QR-kod"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Rensa all data"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 1416ab36a..948cff010 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "กลยุทธ์สำคัญแจ้งเตือน"; "modal_seed_title" = "วลีกู้คืนของคุณ"; "modal_seed_explanation" = "นี่คือวลีกู้คืนของคุณ ด้วยวิธีนี้ คุณสามารถกู้คืนหรือย้ายไอดีเซสชันSessionของคุณไปยังอุปกรณ์ใหม่ได้"; -"modal_clear_all_data_title" = "ลบข้อมูลไปหมด"; -"modal_clear_all_data_explanation" = "การดำเนินการนี้จะลบข้อความแชตและผู้ติดต่อของคุณ"; -"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; -"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Device Only"; -"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "QR โค้ด"; "vc_qr_code_view_my_qr_code_tab_title" = "แสดง QR โค้ดของคุน"; "vc_qr_code_view_scan_qr_code_tab_title" = "สแกน QR โค้ด"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "ลบข้อมูลไปหมด"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index e349b3ecf..5a93d3753 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "Chiến lược thông báo"; "modal_seed_title" = "Cụm từ khôi phục của bạn"; "modal_seed_explanation" = "Đây là cụm từ khôi phục của bạn. Bạn có thể dùng nó để khôi phục hoặc chuyển Session ID của mình sang một thiết bị mới."; -"modal_clear_all_data_title" = "Xóa tất cả dữ liệu"; -"modal_clear_all_data_explanation" = "Thao tác này sẽ xóa vĩnh viễn các tin nhắn, sessions, và danh bạ của bạn."; -"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; -"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; -"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; -"modal_clear_all_data_device_only_button_title" = "Device Only"; -"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "Mã QR"; "vc_qr_code_view_my_qr_code_tab_title" = "Xem mã QR của tôi"; "vc_qr_code_view_scan_qr_code_tab_title" = "Quét mã QR"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "Xóa tất cả dữ liệu"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "Data not deleted by 1 Service Node. Service Node ID: %@."; +"dialog_clear_all_data_deletion_failed_2" = "Data not deleted by %@ Service Nodes. Service Node IDs: %@."; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 6d31e06d3..9e9affdb2 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "通知類型"; "modal_seed_title" = "您的回復用字句"; "modal_seed_explanation" = "這是您的回復用字句,您可以利用此字句來回復或轉移您的帳號至新的裝置上。"; -"modal_clear_all_data_title" = "清除所有資料"; -"modal_clear_all_data_explanation" = "這樣做將永久清除您的訊息,帳號與聯絡人。"; -"modal_clear_all_data_explanation_2" = "你要清附這部裝置的資料,還是你整個賬戶的資料?"; -"dialog_clear_all_data_deletion_failed_1" = "數據已被1個服務節點刪除。節點ID: %@"; -"dialog_clear_all_data_deletion_failed_2" = "數據沒有被%@個服務節點刪除。節點ID: %@"; -"modal_clear_all_data_device_only_button_title" = "僅限裝置"; -"modal_clear_all_data_entire_account_button_title" = "整個賬戶"; "vc_qr_code_title" = "QR Code"; "vc_qr_code_view_my_qr_code_tab_title" = "查看我的 QR Code"; "vc_qr_code_view_scan_qr_code_tab_title" = "掃描 QR Code"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "清除所有資料"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "數據已被1個服務節點刪除。節點ID: %@"; +"dialog_clear_all_data_deletion_failed_2" = "數據沒有被%@個服務節點刪除。節點ID: %@"; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index b55b339b6..e73a1967c 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -522,13 +522,6 @@ "preferences_notifications_strategy_category_title" = "通知选项"; "modal_seed_title" = "您的恢复口令"; "modal_seed_explanation" = "这是您的恢复口令。您可以通过该口令将Session ID还原或迁移到新设备上。"; -"modal_clear_all_data_title" = "清除所有数据"; -"modal_clear_all_data_explanation" = "这将永久删除您的消息、对话和联系人。"; -"modal_clear_all_data_explanation_2" = "你想只清除这个设备,还是删除你的整个账户?"; -"dialog_clear_all_data_deletion_failed_1" = "数据未被一个服务节点删除。服务节点ID: %@"; -"dialog_clear_all_data_deletion_failed_2" = "数据未被 %@ 服务节点删除。服务节点ID: %@"; -"modal_clear_all_data_device_only_button_title" = "仅设备"; -"modal_clear_all_data_entire_account_button_title" = "整个账户"; "vc_qr_code_title" = "二维码"; "vc_qr_code_view_my_qr_code_tab_title" = "查看我的二维码"; "vc_qr_code_view_scan_qr_code_tab_title" = "扫描二维码"; @@ -684,6 +677,26 @@ "SEND_FAILED_NOTIFICATION_BODY" = "Your message failed to send."; "INVALID_SESSION_ID_MESSAGE" = "Please check the Session ID and try again."; "INVALID_RECOVERY_PHRASE_MESSAGE" = "Please check the Recovery Phrase and try again."; +"PRIVACY_TITLE" = "Privacy"; +"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session"; +"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session."; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_TITLE" = "Screenshot Notifications"; +"PRIVACY_SCREEN_SECURITY_SCREENSHOT_NOTIFICATIONS_DESCRIPTION" = "Receive a notification when a contact takes a screenshot of a direct message."; +"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts"; +"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "See and share read receipts in one-to-one chats."; +"PRIVACY_SECTION_TYPING_INDICATORS" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_TITLE" = "Typing Indicators"; +"PRIVACY_TYPING_INDICATORS_DESCRIPTION" = "See and share typing indicators in one-to-one chats."; +"PRIVACY_SECTION_LINK_PREVIEWS" = "Link Previews"; +"PRIVACY_LINK_PREVIEWS_TITLE" = "Send Link Previews"; +"PRIVACY_LINK_PREVIEWS_DESCRIPTION" = "Generate link previews for supported URLs."; +"PRIVACY_SECTION_CALLS" = "Calls (Beta)"; +"PRIVACY_CALLS_TITLE" = "Voice and Video Calls"; +"PRIVACY_CALLS_DESCRIPTION" = "Enables voice and video calls to and from other users."; +"PRIVACY_CALLS_WARNING_TITLE" = "Voice and Video Calls (Beta)"; +"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Your IP address is visible to your call partner and an Oxen Foundation server while using beta calls. Are you sure you want to enable Voice and Video Calls?"; "NOTIFICATIONS_TITLE" = "Notifications"; "NOTIFICATIONS_SECTION_STRATEGY" = "Notification Strategy"; "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Use Fast Mode"; @@ -714,3 +727,11 @@ "HELP_FEEDBACK_TITLE" = "We'd love your Feedback"; "HELP_FAQ_TITLE" = "FAQ"; "HELP_SUPPORT_TITLE" = "Support"; +"modal_clear_all_data_title" = "清除所有数据"; +"modal_clear_all_data_explanation" = "This will permanently delete your messages and contacts. Would you like to clear this device only, or delete your data from the network as well?"; +"modal_clear_all_data_explanation_2" = "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts."; +"modal_clear_all_data_device_only_button_title" = "Clear Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Clear Device and Network"; +"dialog_clear_all_data_deletion_failed_1" = "数据未被一个服务节点删除。服务节点ID: %@"; +"dialog_clear_all_data_deletion_failed_2" = "数据未被 %@ 服务节点删除。服务节点ID: %@"; +"modal_clear_all_data_confirm" = "Clear"; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index bc705c173..d2b249b2f 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -112,29 +112,9 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { super.init() - AppReadiness.runNowOrWhenAppDidBecomeReady { - NotificationCenter.default.addObserver(self, selector: #selector(self.handleMessageRead), name: .incomingMessageMarkedAsRead, object: nil) - } SwiftSingletons.register(self) } - // MARK: - - - @objc - func handleMessageRead(notification: Notification) { - AssertIsOnMainThread() - - switch notification.object { - case let interaction as Interaction: - guard interaction.variant == .standardIncoming else { return } - - Logger.debug("canceled notification for message: \(interaction)") - cancelNotifications(identifiers: interaction.notificationIdentifiers) - - default: break - } - } - // MARK: - Presenting Notifications func registerNotificationSettings() -> Promise { @@ -165,7 +145,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { let senderName = Profile.displayName(db, id: interaction.authorId, threadVariant: thread.variant) let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] - .defaulting(to: .nameAndPreview) + .defaulting(to: .defaultPreviewType) switch previewType { case .noNameNoPreview: @@ -305,7 +285,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { public func notifyForFailedSend(_ db: Database, in thread: SessionThread) { let notificationTitle: String? let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] - .defaulting(to: .nameAndPreview) + .defaulting(to: .defaultPreviewType) switch previewType { case .noNameNoPreview: notificationTitle = nil diff --git a/Session/Path/PathStatusView.swift b/Session/Path/PathStatusView.swift index f3dec6a7e..f9250ef4f 100644 --- a/Session/Path/PathStatusView.swift +++ b/Session/Path/PathStatusView.swift @@ -1,26 +1,46 @@ -import UIKit +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -final class PathStatusView : UIView { +import UIKit +import SessionUIKit + +final class PathStatusView: UIView { + enum Status { + case unknown + case connecting + case connected + case error + + var themeColor: ThemeValue { + switch self { + case .unknown: return .path_unknown + case .connecting: return .path_connecting + case .connected: return .path_connected + case .error: return .path_error + } + } + } - static let size = CGFloat(8) + static let size: CGFloat = 8 override init(frame: CGRect) { super.init(frame: frame) + setUpViewHierarchy() registerObservers() } required init?(coder: NSCoder) { super.init(coder: coder) + setUpViewHierarchy() registerObservers() } private func setUpViewHierarchy() { - layer.cornerRadius = PathStatusView.size / 2 + layer.cornerRadius = (PathStatusView.size / 2) layer.masksToBounds = false - let color = (!OnionRequestAPI.paths.isEmpty ? Colors.accent : Colors.pathsBuilding) - setColor(to: color, isAnimated: false) + + setStatus(to: (!OnionRequestAPI.paths.isEmpty ? .connected : .connecting)) } private func registerObservers() { @@ -33,18 +53,28 @@ final class PathStatusView : UIView { NotificationCenter.default.removeObserver(self) } - private func setColor(to color: UIColor, isAnimated: Bool) { - backgroundColor = color - let size = PathStatusView.size - let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: color, isAnimated: isAnimated, radius: isLightMode ? 6 : 8) - setCircularGlow(with: glowConfiguration) + private func setStatus(to status: Status) { + themeBackgroundColor = status.themeColor + layer.themeShadowColor = status.themeColor + layer.shadowOffset = CGSize(width: 0, height: 0.8) + layer.shadowPath = UIBezierPath( + ovalIn: CGRect( + origin: CGPoint.zero, + size: CGSize(width: PathStatusView.size, height: PathStatusView.size) + ) + ).cgPath + + ThemeManager.onThemeChange(observer: self) { [weak self] theme, _ in + self?.layer.shadowOpacity = (theme.interfaceStyle == .light ? 0.4 : 1) + self?.layer.shadowRadius = (theme.interfaceStyle == .light ? 6 : 8) + } } @objc private func handleBuildingPathsNotification() { - setColor(to: Colors.pathsBuilding, isAnimated: true) + setStatus(to: .connecting) } @objc private func handlePathsBuiltNotification() { - setColor(to: Colors.accent, isAnimated: true) + setStatus(to: .connected) } } diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index 144b8de70..4cab218ca 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -154,28 +154,37 @@ final class PathVC: BaseVC { // MARK: General private func getPathRow(title: String, subtitle: String?, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) -> UIStackView { - let lineView = LineView(location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval) + let lineView = LineView( + location: location, + dotAnimationStartDelay: dotAnimationStartDelay, + dotAnimationRepeatInterval: dotAnimationRepeatInterval + ) lineView.set(.width, to: PathVC.expandedDotSize) lineView.set(.height, to: PathVC.rowHeight) - let titleLabel = UILabel() - titleLabel.textColor = Colors.text + + let titleLabel: UILabel = UILabel() titleLabel.font = .systemFont(ofSize: Values.mediumFontSize) titleLabel.text = title + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byTruncatingTail + let titleStackView = UIStackView(arrangedSubviews: [ titleLabel ]) titleStackView.axis = .vertical + if let subtitle = subtitle { let subtitleLabel = UILabel() - subtitleLabel.textColor = Colors.text subtitleLabel.font = .systemFont(ofSize: Values.verySmallFontSize) subtitleLabel.text = subtitle + subtitleLabel.themeTextColor = .textPrimary subtitleLabel.lineBreakMode = .byTruncatingTail titleStackView.addArrangedSubview(subtitleLabel) } + let stackView = UIStackView(arrangedSubviews: [ lineView, titleStackView ]) stackView.axis = .horizontal stackView.spacing = Values.largeSpacing stackView.alignment = .center + return stackView } @@ -221,7 +230,9 @@ private final class LineView : UIView { self.location = location self.dotAnimationStartDelay = dotAnimationStartDelay self.dotAnimationRepeatInterval = dotAnimationRepeatInterval + super.init(frame: CGRect.zero) + setUpViewHierarchy() } diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 68a2799a7..4c8e53521 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -102,6 +102,22 @@ class HelpViewModel: SettingsTableViewModel [SectionModel] in let currentSelection: Preferences.NotificationPreviewType? = db[.preferencesNotificationPreviewType] + .defaulting(to: .defaultPreviewType) return [ SectionModel( diff --git a/Session/Settings/NotificationSettingsViewModel.swift b/Session/Settings/NotificationSettingsViewModel.swift index e31fc0950..2e6fbf6fd 100644 --- a/Session/Settings/NotificationSettingsViewModel.swift +++ b/Session/Settings/NotificationSettingsViewModel.swift @@ -65,7 +65,7 @@ class NotificationSettingsViewModel: SettingsTableViewModel [SectionModel] in self?.currentSelection = (self?.currentSelection ?? db[.defaultNotificationSound]) + .defaulting(to: .defaultNotificationSound) return [ SectionModel( @@ -63,7 +64,10 @@ class NotificationSoundViewModel: SettingsTableViewModel - -#import -#import -#import -#import -#import - -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 diff --git a/Session/Settings/PrivacySettingsTableViewController.swift b/Session/Settings/PrivacySettingsTableViewController.swift deleted file mode 100644 index ff07d8018..000000000 --- a/Session/Settings/PrivacySettingsTableViewController.swift +++ /dev/null @@ -1,6 +0,0 @@ -extension PrivacySettingsTableViewController { - - @objc func objc_requestMicrophonePermissionIfNeeded() { - requestMicrophonePermissionIfNeeded { } - } -} diff --git a/Session/Settings/PrivacySettingsViewModel.swift b/Session/Settings/PrivacySettingsViewModel.swift new file mode 100644 index 000000000..f0bfeac76 --- /dev/null +++ b/Session/Settings/PrivacySettingsViewModel.swift @@ -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 { + // 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() {} +} diff --git a/Session/Settings/QRCodeVC.swift b/Session/Settings/QRCodeVC.swift index c690fa25f..0561a1910 100644 --- a/Session/Settings/QRCodeVC.swift +++ b/Session/Settings/QRCodeVC.swift @@ -162,39 +162,71 @@ private final class ViewMyQRCodeVC : UIViewController { override func viewDidLoad() { // Remove background color - view.backgroundColor = .clear + view.themeBackgroundColor = .clear + // Set up title label let titleLabel = UILabel() - titleLabel.textColor = Colors.text titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize) titleLabel.text = "Scan Me" - titleLabel.numberOfLines = 1 + titleLabel.themeTextColor = .textPrimary titleLabel.textAlignment = .center titleLabel.lineBreakMode = .byWordWrapping + titleLabel.numberOfLines = 1 titleLabel.set(.height, to: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize) + // Set up QR code image view - let qrCodeImageView = UIImageView() - let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true) - qrCodeImageView.image = qrCode - qrCodeImageView.contentMode = .scaleAspectFit + let qrCodeImageView = UIImageView( + image: QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: false) + .withRenderingMode(.alwaysTemplate) + ) qrCodeImageView.set(.height, to: isIPhone5OrSmaller ? 180 : 240) qrCodeImageView.set(.width, to: isIPhone5OrSmaller ? 180 : 240) + +#if targetEnvironment(simulator) +#else + // Note: For some reason setting this seems to stop the QRCode from rendering on the + // simulator so only doing it on device + qrCodeImageView.contentMode = .scaleAspectFit +#endif + + let qrCodeImageViewBackgroundView = UIView() + qrCodeImageViewBackgroundView.layer.cornerRadius = 8 + qrCodeImageViewBackgroundView.addSubview(qrCodeImageView) + qrCodeImageView.pin( + to: qrCodeImageViewBackgroundView, + withInset: 5 // The QRCode image has about 6pt of padding and we want 11 in total + ) + + ThemeManager.onThemeChange(observer: qrCodeImageView) { theme, _ in + switch theme.interfaceStyle { + case .light: + qrCodeImageView.tintColor = theme.colors[.textPrimary] + qrCodeImageViewBackgroundView.backgroundColor = nil + + default: + qrCodeImageView.tintColor = theme.colors[.backgroundPrimary] + qrCodeImageViewBackgroundView.backgroundColor = .white + } + + } + // Set up QR code image view container let qrCodeImageViewContainer = UIView() qrCodeImageViewContainer.accessibilityLabel = "Your QR code" qrCodeImageViewContainer.isAccessibilityElement = true - qrCodeImageViewContainer.addSubview(qrCodeImageView) - qrCodeImageView.center(.horizontal, in: qrCodeImageViewContainer) - qrCodeImageView.pin(.top, to: .top, of: qrCodeImageViewContainer) - qrCodeImageView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer) + qrCodeImageViewContainer.addSubview(qrCodeImageViewBackgroundView) + qrCodeImageViewBackgroundView.center(.horizontal, in: qrCodeImageViewContainer) + qrCodeImageViewBackgroundView.pin(.top, to: .top, of: qrCodeImageViewContainer) + qrCodeImageViewBackgroundView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer) + // Set up explanation label let explanationLabel = UILabel() - explanationLabel.textColor = Colors.text explanationLabel.font = .systemFont(ofSize: Values.mediumFontSize) - explanationLabel.text = NSLocalizedString("vc_view_my_qr_code_explanation", comment: "") - explanationLabel.numberOfLines = 0 + explanationLabel.text = "vc_view_my_qr_code_explanation".localized() + explanationLabel.themeTextColor = .textPrimary explanationLabel.textAlignment = .center explanationLabel.lineBreakMode = .byWordWrapping + explanationLabel.numberOfLines = 0 // Set up share button let shareButton = OutlineButton(style: .regular, size: .large) @@ -222,16 +254,19 @@ private final class ViewMyQRCodeVC : UIViewController { stackView.pin(.top, to: .top, of: view) view.pin(.trailing, to: .trailing, of: stackView) bottomConstraint = view.pin(.bottom, to: .bottom, of: stackView) + // Set up width constraint view.set(.width, to: UIScreen.main.bounds.width) } - // MARK: General + // MARK: - General + func constrainHeight(to height: CGFloat) { view.set(.height, to: height) } - // MARK: Interaction + // MARK: - Interaction + @objc private func shareQRCode() { let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true) let shareVC = UIActivityViewController(activityItems: [ qrCode ], applicationActivities: nil) @@ -250,26 +285,30 @@ private final class ScanQRCodePlaceholderVC : UIViewController { override func viewDidLoad() { // Remove background color - view.backgroundColor = .clear + view.themeBackgroundColor = .clear + // Set up explanation label let explanationLabel = UILabel() - explanationLabel.textColor = Colors.text explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) - explanationLabel.text = NSLocalizedString("vc_scan_qr_code_camera_access_explanation", comment: "") - explanationLabel.numberOfLines = 0 + explanationLabel.text = "vc_scan_qr_code_camera_access_explanation".localized() + explanationLabel.themeTextColor = .textPrimary explanationLabel.textAlignment = .center explanationLabel.lineBreakMode = .byWordWrapping + explanationLabel.numberOfLines = 0 + // Set up call to action button let callToActionButton = UIButton() - callToActionButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize) - callToActionButton.setTitleColor(Colors.accent, for: UIControl.State.normal) - callToActionButton.setTitle(NSLocalizedString("vc_scan_qr_code_grant_camera_access_button_title", comment: ""), for: UIControl.State.normal) + callToActionButton.titleLabel?.font = .boldSystemFont(ofSize: Values.mediumFontSize) + callToActionButton.setTitle("vc_scan_qr_code_grant_camera_access_button_title".localized(), for: UIControl.State.normal) + callToActionButton.setThemeTitleColor(.primary, for: .normal) callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside) + // Set up stack view let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ]) stackView.axis = .vertical stackView.spacing = Values.mediumSpacing stackView.alignment = .center + // Set up constraints view.set(.width, to: UIScreen.main.bounds.width) view.addSubview(stackView) diff --git a/Session/Settings/SettingsTableViewController.swift b/Session/Settings/SettingsTableViewController.swift index f15c74941..e2d696d16 100644 --- a/Session/Settings/SettingsTableViewController.swift +++ b/Session/Settings/SettingsTableViewController.swift @@ -11,6 +11,7 @@ class SettingsTableViewController.SectionModel private let viewModel: SettingsTableViewModel + private let shouldShowCloseButton: Bool private var dataChangeObservable: DatabaseCancellable? private var hasLoadedInitialSettingsData: Bool = false @@ -37,8 +38,9 @@ class SettingsTableViewController) { + init(viewModel: SettingsTableViewModel, shouldShowCloseButton: Bool = false) { self.viewModel = viewModel + self.shouldShowCloseButton = shouldShowCloseButton super.init(nibName: nil, bundle: nil) } @@ -163,7 +165,16 @@ class SettingsTableViewController Void)? ) - case settingBool(key: Setting.BoolKey) + case settingBool( + key: Setting.BoolKey, + confirmationInfo: ConfirmationModal.Info? + ) case settingEnum( key: String, title: String?, @@ -152,6 +155,10 @@ public enum SettingsAction: Hashable, Equatable { createUpdateScreen: createUpdateScreen ) } + + public static func settingBool(key: Setting.BoolKey) -> SettingsAction { + return .settingBool(key: key, confirmationInfo: nil) + } // MARK: - Conformance @@ -160,7 +167,10 @@ public enum SettingsAction: Hashable, Equatable { switch self { case .userDefaultsBool(_, let key, _): key.hash(into: &hasher) - case .settingBool(let key): key.hash(into: &hasher) + case .settingBool(let key, let confirmationInfo): + key.hash(into: &hasher) + confirmationInfo.hash(into: &hasher) + case .settingEnum(let key, let title, _): key.hash(into: &hasher) title.hash(into: &hasher) @@ -179,7 +189,11 @@ public enum SettingsAction: Hashable, Equatable { case (.userDefaultsBool(_, let lhsKey, _), .userDefaultsBool(_, let rhsKey, _)): return (lhsKey == rhsKey) - case (.settingBool(let lhsKey), .settingBool(let rhsKey)): return (lhsKey == rhsKey) + case (.settingBool(let lhsKey, let lhsConfirmationInfo), .settingBool(let rhsKey, let rhsConfirmationInfo)): + return ( + lhsKey == rhsKey && + lhsConfirmationInfo == rhsConfirmationInfo + ) case (.settingEnum(let lhsKey, let lhsTitle, _), .settingEnum(let rhsKey, let rhsTitle, _)): return ( diff --git a/Session/Settings/Views/SettingsCell.swift b/Session/Settings/Views/SettingsCell.swift index 110136b99..f97ff0cae 100644 --- a/Session/Settings/Views/SettingsCell.swift +++ b/Session/Settings/Views/SettingsCell.swift @@ -322,7 +322,7 @@ class SettingsCell: UITableViewCell { toggleSwitch.setOn(newValue, animated: true) } - case .settingBool(let key): + case .settingBool(let key, _): actionContainerView.isHidden = false toggleSwitch.isHidden = false @@ -373,19 +373,23 @@ class SettingsCell: UITableViewCell { override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) - // Note: Only setting the highlighted state is done here, the unhighlight is done - // in 'setSelected' - guard highlighted else { return } - - rightActionButtonContainerView.themeBackgroundColor = .solidButton_highlight + rightActionButtonContainerView.themeBackgroundColor = (highlighted ? + .solidButton_highlight : + .solidButton_background + ) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - // Note: Only un-setting the unhighlighted state is done here, the highlighted state is done - // in 'setHighlighted' - guard !selected else { return } + // Note: When initially triggering a selection we will be coming from the highlighted + // state but will have already set highlighted to false at this stage, as a result we + // need to swap back into the "highlighted" state so we can properly unhighlight within + // the "deselect" animation + guard !selected else { + rightActionButtonContainerView.themeBackgroundColor = .solidButton_highlight + return + } guard animated else { rightActionButtonContainerView.themeBackgroundColor = .solidButton_background return diff --git a/Session/Shared/BaseVC.swift b/Session/Shared/BaseVC.swift index 25cf73161..175e94407 100644 --- a/Session/Shared/BaseVC.swift +++ b/Session/Shared/BaseVC.swift @@ -3,8 +3,10 @@ import UIKit import SessionUIKit -class BaseVC: UIViewController { - override var preferredStatusBarStyle: UIStatusBarStyle { return ThemeManager.currentTheme.statusBarStyle } +public class BaseVC: UIViewController { + public override var preferredStatusBarStyle: UIStatusBarStyle { + return ThemeManager.currentTheme.statusBarStyle + } lazy var navBarTitleLabel: UILabel = { let result = UILabel() @@ -26,7 +28,7 @@ class BaseVC: UIViewController { return result }() - override func viewDidLoad() { + public override func viewDidLoad() { super.viewDidLoad() navigationItem.backButtonTitle = "" diff --git a/Session/Shared/OWSScreenLockUI.h b/Session/Shared/OWSScreenLockUI.h deleted file mode 100644 index d8fad7c96..000000000 --- a/Session/Shared/OWSScreenLockUI.h +++ /dev/null @@ -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 diff --git a/Session/Shared/OWSScreenLockUI.m b/Session/Shared/OWSScreenLockUI.m deleted file mode 100644 index 59d13d917..000000000 --- a/Session/Shared/OWSScreenLockUI.m +++ /dev/null @@ -1,511 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSScreenLockUI.h" -#import "OWSWindowManager.h" -#import "Session-Swift.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSScreenLockUI () - -@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 diff --git a/Session/Shared/ScreenLockUI.swift b/Session/Shared/ScreenLockUI.swift new file mode 100644 index 000000000..be33931e5 --- /dev/null +++ b/Session/Shared/ScreenLockUI.swift @@ -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() + } +} diff --git a/Session/Sheets & Modals/ConfirmationModal.swift b/Session/Sheets & Modals/ConfirmationModal.swift new file mode 100644 index 000000000..2b4177718 --- /dev/null +++ b/Session/Sheets & Modals/ConfirmationModal.swift @@ -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) + } +} diff --git a/Session/Sheets & Modals/Modal.swift b/Session/Sheets & Modals/Modal.swift index 9bd7c165f..1f56e722f 100644 --- a/Session/Sheets & Modals/Modal.swift +++ b/Session/Sheets & Modals/Modal.swift @@ -3,66 +3,92 @@ import UIKit import SessionUIKit -@objc(LKModal) -class Modal: BaseVC, UIGestureRecognizerDelegate { +public class Modal: BaseVC, UIGestureRecognizerDelegate { + private static let cornerRadius: CGFloat = 11 - // MARK: Components - lazy var contentView: UIView = { - let result = UIView() - result.backgroundColor = Colors.modalBackground + // MARK: - Components + + lazy var dimmingView: UIView = { + let result = UIVisualEffectView() + + ThemeManager.onThemeChange(observer: result) { [weak result] theme, _ in + result?.effect = UIBlurEffect( + style: (theme.interfaceStyle == .light ? + UIBlurEffect.Style.systemUltraThinMaterialLight : + UIBlurEffect.Style.systemUltraThinMaterial + ) + ) + } + + return result + }() + + lazy var containerView: UIView = { + let result: UIView = UIView() + result.clipsToBounds = false + result.themeBackgroundColor = .alert_background + result.themeShadowColor = .black result.layer.cornerRadius = Modal.cornerRadius - result.layer.masksToBounds = false - result.layer.borderColor = isLightMode ? UIColor.white.cgColor : Colors.modalBorder.cgColor - result.layer.borderWidth = 1 - result.layer.shadowColor = UIColor.black.cgColor - result.layer.shadowRadius = isLightMode ? 2 : 8 - result.layer.shadowOpacity = isLightMode ? 0.1 : 0.64 + result.layer.shadowRadius = 10 + result.layer.shadowOpacity = 0.4 + + return result + }() + + lazy var contentView: UIView = { + let result: UIView = UIView() + result.clipsToBounds = true + result.layer.cornerRadius = Modal.cornerRadius + return result }() lazy var cancelButton: UIButton = { - let result = UIButton() - result.set(.height, to: Values.mediumButtonHeight) - result.layer.cornerRadius = Modal.buttonCornerRadius - result.backgroundColor = Colors.buttonBackground - result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) - result.setTitleColor(Colors.text, for: UIControl.State.normal) - result.setTitle(NSLocalizedString("cancel", comment: ""), for: UIControl.State.normal) + let result: UIButton = Modal.createButton(title: "cancel".localized(), titleColor: .textPrimary) + result.addTarget(self, action: #selector(close), for: .touchUpInside) + return result }() - // MARK: Settings - private static let cornerRadius: CGFloat = 10 - static let buttonCornerRadius = CGFloat(5) + // MARK: - Lifecycle - // MARK: Lifecycle - override func viewDidLoad() { + public override func viewDidLoad() { super.viewDidLoad() - let alpha = isLightMode ? CGFloat(0.1) : Values.highOpacity - view.backgroundColor = UIColor(hex: 0x000000).withAlphaComponent(alpha) - cancelButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Need to remove the background color which is added by the BaseVC + view.themeBackgroundColor = .clear + + view.addSubview(dimmingView) + view.addSubview(containerView) + + containerView.addSubview(contentView) + + dimmingView.pin(to: view) + contentView.pin(to: containerView) + + if UIDevice.current.isIPad { + containerView.set(.width, to: Values.iPadModalWidth) + containerView.center(in: view) + } + else { + containerView.leadingAnchor + .constraint(equalTo: view.leadingAnchor, constant: Values.veryLargeSpacing) + .isActive = true + view.trailingAnchor + .constraint(equalTo: containerView.trailingAnchor, constant: Values.veryLargeSpacing) + .isActive = true + containerView.center(.vertical, in: view) + } + + // Gestures let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(close)) swipeGestureRecognizer.direction = .down - view.addGestureRecognizer(swipeGestureRecognizer) + dimmingView.addGestureRecognizer(swipeGestureRecognizer) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(close)) tapGestureRecognizer.delegate = self - view.addGestureRecognizer(tapGestureRecognizer) + dimmingView.addGestureRecognizer(tapGestureRecognizer) - setUpViewHierarchy() - } - - private func setUpViewHierarchy() { - view.addSubview(contentView) - if UIDevice.current.isIPad { - contentView.set(.width, to: Values.iPadModalWidth) - contentView.center(in: view) - } else { - contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Values.veryLargeSpacing).isActive = true - view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: Values.veryLargeSpacing).isActive = true - contentView.center(.vertical, in: view) - } populateContentView() } @@ -71,6 +97,18 @@ class Modal: BaseVC, UIGestureRecognizerDelegate { preconditionFailure("populateContentView() is abstract and must be overridden.") } + static func createButton(title: String, titleColor: ThemeValue) -> UIButton { + let result: UIButton = UIButton() + result.titleLabel?.font = .systemFont(ofSize: Values.mediumFontSize, weight: UIFont.Weight(600)) + result.setTitle(title, for: .normal) + result.setThemeTitleColor(titleColor, for: .normal) + result.setThemeBackgroundColor(.alert_buttonBackground, for: .normal) + result.setThemeBackgroundColor(.alert_buttonHighlight, for: .highlighted) + result.set(.height, to: Values.alertButtonHeight) + + return result + } + // MARK: - Interaction @objc func close() { @@ -79,7 +117,7 @@ class Modal: BaseVC, UIGestureRecognizerDelegate { // MARK: - UIGestureRecognizerDelegate - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { let location: CGPoint = touch.location(in: contentView) return !contentView.point(inside: location, with: nil) diff --git a/Session/Utilities/Permissions.swift b/Session/Utilities/Permissions.swift index 66cd628b3..62d6c9586 100644 --- a/Session/Utilities/Permissions.swift +++ b/Session/Utilities/Permissions.swift @@ -1,39 +1,47 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit import Photos import PhotosUI +import SessionUtilitiesKit public func requestCameraPermissionIfNeeded() -> Bool { switch AVCaptureDevice.authorizationStatus(for: .video) { - case .authorized: return true - case .denied, .restricted: - let modal = PermissionMissingModal(permission: "camera") { } - modal.modalPresentationStyle = .overFullScreen - modal.modalTransitionStyle = .crossDissolve - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } - presentingVC.present(modal, animated: true, completion: nil) - return false - case .notDetermined: - AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in }) - return false - default: return false + case .authorized: return true + case .denied, .restricted: + let modal = PermissionMissingModal(permission: "camera") { } + modal.modalPresentationStyle = .overFullScreen + modal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } + presentingVC.present(modal, animated: true, completion: nil) + return false + + case .notDetermined: + AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in }) + return false + + default: return false } } -public func requestMicrophonePermissionIfNeeded(onNotGranted: @escaping () -> Void) { +public func requestMicrophonePermissionIfNeeded(onNotGranted: (() -> Void)? = nil) { switch AVAudioSession.sharedInstance().recordPermission { - case .granted: break - case .denied: - onNotGranted() - let modal = PermissionMissingModal(permission: "microphone") { - onNotGranted() - } - modal.modalPresentationStyle = .overFullScreen - modal.modalTransitionStyle = .crossDissolve - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } - presentingVC.present(modal, animated: true, completion: nil) - case .undetermined: - onNotGranted() - AVAudioSession.sharedInstance().requestRecordPermission { _ in } - default: break + case .granted: break + case .denied: + onNotGranted?() + let modal = PermissionMissingModal(permission: "microphone") { + onNotGranted?() + } + modal.modalPresentationStyle = .overFullScreen + modal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } + presentingVC.present(modal, animated: true, completion: nil) + + case .undetermined: + onNotGranted?() + AVAudioSession.sharedInstance().requestRecordPermission { _ in } + + default: break } } @@ -66,7 +74,8 @@ public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void) } } } - } else { + } + else { authorizationStatus = PHPhotoLibrary.authorizationStatus() if authorizationStatus == .notDetermined { PHPhotoLibrary.requestAuthorization { status in @@ -76,15 +85,16 @@ public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void) } } } + switch authorizationStatus { - case .authorized, .limited: - onAuthorized() - case .denied, .restricted: - let modal = PermissionMissingModal(permission: "library") { } - modal.modalPresentationStyle = .overFullScreen - modal.modalTransitionStyle = .crossDissolve - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } - presentingVC.present(modal, animated: true, completion: nil) - default: return + case .authorized, .limited: onAuthorized() + case .denied, .restricted: + let modal = PermissionMissingModal(permission: "library") { } + modal.modalPresentationStyle = .overFullScreen + modal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } + presentingVC.present(modal, animated: true, completion: nil) + + default: return } } diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index e1c8d8b4e..aca09b8bd 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -1438,7 +1438,7 @@ enum _003_YDBToGRDBMigration: Migration { .defaulting(to: Preferences.Sound.defaultNotificationSound) db[.playNotificationSoundInForeground] = (legacyPreferences[SMKLegacy.preferencesKeyNotificationSoundInForeground] as? Bool == true) db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType(rawValue: legacyPreferences[SMKLegacy.preferencesKeyNotificationPreviewType] as? Int ?? -1) - .defaulting(to: .nameAndPreview) + .defaulting(to: .defaultPreviewType) if let lastPushToken: String = legacyPreferences[SMKLegacy.preferencesKeyLastRecordedPushToken] as? String { db[.lastRecordedPushToken] = lastPushToken @@ -1454,8 +1454,13 @@ enum _003_YDBToGRDBMigration: Migration { db[.areReadReceiptsEnabled] = (legacyPreferences[SMKLegacy.readReceiptManagerAreReadReceiptsEnabled] as? Bool == true) db[.typingIndicatorsEnabled] = (legacyPreferences[SMKLegacy.typingIndicatorsEnabledKey] as? Bool == true) db[.isScreenLockEnabled] = (legacyPreferences[SMKLegacy.screenLockIsScreenLockEnabledKey] as? Bool == true) - db[.screenLockTimeoutSeconds] = (legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] as? Double) - .defaulting(to: (15 * 60)) + // Note: 'screenLockTimeoutSeconds' has been removed, but we want to avoid changing the behaviour + // of old migrations when possible + db.unsafeSet( + key: "screenLockTimeoutSeconds", + value: (legacyPreferences[SMKLegacy.screenLockScreenLockTimeoutSecondsKey] as? Double) + .defaulting(to: (15 * 60)) + ) db[.appSwitcherPreviewEnabled] = (legacyPreferences[SMKLegacy.preferencesKeyScreenSecurityDisabled] as? Bool == false) db[.areLinkPreviewsEnabled] = (legacyPreferences[SMKLegacy.preferencesKeyAreLinkPreviewsEnabled] as? Bool == true) db[.areCallsEnabled] = (legacyPreferences[SMKLegacy.preferencesKeyAreCallsEnabled] as? Bool == true) diff --git a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift index d8ceb8bba..5c6ffe962 100644 --- a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift @@ -5,7 +5,6 @@ import Foundation public extension Notification.Name { static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived") - static let incomingMessageMarkedAsRead = Notification.Name("incomingMessageMarkedAsRead") static let missedCall = Notification.Name("missedCall") } @@ -16,5 +15,4 @@ public extension Notification.Key { @objc public extension NSNotification { @objc static let initialConfigurationMessageReceived = Notification.Name.initialConfigurationMessageReceived.rawValue as NSString - @objc static let incomingMessageMarkedAsRead = Notification.Name.incomingMessageMarkedAsRead.rawValue as NSString } diff --git a/SessionMessagingKit/Utilities/Preferences.swift b/SessionMessagingKit/Utilities/Preferences.swift index 1f9385a6c..82745b74d 100644 --- a/SessionMessagingKit/Utilities/Preferences.swift +++ b/SessionMessagingKit/Utilities/Preferences.swift @@ -62,6 +62,10 @@ public extension Setting.BoolKey { /// A flag indicating whether the app is ready for app extensions to run static let isReadyForAppExtensions: Setting.BoolKey = "isReadyForAppExtensions" + + /// Controls whether the device should show screenshot notifications in one-to-one conversations (will always + /// send screenshot notifications, this just controls whether they get filtered out or not) + static let showScreenshotNotifications: Setting.BoolKey = "showScreenshotNotifications" } public extension Setting.StringKey { @@ -74,11 +78,14 @@ public extension Setting.StringKey { public extension Setting.DoubleKey { /// The duration of the timeout for screen lock in seconds + @available(*, unavailable, message: "Screen Lock should always be instant now") static let screenLockTimeoutSeconds: Setting.DoubleKey = "screenLockTimeoutSeconds" } public enum Preferences { public enum NotificationPreviewType: Int, CaseIterable, EnumIntSetting, Differentiable { + public static var defaultPreviewType: NotificationPreviewType = .nameAndPreview + /// Notifications should include both the sender name and a preview of the message content case nameAndPreview @@ -303,61 +310,7 @@ public enum Preferences { // MARK: - Objective C Support -// FIXME: Remove the below the 'NotificationSettingsViewController' and 'OWSSoundSettingsViewController' have been refactored to Swift - -@objc(SMKPreferences) -public class SMKPreferences: NSObject { - @objc(setScreenSecurity:) - static func objc_setScreenSecurity(_ enabled: Bool) { - Storage.shared.write { db in db[.appSwitcherPreviewEnabled] = enabled } - } - - @objc(isScreenSecurityEnabled) - static func objc_isScreenSecurityEnabled() -> Bool { - return Storage.shared[.appSwitcherPreviewEnabled] - } - - @objc(setAreReadReceiptsEnabled:) - static func objc_setAreReadReceiptsEnabled(_ enabled: Bool) { - Storage.shared.write { db in db[.areReadReceiptsEnabled] = enabled } - } - - @objc(areReadReceiptsEnabled) - static func objc_areReadReceiptsEnabled() -> Bool { - return Storage.shared[.areReadReceiptsEnabled] - } - - @objc(setTypingIndicatorsEnabled:) - static func objc_setTypingIndicatorsEnabled(_ enabled: Bool) { - Storage.shared.write { db in db[.typingIndicatorsEnabled] = enabled } - } - - @objc(areTypingIndicatorsEnabled) - static func objc_areTypingIndicatorsEnabled() -> Bool { - return Storage.shared[.typingIndicatorsEnabled] - } - - @objc(setLinkPreviewsEnabled:) - static func objc_setLinkPreviewsEnabled(_ enabled: Bool) { - Storage.shared.write { db in db[.areLinkPreviewsEnabled] = enabled } - } - - @objc(areLinkPreviewsEnabled) - static func objc_areLinkPreviewsEnabled() -> Bool { - return Storage.shared[.areLinkPreviewsEnabled] - } - - @objc(setCallsEnabled:) - static func objc_setCallsEnabled(_ enabled: Bool) { - Storage.shared.write { db in db[.areCallsEnabled] = enabled } - } - - @objc(areCallsEnabled) - static func objc_areCallsEnabled() -> Bool { - return Storage.shared[.areCallsEnabled] - } -} - +// FIXME: Remove the below when OWSConversationSettingsViewController no longer nees SMKSound @objc(SMKSound) public class SMKSound: NSObject { @objc public static var notificationSounds: [Int] = Preferences.Sound.notificationSounds.map { $0.rawValue } diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 04503ed5f..991c989df 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -65,7 +65,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { // Title & body let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] - .defaulting(to: .nameAndPreview) + .defaulting(to: .defaultPreviewType) switch previewType { case .nameAndPreview: diff --git a/SessionShareExtension/SAEScreenLockViewController.swift b/SessionShareExtension/SAEScreenLockViewController.swift index dd8bd11d4..b196ef1a4 100644 --- a/SessionShareExtension/SAEScreenLockViewController.swift +++ b/SessionShareExtension/SAEScreenLockViewController.swift @@ -7,7 +7,7 @@ import SignalUtilitiesKit import SessionUIKit import SessionUtilitiesKit -final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockViewDelegate { +final class SAEScreenLockViewController: ScreenLockViewController { private var hasShownAuthUIOnce: Bool = false private var isShowingAuthUI: Bool = false @@ -16,10 +16,10 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie // MARK: - Initialization init(shareViewDelegate: ShareViewDelegate) { - super.init(nibName: nil, bundle: nil) + super.init() + self.onUnlockPressed = { [weak self] in self?.unlockButtonWasTapped() } self.shareViewDelegate = shareViewDelegate - self.delegate = self } required init?(coder aDecoder: NSCoder) { @@ -32,57 +32,52 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie // MARK: - UI - private lazy var gradientBackground: CAGradientLayer = { - let layer: CAGradientLayer = CAGradientLayer() - - let gradientStartColor: UIColor = (LKAppModeUtilities.isLightMode ? - UIColor(rgbHex: 0xFCFCFC) : - UIColor(rgbHex: 0x171717) - ) - let gradientEndColor: UIColor = (LKAppModeUtilities.isLightMode ? - UIColor(rgbHex: 0xFFFFFF) : - UIColor(rgbHex: 0x121212) - ) - layer.colors = [gradientStartColor.cgColor, gradientEndColor.cgColor] - - return layer - }() - private lazy var titleLabel: UILabel = { let titleLabel: UILabel = UILabel() titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) titleLabel.text = "vc_share_title".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary return titleLabel }() private lazy var closeButton: UIBarButtonItem = { let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(dismissPressed)) - closeButton.tintColor = Colors.text + closeButton.themeTintColor = .textPrimary return closeButton }() // MARK: - Lifecycle - override func loadView() { + public override func loadView() { super.loadView() - UIView.appearance().tintColor = Colors.text - - self.view.backgroundColor = UIColor.clear - self.view.layer.insertSublayer(gradientBackground, at: 0) - - self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) - self.navigationController?.navigationBar.shadowImage = UIImage() - self.navigationController?.navigationBar.isTranslucent = false - self.navigationController?.navigationBar.tintColor = Colors.navigationBarBackground + UIView.appearance().themeTintColor = .textPrimary + self.view.themeBackgroundColor = .backgroundPrimary self.navigationItem.titleView = titleLabel self.navigationItem.leftBarButtonItem = closeButton - setupLayout() + ThemeManager.onThemeChange(observer: self.unlockButton) { [weak self] theme, _ in + switch theme.interfaceStyle { + case .light: + self?.unlockButton.setTitleColor(theme.colors[.textPrimary], for: .normal) + self?.unlockButton.setBackgroundImage( + theme.colors[.textPrimary]?.withAlphaComponent(0.3).toImage(), + for: .highlighted + ) + self?.unlockButton.layer.borderColor = theme.colors[.textPrimary]?.cgColor + + default: + self?.unlockButton.setTitleColor(Theme.PrimaryColor.green.color, for: .normal) + self?.unlockButton.setBackgroundImage( + Theme.PrimaryColor.green.color.withAlphaComponent(0.3).toImage(), + for: .highlighted + ) + self?.unlockButton.layer.borderColor = Theme.PrimaryColor.green.color.cgColor + } + } } override func viewWillAppear(_ animated: Bool) { @@ -104,12 +99,6 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie } } - // MARK: - Layout - - private func setupLayout() { - gradientBackground.frame = UIScreen.main.bounds - } - // MARK: - Functions private func tryToPresentAuthUIToUnlockScreenLock() { @@ -122,7 +111,7 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie isShowingAuthUI = true - OWSScreenLock.shared.tryToUnlockScreenLock( + ScreenLock.shared.tryToUnlockScreenLock( success: { [weak self] in AssertIsOnMainThread() OWSLogger.info("unlock screen lock succeeded.") @@ -164,7 +153,7 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie } private func ensureUI() { - self.updateUI(with: .screenLock, isLogoAtTop: false, animated: false) + self.updateUI(state: .lock, animated: false) } private func showScreenLockFailureAlert(message: String) { @@ -183,6 +172,13 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie ) } + func unlockButtonWasTapped() { + AssertIsOnMainThread() + OWSLogger.info("unlockButtonWasTapped") + + self.tryToPresentAuthUIToUnlockScreenLock() + } + // MARK: - Transitions @objc private func dismissPressed() { @@ -194,13 +190,4 @@ final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockVie private func cancelShareExperience() { self.shareViewDelegate?.shareViewWasCancelled() } - - // MARK: - ScreenLockViewDelegate - - func unlockButtonWasTapped() { - AssertIsOnMainThread() - OWSLogger.info("unlockButtonWasTapped") - - self.tryToPresentAuthUIToUnlockScreenLock() - } } diff --git a/SessionShareExtension/ShareVC.swift b/SessionShareExtension/ShareVC.swift index 006c162e7..eb98062cb 100644 --- a/SessionShareExtension/ShareVC.swift +++ b/SessionShareExtension/ShareVC.swift @@ -24,14 +24,17 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe override func loadView() { super.loadView() - // This should be the first thing we do. - let appContext = ShareAppExtensionContext(rootViewController: self) - SetCurrentAppContext(appContext) - - AppModeManager.configure(delegate: self) - // Need to manually trigger these since we don't have a "mainWindow" here - ThemeManager.applyNavigationStyling() - ThemeManager.applyWindowStyling() + // This should be the first thing we do (Note: If you leave the share context and return to it + // the context will already exist, trying to override it results in the share context crashing + // so ensure it doesn't exist first) + if !HasAppContext() { + let appContext = ShareAppExtensionContext(rootViewController: self) + SetCurrentAppContext(appContext) + } + + // Need to manually trigger these since we don't have a "mainWindow" here and the current theme + // might have been changed since the share extension was last opened + ThemeManager.applySavedTheme() Logger.info("") @@ -144,7 +147,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe Logger.info("") - if OWSScreenLock.shared.isScreenLockEnabled() { + if Storage.shared[.isScreenLockEnabled] { self.dismiss(animated: false) { [weak self] in AssertIsOnMainThread() self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) @@ -173,7 +176,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate, AppModeManagerDe // MARK: Updating private func showLockScreenOrMainContent() { - if OWSScreenLock.shared.isScreenLockEnabled() { + if Storage.shared[.isScreenLockEnabled] { showLockScreen() } else { diff --git a/SessionUIKit/Components/OutlineButton.swift b/SessionUIKit/Components/OutlineButton.swift index 074a3aa40..6c5d155d2 100644 --- a/SessionUIKit/Components/OutlineButton.swift +++ b/SessionUIKit/Components/OutlineButton.swift @@ -32,6 +32,12 @@ public final class OutlineButton: UIButton { private func setUpStyle(style: Style, size: Size) { clipsToBounds = true + contentEdgeInsets = UIEdgeInsets( + top: 0, + left: Values.smallSpacing, + bottom: 0, + right: Values.smallSpacing + ) titleLabel?.font = .boldSystemFont(ofSize: (size == .small ? Values.smallFontSize : Values.mediumFontSize diff --git a/SessionUIKit/Components/RadioButton.swift b/SessionUIKit/Components/RadioButton.swift new file mode 100644 index 000000000..1030a178a --- /dev/null +++ b/SessionUIKit/Components/RadioButton.swift @@ -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) + } +} diff --git a/SessionUIKit/Components/TabBar.swift b/SessionUIKit/Components/TabBar.swift index 612604a7c..2c7bb1bcd 100644 --- a/SessionUIKit/Components/TabBar.swift +++ b/SessionUIKit/Components/TabBar.swift @@ -1,28 +1,34 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + import UIKit -public final class TabBar : UIView { +public final class TabBar: UIView { private let tabs: [Tab] private var accentLineViewHorizontalCenteringConstraint: NSLayoutConstraint! private var accentLineViewWidthConstraint: NSLayoutConstraint! - // MARK: Components + // MARK: - Components + private lazy var tabLabels: [UILabel] = tabs.map { tab in let result = UILabel() result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) - result.textAlignment = .center result.text = tab.title + result.themeTextColor = .textPrimary + result.textAlignment = .center + result.alpha = Values.mediumOpacity result.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness) + return result } private lazy var accentLineView: UIView = { let result = UIView() - result.backgroundColor = Colors.accent + result.themeBackgroundColor = .primary return result }() - // MARK: Types + // MARK: - Types + public struct Tab { let title: String let onTap: () -> Void @@ -33,10 +39,12 @@ public final class TabBar : UIView { } } - // MARK: Settings + // MARK: - Settings + public static let snHeight = isIPhone5OrSmaller ? CGFloat(32) : CGFloat(48) - // MARK: Lifecycle + // MARK: - Lifecycle + public init(tabs: [Tab]) { self.tabs = tabs super.init(frame: CGRect.zero) @@ -53,37 +61,48 @@ public final class TabBar : UIView { private func setUpViewHierarchy() { set(.height, to: TabBar.snHeight) + tabLabels.forEach { tabLabel in let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:))) tabLabel.addGestureRecognizer(tapGestureRecognizer) } + let tabLabelStackView = UIStackView(arrangedSubviews: tabLabels) tabLabelStackView.axis = .horizontal tabLabelStackView.distribution = .fillEqually tabLabelStackView.spacing = Values.mediumSpacing + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:))) tabLabelStackView.addGestureRecognizer(tapGestureRecognizer) tabLabelStackView.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness) addSubview(tabLabelStackView) + let separator = UIView() - separator.backgroundColor = Colors.separator + separator.themeBackgroundColor = .borderSeparator separator.set(.height, to: Values.separatorThickness) addSubview(separator) + accentLineView.set(.height, to: Values.accentLineThickness) addSubview(accentLineView) + tabLabelStackView.pin(.leading, to: .leading, of: self) tabLabelStackView.pin(.top, to: .top, of: self) + pin(.trailing, to: .trailing, of: tabLabelStackView) separator.pin(.leading, to: .leading, of: self) separator.pin(.top, to: .bottom, of: tabLabelStackView) + pin(.trailing, to: .trailing, of: separator) accentLineView.translatesAutoresizingMaskIntoConstraints = false + selectTab(at: 0, withAnimatedTransition: false) + accentLineView.pin(.top, to: .bottom, of: separator) pin(.bottom, to: .bottom, of: accentLineView) } - // MARK: Updating + // MARK: - Updating + public func selectTab(at index: Int, withAnimatedTransition isAnimated: Bool = true) { let tabLabel = tabLabels[index] accentLineViewHorizontalCenteringConstraint?.isActive = false @@ -92,16 +111,20 @@ public final class TabBar : UIView { accentLineViewWidthConstraint?.isActive = false accentLineViewWidthConstraint = accentLineView.widthAnchor.constraint(equalTo: tabLabel.widthAnchor) accentLineViewWidthConstraint.isActive = true + var tabLabelsCopy = tabLabels tabLabelsCopy.remove(at: index) + UIView.animate(withDuration: isAnimated ? 0.25 : 0) { - tabLabel.textColor = Colors.text - tabLabelsCopy.forEach { $0.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) } + tabLabel.alpha = 1 + tabLabelsCopy.forEach { $0.alpha = Values.mediumOpacity } + self.layoutIfNeeded() } } - // MARK: Interaction + // MARK: - Interaction + @objc private func handleTabLabelTapped(_ sender: UITapGestureRecognizer) { guard let tabLabel = tabLabels.first(where: { $0.bounds.contains(sender.location(in: $0)) }), let index = tabLabels.firstIndex(of: tabLabel) else { return } selectTab(at: index) diff --git a/SessionUIKit/Style Guide/Gradients.swift b/SessionUIKit/Style Guide/Gradients.swift deleted file mode 100644 index 42994a427..000000000 --- a/SessionUIKit/Style Guide/Gradients.swift +++ /dev/null @@ -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)) - } - } -} diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index a710a51ab..c6d65086a 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -22,6 +22,8 @@ public extension Setting.BoolKey { // MARK: - ThemeManager public enum ThemeManager { + private static var hasSetInitialSystemTrait: Bool = false + /// **Note:** Using `weakToStrongObjects` means that the value types will continue to be maintained until the map table resizes /// itself (ie. until a new UI element is registered to the table) /// @@ -111,6 +113,11 @@ public enum ThemeManager { } } + public static func applySavedTheme() { + ThemeManager.primaryColor = Storage.shared[.themePrimaryColor].defaulting(to: Theme.PrimaryColor.green) + ThemeManager.currentTheme = Storage.shared[.theme].defaulting(to: Theme.classicDark) + } + public static func applyNavigationStyling() { let textPrimary: UIColor = (ThemeManager.currentTheme.colors[.textPrimary] ?? .white) @@ -224,6 +231,11 @@ public enum ThemeManager { applyNavigationStyling() applyWindowStyling() + + if !hasSetInitialSystemTrait { + traitCollectionDidChange(nil) + hasSetInitialSystemTrait = true + } } internal static func set( diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index b926b4089..35e8b2740 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -5,74 +5,87 @@ import UIKit.UIColor internal enum Theme_ClassicDark: ThemeColors { static let theme: [ThemeValue: UIColor] = [ // General + .white: .white, + .black: .black, + .clear: .clear, .primary: .primary, .defaultPrimary: Theme.PrimaryColor.green.color, - .danger: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), - .white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), - .backgroundPrimary: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .backgroundSecondary: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), - .backgroundTertiary: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), - .textPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .textSecondary: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1), - .borderSeparator: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), + .danger: .dangerDark, + .backgroundPrimary: .classicDark0, + .backgroundSecondary: .classicDark1, + .textPrimary: .classicDark6, + .textSecondary: .classicDark5, + .borderSeparator: .classicDark3, + + // Path + .path_connected: .pathConnected, + .path_connecting: .pathConnecting, + .path_error: .pathError, + .path_unknown: .classicDark4, // TextBox - .textBox_background: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .textBox_border: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), + .textBox_background: .classicDark1, + .textBox_border: .classicDark3, // MessageBubble .messageBubble_outgoingBackground: .primary, - .messageBubble_incomingBackground: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), - .messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .messageBubble_incomingText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .messageBubble_incomingBackground: .classicDark2, + .messageBubble_outgoingText: .classicDark0, + .messageBubble_incomingText: .classicDark6, // MenuButton .menuButton_background: .primary, - .menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .menuButton_icon: .classicDark6, .menuButton_outerShadow: .primary, - .menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .menuButton_innerShadow: .classicDark6, // RadioButton .radioButton_selectedBackground: .primary, .radioButton_unselectedBackground: .clear, - .radioButton_selectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .radioButton_unselectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .radioButton_selectedBorder: .classicDark6, + .radioButton_unselectedBorder: .classicDark6, // OutlineButton .outlineButton_text: .primary, .outlineButton_background: .clear, - .outlineButton_highlight: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3), + .outlineButton_highlight: .classicDark6.withAlphaComponent(0.3), .outlineButton_border: .primary, - .outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .outlineButton_filledBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), - .outlineButton_filledHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), - .outlineButton_destructiveText: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), + .outlineButton_filledText: .classicDark6, + .outlineButton_filledBackground: .classicDark1, + .outlineButton_filledHighlight: .classicDark3, + .outlineButton_destructiveText: .dangerDark, .outlineButton_destructiveBackground: .clear, - .outlineButton_destructiveHighlight: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 0.3), - .outlineButton_destructiveBorder: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), + .outlineButton_destructiveHighlight: .dangerDark.withAlphaComponent(0.3), + .outlineButton_destructiveBorder: .dangerDark, // SolidButton - .solidButton_background: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), - .solidButton_highlight: #colorLiteral(red: 0.3254901961, green: 0.3254901961, blue: 0.3254901961, alpha: 1), + .solidButton_background: .classicDark3, + .solidButton_highlight: .classicDark4, // Settings - .settings_tabBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), - .settings_tabHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), + .settings_tabBackground: .classicDark1, + .settings_tabHighlight: .classicDark3, // Appearance - .appearance_sectionBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), - .appearance_buttonBackground: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), - .appearance_buttonHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), + .appearance_sectionBackground: .classicDark1, + .appearance_buttonBackground: .classicDark1, + .appearance_buttonHighlight: .classicDark3, + + // Alert + .alert_background: .classicDark1, + .alert_buttonBackground: .classicDark1, + .alert_buttonHighlight: .classicDark3, // ConversationButton - .conversationButton_background: #colorLiteral(red: 0.1058823529, green: 0.1058823529, blue: 0.1058823529, alpha: 1), - .conversationButton_highlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), - .conversationButton_unreadBackground: #colorLiteral(red: 0.1764705882, green: 0.1764705882, blue: 0.1764705882, alpha: 1), - .conversationButton_unreadHighlight: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), + .conversationButton_background: .classicDark1, + .conversationButton_highlight: .classicDark3, + .conversationButton_unreadBackground: .classicDark2, + .conversationButton_unreadHighlight: .classicDark3, .conversationButton_unreadStripBackground: .primary, - .conversationButton_unreadBubbleBackground: #colorLiteral(red: 0.2549019608, green: 0.2549019608, blue: 0.2549019608, alpha: 1), - .conversationButton_unreadBubbleText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .conversationButton_pinBackground: Theme.PrimaryColor.yellow.color + .conversationButton_unreadBubbleBackground: .classicDark3, + .conversationButton_unreadBubbleText: .classicDark6, + .conversationButton_swipeDestructive: .dangerDark, + .conversationButton_swipeSecondary: .classicDark2, + .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index ae3a5c7f7..cb7eddd46 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -5,74 +5,87 @@ import UIKit.UIColor internal enum Theme_ClassicLight: ThemeColors { static let theme: [ThemeValue: UIColor] = [ // General + .white: .white, + .black: .black, + .clear: .clear, .primary: .primary, .defaultPrimary: Theme.PrimaryColor.green.color, - .danger: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), - .white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), - .backgroundPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .backgroundSecondary: #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1), - .backgroundTertiary: #colorLiteral(red: 0.9058823529, green: 0.9058823529, blue: 0.9058823529, alpha: 1), - .textPrimary: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .textSecondary: #colorLiteral(red: 0.4274509804, green: 0.4274509804, blue: 0.4274509804, alpha: 1), - .borderSeparator: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1), + .danger: .dangerLight, + .backgroundPrimary: .classicLight6, + .backgroundSecondary: .classicLight5, + .textPrimary: .classicLight0, + .textSecondary: .classicLight1, + .borderSeparator: .classicLight2, + + // Path + .path_connected: .pathConnected, + .path_connecting: .pathConnecting, + .path_error: .pathError, + .path_unknown: .classicLight4, // TextBox - .textBox_background: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .textBox_border: #colorLiteral(red: 0.631372549, green: 0.6352941176, blue: 0.631372549, alpha: 1), + .textBox_background: .classicLight6, + .textBox_border: .classicLight2, // MessageBubble .messageBubble_outgoingBackground: .primary, - .messageBubble_incomingBackground: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), - .messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .messageBubble_incomingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), + .messageBubble_incomingBackground: .classicLight4, + .messageBubble_outgoingText: .classicLight0, + .messageBubble_incomingText: .classicLight0, // MenuButton .menuButton_background: .primary, - .menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .menuButton_outerShadow: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .menuButton_icon: .classicLight6, + .menuButton_outerShadow: .classicLight0, + .menuButton_innerShadow: .classicLight6, // RadioButton .radioButton_selectedBackground: .primary, .radioButton_unselectedBackground: .clear, - .radioButton_selectedBorder: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .radioButton_unselectedBorder: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), + .radioButton_selectedBorder: .classicLight0, + .radioButton_unselectedBorder: .classicLight0, // OutlineButton - .outlineButton_text: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), + .outlineButton_text: .classicLight0, .outlineButton_background: .clear, - .outlineButton_highlight: #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 0.1), - .outlineButton_border: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .outlineButton_filledBackground: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .outlineButton_filledHighlight: #colorLiteral(red: 0.4274509804, green: 0.4274509804, blue: 0.4274509804, alpha: 1), - .outlineButton_destructiveText: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), + .outlineButton_highlight: .classicLight0.withAlphaComponent(0.1), + .outlineButton_border: .classicLight0, + .outlineButton_filledText: .classicLight6, + .outlineButton_filledBackground: .classicLight0, + .outlineButton_filledHighlight: .classicLight1, + .outlineButton_destructiveText: .dangerLight, .outlineButton_destructiveBackground: .clear, - .outlineButton_destructiveHighlight: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 0.3), - .outlineButton_destructiveBorder: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), + .outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3), + .outlineButton_destructiveBorder: .dangerLight, // SolidButton - .solidButton_background: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), - .solidButton_highlight: #colorLiteral(red: 0.8156862745, green: 0.8156862745, blue: 0.8156862745, alpha: 1), + .solidButton_background: .classicLight3, + .solidButton_highlight: .classicLight4, // Settings - .settings_tabBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .settings_tabHighlight: #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1), + .settings_tabBackground: .classicLight6, + .settings_tabHighlight: .classicLight3, // AppearanceButton - .appearance_sectionBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .appearance_buttonBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .appearance_buttonHighlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), + .appearance_sectionBackground: .classicLight6, + .appearance_buttonBackground: .classicLight6, + .appearance_buttonHighlight: .classicLight4, + + // Alert + .alert_background: .classicLight6, + .alert_buttonBackground: .classicLight6, + .alert_buttonHighlight: .classicLight4, // ConversationButton - .conversationButton_background: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .conversationButton_highlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), - .conversationButton_unreadBackground: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .conversationButton_unreadHighlight: #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9411764706, alpha: 1), + .conversationButton_background: .classicLight6, + .conversationButton_highlight: .classicLight4, + .conversationButton_unreadBackground: .classicLight6, + .conversationButton_unreadHighlight: .classicLight4, .conversationButton_unreadStripBackground: .primary, - .conversationButton_unreadBubbleBackground: #colorLiteral(red: 0.8745098039, green: 0.8745098039, blue: 0.8745098039, alpha: 1), - .conversationButton_unreadBubbleText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .conversationButton_pinBackground: Theme.PrimaryColor.yellow.color + .conversationButton_unreadBubbleBackground: .classicLight3, + .conversationButton_unreadBubbleText: .classicLight0, + .conversationButton_swipeDestructive: .dangerLight, + .conversationButton_swipeSecondary: .classicLight1, + .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+Colors.swift b/SessionUIKit/Style Guide/Themes/Theme+Colors.swift new file mode 100644 index 000000000..0933ba76b --- /dev/null +++ b/SessionUIKit/Style Guide/Themes/Theme+Colors.swift @@ -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 + }) +} diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index 2da414c6e..28540d187 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -5,74 +5,87 @@ import UIKit.UIColor internal enum Theme_OceanDark: ThemeColors { static let theme: [ThemeValue: UIColor] = [ // General + .white: .white, + .black: .black, + .clear: .clear, .primary: .primary, .defaultPrimary: Theme.PrimaryColor.blue.color, - .danger: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), - .white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), - .backgroundPrimary: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1), - .backgroundSecondary: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), - .backgroundTertiary: #colorLiteral(red: 0.1725490196, green: 0.1803921569, blue: 0.2274509804, alpha: 1), - .textPrimary: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .textSecondary: #colorLiteral(red: 0.6509803922, green: 0.662745098, blue: 0.8078431373, alpha: 1), - .borderSeparator: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1), + .danger: .dangerDark, + .backgroundPrimary: .oceanDark2, + .backgroundSecondary: .oceanDark1, + .textPrimary: .oceanDark6, + .textSecondary: .oceanDark5, + .borderSeparator: .oceanDark4, + + // Path + .path_connected: .pathConnected, + .path_connecting: .pathConnecting, + .path_error: .pathError, + .path_unknown: .oceanDark4, // TextBox - .textBox_background: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), - .textBox_border: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1), + .textBox_background: .oceanDark1, + .textBox_border: .oceanDark4, // MessageBubble .messageBubble_outgoingBackground: .primary, - .messageBubble_incomingBackground: #colorLiteral(red: 0.2392156863, green: 0.2901960784, blue: 0.3647058824, alpha: 1), - .messageBubble_outgoingText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .messageBubble_incomingText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .messageBubble_incomingBackground: .oceanDark4, + .messageBubble_outgoingText: .oceanDark0, + .messageBubble_incomingText: .oceanDark6, // MenuButton .menuButton_background: .primary, - .menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .menuButton_icon: .oceanDark6, .menuButton_outerShadow: .primary, - .menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .menuButton_innerShadow: .oceanDark6, // RadioButton .radioButton_selectedBackground: .primary, .radioButton_unselectedBackground: .clear, - .radioButton_selectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .radioButton_unselectedBorder: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .radioButton_selectedBorder: .oceanDark6, + .radioButton_unselectedBorder: .oceanDark6, // OutlineButton .outlineButton_text: .primary, .outlineButton_background: .clear, - .outlineButton_highlight: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3), + .outlineButton_highlight: .oceanDark6.withAlphaComponent(0.3), .outlineButton_border: .primary, - .outlineButton_filledText: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .outlineButton_filledBackground: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), - .outlineButton_filledHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), - .outlineButton_destructiveText: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), + .outlineButton_filledText: .oceanDark6, + .outlineButton_filledBackground: .oceanDark1, + .outlineButton_filledHighlight: .oceanDark3, + .outlineButton_destructiveText: .dangerDark, .outlineButton_destructiveBackground: .clear, - .outlineButton_destructiveHighlight: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 0.3), - .outlineButton_destructiveBorder: #colorLiteral(red: 1, green: 0.2274509804, blue: 0.2274509804, alpha: 1), + .outlineButton_destructiveHighlight: .dangerDark.withAlphaComponent(0.3), + .outlineButton_destructiveBorder: .dangerDark, // SolidButton - .solidButton_background: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1), - .solidButton_highlight: #colorLiteral(red: 0.2117647059, green: 0.2196078431, blue: 0.3019607844, alpha: 1), + .solidButton_background: .oceanDark2, + .solidButton_highlight: .oceanDark4, // Settings - .settings_tabBackground: #colorLiteral(red: 0.1019607843, green: 0.1098039216, blue: 0.1568627451, alpha: 1), - .settings_tabHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), + .settings_tabBackground: .oceanDark1, + .settings_tabHighlight: .oceanDark3, // Appearance - .appearance_sectionBackground: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), - .appearance_buttonBackground: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), - .appearance_buttonHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), + .appearance_sectionBackground: .oceanDark3, + .appearance_buttonBackground: .oceanDark3, + .appearance_buttonHighlight: .oceanDark4, + + // Alert + .alert_background: .oceanDark3, + .alert_buttonBackground: .oceanDark3, + .alert_buttonHighlight: .oceanDark4, // ConversationButton - .conversationButton_background: #colorLiteral(red: 0.168627451, green: 0.168627451, blue: 0.2509803922, alpha: 1), - .conversationButton_highlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), - .conversationButton_unreadBackground: #colorLiteral(red: 0.1450980392, green: 0.1529411765, blue: 0.2078431373, alpha: 1), - .conversationButton_unreadHighlight: #colorLiteral(red: 0.168627451, green: 0.1764705882, blue: 0.2509803922, alpha: 1), + .conversationButton_background: .oceanDark3, + .conversationButton_highlight: .oceanDark3, + .conversationButton_unreadBackground: .oceanDark2, + .conversationButton_unreadHighlight: .oceanDark3, .conversationButton_unreadStripBackground: .primary, .conversationButton_unreadBubbleBackground: .primary, - .conversationButton_unreadBubbleText: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .conversationButton_pinBackground: Theme.PrimaryColor.yellow.color + .conversationButton_unreadBubbleText: .oceanDark0, + .conversationButton_swipeDestructive: .dangerDark, + .conversationButton_swipeSecondary: .oceanDark2, + .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index cf64c3c76..40e2c8196 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -5,74 +5,87 @@ import UIKit.UIColor internal enum Theme_OceanLight: ThemeColors { static let theme: [ThemeValue: UIColor] = [ // General + .white: .white, + .black: .black, + .clear: .clear, .primary: .primary, .defaultPrimary: Theme.PrimaryColor.blue.color, - .danger: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), - .white: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .clear: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0), - .backgroundPrimary: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .backgroundSecondary: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1), - .backgroundTertiary: #colorLiteral(red: 0.8549019608, green: 0.9098039216, blue: 0.9137254902, alpha: 1), - .textPrimary: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), - .textSecondary: #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1), - .borderSeparator: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1), + .danger: .dangerLight, + .backgroundPrimary: .oceanLight6, + .backgroundSecondary: .oceanLight5, + .textPrimary: .oceanLight0, + .textSecondary: .oceanLight1, + .borderSeparator: .oceanLight2, + + // Path + .path_connected: .pathConnected, + .path_connecting: .pathConnecting, + .path_error: .pathError, + .path_unknown: .oceanLight4, // TextBox - .textBox_background: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .textBox_border: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1), + .textBox_background: .oceanLight6, + .textBox_border: .oceanLight2, // MessageBubble .messageBubble_outgoingBackground: .primary, - .messageBubble_incomingBackground: #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.9490196078, alpha: 1), - .messageBubble_outgoingText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), - .messageBubble_incomingText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), + .messageBubble_incomingBackground: .oceanLight3, + .messageBubble_outgoingText: .oceanLight0, + .messageBubble_incomingText: .oceanLight0, // MenuButton .menuButton_background: .primary, - .menuButton_icon: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), - .menuButton_outerShadow: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), - .menuButton_innerShadow: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + .menuButton_icon: .white, + .menuButton_outerShadow: .black, + .menuButton_innerShadow: .white, // RadioButton .radioButton_selectedBackground: .primary, .radioButton_unselectedBackground: .clear, - .radioButton_selectedBorder: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), - .radioButton_unselectedBorder: #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1), + .radioButton_selectedBorder: .oceanLight0, + .radioButton_unselectedBorder: .oceanLight2, // OutlineButton - .outlineButton_text: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), + .outlineButton_text: .oceanLight0, .outlineButton_background: .clear, - .outlineButton_highlight: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 0.1), - .outlineButton_border: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), - .outlineButton_filledText: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .outlineButton_filledBackground: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), - .outlineButton_filledHighlight: #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1), - .outlineButton_destructiveText: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), + .outlineButton_highlight: .oceanLight0.withAlphaComponent(0.1), + .outlineButton_border: .oceanLight0, + .outlineButton_filledText: .oceanLight6, + .outlineButton_filledBackground: .oceanLight0, + .outlineButton_filledHighlight: .oceanLight1, + .outlineButton_destructiveText: .dangerLight, .outlineButton_destructiveBackground: .clear, - .outlineButton_destructiveHighlight: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 0.3), - .outlineButton_destructiveBorder: #colorLiteral(red: 0.8823529412, green: 0.1764705882, blue: 0.09803921569, alpha: 1), + .outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3), + .outlineButton_destructiveBorder: .dangerLight, // SolidButton - .solidButton_background: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1), - .solidButton_highlight: #colorLiteral(red: 0.8431372549, green: 0.9333333334, blue: 0.9411764706, alpha: 1), + .solidButton_background: .oceanLight4, + .solidButton_highlight: .oceanLight5, // Settings - .settings_tabBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .settings_tabHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), + .settings_tabBackground: .oceanLight6, + .settings_tabHighlight: .oceanLight4, // Appearance - .appearance_sectionBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .appearance_buttonBackground: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .appearance_buttonHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), + .appearance_sectionBackground: .oceanLight6, + .appearance_buttonBackground: .oceanLight6, + .appearance_buttonHighlight: .oceanLight4, + + // Alert + .alert_background: .oceanLight6, + .alert_buttonBackground: .oceanLight6, + .alert_buttonHighlight: .oceanLight4, // ConversationButton - .conversationButton_background: #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1), - .conversationButton_highlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), - .conversationButton_unreadBackground: #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1), - .conversationButton_unreadHighlight: #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1), + .conversationButton_background: .oceanLight6, + .conversationButton_highlight: .oceanLight4, + .conversationButton_unreadBackground: .oceanLight5, + .conversationButton_unreadHighlight: .oceanLight4, .conversationButton_unreadStripBackground: .primary, .conversationButton_unreadBubbleBackground: .primary, - .conversationButton_unreadBubbleText: #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1), - .conversationButton_pinBackground: Theme.PrimaryColor.yellow.color + .conversationButton_unreadBubbleText: .oceanLight0, + .conversationButton_swipeDestructive: .dangerLight, + .conversationButton_swipeSecondary: .oceanLight1, + .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+PrimaryColors.swift b/SessionUIKit/Style Guide/Themes/Theme+PrimaryColors.swift deleted file mode 100644 index 162aa68cc..000000000 --- a/SessionUIKit/Style Guide/Themes/Theme+PrimaryColors.swift +++ /dev/null @@ -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 - }) -} diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index 8d084df98..92303b889 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -56,18 +56,24 @@ public protocol ThemeColors { public enum ThemeValue { // General + case white + case black + case clear case primary case defaultPrimary case danger - case white - case clear case backgroundPrimary case backgroundSecondary - case backgroundTertiary case textPrimary case textSecondary case borderSeparator + // Path + case path_connected + case path_connecting + case path_error + case path_unknown + // TextBox case textBox_background case textBox_border @@ -116,6 +122,11 @@ public enum ThemeValue { case appearance_buttonBackground case appearance_buttonHighlight + // Alert + case alert_background + case alert_buttonBackground + case alert_buttonHighlight + // ConversationButton case conversationButton_background case conversationButton_highlight @@ -124,5 +135,7 @@ public enum ThemeValue { case conversationButton_unreadStripBackground case conversationButton_unreadBubbleBackground case conversationButton_unreadBubbleText - case conversationButton_pinBackground + case conversationButton_swipeDestructive + case conversationButton_swipeSecondary + case conversationButton_swipeTertiary } diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index 3f86dd61c..5e14957ae 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -21,6 +21,7 @@ public final class Values : NSObject { @objc public static let smallButtonHeight = isIPhone5OrSmaller ? CGFloat(24) : CGFloat(27) @objc public static let mediumButtonHeight = isIPhone5OrSmaller ? CGFloat(30) : CGFloat(34) @objc public static let largeButtonHeight = isIPhone5OrSmaller ? CGFloat(40) : CGFloat(45) + @objc public static let alertButtonHeight: CGFloat = 50 @objc public static let accentLineThickness = CGFloat(4) diff --git a/SessionUIKit/Utilities/UIColor+Utilities.swift b/SessionUIKit/Utilities/UIColor+Utilities.swift index 7fac1ebbb..7f0f4eac5 100644 --- a/SessionUIKit/Utilities/UIColor+Utilities.swift +++ b/SessionUIKit/Utilities/UIColor+Utilities.swift @@ -2,7 +2,7 @@ import UIKit.UIColor -internal extension UIColor { +public extension UIColor { func toImage() -> UIImage { let bounds: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(bounds: bounds) diff --git a/SessionUIKit/Utilities/UIView+Constraints.swift b/SessionUIKit/Utilities/UIView+Constraints.swift index f9b237c64..c53285bdd 100644 --- a/SessionUIKit/Utilities/UIView+Constraints.swift +++ b/SessionUIKit/Utilities/UIView+Constraints.swift @@ -66,12 +66,12 @@ public extension UIView { } @discardableResult - func center(_ direction: Direction, in view: UIView) -> NSLayoutConstraint { + func center(_ direction: Direction, in view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint { translatesAutoresizingMaskIntoConstraints = false let constraint: NSLayoutConstraint = { switch direction { - case .horizontal: return centerXAnchor.constraint(equalTo: view.centerXAnchor) - case .vertical: return centerYAnchor.constraint(equalTo: view.centerYAnchor) + case .horizontal: return centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: inset) + case .vertical: return centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: inset) } }() constraint.isActive = true diff --git a/SessionUtilitiesKit/Database/Models/Setting.swift b/SessionUtilitiesKit/Database/Models/Setting.swift index 41c7cd4e8..1f634dac5 100644 --- a/SessionUtilitiesKit/Database/Models/Setting.swift +++ b/SessionUtilitiesKit/Database/Models/Setting.swift @@ -165,6 +165,15 @@ public extension Storage { } public extension Database { + @discardableResult func unsafeSet(key: String, value: T?) -> Setting? { + guard let value: T = value else { + _ = try? Setting.filter(id: key).deleteAll(self) + return nil + } + + return try? Setting(key: key, value: value)?.saved(self) + } + private subscript(key: String) -> Setting? { get { try? Setting.filter(id: key).fetchOne(self) } set { diff --git a/SessionUtilitiesKit/General/AppContext.h b/SessionUtilitiesKit/General/AppContext.h index ef759328d..626c93a2e 100755 --- a/SessionUtilitiesKit/General/AppContext.h +++ b/SessionUtilitiesKit/General/AppContext.h @@ -113,6 +113,7 @@ NSString *NSStringForUIApplicationState(UIApplicationState value); @end id CurrentAppContext(void); +BOOL HasAppContext(void); void SetCurrentAppContext(id appContext); void ExitShareExtension(void); diff --git a/SessionUtilitiesKit/General/AppContext.m b/SessionUtilitiesKit/General/AppContext.m index b99a3093b..d2d00a177 100755 --- a/SessionUtilitiesKit/General/AppContext.m +++ b/SessionUtilitiesKit/General/AppContext.m @@ -26,6 +26,11 @@ id CurrentAppContext(void) return currentAppContext; } +BOOL HasAppContext(void) +{ + return (currentAppContext != nil); +} + void SetCurrentAppContext(id appContext) { // The main app context should only be set once. diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index f76a0cba6..9325d5770 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -21,7 +21,6 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import -#import #import #import #import diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index 7cba13f55..ef57304fb 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -22,7 +22,7 @@ public final class ProfilePictureView: UIView { private lazy var imageView: YYAnimatedImageView = getImageView() private lazy var additionalImageView: YYAnimatedImageView = { let result: YYAnimatedImageView = getImageView() - result.themeBackgroundColor = .backgroundTertiary + result.themeBackgroundColor = .primary result.themeBorderColor = .backgroundPrimary result.layer.borderWidth = Values.separatorThickness diff --git a/SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift b/SignalUtilitiesKit/Screen Lock/ScreenLock.swift similarity index 50% rename from SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift rename to SignalUtilitiesKit/Screen Lock/ScreenLock.swift index 306bac424..ca9896bf2 100644 --- a/SignalUtilitiesKit/Screen Lock/OWSScreenLock.swift +++ b/SignalUtilitiesKit/Screen Lock/ScreenLock.swift @@ -5,18 +5,16 @@ import GRDB import LocalAuthentication import SessionMessagingKit -// FIXME: Refactor this once the 'PrivacySettingsTableViewController' and 'OWSScreenLockUI' have been refactored -@objc public class OWSScreenLock: NSObject { - - public enum OWSScreenLockOutcome { +public class ScreenLock { + public enum Outcome { case success case cancel case failure(error: String) case unexpectedFailure(error: String) } - @objc public let screenLockTimeoutDefault = (15 * kMinuteInterval) - @objc public let screenLockTimeouts = [ + public let screenLockTimeoutDefault = (15 * kMinuteInterval) + public let screenLockTimeouts = [ 1 * kMinuteInterval, 5 * kMinuteInterval, 15 * kMinuteInterval, @@ -24,104 +22,72 @@ import SessionMessagingKit 1 * kHourInterval, 0 ] - - @objc public static let ScreenLockDidChange = Notification.Name("ScreenLockDidChange") - - // MARK: - Singleton class - - @objc(sharedManager) - public static let shared = OWSScreenLock() - - private override init() { - super.init() - - SwiftSingletons.register(self) - } - - // MARK: - Properties - - @objc public func isScreenLockEnabled() -> Bool { - return Storage.shared[.isScreenLockEnabled] - } - - @objc - public func setIsScreenLockEnabled(_ value: Bool) { - Storage.shared.writeAsync( - updates: { db in db[.isScreenLockEnabled] = value }, - completion: { _, _ in - NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil) - } - ) - } - - @objc public func screenLockTimeout() -> TimeInterval { - return Storage.shared[.screenLockTimeoutSeconds] - .defaulting(to: screenLockTimeoutDefault) - } - - @objc public func setScreenLockTimeout(_ value: TimeInterval) { - Storage.shared.writeAsync( - updates: { db in db[.screenLockTimeoutSeconds] = value }, - completion: { _, _ in - NotificationCenter.default.postNotificationNameAsync(OWSScreenLock.ScreenLockDidChange, object: nil) - } - ) - } + + public static let shared: ScreenLock = ScreenLock() // MARK: - Methods - // This method should only be called: - // - // * On the main thread. - // - // Exactly one of these completions will be performed: - // - // * Asynchronously. - // * On the main thread. - @objc public func tryToUnlockScreenLock(success: @escaping (() -> Void), - failure: @escaping ((Error) -> Void), - unexpectedFailure: @escaping ((Error) -> Void), - cancel: @escaping (() -> Void)) { + /// This method should only be called: + /// + /// * On the main thread. + /// + /// Exactly one of these completions will be performed: + /// + /// * Asynchronously. + /// * On the main thread. + public func tryToUnlockScreenLock( + success: @escaping (() -> Void), + failure: @escaping ((Error) -> Void), + unexpectedFailure: @escaping ((Error) -> Void), + cancel: @escaping (() -> Void) + ) { AssertIsOnMainThread() - tryToVerifyLocalAuthentication(localizedReason: NSLocalizedString("SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK", - comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'."), - completion: { (outcome: OWSScreenLockOutcome) in - AssertIsOnMainThread() - - switch outcome { - case .failure(let error): - Logger.error("local authentication failed with error: \(error)") - failure(self.authenticationError(errorDescription: error)) - case .unexpectedFailure(let error): - Logger.error("local authentication failed with unexpected error: \(error)") - unexpectedFailure(self.authenticationError(errorDescription: error)) - case .success: - Logger.verbose("local authentication succeeded.") - success() - case .cancel: - Logger.verbose("local authentication cancelled.") - cancel() - } - }) + tryToVerifyLocalAuthentication( + // Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to + // unlock 'screen lock'. + localizedReason: "SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK".localized() + ) { outcome in + AssertIsOnMainThread() + + switch outcome { + case .failure(let error): + Logger.error("local authentication failed with error: \(error)") + failure(self.authenticationError(errorDescription: error)) + + case .unexpectedFailure(let error): + Logger.error("local authentication failed with unexpected error: \(error)") + unexpectedFailure(self.authenticationError(errorDescription: error)) + + case .success: + Logger.verbose("local authentication succeeded.") + success() + + case .cancel: + Logger.verbose("local authentication cancelled.") + cancel() + } + } } - // This method should only be called: - // - // * On the main thread. - // - // completionParam will be performed: - // - // * Asynchronously. - // * On the main thread. - private func tryToVerifyLocalAuthentication(localizedReason: String, - completion completionParam: @escaping ((OWSScreenLockOutcome) -> Void)) { + /// This method should only be called: + /// + /// * On the main thread. + /// + /// completionParam will be performed: + /// + /// * Asynchronously. + /// * On the main thread. + private func tryToVerifyLocalAuthentication( + localizedReason: String, + completion completionParam: @escaping ((Outcome) -> Void) + ) { AssertIsOnMainThread() let defaultErrorDescription = "SCREEN_LOCK_ENABLE_UNKNOWN_ERROR".localized() // Ensure completion is always called on the main thread. - let completion = { (outcome: OWSScreenLockOutcome) in + let completion = { outcome in DispatchQueue.main.async { completionParam(outcome) } @@ -131,18 +97,20 @@ import SessionMessagingKit var authError: NSError? let canEvaluatePolicy = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authError) + if !canEvaluatePolicy || authError != nil { Logger.error("could not determine if local authentication is supported: \(String(describing: authError))") let outcome = self.outcomeForLAError(errorParam: authError, defaultErrorDescription: defaultErrorDescription) switch outcome { - case .success: - owsFailDebug("local authentication unexpected success") - completion(.failure(error: defaultErrorDescription)) - case .cancel, .failure, .unexpectedFailure: - completion(outcome) - } + case .success: + owsFailDebug("local authentication unexpected success") + completion(.failure(error: defaultErrorDescription)) + + case .cancel, .failure, .unexpectedFailure: + completion(outcome) + } return } @@ -151,38 +119,46 @@ import SessionMessagingKit if success { Logger.info("local authentication succeeded.") completion(.success) - } else { - let outcome = self.outcomeForLAError(errorParam: evaluateError, - defaultErrorDescription: defaultErrorDescription) - switch outcome { + return + } + + let outcome = self.outcomeForLAError( + errorParam: evaluateError, + defaultErrorDescription: defaultErrorDescription + ) + + switch outcome { case .success: owsFailDebug("local authentication unexpected success") completion(.failure(error:defaultErrorDescription)) + case .cancel, .failure, .unexpectedFailure: completion(outcome) - } } } } // MARK: - Outcome - private func outcomeForLAError(errorParam: Error?, defaultErrorDescription: String) -> OWSScreenLockOutcome { + private func outcomeForLAError(errorParam: Error?, defaultErrorDescription: String) -> Outcome { if let error = errorParam { guard let laError = error as? LAError else { - return .failure(error:defaultErrorDescription) + return .failure(error: defaultErrorDescription) } switch laError.code { case .biometryNotAvailable: Logger.error("local authentication error: biometryNotAvailable.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE".localized()) + case .biometryNotEnrolled: Logger.error("local authentication error: biometryNotEnrolled.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED".localized()) + case .biometryLockout: Logger.error("local authentication error: biometryLockout.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT".localized()) + default: // Fall through to second switch break @@ -192,27 +168,37 @@ import SessionMessagingKit case .authenticationFailed: Logger.error("local authentication error: authenticationFailed.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_FAILED".localized()) + case .userCancel, .userFallback, .systemCancel, .appCancel: Logger.info("local authentication cancelled.") return .cancel + case .passcodeNotSet: Logger.error("local authentication error: passcodeNotSet.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_PASSCODE_NOT_SET".localized()) + case .touchIDNotAvailable: Logger.error("local authentication error: touchIDNotAvailable.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_AVAILABLE".localized()) + case .touchIDNotEnrolled: Logger.error("local authentication error: touchIDNotEnrolled.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_NOT_ENROLLED".localized()) + case .touchIDLockout: Logger.error("local authentication error: touchIDLockout.") return .failure(error: "SCREEN_LOCK_ERROR_LOCAL_AUTHENTICATION_LOCKOUT".localized()) + case .invalidContext: owsFailDebug("context not valid.") - return .unexpectedFailure(error:defaultErrorDescription) + return .unexpectedFailure(error: defaultErrorDescription) + case .notInteractive: owsFailDebug("context not interactive.") - return .unexpectedFailure(error:defaultErrorDescription) + return .unexpectedFailure(error: defaultErrorDescription) + + @unknown default: + return .failure(error: defaultErrorDescription) } } @@ -220,8 +206,7 @@ import SessionMessagingKit } private func authenticationError(errorDescription: String) -> Error { - return OWSErrorWithCodeDescription(.localAuthenticationError, - errorDescription) + return OWSErrorWithCodeDescription(.localAuthenticationError, errorDescription) } // MARK: - Context diff --git a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.h b/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.h deleted file mode 100644 index 03e9cb856..000000000 --- a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.h +++ /dev/null @@ -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 - -- (void)unlockButtonWasTapped; - -@end - -#pragma mark - - -@interface ScreenLockViewController : UIViewController - -@property (nonatomic, weak) id delegate; - -- (void)updateUIWithState:(ScreenLockUIState)uiState isLogoAtTop:(BOOL)isLogoAtTop animated:(BOOL)animated; - -@end diff --git a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m b/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m deleted file mode 100644 index b8d01d661..000000000 --- a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m +++ /dev/null @@ -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 -#import - -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 *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 *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 diff --git a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift b/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift new file mode 100644 index 000000000..a78e077ce --- /dev/null +++ b/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift @@ -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?() + } +} diff --git a/SignalUtilitiesKit/Utilities/UIColor+Extensions.swift b/SignalUtilitiesKit/Utilities/UIColor+Extensions.swift index ada78290b..faf49a56e 100644 --- a/SignalUtilitiesKit/Utilities/UIColor+Extensions.swift +++ b/SignalUtilitiesKit/Utilities/UIColor+Extensions.swift @@ -24,22 +24,6 @@ public extension UIColor { // MARK: - Functions - func toImage(isDarkMode: Bool) -> UIImage { - let bounds: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1) - let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(bounds: bounds) - - return renderer.image { rendererContext in - rendererContext.cgContext - .setFillColor( - self.resolvedColor( - // Note: This is needed for '.cgColor' to support dark mode - with: UITraitCollection(userInterfaceStyle: isDarkMode ? .dark : .light) - ).cgColor - ) - rendererContext.cgContext.fill(bounds) - } - } - func darken(by percentage: CGFloat) -> UIColor { guard percentage != 0 else { return self } guard let hsba: HSBA = self.hsba else { return self }