Partially remove unused Signal code

This commit is contained in:
nielsandriesse 2020-07-23 10:01:49 +10:00
parent 4c6f25343f
commit 7dcb3d57b9
68 changed files with 29 additions and 10600 deletions

View File

@ -34,9 +34,7 @@
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
340FC8AB204DAC8D007AEB0F /* DomainFrontingCountryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87D204DAC8C007AEB0F /* DomainFrontingCountryViewController.m */; };
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */; };
340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */; };
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */; };
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */; };
340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */; };
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC887204DAC8C007AEB0F /* BlockListViewController.m */; };
340FC8B2204DAC8D007AEB0F /* AdvancedSettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC88C204DAC8C007AEB0F /* AdvancedSettingsTableViewController.m */; };
@ -69,8 +67,6 @@
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A4D207D0C01009F5D9C /* HomeViewController.m */; };
34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A50207D0C01009F5D9C /* HomeViewCell.m */; };
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
343A65951FC47D5E000477A1 /* DebugUISyncMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */; };
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */; };
@ -89,12 +85,8 @@
34480B681FD0AA9400BC14EF /* UIFont+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B661FD0AA9400BC14EF /* UIFont+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */; };
3448E15C22133274004B052E /* OnboardingPermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */; };
3448E15E221333F5004B052E /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15D221333F5004B052E /* OnboardingController.swift */; };
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */; };
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */; };
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */; };
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; };
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */; };
3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; };
346129391FD1B47300532771 /* OWSPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129371FD1B47200532771 /* OWSPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; };
3461293A1FD1B47300532771 /* OWSPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129381FD1B47200532771 /* OWSPreferences.m */; };
@ -123,19 +115,6 @@
346129E31FD5C0BE00532771 /* VersionMigrations.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129E11FD5C0BE00532771 /* VersionMigrations.m */; };
346129E61FD5C0C600532771 /* OWSDatabaseMigrationRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129E41FD5C0C600532771 /* OWSDatabaseMigrationRunner.m */; };
346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */; };
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */; };
346129F61FD5F31400532771 /* OWS103EnableVideoCalling.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */; };
346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */; };
346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */; };
346129F91FD5F31400532771 /* OWS104CreateRecipientIdentities.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */; };
346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */; };
346129FB1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */; };
346129FC1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */; };
346129FD1FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */; };
346129FE1FD5F31400532771 /* OWS106EnsureProfileComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */; };
346129FF1FD5F31400532771 /* OWS103EnableVideoCalling.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */; };
34612A001FD5F31400532771 /* OWS105AttachmentFilePaths.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */; };
34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */; };
34612A061FD7238600532771 /* OWSSyncManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 34612A041FD7238500532771 /* OWSSyncManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
34612A071FD7238600532771 /* OWSSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34612A051FD7238500532771 /* OWSSyncManager.m */; };
34641E182088D7E900E2EDE5 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */; };
@ -148,7 +127,6 @@
346941A4215D2EE400B5BFAD /* OWSConversationColor.h in Headers */ = {isa = PBXBuildFile; fileRef = 346941A0215D2EE400B5BFAD /* OWSConversationColor.h */; settings = {ATTRIBUTES = (Public, ); }; };
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
346E35BE224283B100E55D5F /* UIAlertController+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */; };
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E9D5321B040B600562252 /* RegistrationController.swift */; };
347850311FD7494A007B8332 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
347850321FD7494A007B8332 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
347850331FD7494A007B8332 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
@ -180,11 +158,7 @@
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956921A301A100DCFE74 /* OWSBackup.m */; };
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956A21A301A100DCFE74 /* OWSBackupJob.m */; };
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; };
349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */; };
349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */; };
349ED992221EE80D008045B0 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349ED991221EE80D008045B0 /* AppPreferences.swift */; };
34A4C61E221613D00042EF2E /* OnboardingVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */; };
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */; };
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; };
34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */; };
34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */; };
@ -214,8 +188,6 @@
34AC09F4211B39B100997B47 /* SelectThreadViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09D6211B39B100997B47 /* SelectThreadViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
34AC09F5211B39B100997B47 /* SharingThreadPickerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09D7211B39B100997B47 /* SharingThreadPickerViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
34AC09F7211B39B100997B47 /* MediaMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34AC09D9211B39B100997B47 /* MediaMessageView.swift */; };
34AC09F8211B39B100997B47 /* CountryCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34AC09DA211B39B100997B47 /* CountryCodeViewController.m */; };
34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09DB211B39B100997B47 /* CountryCodeViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
34AC09FA211B39B100997B47 /* SharingThreadPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */; };
34AC0A0E211B39EA00997B47 /* ContactsViewHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09FB211B39E700997B47 /* ContactsViewHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
34AC0A0F211B39EA00997B47 /* ContactTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09FC211B39E700997B47 /* ContactTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -240,15 +212,11 @@
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B0796B1FCF46B000E248C2 /* MainAppContext.m */; };
34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */; };
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */; };
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */; };
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */; };
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */; };
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */; };
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */; };
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */; };
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */; };
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */; };
34B6A909218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */; };
34B6A90B218BA1D1007C4606 /* typing-animation.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34B6A90A218BA1D0007C4606 /* typing-animation.gif */; };
34B6D27420F664C900765BE2 /* OWSUnreadIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */; };
@ -267,7 +235,6 @@
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2A1F74C12700D7438D /* DebugUIStress.m */; };
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; };
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */; };
34BEDB0B21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */; };
34BEDB0E21C405B0007B0EAE /* ImageEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB0D21C405B0007B0EAE /* ImageEditorModel.swift */; };
34BEDB1321C43F6A007B0EAE /* ImageEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB1221C43F69007B0EAE /* ImageEditorView.swift */; };
34BEDB1621C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34BEDB1421C80BC9007B0EAE /* OWSAnyTouchGestureRecognizer.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -306,8 +273,6 @@
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; };
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */; };
34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */; };
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */; };
34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */ = {isa = PBXBuildFile; fileRef = 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */; };
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
@ -323,7 +288,6 @@
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; };
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; };
34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E5DC8120D8050D00C08145 /* RegistrationUtils.m */; };
34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E88D252098C5AE00A608F4 /* ContactViewController.swift */; };
34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */; };
34EA69402194933900702471 /* MediaDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EA693F2194933900702471 /* MediaDownloadView.swift */; };
@ -333,8 +297,6 @@
390650A6D345BFE01E006DB0 /* Pods_LokiPushNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04912E453971FB16E5E78EC6 /* Pods_LokiPushNotificationService.framework */; };
4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */; };
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BC20470A5B00CEE724 /* classic.aifc */; };
4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */; };
4503F1C4204711D300CEE724 /* OWS107LegacySounds.h in Headers */ = {isa = PBXBuildFile; fileRef = 4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */; };
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */; };
450998651FD8A34D00D89EB3 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; };
450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */; };
@ -411,8 +373,6 @@
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */; };
458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38391D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m */; };
459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */; };
4598198E204E2F28009414F2 /* OWS108CallLoggingPreference.h in Headers */ = {isa = PBXBuildFile; fileRef = 4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */; };
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */ = {isa = PBXBuildFile; fileRef = 4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */; };
459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */; };
459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
45A2F005204473A3002E978A /* NewMessage.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45A2F004204473A3002E978A /* NewMessage.aifc */; };
@ -453,7 +413,6 @@
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */; };
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */; };
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */; };
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */; };
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */ = {isa = PBXBuildFile; fileRef = 45D308AC2049A439000189E4 /* PinEntryView.m */; };
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */; };
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; };
@ -473,11 +432,9 @@
4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */; };
4C04F58421C860C50090D0BB /* MantlePerfTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C04F58321C860C50090D0BB /* MantlePerfTest.swift */; };
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; };
4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */; };
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */; };
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
4C21D5D6223A9DC500EF8A77 /* UIAlerts+iOS9.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D5223A9DC500EF8A77 /* UIAlerts+iOS9.m */; };
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */; };
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */; };
@ -488,13 +445,11 @@
4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; };
4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */; };
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; };
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */; };
4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */; };
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C618198219DF03A009BD6B5 /* OWSButton.swift */; };
4C61819F219E1796009BD6B5 /* typing-animation-dark.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */; };
4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; };
4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; };
4C7537892193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */; };
4C858A52212DC5E1001B45D3 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */; };
4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C948FF62146EB4800349F0D /* BlockListCache.swift */; };
4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9CA25C217E676900607C63 /* ZXingObjC.framework */; };
@ -504,7 +459,6 @@
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; };
4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */; };
4CBBCA6321714B4500EEB37D /* OWS110SortIdMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */; };
4CC0B59C20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */; };
4CC1ECF9211A47CE00CC13BE /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; };
@ -775,10 +729,8 @@
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupSettingsViewController.h; sourceTree = "<group>"; };
340FC880204DAC8C007AEB0F /* AppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSettingsViewController.h; sourceTree = "<group>"; };
340FC881204DAC8C007AEB0F /* AdvancedSettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AdvancedSettingsTableViewController.h; sourceTree = "<group>"; };
340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkedDevicesTableViewController.m; sourceTree = "<group>"; };
340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSoundSettingsViewController.m; sourceTree = "<group>"; };
340FC884204DAC8C007AEB0F /* AboutTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutTableViewController.h; sourceTree = "<group>"; };
340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkDeviceViewController.m; sourceTree = "<group>"; };
340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = "<group>"; };
340FC887204DAC8C007AEB0F /* BlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockListViewController.m; sourceTree = "<group>"; };
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = "<group>"; };
@ -790,11 +742,9 @@
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupSettingsViewController.m; sourceTree = "<group>"; };
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = "<group>"; };
340FC890204DAC8C007AEB0F /* BlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockListViewController.h; sourceTree = "<group>"; };
340FC891204DAC8C007AEB0F /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = "<group>"; };
340FC892204DAC8C007AEB0F /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = "<group>"; };
340FC893204DAC8C007AEB0F /* AboutTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutTableViewController.m; sourceTree = "<group>"; };
340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSoundSettingsViewController.h; sourceTree = "<group>"; };
340FC895204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = "<group>"; };
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = "<group>"; };
340FC898204DAC8D007AEB0F /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = "<group>"; };
340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = "<group>"; };
@ -836,10 +786,6 @@
34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = "<group>"; };
34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = "<group>"; };
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
34386A4D207D0C01009F5D9C /* HomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewController.m; sourceTree = "<group>"; };
34386A4E207D0C01009F5D9C /* HomeViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewCell.h; sourceTree = "<group>"; };
34386A4F207D0C01009F5D9C /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = "<group>"; };
34386A50207D0C01009F5D9C /* HomeViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewCell.m; sourceTree = "<group>"; };
34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = "<group>"; };
343A65931FC47D5D000477A1 /* DebugUISyncMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUISyncMessages.h; sourceTree = "<group>"; };
343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUISyncMessages.m; sourceTree = "<group>"; };
@ -866,14 +812,9 @@
344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOrphanDataCleaner.h; sourceTree = "<group>"; };
344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleaner.m; sourceTree = "<group>"; };
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = "<group>"; };
3448E15D221333F5004B052E /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = "<group>"; };
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSplashViewController.swift; sourceTree = "<group>"; };
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBaseViewController.swift; sourceTree = "<group>"; };
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCaptchaViewController.swift; sourceTree = "<group>"; };
34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = "<group>"; };
344F248C2007CCD600CFB4F4 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = "<group>"; };
345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FASettingsViewController.h; sourceTree = "<group>"; };
345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS2FASettingsViewController.m; sourceTree = "<group>"; };
3461284A1FD0B93F00532771 /* SAELoadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAELoadViewController.swift; sourceTree = "<group>"; };
346129371FD1B47200532771 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSPreferences.h; sourceTree = "<group>"; };
346129381FD1B47200532771 /* OWSPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSPreferences.m; sourceTree = "<group>"; };
@ -905,19 +846,6 @@
346129E11FD5C0BE00532771 /* VersionMigrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VersionMigrations.m; sourceTree = "<group>"; };
346129E41FD5C0C600532771 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseMigrationRunner.m; sourceTree = "<group>"; };
346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDatabaseMigrationRunner.h; sourceTree = "<group>"; };
346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS102MoveLoggingPreferenceToUserDefaults.m; sourceTree = "<group>"; };
346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS103EnableVideoCalling.h; sourceTree = "<group>"; };
346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS105AttachmentFilePaths.m; sourceTree = "<group>"; };
346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS100RemoveTSRecipientsMigration.m; sourceTree = "<group>"; };
346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS104CreateRecipientIdentities.m; sourceTree = "<group>"; };
346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS100RemoveTSRecipientsMigration.h; sourceTree = "<group>"; };
346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS101ExistingUsersBlockOnIdentityChange.m; sourceTree = "<group>"; };
346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS101ExistingUsersBlockOnIdentityChange.h; sourceTree = "<group>"; };
346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS102MoveLoggingPreferenceToUserDefaults.h; sourceTree = "<group>"; };
346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS106EnsureProfileComplete.swift; sourceTree = "<group>"; };
346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS103EnableVideoCalling.m; sourceTree = "<group>"; };
346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS105AttachmentFilePaths.h; sourceTree = "<group>"; };
346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS104CreateRecipientIdentities.h; sourceTree = "<group>"; };
34612A041FD7238500532771 /* OWSSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSyncManager.h; sourceTree = "<group>"; };
34612A051FD7238500532771 /* OWSSyncManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSyncManager.m; sourceTree = "<group>"; };
34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWindowManager.m; sourceTree = "<group>"; };
@ -933,7 +861,6 @@
346941A0215D2EE400B5BFAD /* OWSConversationColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationColor.h; sourceTree = "<group>"; };
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+OWS.swift"; sourceTree = "<group>"; };
346E9D5321B040B600562252 /* RegistrationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = "<group>"; };
347850561FD86544007B8332 /* SAEFailedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEFailedViewController.swift; sourceTree = "<group>"; };
3478505A1FD999D5007B8332 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = translations/et.lproj/Localizable.strings; sourceTree = "<group>"; };
3478505C1FD99A1F007B8332 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = translations/zh_TW.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -973,11 +900,7 @@
3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupAPI.swift; sourceTree = "<group>"; };
3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportJob.h; sourceTree = "<group>"; };
3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = "<group>"; };
349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS111UDAttributesMigration.swift; sourceTree = "<group>"; };
349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Onboarding2FAViewController.swift; sourceTree = "<group>"; };
349ED991221EE80D008045B0 /* AppPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = "<group>"; };
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingVerificationViewController.swift; sourceTree = "<group>"; };
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingProfileViewController.swift; sourceTree = "<group>"; };
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = "<group>"; };
34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumCellView.swift; sourceTree = "<group>"; };
34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSResaveCollectionDBMigration.m; sourceTree = "<group>"; };
@ -1007,8 +930,6 @@
34AC09D6211B39B100997B47 /* SelectThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectThreadViewController.h; sourceTree = "<group>"; };
34AC09D7211B39B100997B47 /* SharingThreadPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharingThreadPickerViewController.h; sourceTree = "<group>"; };
34AC09D9211B39B100997B47 /* MediaMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageView.swift; sourceTree = "<group>"; };
34AC09DA211B39B100997B47 /* CountryCodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryCodeViewController.m; sourceTree = "<group>"; };
34AC09DB211B39B100997B47 /* CountryCodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryCodeViewController.h; sourceTree = "<group>"; };
34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharingThreadPickerViewController.m; sourceTree = "<group>"; };
34AC09FB211B39E700997B47 /* ContactsViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactsViewHelper.h; sourceTree = "<group>"; };
34AC09FC211B39E700997B47 /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactTableViewCell.h; sourceTree = "<group>"; };
@ -1038,18 +959,12 @@
34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentSharing.m; sourceTree = "<group>"; };
34B3F83B1E8DF1700035BE1A /* CallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = "<group>"; };
34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsPicker.swift; sourceTree = "<group>"; };
34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExperienceUpgradesPageViewController.swift; sourceTree = "<group>"; };
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteFlow.swift; sourceTree = "<group>"; };
34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewContactThreadViewController.h; sourceTree = "<group>"; };
34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewContactThreadViewController.m; sourceTree = "<group>"; };
34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewGroupViewController.h; sourceTree = "<group>"; };
34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewGroupViewController.m; sourceTree = "<group>"; };
34B3F86D1E8DF1700035BE1A /* SignalsNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalsNavigationController.h; sourceTree = "<group>"; };
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalsNavigationController.m; sourceTree = "<group>"; };
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = "<group>"; };
34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorInteraction.swift; sourceTree = "<group>"; };
34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = "<group>"; };
34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS112TypingIndicatorsMigration.swift; sourceTree = "<group>"; };
34B6A90A218BA1D0007C4606 /* typing-animation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation.gif"; sourceTree = "<group>"; };
34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicator.h; sourceTree = "<group>"; };
34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUnreadIndicator.m; sourceTree = "<group>"; };
@ -1069,7 +984,6 @@
34BECE2A1F74C12700D7438D /* DebugUIStress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIStress.m; sourceTree = "<group>"; };
34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerViewController.swift; sourceTree = "<group>"; };
34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerLayout.swift; sourceTree = "<group>"; };
34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS114RemoveDynamicInteractions.swift; sourceTree = "<group>"; };
34BEDB0D21C405B0007B0EAE /* ImageEditorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorModel.swift; sourceTree = "<group>"; };
34BEDB1221C43F69007B0EAE /* ImageEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorView.swift; sourceTree = "<group>"; };
34BEDB1421C80BC9007B0EAE /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAnyTouchGestureRecognizer.h; sourceTree = "<group>"; };
@ -1132,8 +1046,6 @@
34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAction.h; sourceTree = "<group>"; };
34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAssetLoader.h; sourceTree = "<group>"; };
34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesUtils.h; sourceTree = "<group>"; };
34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS109OutgoingMessageState.m; sourceTree = "<group>"; };
34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS109OutgoingMessageState.h; sourceTree = "<group>"; };
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = "<group>"; };
@ -1161,8 +1073,6 @@
34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIDiskUsage.m; sourceTree = "<group>"; };
34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIPage.h; sourceTree = "<group>"; };
34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIPage.m; sourceTree = "<group>"; };
34E5DC8020D8050D00C08145 /* RegistrationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegistrationUtils.h; sourceTree = "<group>"; };
34E5DC8120D8050D00C08145 /* RegistrationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegistrationUtils.m; sourceTree = "<group>"; };
34E88D252098C5AE00A608F4 /* ContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewController.swift; sourceTree = "<group>"; };
34E8A8D02085238900B272B1 /* ProtoParsingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProtoParsingTest.m; sourceTree = "<group>"; };
34EA693F2194933900702471 /* MediaDownloadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaDownloadView.swift; sourceTree = "<group>"; };
@ -1173,8 +1083,6 @@
435EAC2E5E22D3F087EB3192 /* Pods-SignalShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.app store release.xcconfig"; sourceTree = "<group>"; };
4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "classic-quiet.aifc"; sourceTree = "<group>"; };
4503F1BC20470A5B00CEE724 /* classic.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = classic.aifc; sourceTree = "<group>"; };
4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS107LegacySounds.m; sourceTree = "<group>"; };
4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS107LegacySounds.h; sourceTree = "<group>"; };
4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ExperienceUpgrade.swift; path = ExperienceUpgrades/ExperienceUpgrade.swift; sourceTree = "<group>"; };
4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = ThirdParty/WebRTC/Build/WebRTC.framework; sourceTree = "<group>"; };
450D19111F85236600970622 /* RemoteVideoView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemoteVideoView.h; sourceTree = "<group>"; };
@ -1245,8 +1153,6 @@
459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDeviceTableViewCell.m; sourceTree = "<group>"; };
4597E94E1D8313C100040CDE /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = translations/sq.lproj/Localizable.strings; sourceTree = "<group>"; };
4597E94F1D8313CB00040CDE /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = translations/bg.lproj/Localizable.strings; sourceTree = "<group>"; };
4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWS108CallLoggingPreference.h; sourceTree = "<group>"; };
4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWS108CallLoggingPreference.m; sourceTree = "<group>"; };
459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSQuotedReplyModel.h; sourceTree = "<group>"; };
459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedReplyModel.m; sourceTree = "<group>"; };
45A2F004204473A3002E978A /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; name = NewMessage.aifc; path = Signal/AudioFiles/NewMessage.aifc; sourceTree = SOURCE_ROOT; };
@ -1291,7 +1197,6 @@
45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Signal/src/util/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; };
45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = "<group>"; };
45D231761DC7E8F10034FA89 /* SessionResetJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionResetJob.swift; sourceTree = "<group>"; };
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS2FAReminderViewController.swift; sourceTree = "<group>"; };
45D308AB2049A439000189E4 /* PinEntryView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PinEntryView.h; sourceTree = "<group>"; };
45D308AC2049A439000189E4 /* PinEntryView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PinEntryView.m; sourceTree = "<group>"; };
45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationHeaderView.swift; sourceTree = "<group>"; };
@ -1312,7 +1217,6 @@
4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceNoteLock.swift; sourceTree = "<group>"; };
4C04F58321C860C50090D0BB /* MantlePerfTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MantlePerfTest.swift; path = Models/MantlePerfTest.swift; sourceTree = "<group>"; };
4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = "<group>"; };
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoGridViewCell.swift; sourceTree = "<group>"; };
4C1D2333218B692800A0598F /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = translations/ko.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1323,7 +1227,6 @@
4C1D2339218B6C6D00A0598F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = translations/sv.lproj/Localizable.strings; sourceTree = "<group>"; };
4C1D233A218B6CDB00A0598F /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = translations/th.lproj/Localizable.strings; sourceTree = "<group>"; };
4C1D233B218B6D3100A0598F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = translations/tr.lproj/Localizable.strings; sourceTree = "<group>"; };
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
4C21D5D5223A9DC500EF8A77 /* UIAlerts+iOS9.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIAlerts+iOS9.m"; sourceTree = "<group>"; };
4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCapture.swift; sourceTree = "<group>"; };
4C23A5F1215C4ADE00534937 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = "<group>"; };
@ -1332,14 +1235,12 @@
4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = "<group>"; };
4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMediaNavigationController.swift; sourceTree = "<group>"; };
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = "<group>"; };
4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidator.swift; sourceTree = "<group>"; };
4C586924224FAB83003FD070 /* AVAudioSession+OWS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "AVAudioSession+OWS.h"; path = "util/UI Categories/AVAudioSession+OWS.h"; sourceTree = "<group>"; };
4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "AVAudioSession+OWS.m"; path = "util/UI Categories/AVAudioSession+OWS.m"; sourceTree = "<group>"; };
4C618198219DF03A009BD6B5 /* OWSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSButton.swift; sourceTree = "<group>"; };
4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation-dark.gif"; sourceTree = "<group>"; };
4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = "<group>"; };
4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = "<group>"; };
4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS113MultiAttachmentMediaMessages.swift; sourceTree = "<group>"; };
4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+OWS.swift"; sourceTree = "<group>"; };
4C948FF62146EB4800349F0D /* BlockListCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListCache.swift; sourceTree = "<group>"; };
4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = "<group>"; };
@ -1349,7 +1250,6 @@
4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; };
4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = "<group>"; };
4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS110SortIdMigration.swift; sourceTree = "<group>"; };
4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = "<group>"; };
4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateNag.swift; sourceTree = "<group>"; };
@ -1701,15 +1601,8 @@
isa = PBXGroup;
children = (
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */,
349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */,
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */,
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */,
3448E15D221333F5004B052E /* OnboardingController.swift */,
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */,
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */,
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */,
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */,
346E9D5321B040B600562252 /* RegistrationController.swift */,
);
path = Registration;
sourceTree = "<group>";
@ -1735,10 +1628,6 @@
340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */,
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */,
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */,
340FC891204DAC8C007AEB0F /* OWSLinkDeviceViewController.h */,
340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */,
340FC895204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.h */,
340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */,
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */,
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */,
340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */,
@ -1782,19 +1671,6 @@
path = Fonts;
sourceTree = "<group>";
};
34386A4C207D0C01009F5D9C /* HomeView */ = {
isa = PBXGroup;
children = (
34386A4E207D0C01009F5D9C /* HomeViewCell.h */,
34386A50207D0C01009F5D9C /* HomeViewCell.m */,
34386A4F207D0C01009F5D9C /* HomeViewController.h */,
34386A4D207D0C01009F5D9C /* HomeViewController.m */,
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */,
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */,
);
path = HomeView;
sourceTree = "<group>";
};
34480B2F1FD0921000BC14EF /* utils */ = {
isa = PBXGroup;
children = (
@ -1923,30 +1799,6 @@
346129921FD1E30000532771 /* migrations */ = {
isa = PBXGroup;
children = (
346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */,
346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */,
346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */,
346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */,
346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */,
346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */,
346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */,
346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */,
346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */,
346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */,
346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */,
346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */,
346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */,
4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */,
4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */,
4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */,
4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */,
34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */,
34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */,
4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */,
349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */,
34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */,
4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */,
34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */,
346129931FD1E30000532771 /* OWSDatabaseMigration.h */,
346129941FD1E30000532771 /* OWSDatabaseMigration.m */,
346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */,
@ -2086,9 +1938,7 @@
3448BFC01EDF0EA7005B2D69 /* ConversationView */,
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */,
34D8C0221ED3673300188D7C /* DebugUI */,
34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */,
34BECE2C1F7ABCE000D7438D /* GifPicker */,
34386A4C207D0C01009F5D9C /* HomeView */,
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */,
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */,
3496744E2076ACCE00080B5F /* LongTextViewController.swift */,
@ -2099,13 +1949,6 @@
454A84032059C787008B8C75 /* MediaTileViewController.swift */,
4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */,
34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */,
34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */,
34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */,
34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */,
34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */,
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */,
345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */,
345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */,
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */,
34969558219B605E00DCFE74 /* Photos */,
34CE88E51F2FB9A10098030F /* ProfileViewController.h */,
@ -2349,8 +2192,6 @@
340872C22239563500CB25B0 /* AttachmentApproval */,
34AC09CF211B39B000997B47 /* ContactFieldView.swift */,
34AC09CD211B39B000997B47 /* ContactShareApprovalViewController.swift */,
34AC09DB211B39B100997B47 /* CountryCodeViewController.h */,
34AC09DA211B39B100997B47 /* CountryCodeViewController.m */,
34AC09D0211B39B000997B47 /* EditContactShareNameViewController.swift */,
34AC09D9211B39B100997B47 /* MediaMessageView.swift */,
34AC09C9211B39AF00997B47 /* MessageApprovalViewController.swift */,
@ -2469,7 +2310,6 @@
458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */,
458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */,
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */,
);
path = Models;
sourceTree = "<group>";
@ -2605,8 +2445,6 @@
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */,
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */,
450DF2041E0D74AC003D14BE /* Platform.swift */,
34E5DC8020D8050D00C08145 /* RegistrationUtils.h */,
34E5DC8120D8050D00C08145 /* RegistrationUtils.m */,
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
FCFA64B11A24F29E0007FB87 /* UI Categories */,
);
@ -3089,18 +2927,13 @@
buildActionMask = 2147483647;
files = (
346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */,
34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */,
34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */,
459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */,
34612A001FD5F31400532771 /* OWS105AttachmentFilePaths.h in Headers */,
346129F61FD5F31400532771 /* OWS103EnableVideoCalling.h in Headers */,
346129A91FD1F0E000532771 /* OWSFormat.h in Headers */,
34480B551FD0A7A400BC14EF /* DebugLogger.h in Headers */,
4503F1C4204711D300CEE724 /* OWS107LegacySounds.h in Headers */,
346129711FD1D74C00532771 /* SignalKeyingStorage.h in Headers */,
34AC0A20211B39EA00997B47 /* ThreadViewHelper.h in Headers */,
34AC09DE211B39B100997B47 /* OWSNavigationController.h in Headers */,
34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */,
450998691FD8C10200D89EB3 /* AttachmentSharing.h in Headers */,
34BEDB1621C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.h in Headers */,
34AC09EC211B39B100997B47 /* OWSTableViewController.h in Headers */,
@ -3108,7 +2941,6 @@
346129D61FD20ADC00532771 /* UIViewController+OWS.h in Headers */,
34612A061FD7238600532771 /* OWSSyncManager.h in Headers */,
34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */,
346129FC1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h in Headers */,
34AC09F2211B39B100997B47 /* OWSViewController.h in Headers */,
451F8A491FD715CF005CB9DA /* OWSAvatarBuilder.h in Headers */,
346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */,
@ -3117,7 +2949,6 @@
346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */,
342950892124CB0A0000B063 /* OWSSearchBar.h in Headers */,
346941A1215D2EE400B5BFAD /* Theme.h in Headers */,
346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */,
346129E21FD5C0BE00532771 /* VersionMigrations.h in Headers */,
34AC09E8211B39B100997B47 /* NewNonContactConversationViewController.h in Headers */,
34480B611FD0A98800BC14EF /* UIColor+OWS.h in Headers */,
@ -3125,8 +2956,6 @@
346941A4215D2EE400B5BFAD /* OWSConversationColor.h in Headers */,
3461295A1FD1D74C00532771 /* Environment.h in Headers */,
450C801020AD1AE400F3A091 /* OWSWindowManager.h in Headers */,
34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */,
4598198E204E2F28009414F2 /* OWS108CallLoggingPreference.h in Headers */,
34AC0A0F211B39EA00997B47 /* ContactTableViewCell.h in Headers */,
34480B631FD0A98800BC14EF /* UIView+OWS.h in Headers */,
451F8A4B1FD715E1005CB9DA /* OWSGroupAvatarBuilder.h in Headers */,
@ -3150,7 +2979,6 @@
342950852124C9750000B063 /* OWSTextField.h in Headers */,
45194F901FD7200000333B2C /* ThreadUtil.h in Headers */,
346129CC1FD2072E00532771 /* NSAttributedString+OWS.h in Headers */,
346129FD1FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h in Headers */,
34074F62203D0CBE004596AE /* OWSSounds.h in Headers */,
34AC0A1D211B39EA00997B47 /* ContactCellView.h in Headers */,
);
@ -3221,7 +3049,6 @@
buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Signal" */;
buildPhases = (
1460156AE01E0DB0949D61FE /* [CP] Check Pods Manifest.lock */,
45AE48531E073428004D96C2 /* Swift Lint */,
D221A085169C9E5E00537ABF /* Sources */,
D221A086169C9E5E00537ABF /* Frameworks */,
34C239432180B01B00B6108F /* Run Script: update_list_info */,
@ -3630,20 +3457,6 @@
shellPath = /bin/sh;
shellScript = "/usr/local/bin/carthage copy-frameworks\n";
};
45AE48531E073428004D96C2 /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Swift Lint";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n# disabled for now. too many lint errors outside of the scope of this branch\n#(cd Signal && swiftlint)\n# never fail.\nexit 0\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
4B4609DACEC6E462A2394D2F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -3856,20 +3669,16 @@
34AC0A17211B39EA00997B47 /* VideoPlayerView.swift in Sources */,
34BEDB1321C43F6A007B0EAE /* ImageEditorView.swift in Sources */,
34AC09EE211B39B100997B47 /* EditContactShareNameViewController.swift in Sources */,
346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */,
45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */,
450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */,
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */,
346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */,
34AC09DF211B39B100997B47 /* OWSNavigationController.m in Sources */,
34074F61203D0CBE004596AE /* OWSSounds.m in Sources */,
34BEDB1721C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.m in Sources */,
340872C92239563500CB25B0 /* AttachmentItemCollection.swift in Sources */,
34080EFE2225F96D0087E99F /* ImageEditorPaletteView.swift in Sources */,
34B6A909218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift in Sources */,
4C3E245D21F2B395000AE092 /* DirectionalPanGestureRecognizer.swift in Sources */,
346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */,
4CBBCA6321714B4500EEB37D /* OWS110SortIdMigration.swift in Sources */,
342950832124C9750000B063 /* OWSTextView.m in Sources */,
452EC6E1205FF5DC000E787C /* Bench.swift in Sources */,
B8C9689923FA1B72005F64E0 /* Values.swift in Sources */,
@ -3904,7 +3713,6 @@
451F8A461FD715BA005CB9DA /* OWSGroupAvatarBuilder.m in Sources */,
34BBC85B220C7ADA00857249 /* OrderedDictionary.swift in Sources */,
346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */,
346129FB1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */,
B8544E3523D5201400299F14 /* UIView+Constraints.swift in Sources */,
34AC09EA211B39B100997B47 /* ModalActivityIndicatorViewController.swift in Sources */,
B8C9689523FA1B72005F64E0 /* AppMode.swift in Sources */,
@ -3917,19 +3725,14 @@
340872CA2239563500CB25B0 /* AttachmentApprovalViewController.swift in Sources */,
346129AD1FD1F34E00532771 /* ImageCache.swift in Sources */,
452C7CA72037628B003D51A5 /* Weak.swift in Sources */,
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */,
340872D02239787F00CB25B0 /* AttachmentTextToolbar.swift in Sources */,
34AC09F8211B39B100997B47 /* CountryCodeViewController.m in Sources */,
451F8A341FD710C3005CB9DA /* FullTextSearcher.swift in Sources */,
34080F04222858DC0087E99F /* OWSViewController+ImageEditor.swift in Sources */,
346129FE1FD5F31400532771 /* OWS106EnsureProfileComplete.swift in Sources */,
34AC0A10211B39EA00997B47 /* TappableView.swift in Sources */,
346129F91FD5F31400532771 /* OWS104CreateRecipientIdentities.m in Sources */,
B8C9689A23FA1B95005F64E0 /* DeviceUtilities.swift in Sources */,
346129B61FD1F7E800532771 /* ProfileFetcherJob.swift in Sources */,
34AC09E9211B39B100997B47 /* OWSTableViewController.m in Sources */,
340872CE2239596100CB25B0 /* AttachmentApprovalInputAccessoryView.swift in Sources */,
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */,
45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */,
34BEDB0E21C405B0007B0EAE /* ImageEditorModel.swift in Sources */,
451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */,
@ -3948,13 +3751,10 @@
34080F02222853E30087E99F /* ImageEditorBrushViewController.swift in Sources */,
34AC0A14211B39EA00997B47 /* ContactCellView.m in Sources */,
34AC0A15211B39EA00997B47 /* ContactsViewHelper.m in Sources */,
346129FF1FD5F31400532771 /* OWS103EnableVideoCalling.m in Sources */,
346129E31FD5C0BE00532771 /* VersionMigrations.m in Sources */,
4C7537892193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift in Sources */,
C3638C0524C7F0B500AF29BC /* LK002RemoveFriendRequests.swift in Sources */,
340872CB2239563500CB25B0 /* AttachmentPrepViewController.swift in Sources */,
34AC0A16211B39EA00997B47 /* OWSNavigationBar.swift in Sources */,
34BEDB0B21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift in Sources */,
34AC0A1A211B39EA00997B47 /* CommonStrings.swift in Sources */,
34AC0A19211B39EA00997B47 /* OWSAlerts.swift in Sources */,
340872DA22397FEB00CB25B0 /* AttachmentTextView.swift in Sources */,
@ -3962,14 +3762,12 @@
34BBC859220C7ADA00857249 /* ImageEditorStrokeItem.swift in Sources */,
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */,
451F8A481FD715BA005CB9DA /* OWSContactAvatarBuilder.m in Sources */,
4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */,
34AC0A18211B39EA00997B47 /* TappableStackView.swift in Sources */,
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */,
346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */,
4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */,
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */,
241C6314231F64C000B4198E /* JazzIcon.swift in Sources */,
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */,
34AC09F3211B39B100997B47 /* NewNonContactConversationViewController.m in Sources */,
4C3E245C21F29FCE000AE092 /* Toast.swift in Sources */,
34BBC84B220B2CB200857249 /* ImageEditorTextViewController.swift in Sources */,
@ -3987,7 +3785,6 @@
34641E182088D7E900E2EDE5 /* OWSScreenLock.swift in Sources */,
346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */,
241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */,
349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */,
34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */,
459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */,
340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */,
@ -4025,7 +3822,6 @@
34C4E2582118957600BEA353 /* WebRTCProto.swift in Sources */,
34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */,
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */,
34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */,
34DC9BD921543E0C00FDDCEC /* DebugContactsUtils.m in Sources */,
34DBF007206C3CB200025978 /* OWSBubbleShapeView.m in Sources */,
4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */,
@ -4044,7 +3840,6 @@
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */,
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */,
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */,
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
@ -4061,9 +3856,7 @@
B8BB82B92394911B00BA5194 /* Separator.swift in Sources */,
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */,
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */,
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */,
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */,
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */,
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,
@ -4072,7 +3865,6 @@
45794E861E00620000066731 /* CallUIAdapter.swift in Sources */,
340FC8BA204DAC8D007AEB0F /* FingerprintViewScanController.m in Sources */,
4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */,
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */,
B85357C123A1B81900AAF6CD /* SeedReminderViewDelegate.swift in Sources */,
450D19131F85236600970622 /* RemoteVideoView.m in Sources */,
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
@ -4083,8 +3875,6 @@
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */,
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */,
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */,
340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */,
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */,
B80C6B592384C4E700FDBC8B /* DeviceNameModal.swift in Sources */,
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */,
@ -4106,7 +3896,6 @@
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */,
3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */,
B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */,
349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */,
C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */,
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */,
@ -4116,15 +3905,11 @@
D221A09A169C9E5E00537ABF /* main.m in Sources */,
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */,
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */,
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */,
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */,
34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */,
45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */,
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */,
340FC8BC204DAC8D007AEB0F /* FingerprintViewController.m in Sources */,
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */,
34A4C61E221613D00042EF2E /* OnboardingVerificationViewController.swift in Sources */,
4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */,
340FC8B2204DAC8D007AEB0F /* AdvancedSettingsTableViewController.m in Sources */,
452B999020A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift in Sources */,
@ -4143,7 +3928,6 @@
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
@ -4159,7 +3943,6 @@
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */,
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */,
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
B8CCF639239721E20091D419 /* TabBar.swift in Sources */,
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
@ -4179,13 +3962,11 @@
C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */,
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
B879D44B247E1D9200DB3608 /* PathStatusView.swift in Sources */,
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */,
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */,
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */,
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */,
@ -4195,7 +3976,6 @@
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */,
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */,
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */,
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
@ -4211,7 +3991,6 @@
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */,
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */,
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
@ -4254,7 +4033,6 @@
B85357C723A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift in Sources */,
340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */,
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */,
4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */,
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */,
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */,
340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */,
@ -4273,7 +4051,6 @@
34D1F0861F8678AA0066283D /* ConversationViewController.m in Sources */,
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */,
B90418E6183E9DD40038554A /* DateUtil.m in Sources */,
3448E15E221333F5004B052E /* OnboardingController.swift in Sources */,
340FC8BD204DAC8D007AEB0F /* ShowGroupMembersViewController.m in Sources */,
3496956F21A301A100DCFE74 /* OWSBackupLazyRestore.swift in Sources */,
459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */,

View File

@ -4,9 +4,7 @@
#import "AppDelegate.h"
#import "DebugLogger.h"
#import "HomeViewController.h"
#import "MainAppContext.h"
#import "OWS2FASettingsViewController.h"
#import "OWSBackup.h"
#import "OWSOrphanDataCleaner.h"
#import "OWSScreenLockUI.h"
@ -946,7 +944,7 @@ static NSTimeInterval launchStartedAt;
rootViewController = [HomeVC new];
}
} else {
rootViewController = [[OnboardingController new] initialViewController];
rootViewController = [LandingVC new];
navigationBarHidden = NO;
}
OWSAssertDebug(rootViewController);

View File

@ -1,47 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SessionServiceKit
@objc
public enum ValidatedPhoneCountryCodes: UInt {
case unitedStates = 1
case brazil = 55
}
@objc
public class PhoneNumberValidator: NSObject {
@objc
public func isValidForRegistration(phoneNumber: PhoneNumber) -> Bool {
guard let countryCode = phoneNumber.getCountryCode() else {
return false
}
guard let validatedCountryCode = ValidatedPhoneCountryCodes(rawValue: countryCode.uintValue) else {
// no extra validation for this country
return true
}
switch validatedCountryCode {
case .brazil:
return isValidForBrazilRegistration(phoneNumber: phoneNumber)
case .unitedStates:
return isValidForUnitedStatesRegistration(phoneNumber: phoneNumber)
}
}
let validBrazilPhoneNumberRegex = try! NSRegularExpression(pattern: "^\\+55\\d{2}9?\\d{8}$", options: [])
private func isValidForBrazilRegistration(phoneNumber: PhoneNumber) -> Bool {
let e164 = phoneNumber.toE164()
return validBrazilPhoneNumberRegex.hasMatch(input: e164)
}
let validUnitedStatesPhoneNumberRegex = try! NSRegularExpression(pattern: "^\\+1\\d{10}$", options: [])
private func isValidForUnitedStatesRegistration(phoneNumber: PhoneNumber) -> Bool {
let e164 = phoneNumber.toE164()
return validUnitedStatesPhoneNumberRegex.hasMatch(input: e164)
}
}

View File

@ -19,8 +19,6 @@
#import "DebugUIPage.h"
#import "DebugUITableViewController.h"
#import "FingerprintViewController.h"
#import "HomeViewCell.h"
#import "HomeViewController.h"
#import "MediaDetailViewController.h"
#import "NotificationSettingsViewController.h"
#import "OWSAddToContactViewController.h"
@ -129,4 +127,3 @@
#import <WebRTC/RTCAudioSession.h>
#import <WebRTC/RTCCameraPreviewView.h>
#import <YYImage/YYImage.h>
#import "NewGroupViewController.h"

View File

@ -10,11 +10,9 @@
#import "NotificationSettingsViewController.h"
#import "OWSBackup.h"
#import "OWSBackupSettingsViewController.h"
#import "OWSLinkedDevicesTableViewController.h"
#import "OWSNavigationController.h"
#import "PrivacySettingsTableViewController.h"
#import "ProfileViewController.h"
#import "RegistrationUtils.h"
#import "Session-Swift.h"
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h>
@ -559,7 +557,7 @@
- (void)reregisterUser
{
[RegistrationUtils showReregistrationUIFromViewController:self];
}
#pragma mark - Dark Theme

View File

@ -1,20 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSQRCodeScanningViewController.h"
#import <SignalMessaging/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@class OWSLinkedDevicesTableViewController;
@interface OWSLinkDeviceViewController : OWSViewController
@property (nonatomic, weak) OWSLinkedDevicesTableViewController *linkedDevicesTableViewController;
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithString:(NSString *)string;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,279 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSLinkDeviceViewController.h"
#import "OWSDeviceProvisioningURLParser.h"
#import "OWSLinkedDevicesTableViewController.h"
#import "Session-Swift.h"
#import <SessionCoreKit/Cryptography.h>
#import <SignalMessaging/OWSProfileManager.h>
#import <SessionServiceKit/OWSDevice.h>
#import <SessionServiceKit/OWSDeviceProvisioner.h>
#import <SessionServiceKit/OWSIdentityManager.h>
#import <SessionServiceKit/OWSReadReceiptManager.h>
#import <SessionServiceKit/TSAccountManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSLinkDeviceViewController () <OWSQRScannerDelegate>
@property (nonatomic) YapDatabaseConnection *dbConnection;
@property (nonatomic) UIView *qrScanningView;
@property (nonatomic) UILabel *scanningInstructionsLabel;
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager;
@end
@implementation OWSLinkDeviceViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = Theme.backgroundColor;
self.dbConnection = [[OWSPrimaryStorage sharedManager] newDatabaseConnection];
UIImage *heroImage = [UIImage imageNamed:@"ic_devices_ios"];
OWSAssertDebug(heroImage);
UIImageView *heroImageView = [[UIImageView alloc] initWithImage:heroImage];
[heroImageView autoSetDimensionsToSize:heroImage.size];
self.scanningInstructionsLabel = [UILabel new];
self.scanningInstructionsLabel.text = NSLocalizedString(@"LINK_DEVICE_SCANNING_INSTRUCTIONS",
@"QR Scanning screen instructions, placed alongside a camera view for scanning QR Codes");
self.scanningInstructionsLabel.textColor = Theme.primaryColor;
self.scanningInstructionsLabel.font = UIFont.ows_dynamicTypeCaption1Font;
self.scanningInstructionsLabel.numberOfLines = 0;
self.scanningInstructionsLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.scanningInstructionsLabel.textAlignment = NSTextAlignmentCenter;
self.qrScanningController = [OWSQRCodeScanningViewController new];
self.qrScanningController.scanDelegate = self;
[self.view addSubview:self.qrScanningController.view];
[self.qrScanningController.view autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[self.qrScanningController.view autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.qrScanningController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view];
[self.qrScanningController.view autoPinToSquareAspectRatio];
UIView *bottomView = [UIView new];
[self.view addSubview:bottomView];
[bottomView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.qrScanningController.view];
[bottomView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[bottomView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[bottomView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
UIStackView *bottomStack = [[UIStackView alloc] initWithArrangedSubviews:@[
heroImageView,
self.scanningInstructionsLabel,
]];
bottomStack.axis = UILayoutConstraintAxisVertical;
bottomStack.alignment = UIStackViewAlignmentCenter;
bottomStack.spacing = 2;
bottomStack.layoutMarginsRelativeArrangement = YES;
bottomStack.layoutMargins = UIEdgeInsetsMake(20, 20, 20, 20);
[bottomView addSubview:bottomStack];
[bottomStack autoPinWidthToSuperview];
[bottomStack autoVCenterInSuperview];
self.title
= NSLocalizedString(@"LINK_NEW_DEVICE_TITLE", "Navigation title when scanning QR code to add new device.");
}
#pragma mark - Dependencies
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (OWSReadReceiptManager *)readReceiptManager
{
return [OWSReadReceiptManager sharedManager];
}
- (id<OWSUDManager>)udManager
{
return SSKEnvironment.shared.udManager;
}
- (TSAccountManager *)tsAccountManager
{
return TSAccountManager.sharedInstance;
}
- (TSSocketManager *)socketManager
{
return SSKEnvironment.shared.socketManager;
}
#pragma mark -
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[UIDevice.currentDevice ows_setOrientation:UIInterfaceOrientationPortrait];
dispatch_async(dispatch_get_main_queue(), ^{
[self.qrScanningController startCapture];
});
}
#pragma mark - OWSQRScannerDelegate
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithString:(NSString *)string
{
OWSDeviceProvisioningURLParser *parser = [[OWSDeviceProvisioningURLParser alloc] initWithProvisioningURL:string];
if (!parser.isValid) {
OWSLogError(@"Unable to parse provisioning params from QRCode: %@", string);
NSString *title = NSLocalizedString(@"LINK_DEVICE_INVALID_CODE_TITLE", @"report an invalid linking code");
NSString *body = NSLocalizedString(@"LINK_DEVICE_INVALID_CODE_BODY", @"report an invalid linking code");
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self popToLinkedDeviceList];
});
}];
[alert addAction:cancelAction];
UIAlertAction *proceedAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"LINK_DEVICE_RESTART", @"attempt another linking")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self.qrScanningController startCapture];
}];
[alert addAction:proceedAction];
[self presentAlert:alert];
} else {
NSString *title = NSLocalizedString(
@"LINK_DEVICE_PERMISSION_ALERT_TITLE", @"confirm the users intent to link a new device");
NSString *linkingDescription
= NSLocalizedString(@"LINK_DEVICE_PERMISSION_ALERT_BODY", @"confirm the users intent to link a new device");
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
message:linkingDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self popToLinkedDeviceList];
});
}];
[alert addAction:cancelAction];
UIAlertAction *proceedAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"CONFIRM_LINK_NEW_DEVICE_ACTION", @"Button text")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self provisionWithParser:parser];
}];
[alert addAction:proceedAction];
[self presentAlert:alert];
}
}
- (void)provisionWithParser:(OWSDeviceProvisioningURLParser *)parser
{
// Optimistically set this flag.
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
OWSAssertDebug(identityKeyPair);
NSData *myPublicKey = identityKeyPair.publicKey;
NSData *myPrivateKey = identityKeyPair.privateKey;
NSString *accountIdentifier = [TSAccountManager localNumber];
NSData *myProfileKeyData = self.profileManager.localProfileKey.keyData;
BOOL areReadReceiptsEnabled = self.readReceiptManager.areReadReceiptsEnabled;
OWSDeviceProvisioner *provisioner = [[OWSDeviceProvisioner alloc] initWithMyPublicKey:myPublicKey
myPrivateKey:myPrivateKey
theirPublicKey:parser.publicKey
theirEphemeralDeviceId:parser.ephemeralDeviceId
accountIdentifier:accountIdentifier
profileKey:myProfileKeyData
readReceiptsEnabled:areReadReceiptsEnabled];
[provisioner
provisionWithSuccess:^{
OWSLogInfo(@"Successfully provisioned device.");
dispatch_async(dispatch_get_main_queue(), ^{
[self.linkedDevicesTableViewController expectMoreDevices];
[self popToLinkedDeviceList];
// The service implementation of the socket connection caches the linked device state,
// so all sync message sends will fail on the socket until it is cycled.
[TSSocketManager.shared cycleSocket];
// Fetch the local profile to determine if all
// linked devices support UD.
[self.profileManager fetchLocalUsersProfile];
});
}
failure:^(NSError *error) {
OWSLogError(@"Failed to provision device with error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[self presentAlert:[self retryAlertControllerWithError:error
retryBlock:^{
[self provisionWithParser:parser];
}]];
});
}];
}
- (UIAlertController *)retryAlertControllerWithError:(NSError *)error retryBlock:(void (^)(void))retryBlock
{
NSString *title = NSLocalizedString(@"LINKING_DEVICE_FAILED_TITLE", @"Alert Title");
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
retryBlock();
}];
[alertController addAction:retryAction];
UIAlertAction *cancelAction =
[UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
}];
[alertController addAction:cancelAction];
return alertController;
}
- (void)popToLinkedDeviceList
{
[self.navigationController popViewControllerWithAnimated:YES
completion:^{
[UIViewController attemptRotationToDeviceOrientation];
}];
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,14 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface OWSLinkedDevicesTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
/**
* This is used to show the user there is a device provisioning in-progress.
*/
- (void)expectMoreDevices;
@end

View File

@ -1,445 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSLinkedDevicesTableViewController.h"
#import "OWSDeviceTableViewCell.h"
#import "OWSLinkDeviceViewController.h"
#import "Session-Swift.h"
#import "UIViewController+Permissions.h"
#import <SessionServiceKit/NSTimer+OWS.h>
#import <SessionServiceKit/OWSDevice.h>
#import <SessionServiceKit/OWSDevicesService.h>
#import <SessionServiceKit/OWSPrimaryStorage.h>
#import <SessionServiceKit/TSDatabaseView.h>
#import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseViewConnection.h>
#import <YapDatabase/YapDatabaseViewMappings.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSLinkedDevicesTableViewController ()
@property (nonatomic) YapDatabaseConnection *dbConnection;
@property (nonatomic) YapDatabaseViewMappings *deviceMappings;
@property (nonatomic) NSTimer *pollingRefreshTimer;
@property (nonatomic) BOOL isExpectingMoreDevices;
@end
int const OWSLinkedDevicesTableViewControllerSectionExistingDevices = 0;
int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
@implementation OWSLinkedDevicesTableViewController
- (void)dealloc
{
OWSLogVerbose(@"");
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = Theme.backgroundColor;
self.title = NSLocalizedString(@"LINKED_DEVICES_TITLE", @"Menu item and navbar title for the device manager");
self.isExpectingMoreDevices = NO;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60;
self.tableView.separatorColor = Theme.cellSeparatorColor;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"AddNewDevice"];
[self.tableView registerClass:[OWSDeviceTableViewCell class] forCellReuseIdentifier:@"ExistingDevice"];
[self.tableView applyScrollViewInsetsFix];
self.dbConnection = [[OWSPrimaryStorage sharedManager] newDatabaseConnection];
[self.dbConnection beginLongLivedReadTransaction];
self.deviceMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ TSSecondaryDevicesGroup ]
view:TSSecondaryDevicesDatabaseViewExtensionName];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.deviceMappings updateWithTransaction:transaction];
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:OWSPrimaryStorage.sharedManager.dbNotificationObject];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModifiedExternally:)
name:YapDatabaseModifiedExternallyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceListUpdateSucceeded:)
name:NSNotificationName_DeviceListUpdateSucceeded
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceListUpdateFailed:)
name:NSNotificationName_DeviceListUpdateFailed
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceListUpdateModifiedDeviceList:)
name:NSNotificationName_DeviceListUpdateModifiedDeviceList
object:nil];
self.refreshControl = [UIRefreshControl new];
[self.refreshControl addTarget:self action:@selector(refreshDevices) forControlEvents:UIControlEventValueChanged];
[self setupEditButton];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self refreshDevices];
NSIndexPath *_Nullable selectedPath = [self.tableView indexPathForSelectedRow];
if (selectedPath) {
// HACK to unselect rows when swiping back
// http://stackoverflow.com/questions/19379510/uitableviewcell-doesnt-get-deselected-when-swiping-back-quickly
[self.tableView deselectRowAtIndexPath:selectedPath animated:animated];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.pollingRefreshTimer invalidate];
}
// Don't show edit button for an empty table
- (void)setupEditButton
{
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
if ([OWSDevice hasSecondaryDevicesWithTransaction:transaction]) {
self.navigationItem.rightBarButtonItem = self.editButtonItem;
} else {
self.navigationItem.rightBarButtonItem = nil;
}
}];
}
- (void)expectMoreDevices
{
self.isExpectingMoreDevices = YES;
// When you delete and re-add a device, you will be returned to this view in editing mode, making your newly
// added device appear with a delete icon. Probably not what you want.
self.editing = NO;
__weak typeof(self) wself = self;
[self.pollingRefreshTimer invalidate];
self.pollingRefreshTimer = [NSTimer weakScheduledTimerWithTimeInterval:(10.0)target:wself
selector:@selector(refreshDevices)
userInfo:nil
repeats:YES];
NSString *progressText = NSLocalizedString(@"WAITING_TO_COMPLETE_DEVICE_LINK_TEXT",
@"Activity indicator title, shown upon returning to the device "
@"manager, until you complete the provisioning process on desktop");
NSAttributedString *progressTitle = [[NSAttributedString alloc] initWithString:progressText];
// HACK to get refreshControl title to align properly.
self.refreshControl.attributedTitle = progressTitle;
[self.refreshControl endRefreshing];
dispatch_async(dispatch_get_main_queue(), ^{
self.refreshControl.attributedTitle = progressTitle;
[self.refreshControl beginRefreshing];
// Needed to show refresh control programatically
[self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:NO];
});
// END HACK to get refreshControl title to align properly.
}
- (void)refreshDevices
{
[OWSDevicesService refreshDevices];
}
- (void)deviceListUpdateSucceeded:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self.refreshControl endRefreshing];
}
- (void)deviceListUpdateFailed:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
NSError *error = notification.object;
OWSAssertDebug(error);
NSString *alertTitle = NSLocalizedString(
@"DEVICE_LIST_UPDATE_FAILED_TITLE", @"Alert title that can occur when viewing device manager.");
UIAlertController *alert = [UIAlertController alertControllerWithTitle:alertTitle
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self refreshDevices];
}];
[alert addAction:retryAction];
UIAlertAction *dismissAction =
[UIAlertAction actionWithTitle:CommonStrings.dismissButton style:UIAlertActionStyleCancel handler:nil];
[alert addAction:dismissAction];
[self.refreshControl endRefreshing];
[self presentAlert:alert];
}
- (void)deviceListUpdateModifiedDeviceList:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
// Got our new device, we can stop refreshing.
self.isExpectingMoreDevices = NO;
[self.pollingRefreshTimer invalidate];
dispatch_async(dispatch_get_main_queue(), ^{
self.refreshControl.attributedTitle = nil;
});
}
#pragma mark - Table view data source
- (void)yapDatabaseModifiedExternally:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
// External database modifications can't be converted into incremental updates,
// so rebuild everything. This is expensive and usually isn't necessary, but
// there's no alternative.
[self.dbConnection beginLongLivedReadTransaction];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.deviceMappings updateWithTransaction:transaction];
}];
[self.tableView reloadData];
}
- (void)yapDatabaseModified:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogVerbose(@"");
NSArray *notifications = [self.dbConnection beginLongLivedReadTransaction];
[self setupEditButton];
if ([notifications count] == 0) {
return; // already processed commit
}
NSArray *rowChanges;
[[self.dbConnection ext:TSSecondaryDevicesDatabaseViewExtensionName] getSectionChanges:nil
rowChanges:&rowChanges
forNotifications:notifications
withMappings:self.deviceMappings];
if (rowChanges.count == 0) {
// There aren't any changes that affect our tableView!
return;
}
[self.tableView beginUpdates];
for (YapDatabaseViewRowChange *rowChange in rowChanges) {
switch (rowChange.type) {
case YapDatabaseViewChangeDelete: {
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case YapDatabaseViewChangeInsert: {
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case YapDatabaseViewChangeMove: {
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case YapDatabaseViewChangeUpdate: {
[self.tableView reloadRowsAtIndexPaths:@[ rowChange.indexPath ]
withRowAnimation:UITableViewRowAnimationNone];
break;
}
}
}
[self.tableView endUpdates];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (section) {
case OWSLinkedDevicesTableViewControllerSectionExistingDevices:
return (NSInteger)[self.deviceMappings numberOfItemsInSection:(NSUInteger)section];
case OWSLinkedDevicesTableViewControllerSectionAddDevice:
return 1;
default:
OWSLogError(@"Unknown section: %ld", (long)section);
return 0;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionAddDevice) {
[self ows_askForCameraPermissions:^(BOOL granted) {
if (!granted) {
return;
}
[self showLinkNewDeviceView];
}];
}
}
- (void)showLinkNewDeviceView
{
OWSLinkDeviceViewController *vc = [OWSLinkDeviceViewController new];
vc.linkedDevicesTableViewController = self;
[self.navigationController pushViewController:vc animated:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionAddDevice) {
UITableViewCell *cell =
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"AddNewDevice"];
[OWSTableItem configureCell:cell];
cell.textLabel.text
= NSLocalizedString(@"LINK_NEW_DEVICE_TITLE", @"Navigation title when scanning QR code to add new device.");
cell.detailTextLabel.text
= NSLocalizedString(@"LINK_NEW_DEVICE_SUBTITLE", @"Subheading for 'Link New Device' navigation");
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(OWSLinkedDevicesTableViewController, @"add");
return cell;
} else if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionExistingDevices) {
OWSDeviceTableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@"ExistingDevice" forIndexPath:indexPath];
OWSDevice *device = [self deviceForRowAtIndexPath:indexPath];
[cell configureWithDevice:device];
return cell;
} else {
OWSLogError(@"Unknown section: %@", indexPath);
return [UITableViewCell new];
}
}
- (nullable OWSDevice *)deviceForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionExistingDevices) {
__block OWSDevice *device;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
device = [[transaction extension:TSSecondaryDevicesDatabaseViewExtensionName]
objectAtIndexPath:indexPath
withMappings:self.deviceMappings];
}];
return device;
}
return nil;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return indexPath.section == OWSLinkedDevicesTableViewControllerSectionExistingDevices;
}
- (nullable NSString *)tableView:(UITableView *)tableView
titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
return NSLocalizedString(@"UNLINK_ACTION", "button title for unlinking a device");
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
OWSDevice *device = [self deviceForRowAtIndexPath:indexPath];
[self
touchedUnlinkControlForDevice:device
success:^{
OWSLogInfo(@"Removing unlinked device with deviceId: %ld", (long)device.deviceId);
[device remove];
}];
}
}
- (void)touchedUnlinkControlForDevice:(OWSDevice *)device success:(void (^)(void))successCallback
{
NSString *confirmationTitleFormat
= NSLocalizedString(@"UNLINK_CONFIRMATION_ALERT_TITLE", @"Alert title for confirming device deletion");
NSString *confirmationTitle = [NSString stringWithFormat:confirmationTitleFormat, device.displayName];
NSString *confirmationMessage
= NSLocalizedString(@"UNLINK_CONFIRMATION_ALERT_BODY", @"Alert message to confirm unlinking a device");
UIAlertController *alert = [UIAlertController alertControllerWithTitle:confirmationTitle
message:confirmationMessage
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[OWSAlerts cancelAction]];
UIAlertAction *unlinkAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"UNLINK_ACTION", "button title for unlinking a device")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self unlinkDevice:device success:successCallback];
});
}];
[alert addAction:unlinkAction];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentAlert:alert];
});
}
- (void)unlinkDevice:(OWSDevice *)device success:(void (^)(void))successCallback
{
[OWSDevicesService unlinkDevice:device
success:successCallback
failure:^(NSError *error) {
NSString *title = NSLocalizedString(
@"UNLINKING_FAILED_ALERT_TITLE", @"Alert title when unlinking device fails");
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction =
[UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *aaction) {
[self unlinkDevice:device success:successCallback];
}];
[alert addAction:retryAction];
[alert addAction:[OWSAlerts cancelAction]];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentAlert:alert];
});
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -4,7 +4,6 @@
#import "PrivacySettingsTableViewController.h"
#import "BlockListViewController.h"
#import "OWS2FASettingsViewController.h"
#import "Session-Swift.h"
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSPreferences.h>
@ -555,11 +554,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
- (void)show2FASettings
{
OWSLogInfo(@"");
OWS2FASettingsViewController *vc = [OWS2FASettingsViewController new];
vc.mode = OWS2FASettingsMode_Status;
[self.navigationController pushViewController:vc animated:YES];
}
- (void)isScreenLockEnabledDidChange:(UISwitch *)sender

View File

@ -19,7 +19,6 @@
#import "DebugUITableViewController.h"
#import "FingerprintViewController.h"
#import "NSAttributedString+OWS.h"
#import "NewGroupViewController.h"
#import "OWSAudioPlayer.h"
#import "OWSContactOffersCell.h"
#import "OWSConversationSettingsViewController.h"

View File

@ -105,16 +105,6 @@ NS_ASSUME_NONNULL_BEGIN
}]];
}
[items addObject:[OWSTableItem itemWithTitle:@"Show 2FA Reminder"
actionBlock:^() {
OWSNavigationController *navController =
[OWS2FAReminderViewController wrappedInNavController];
[[[UIApplication sharedApplication] frontmostViewController]
presentViewController:navController
animated:YES
completion:nil];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"Reset 2FA Repetition Interval"
actionBlock:^() {
[OWS2FAManager.sharedManager setDefaultRepetitionInterval];
@ -165,21 +155,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)reregister
{
OWSLogInfo(@"re-registering.");
if (![[TSAccountManager sharedInstance] resetForReregistration]) {
OWSFailDebug(@"could not reset for re-registration.");
return;
}
[Environment.shared.preferences unsetRecordedAPNSTokens];
UIViewController *viewController = [[OnboardingController new] initialViewController];
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:viewController];
navigationController.navigationBarHidden = YES;
[UIApplication sharedApplication].delegate.window.rootViewController = navigationController;
}
+ (void)setManualCensorshipCircumventionEnabled:(BOOL)isEnabled

View File

@ -1,914 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalMessaging
private class IntroducingCustomNotificationAudioExperienceUpgradeViewController: ExperienceUpgradeViewController {
var primaryButtonAction: ((UIButton) -> Void)?
override func loadView() {
self.view = UIView.container()
/// Create Views
// Title label
let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.text = header
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
titleLabel.textColor = UIColor.white
titleLabel.minimumScaleFactor = 0.5
titleLabel.adjustsFontSizeToFitWidth = true
// Body label
let bodyLabel = UILabel()
self.bodyLabel = bodyLabel
view.addSubview(bodyLabel)
bodyLabel.text = body
bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
bodyLabel.textColor = Theme.primaryColor
bodyLabel.numberOfLines = 0
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.textAlignment = .center
// Image
let imageView = UIImageView(image: image)
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_NOTIFICATION_AUDIO_SETTINGS_BUTTON", comment: "button label shown one time, after upgrade")
let button = addPrimaryButton(title: buttonTitle) { _ in
// dismiss the modally presented view controller, then proceed.
self.experienceUpgradesPageViewController.dismiss(animated: true) {
guard let fromViewController = UIApplication.shared.frontmostViewController else {
owsFailDebug("frontmostViewController was unexectedly nil")
return
}
// Construct the "settings" view & push the "notifications settings" view.
let navigationController = AppSettingsViewController.inModalNavigationController()
navigationController.pushViewController(NotificationSettingsViewController(), animated: false)
fromViewController.present(navigationController, animated: true)
}
}
let bottomSpacer = UIView()
view.addSubview(bottomSpacer)
/// Layout Views
// Image layout
imageView.autoAlignAxis(toSuperviewAxis: .vertical)
imageView.autoPinToSquareAspectRatio()
imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))
// Title label layout
titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
titleLabel.autoPinTopToSuperviewMargin()
// Body label layout
bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
bodyLabel.setContentHuggingVerticalHigh()
// Button layout
button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(16))
button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))
bottomSpacer.autoPinEdge(.top, to: .bottom, of: button, withOffset: ScaleFromIPhone5(16))
bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom)
bottomSpacer.autoPinWidthToSuperview()
}
// MARK: - Actions
func addPrimaryButton(title: String, action: @escaping (UIButton) -> Void) -> UIButton {
self.primaryButtonAction = action
let button = MultiLineButton()
view.addSubview(button)
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
button.titleLabel?.textAlignment = .center
button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))
return button
}
@objc func didTapButton(sender: UIButton) {
Logger.debug("")
guard let primaryButtonAction = self.primaryButtonAction else {
owsFailDebug("button action was nil")
return
}
primaryButtonAction(sender)
}
}
private class IntroductingReadReceiptsExperienceUpgradeViewController: ExperienceUpgradeViewController {
var primaryButtonAction: ((UIButton) -> Void)?
override func loadView() {
self.view = UIView.container()
/// Create Views
// Title label
let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.text = header
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
titleLabel.textColor = UIColor.white
titleLabel.minimumScaleFactor = 0.5
titleLabel.adjustsFontSizeToFitWidth = true
// Body label
let bodyLabel = UILabel()
self.bodyLabel = bodyLabel
view.addSubview(bodyLabel)
bodyLabel.text = body
bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
bodyLabel.textColor = Theme.primaryColor
bodyLabel.numberOfLines = 0
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.textAlignment = .center
// Image
let imageView = UIImageView(image: image)
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_READ_RECEIPTS_PRIVACY_SETTINGS", comment: "button label shown one time, after upgrade")
let button = addPrimaryButton(title: buttonTitle) { _ in
// dismiss the modally presented view controller, then proceed.
self.experienceUpgradesPageViewController.dismiss(animated: true) {
guard let fromViewController = UIApplication.shared.frontmostViewController as? HomeViewController else {
owsFailDebug("unexpected frontmostViewController: \(String(describing: UIApplication.shared.frontmostViewController))")
return
}
// Construct the "settings" view & push the "privacy settings" view.
let navigationController = AppSettingsViewController.inModalNavigationController()
navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false)
fromViewController.present(navigationController, animated: true)
}
}
let bottomSpacer = UIView()
view.addSubview(bottomSpacer)
/// Layout Views
// Image layout
imageView.autoAlignAxis(toSuperviewAxis: .vertical)
imageView.autoPinToSquareAspectRatio()
imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))
// Title label layout
titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
titleLabel.autoPinTopToSuperviewMargin()
// Body label layout
bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
bodyLabel.setContentHuggingVerticalHigh()
// Button layout
button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(16))
button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))
bottomSpacer.autoPinEdge(.top, to: .bottom, of: button, withOffset: ScaleFromIPhone5(16))
bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom)
bottomSpacer.autoPinWidthToSuperview()
}
// MARK: - Actions
func addPrimaryButton(title: String, action: @escaping (UIButton) -> Void) -> UIButton {
self.primaryButtonAction = action
let button = MultiLineButton()
view.addSubview(button)
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
button.titleLabel?.textAlignment = .center
button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))
return button
}
@objc func didTapButton(sender: UIButton) {
Logger.debug("")
guard let primaryButtonAction = self.primaryButtonAction else {
owsFailDebug("button action was nil")
return
}
primaryButtonAction(sender)
}
}
private class IntroductingTypingIndicatorsExperienceUpgradeViewController: ExperienceUpgradeViewController {
var primaryButtonAction: ((UIButton) -> Void)?
var typingIndicators: TypingIndicators {
return SSKEnvironment.shared.typingIndicators
}
override func loadView() {
self.view = UIView.container()
/// Create Views
// Title label
let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.text = header
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
titleLabel.textColor = UIColor.white
titleLabel.minimumScaleFactor = 0.5
titleLabel.adjustsFontSizeToFitWidth = true
// Body label
let bodyLabel = UILabel()
self.bodyLabel = bodyLabel
view.addSubview(bodyLabel)
bodyLabel.text = body
bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
bodyLabel.textColor = Theme.primaryColor
bodyLabel.numberOfLines = 0
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.textAlignment = .center
// Image
let imageView: UIView
let imageName = Theme.isDarkThemeEnabled ? "typing-animation-dark" : "typing-animation"
if let gifPath = Bundle.main.path(forResource: imageName, ofType: "gif") {
let animatedImage = YYImage(contentsOfFile: gifPath)
imageView = YYAnimatedImageView(image: animatedImage)
} else {
owsFailDebug("gifPath was unexpectedly nil")
imageView = UIImageView(image: image)
}
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_ENABLE_TYPING_INDICATOR_BUTTON", comment: "button label shown one time, after upgrade")
let button = addPrimaryButton(title: buttonTitle) { _ in
self.typingIndicators.setTypingIndicatorsEnabled(value: true)
self.experienceUpgradesPageViewController.dismiss(animated: true)
}
let bottomSpacer = UIView()
view.addSubview(bottomSpacer)
/// Layout Views
// Image layout
imageView.autoAlignAxis(toSuperviewAxis: .vertical)
imageView.autoPinToSquareAspectRatio()
imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
imageView.autoSetDimension(.width, toSize: ScaleFromIPhone5(180))
// Title label layout
titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
titleLabel.autoPinTopToSuperviewMargin()
// Body label layout
bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
bodyLabel.setContentHuggingVerticalHigh()
// Button layout
button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(16))
button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))
bottomSpacer.autoPinEdge(.top, to: .bottom, of: button, withOffset: ScaleFromIPhone5(16))
bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom)
bottomSpacer.autoPinWidthToSuperview()
}
// MARK: - Actions
func addPrimaryButton(title: String, action: @escaping (UIButton) -> Void) -> UIButton {
self.primaryButtonAction = action
let button = MultiLineButton()
view.addSubview(button)
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
button.titleLabel?.textAlignment = .center
button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))
return button
}
@objc func didTapButton(sender: UIButton) {
Logger.debug("")
guard let primaryButtonAction = self.primaryButtonAction else {
owsFailDebug("button action was nil")
return
}
primaryButtonAction(sender)
}
}
private class IntroducingLinkPreviewsExperienceUpgradeViewController: ExperienceUpgradeViewController {
override func loadView() {
self.view = UIView.container()
/// Create Views
// Title label
let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.text = header
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
titleLabel.textColor = UIColor.white
titleLabel.minimumScaleFactor = 0.5
titleLabel.adjustsFontSizeToFitWidth = true
// Body label
let bodyLabel = UILabel()
self.bodyLabel = bodyLabel
view.addSubview(bodyLabel)
bodyLabel.text = body
bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
bodyLabel.textColor = Theme.primaryColor
bodyLabel.numberOfLines = 0
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.textAlignment = .center
// Subtitle label
let subtitleLabel = UILabel()
view.addSubview(subtitleLabel)
subtitleLabel.text = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_LINK_PREVIEWS_SUBTITLE", comment: "Subtitle for upgrading users")
subtitleLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(14, 17))
subtitleLabel.textColor = Theme.primaryColor
subtitleLabel.numberOfLines = 0
subtitleLabel.lineBreakMode = .byWordWrapping
subtitleLabel.textAlignment = .center
// Image
let imageView = UIImageView(image: image)
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
let bottomSpacer = UIView()
view.addSubview(bottomSpacer)
/// Layout Views
// Image layout
imageView.autoAlignAxis(toSuperviewAxis: .vertical)
imageView.autoPinToSquareAspectRatio()
imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
imageView.autoSetDimension(.width, toSize: ScaleFromIPhone5(180))
// Title label layout
titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
titleLabel.autoPinTopToSuperviewMargin()
// Body label layout
bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
bodyLabel.setContentHuggingVerticalHigh()
// Subtitle label layout
subtitleLabel.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5To7Plus(18, 28))
subtitleLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
subtitleLabel.setContentHuggingVerticalHigh()
// Bottom Spacer layout
bottomSpacer.autoPinEdge(.top, to: .bottom, of: subtitleLabel, withOffset: ScaleFromIPhone5(16))
bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom)
bottomSpacer.autoPinWidthToSuperview()
}
}
/**
* Allows multiple lines of button text, and ensures the buttons intrinsic content size reflects that of it's label.
*/
class MultiLineButton: UIButton {
// MARK: - Init
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
required init() {
super.init(frame: CGRect.zero)
self.commonInit()
}
private func commonInit() {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.lineBreakMode = .byWordWrapping
}
// MARK: - Overrides
override var intrinsicContentSize: CGSize {
guard let titleLabel = titleLabel else {
return CGSize.zero
}
// be more forgiving with the tappable area
let extraPadding: CGFloat = 20
let labelSize = titleLabel.intrinsicContentSize
return CGSize(width: labelSize.width + extraPadding, height: labelSize.height + extraPadding)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = titleLabel?.frame.size.width ?? 0
super.layoutSubviews()
}
}
private class IntroductingProfilesExperienceUpgradeViewController: ExperienceUpgradeViewController {
override func loadView() {
self.view = UIView()
/// Create Views
// Title label
let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.text = header
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
titleLabel.textColor = UIColor.white
titleLabel.minimumScaleFactor = 0.5
titleLabel.adjustsFontSizeToFitWidth = true
// Body label
let bodyLabel = UILabel()
self.bodyLabel = bodyLabel
view.addSubview(bodyLabel)
bodyLabel.text = body
bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
bodyLabel.textColor = Theme.primaryColor
bodyLabel.numberOfLines = 0
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.textAlignment = .center
// Image
let imageView = UIImageView(image: image)
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
// Button
let button = UIButton()
view.addSubview(button)
let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_PROFILES_BUTTON", comment: "button label shown one time, after user upgrades app")
button.setTitle(buttonTitle, for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.backgroundColor = UIColor.ows_materialBlue
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18))
/// Layout Views
// Image layout
imageView.autoAlignAxis(toSuperviewAxis: .vertical)
imageView.autoPinToSquareAspectRatio()
imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40))
imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))
// Title label layout
titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
titleLabel.autoPinEdge(toSuperviewEdge: .top)
// Body label layout
bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
bodyLabel.sizeToFit()
// Button layout
button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(18))
button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32))
button.autoPinEdge(toSuperviewEdge: .bottom, withInset: ScaleFromIPhone5(16))
button.autoSetDimension(.height, toSize: ScaleFromIPhone5(36))
}
// MARK: - Actions
@objc func didTapButton(sender: UIButton) {
Logger.debug("")
// dismiss the modally presented view controller, then proceed.
experienceUpgradesPageViewController.dismiss(animated: true) {
guard let fromViewController = UIApplication.shared.frontmostViewController as? HomeViewController else {
owsFailDebug("unexpected frontmostViewController: \(String(describing: UIApplication.shared.frontmostViewController))")
return
}
ProfileViewController.presentForUpgradeOrNag(from: fromViewController)
}
}
}
private class CallKitExperienceUpgradeViewController: ExperienceUpgradeViewController {
override func loadView() {
super.loadView()
assert(view != nil)
assert(bodyLabel != nil)
// Privacy Settings Button
let privacySettingsButton = UIButton()
view.addSubview(privacySettingsButton)
let privacyTitle = NSLocalizedString("UPGRADE_EXPERIENCE_CALLKIT_PRIVACY_SETTINGS_BUTTON", comment: "button label shown once when when user upgrades app, in context of call kit")
privacySettingsButton.setTitle(privacyTitle, for: .normal)
privacySettingsButton.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
privacySettingsButton.isUserInteractionEnabled = true
privacySettingsButton.addTarget(self, action: #selector(didTapPrivacySettingsButton), for: .touchUpInside)
privacySettingsButton.titleLabel?.font = bodyLabel.font
// Privacy Settings Button layout
privacySettingsButton.autoPinWidthToSuperview(withMargin: bodyMargin)
privacySettingsButton.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(12))
privacySettingsButton.sizeToFit()
}
// MARK: - Actions
@objc func didTapPrivacySettingsButton(sender: UIButton) {
Logger.debug("")
// dismiss the modally presented view controller, then proceed.
experienceUpgradesPageViewController.dismiss(animated: true) {
DispatchQueue.main.async {
guard let fromViewController = UIApplication.shared.frontmostViewController else {
owsFailDebug("fromViewController was unexpectedly nil")
return
}
// Construct the "settings" view & push the "privacy settings" view.
let navigationController = AppSettingsViewController.inModalNavigationController()
navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false)
fromViewController.present(navigationController, animated: true, completion: nil)
}
}
}
}
private class ExperienceUpgradeViewController: OWSViewController {
let header: String
let body: String
let image: UIImage?
let experienceUpgradesPageViewController: ExperienceUpgradesPageViewController
var bodyLabel: UILabel!
let bodyMargin = ScaleFromIPhone5To7Plus(12, 24)
init(experienceUpgrade: ExperienceUpgrade, experienceUpgradesPageViewController: ExperienceUpgradesPageViewController) {
header = experienceUpgrade.title
body = experienceUpgrade.body
image = experienceUpgrade.image
self.experienceUpgradesPageViewController = experienceUpgradesPageViewController
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
override func loadView() {
self.view = UIView()
/// Create Views
// Title label
let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.text = header
titleLabel.textAlignment = .center
titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24))
titleLabel.textColor = UIColor.white
titleLabel.minimumScaleFactor = 0.5
titleLabel.adjustsFontSizeToFitWidth = true
// Body label
let bodyLabel = UILabel()
self.bodyLabel = bodyLabel
view.addSubview(bodyLabel)
bodyLabel.text = body
bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22))
bodyLabel.textColor = Theme.primaryColor
bodyLabel.numberOfLines = 0
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.textAlignment = .center
// Image
let imageView = UIImageView(image: image)
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
/// Layout Views
// Image layout
imageView.autoAlignAxis(toSuperviewAxis: .vertical)
imageView.autoPinToSquareAspectRatio()
imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 60))
imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5(225))
// Title label layout
titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40))
titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24))
titleLabel.autoPinEdge(toSuperviewEdge: .top)
// Body label layout
bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28))
bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin)
bodyLabel.sizeToFit()
bodyLabel.autoPinEdge(toSuperviewEdge: .bottom, withInset: ScaleFromIPhone5(16))
}
}
func setPageControlAppearance() {
let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self])
pageControl.pageIndicatorTintColor = UIColor.lightGray
pageControl.currentPageIndicatorTintColor = UIColor.ows_materialBlue
}
@objc
public class ExperienceUpgradesPageViewController: OWSViewController, UIPageViewControllerDataSource {
private let experienceUpgrades: [ExperienceUpgrade]
private var allViewControllers = [UIViewController]()
private var viewControllerIndexes = [UIViewController: Int]()
let pageViewController: UIPageViewController
let editingDBConnection: YapDatabaseConnection
// MARK: - Initializers
@objc
public required init(experienceUpgrades: [ExperienceUpgrade]) {
self.experienceUpgrades = experienceUpgrades
setPageControlAppearance()
self.editingDBConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
super.init(nibName: nil, bundle: nil)
self.pageViewController.dataSource = self
experienceUpgrades.forEach { addViewController(experienceUpgrade: $0) }
}
@available(*, unavailable, message:"unavailable, use initWithExperienceUpgrade instead")
@objc
public required init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: - View lifecycle
@objc public override func viewDidLoad() {
guard let firstViewController = allViewControllers.first else {
owsFailDebug("no pages to show.")
dismiss(animated: true)
return
}
addDismissGesture()
self.pageViewController.setViewControllers([ firstViewController ], direction: .forward, animated: false, completion: nil)
}
func dismissButtonTitle() -> String {
// This should be true for "Opt-in" features/upgrades.
let useNotNowButton = false
if useNotNowButton {
return NSLocalizedString("EXPERIENCE_UPGRADE_DISMISS_BUTTON",
comment: "Button to dismiss/ignore the one time splash screen that appears after upgrading")
} else {
return NSLocalizedString("OK", comment: "")
}
}
@objc public override func loadView() {
self.view = UIView.container()
view.backgroundColor = Theme.backgroundColor
//// Create Views
// Header Background
let statusBarBackgroundView = UIView.container()
view.addSubview(statusBarBackgroundView)
statusBarBackgroundView.backgroundColor = UIColor.ows_materialBlue
let headerBackgroundView = UIView.container()
view.addSubview(headerBackgroundView)
headerBackgroundView.backgroundColor = UIColor.ows_materialBlue
// Dismiss button
let dismissButton = UIButton()
view.addSubview(dismissButton)
dismissButton.setTitle(dismissButtonTitle(), for: .normal)
dismissButton.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal)
dismissButton.isUserInteractionEnabled = true
dismissButton.addTarget(self, action: #selector(didTapDismissButton), for: .touchUpInside)
dismissButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(16))
let dismissInsetValue: CGFloat = ScaleFromIPhone5(10)
dismissButton.contentEdgeInsets = UIEdgeInsets(top: dismissInsetValue, left: dismissInsetValue, bottom: dismissInsetValue, right: dismissInsetValue)
guard let carouselView = self.pageViewController.view else {
Logger.error("carousel view was unexpectedly nil")
return
}
self.view.addSubview(carouselView)
//// Layout Views
// Status Bar Background layout
//
// We use a separate backgrounds for the "status bar" area (which has very
// different height on iPhone X) and the "header" (which has consistent
// height).
statusBarBackgroundView.autoPinWidthToSuperview()
statusBarBackgroundView.autoPinEdge(toSuperviewEdge: .top)
statusBarBackgroundView.autoPinEdge(.bottom, to: .top, of: headerBackgroundView)
// Header Background layout
statusBarBackgroundView.autoPinWidthToSuperview()
statusBarBackgroundView.autoPinEdge(toSuperviewEdge: .top)
statusBarBackgroundView.autoPinEdge(.bottom, to: .top, of: headerBackgroundView)
headerBackgroundView.autoPinWidthToSuperview()
headerBackgroundView.autoPinTopToSuperviewMargin()
headerBackgroundView.autoSetDimension(.height, toSize: ScaleFromIPhone5(60))
// Dismiss button layout
dismissButton.autoHCenterInSuperview()
dismissButton.autoPinBottomToSuperviewMargin(withInset: ScaleFromIPhone5(10))
// Carousel View layout
carouselView.autoPinWidthToSuperview()
// negative inset so as to overlay the header text in the carousel view with the header background which
// lives outside of the carousel. We do this so that the user can't bounce past the page view controllers
// width limits, exposing the edge of the header.
carouselView.autoPinTopToSuperviewMargin(withInset: ScaleFromIPhone5To7Plus(14, 24))
carouselView.autoPinEdge(.bottom, to: .top, of: dismissButton, withOffset: ScaleFromIPhone5(-10))
}
private func addDismissGesture() {
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleDismissGesture))
swipeGesture.direction = .down
view.addGestureRecognizer(swipeGesture)
}
// MARK: - UIPageViewControllerDataSource
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
Logger.debug("")
guard let currentIndex = self.viewControllerIndexes[viewController] else {
owsFailDebug("unknown view controller: \(viewController)")
return nil
}
if currentIndex + 1 == allViewControllers.count {
// already at last view controller
return nil
}
return allViewControllers[currentIndex + 1]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
Logger.debug("")
guard let currentIndex = self.viewControllerIndexes[viewController] else {
owsFailDebug("unknown view controller: \(viewController)")
return nil
}
if currentIndex <= 0 {
// already at first view controller
return nil
}
return allViewControllers[currentIndex - 1]
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int {
// don't show a page indicator if there's only one page.
return allViewControllers.count == 1 ? 0 : allViewControllers.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let currentViewController = pageViewController.viewControllers?.first else {
Logger.error("unexpectedly empty view controllers.")
return 0
}
guard let currentIndex = self.viewControllerIndexes[currentViewController] else {
Logger.error("unknown view controller: \(currentViewController)")
return 0
}
return currentIndex
}
public func addViewController(experienceUpgrade: ExperienceUpgrade) {
guard let uniqueId = experienceUpgrade.uniqueId else {
Logger.error("experienceUpgrade is missing uniqueId.")
return
}
guard let identifier = ExperienceUpgradeId(rawValue: uniqueId) else {
owsFailDebug("unknown experience upgrade. skipping")
return
}
let viewController: ExperienceUpgradeViewController = {
switch identifier {
case .callKit:
return CallKitExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
case .introducingProfiles:
return IntroductingProfilesExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
case .introducingReadReceipts:
return IntroductingReadReceiptsExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
case .introducingCustomNotificationAudio:
return IntroducingCustomNotificationAudioExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
case .introducingTypingIndicators:
return IntroductingTypingIndicatorsExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
case .introducingLinkPreviews:
return IntroducingLinkPreviewsExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
default:
return ExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self)
}
}()
let count = allViewControllers.count
viewControllerIndexes[viewController] = count
allViewControllers.append(viewController)
}
@objc public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Blocking write before dismiss, to be sure they're marked as complete
// before HomeView.didAppear is re-fired.
try! Storage.writeSync { transaction in
Logger.info("marking all upgrades as seen.")
ExperienceUpgradeFinder.shared.markAllAsSeen(transaction: transaction)
}
super.dismiss(animated: flag, completion: completion)
}
@objc func didTapDismissButton(sender: UIButton) {
Logger.debug("")
self.dismiss(animated: true)
}
@objc func handleDismissGesture(sender: AnyObject) {
Logger.debug("")
self.dismiss(animated: true)
}
// MARK: Orientation
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
}

View File

@ -1,471 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
protocol ConversationSearchViewDelegate: class {
func conversationSearchViewWillBeginDragging()
}
@objc
class ConversationSearchViewController: UITableViewController, BlockListCacheDelegate {
@objc
public weak var delegate: ConversationSearchViewDelegate?
@objc
public var searchText = "" {
didSet {
AssertIsOnMainThread()
// Use a slight delay to debounce updates.
refreshSearchResults()
}
}
var searchResultSet: HomeScreenSearchResultSet = HomeScreenSearchResultSet.empty {
didSet {
AssertIsOnMainThread()
updateSeparators()
}
}
var uiDatabaseConnection: YapDatabaseConnection {
return OWSPrimaryStorage.shared().uiDatabaseConnection
}
var searcher: FullTextSearcher {
return FullTextSearcher.shared
}
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
enum SearchSection: Int {
case noResults
case conversations
case contacts
case messages
}
private var hasThemeChanged = false
var blockListCache: BlockListCache!
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
blockListCache = BlockListCache()
blockListCache.startObservingAndSyncState(delegate: self)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 60
tableView.separatorColor = Theme.cellSeparatorColor
tableView.register(EmptySearchResultCell.self, forCellReuseIdentifier: EmptySearchResultCell.reuseIdentifier)
tableView.register(HomeViewCell.self, forCellReuseIdentifier: HomeViewCell.cellReuseIdentifier())
tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier())
NotificationCenter.default.addObserver(self,
selector: #selector(uiDatabaseModified),
name: .OWSUIDatabaseConnectionDidUpdate,
object: OWSPrimaryStorage.shared().dbNotificationObject)
NotificationCenter.default.addObserver(self,
selector: #selector(themeDidChange),
name: NSNotification.Name.ThemeDidChange,
object: nil)
applyTheme()
updateSeparators()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard hasThemeChanged else {
return
}
hasThemeChanged = false
applyTheme()
self.tableView.reloadData()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc internal func uiDatabaseModified(notification: NSNotification) {
AssertIsOnMainThread()
refreshSearchResults()
}
@objc internal func themeDidChange(notification: NSNotification) {
AssertIsOnMainThread()
applyTheme()
self.tableView.reloadData()
hasThemeChanged = true
}
private func applyTheme() {
AssertIsOnMainThread()
self.view.backgroundColor = Theme.backgroundColor
self.tableView.backgroundColor = Theme.backgroundColor
}
private func updateSeparators() {
AssertIsOnMainThread()
self.tableView.separatorStyle = (searchResultSet.isEmpty
? UITableViewCell.SeparatorStyle.none
: UITableViewCell.SeparatorStyle.singleLine)
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
guard let searchSection = SearchSection(rawValue: indexPath.section) else {
owsFailDebug("unknown section selected.")
return
}
switch searchSection {
case .noResults:
owsFailDebug("shouldn't be able to tap 'no results' section")
case .conversations:
let sectionResults = searchResultSet.conversations
guard let searchResult = sectionResults[safe: indexPath.row] else {
owsFailDebug("unknown row selected.")
return
}
let thread = searchResult.thread
SignalApp.shared().presentConversation(for: thread.threadRecord, action: .compose, animated: true)
case .contacts:
let sectionResults = searchResultSet.contacts
guard let searchResult = sectionResults[safe: indexPath.row] else {
owsFailDebug("unknown row selected.")
return
}
SignalApp.shared().presentConversation(forRecipientId: searchResult.recipientId, action: .compose, animated: true)
case .messages:
let sectionResults = searchResultSet.messages
guard let searchResult = sectionResults[safe: indexPath.row] else {
owsFailDebug("unknown row selected.")
return
}
let thread = searchResult.thread
SignalApp.shared().presentConversation(for: thread.threadRecord,
action: .none,
focusMessageId: searchResult.messageId,
animated: true)
}
}
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let searchSection = SearchSection(rawValue: section) else {
owsFailDebug("unknown section: \(section)")
return 0
}
switch searchSection {
case .noResults:
return searchResultSet.isEmpty ? 1 : 0
case .conversations:
return searchResultSet.conversations.count
case .contacts:
return searchResultSet.contacts.count
case .messages:
return searchResultSet.messages.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let searchSection = SearchSection(rawValue: indexPath.section) else {
return UITableViewCell()
}
switch searchSection {
case .noResults:
guard let cell = tableView.dequeueReusableCell(withIdentifier: EmptySearchResultCell.reuseIdentifier) as? EmptySearchResultCell else {
owsFailDebug("cell was unexpectedly nil")
return UITableViewCell()
}
guard indexPath.row == 0 else {
owsFailDebug("searchResult was unexpected index")
return UITableViewCell()
}
OWSTableItem.configureCell(cell)
let searchText = self.searchResultSet.searchText
cell.configure(searchText: searchText)
return cell
case .conversations:
guard let cell = tableView.dequeueReusableCell(withIdentifier: HomeViewCell.cellReuseIdentifier()) as? HomeViewCell else {
owsFailDebug("cell was unexpectedly nil")
return UITableViewCell()
}
guard let searchResult = self.searchResultSet.conversations[safe: indexPath.row] else {
owsFailDebug("searchResult was unexpectedly nil")
return UITableViewCell()
}
cell.configure(withThread: searchResult.thread, isBlocked: isBlocked(thread: searchResult.thread))
return cell
case .contacts:
guard let cell = tableView.dequeueReusableCell(withIdentifier: ContactTableViewCell.reuseIdentifier()) as? ContactTableViewCell else {
owsFailDebug("cell was unexpectedly nil")
return UITableViewCell()
}
guard let searchResult = self.searchResultSet.contacts[safe: indexPath.row] else {
owsFailDebug("searchResult was unexpectedly nil")
return UITableViewCell()
}
cell.configure(withRecipientId: searchResult.signalAccount.recipientId)
return cell
case .messages:
guard let cell = tableView.dequeueReusableCell(withIdentifier: HomeViewCell.cellReuseIdentifier()) as? HomeViewCell else {
owsFailDebug("cell was unexpectedly nil")
return UITableViewCell()
}
guard let searchResult = self.searchResultSet.messages[safe: indexPath.row] else {
owsFailDebug("searchResult was unexpectedly nil")
return UITableViewCell()
}
var overrideSnippet = NSAttributedString()
var overrideDate: Date?
if searchResult.messageId != nil {
if let messageDate = searchResult.messageDate {
overrideDate = messageDate
} else {
owsFailDebug("message search result is missing message timestamp")
}
// Note that we only use the snippet for message results,
// not conversation results. HomeViewCell will generate
// a snippet for conversations that reflects the latest
// contents.
if let messageSnippet = searchResult.snippet {
overrideSnippet = NSAttributedString(string: messageSnippet,
attributes: [
NSAttributedString.Key.foregroundColor: Theme.secondaryColor
])
} else {
owsFailDebug("message search result is missing message snippet")
}
}
cell.configure(withThread: searchResult.thread,
isBlocked: isBlocked(thread: searchResult.thread),
overrideSnippet: overrideSnippet,
overrideDate: overrideDate)
return cell
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard nil != self.tableView(tableView, titleForHeaderInSection: section) else {
return 0
}
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let title = self.tableView(tableView, titleForHeaderInSection: section) else {
return nil
}
let label = UILabel()
label.textColor = Theme.secondaryColor
label.text = title
label.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
label.tag = section
let hMargin: CGFloat = 15
let vMargin: CGFloat = 4
let wrapper = UIView()
wrapper.backgroundColor = Theme.offBackgroundColor
wrapper.addSubview(label)
label.autoPinWidthToSuperview(withMargin: hMargin)
label.autoPinHeightToSuperview(withMargin: vMargin)
return wrapper
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let searchSection = SearchSection(rawValue: section) else {
owsFailDebug("unknown section: \(section)")
return nil
}
switch searchSection {
case .noResults:
return nil
case .conversations:
if searchResultSet.conversations.count > 0 {
return NSLocalizedString("SEARCH_SECTION_CONVERSATIONS", comment: "section header for search results that match existing conversations (either group or contact conversations)")
} else {
return nil
}
case .contacts:
if searchResultSet.contacts.count > 0 {
return NSLocalizedString("SEARCH_SECTION_CONTACTS", comment: "section header for search results that match a contact who doesn't have an existing conversation")
} else {
return nil
}
case .messages:
if searchResultSet.messages.count > 0 {
return NSLocalizedString("SEARCH_SECTION_MESSAGES", comment: "section header for search results that match a message in a conversation")
} else {
return nil
}
}
}
// MARK: BlockListCacheDelegate
func blockListCacheDidUpdate(_ blocklistCache: BlockListCache) {
refreshSearchResults()
}
// MARK: Update Search Results
var refreshTimer: Timer?
private func refreshSearchResults() {
AssertIsOnMainThread()
guard !searchResultSet.isEmpty else {
// To avoid incorrectly showing the "no results" state,
// always search immediately if the current result set is empty.
refreshTimer?.invalidate()
refreshTimer = nil
updateSearchResults(searchText: searchText)
return
}
if refreshTimer != nil {
// Don't start a new refresh timer if there's already one active.
return
}
refreshTimer?.invalidate()
refreshTimer = WeakTimer.scheduledTimer(timeInterval: 0.1, target: self, userInfo: nil, repeats: false) { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.updateSearchResults(searchText: strongSelf.searchText)
strongSelf.refreshTimer = nil
}
}
private func updateSearchResults(searchText: String) {
guard searchText.stripped.count > 0 else {
self.searchResultSet = HomeScreenSearchResultSet.empty
self.tableView.reloadData()
return
}
var searchResults: HomeScreenSearchResultSet?
self.uiDatabaseConnection.asyncRead({[weak self] transaction in
guard let strongSelf = self else { return }
searchResults = strongSelf.searcher.searchForHomeScreen(searchText: searchText, transaction: transaction, contactsManager: strongSelf.contactsManager)
},
completionBlock: { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
guard let results = searchResults else {
owsFailDebug("searchResults was unexpectedly nil")
return
}
strongSelf.searchResultSet = results
strongSelf.tableView.reloadData()
})
}
// MARK: - UIScrollViewDelegate
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
delegate?.conversationSearchViewWillBeginDragging()
}
// MARK: -
private func isBlocked(thread: ThreadViewModel) -> Bool {
return self.blockListCache.isBlocked(thread: thread.threadRecord)
}
}
class EmptySearchResultCell: UITableViewCell {
static let reuseIdentifier = "EmptySearchResultCell"
let messageLabel: UILabel
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
self.messageLabel = UILabel()
super.init(style: style, reuseIdentifier: reuseIdentifier)
messageLabel.textAlignment = .center
messageLabel.numberOfLines = 3
contentView.addSubview(messageLabel)
messageLabel.autoSetDimension(.height, toSize: 150)
messageLabel.autoPinEdge(toSuperviewMargin: .top, relation: .greaterThanOrEqual)
messageLabel.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual)
messageLabel.autoPinEdge(toSuperviewMargin: .bottom, relation: .greaterThanOrEqual)
messageLabel.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual)
messageLabel.autoVCenterInSuperview()
messageLabel.autoHCenterInSuperview()
messageLabel.setContentHuggingHigh()
messageLabel.setCompressionResistanceHigh()
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
public func configure(searchText: String) {
let format = NSLocalizedString("HOME_VIEW_SEARCH_NO_RESULTS_FORMAT", comment: "Format string when search returns no results. Embeds {{search term}}")
let messageText: String = NSString(format: format as NSString, searchText) as String
self.messageLabel.text = messageText
messageLabel.textColor = Theme.primaryColor
messageLabel.font = UIFont.ows_dynamicTypeBody
}
}

View File

@ -1,24 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class ThreadViewModel;
@class YapDatabaseReadTransaction;
@interface HomeViewCell : UITableViewCell
+ (NSString *)cellReuseIdentifier;
- (void)configureWithThread:(ThreadViewModel *)thread
isBlocked:(BOOL)isBlocked;
- (void)configureWithThread:(ThreadViewModel *)thread
isBlocked:(BOOL)isBlocked
overrideSnippet:(nullable NSAttributedString *)overrideSnippet
overrideDate:(nullable NSDate *)overrideDate;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,581 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "HomeViewCell.h"
#import "OWSAvatarBuilder.h"
#import "Session-Swift.h"
#import <SignalMessaging/OWSFormat.h>
#import <SignalMessaging/OWSUserProfile.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SessionServiceKit/OWSMath.h>
#import <SessionServiceKit/OWSMessageManager.h>
#import <SessionServiceKit/TSContactThread.h>
#import <SessionServiceKit/TSGroupThread.h>
NS_ASSUME_NONNULL_BEGIN
@interface HomeViewCell ()
@property (nonatomic) AvatarImageView *avatarView;
@property (nonatomic) UILabel *nameLabel;
@property (nonatomic) UILabel *snippetLabel;
@property (nonatomic) UILabel *dateTimeLabel;
@property (nonatomic) MessageStatusView *messageStatusView;
@property (nonatomic) TypingIndicatorView *typingIndicatorView;
@property (nonatomic) UIView *unreadBadge;
@property (nonatomic) UILabel *unreadLabel;
@property (nonatomic, nullable) ThreadViewModel *thread;
@property (nonatomic, nullable) NSAttributedString *overrideSnippet;
@property (nonatomic) BOOL isBlocked;
@property (nonatomic, readonly) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
@end
#pragma mark -
@implementation HomeViewCell
#pragma mark - Dependencies
- (OWSContactsManager *)contactsManager
{
OWSAssertDebug(Environment.shared.contactsManager);
return Environment.shared.contactsManager;
}
- (id<OWSTypingIndicators>)typingIndicators
{
return SSKEnvironment.shared.typingIndicators;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark -
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self commontInit];
}
return self;
}
// `[UIView init]` invokes `[self initWithFrame:...]`.
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self commontInit];
}
return self;
}
- (void)commontInit
{
OWSAssertDebug(!self.avatarView);
self.backgroundColor = Theme.backgroundColor;
_viewConstraints = [NSMutableArray new];
self.avatarView = [[AvatarImageView alloc] init];
[self.contentView addSubview:self.avatarView];
[self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize];
[self.avatarView autoSetDimension:ALDimensionHeight toSize:self.avatarSize];
[self.avatarView autoPinLeadingToSuperviewMargin];
[self.avatarView autoVCenterInSuperview];
[self.avatarView setContentHuggingHigh];
[self.avatarView setCompressionResistanceHigh];
// Ensure that the cell's contents never overflow the cell bounds.
[self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual];
[self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual];
self.nameLabel = [UILabel new];
self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self.nameLabel.font = self.nameFont;
[self.nameLabel setContentHuggingHorizontalLow];
[self.nameLabel setCompressionResistanceHorizontalLow];
self.dateTimeLabel = [UILabel new];
[self.dateTimeLabel setContentHuggingHorizontalHigh];
[self.dateTimeLabel setCompressionResistanceHorizontalHigh];
self.messageStatusView = [MessageStatusView new];
[self.messageStatusView setContentHuggingHorizontalHigh];
[self.messageStatusView setCompressionResistanceHorizontalHigh];
UIStackView *topRowView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.nameLabel,
self.dateTimeLabel,
]];
topRowView.axis = UILayoutConstraintAxisHorizontal;
topRowView.alignment = UIStackViewAlignmentLastBaseline;
topRowView.spacing = 6.f;
self.snippetLabel = [UILabel new];
self.snippetLabel.font = [self snippetFont];
self.snippetLabel.numberOfLines = 1;
self.snippetLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[self.snippetLabel setContentHuggingHorizontalLow];
[self.snippetLabel setCompressionResistanceHorizontalLow];
self.typingIndicatorView = [TypingIndicatorView new];
[self.contentView addSubview:self.typingIndicatorView];
UIStackView *bottomRowView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.snippetLabel,
self.messageStatusView,
]];
bottomRowView.axis = UILayoutConstraintAxisHorizontal;
bottomRowView.alignment = UIStackViewAlignmentLastBaseline;
bottomRowView.spacing = 6.f;
UIStackView *vStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
topRowView,
bottomRowView,
]];
vStackView.axis = UILayoutConstraintAxisVertical;
[self.contentView addSubview:vStackView];
[vStackView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing];
[vStackView autoVCenterInSuperview];
// Ensure that the cell's contents never overflow the cell bounds.
[vStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual];
[vStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual];
[vStackView autoPinTrailingToSuperviewMargin];
vStackView.userInteractionEnabled = NO;
self.unreadLabel = [UILabel new];
self.unreadLabel.textColor = [UIColor ows_whiteColor];
self.unreadLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self.unreadLabel.textAlignment = NSTextAlignmentCenter;
[self.unreadLabel setContentHuggingHigh];
[self.unreadLabel setCompressionResistanceHigh];
self.unreadBadge = [NeverClearView new];
self.unreadBadge.backgroundColor = [UIColor ows_materialBlueColor];
[self.unreadBadge addSubview:self.unreadLabel];
[self.unreadLabel autoCenterInSuperview];
[self.unreadBadge setContentHuggingHigh];
[self.unreadBadge setCompressionResistanceHigh];
[self.contentView addSubview:self.unreadBadge];
[self.unreadBadge autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.nameLabel];
[self.typingIndicatorView autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:self.snippetLabel];
[self.typingIndicatorView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.snippetLabel];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
}
- (void)initializeLayout
{
self.selectionStyle = UITableViewCellSelectionStyleDefault;
}
- (nullable NSString *)reuseIdentifier
{
return NSStringFromClass(self.class);
}
- (void)configureWithThread:(ThreadViewModel *)thread
isBlocked:(BOOL)isBlocked
{
[self configureWithThread:thread
isBlocked:isBlocked
overrideSnippet:nil
overrideDate:nil];
}
- (void)configureWithThread:(ThreadViewModel *)thread
isBlocked:(BOOL)isBlocked
overrideSnippet:(nullable NSAttributedString *)overrideSnippet
overrideDate:(nullable NSDate *)overrideDate
{
OWSAssertIsOnMainThread();
OWSAssertDebug(thread);
[OWSTableItem configureCell:self];
self.thread = thread;
self.overrideSnippet = overrideSnippet;
self.isBlocked = isBlocked;
BOOL hasUnreadMessages = thread.hasUnreadMessages;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(otherUsersProfileDidChange:)
name:kNSNotificationName_OtherUsersProfileDidChange
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(typingIndicatorStateDidChange:)
name:[OWSTypingIndicatorsImpl typingIndicatorStateDidChange]
object:nil];
[self updateNameLabel];
[self updateAvatarView];
// We update the fonts every time this cell is configured to ensure that
// changes to the dynamic type settings are reflected.
self.snippetLabel.font = [self snippetFont];
[self updatePreview];
self.dateTimeLabel.text
= (overrideDate ? [self stringForDate:overrideDate] : [self stringForDate:thread.lastMessageDate]);
UIColor *textColor = [Theme secondaryColor];
if (hasUnreadMessages && overrideSnippet == nil) {
textColor = [Theme primaryColor];
self.dateTimeLabel.font = self.dateTimeFont.ows_mediumWeight;
} else {
self.dateTimeLabel.font = self.dateTimeFont;
}
self.dateTimeLabel.textColor = textColor;
NSUInteger unreadCount = thread.unreadCount;
if (overrideSnippet) {
// If we're using the home view cell to render search results,
// don't show "unread badge" or "message status" indicator.
self.unreadBadge.hidden = YES;
self.messageStatusView.hidden = YES;
} else if (unreadCount > 0) {
// If there are unread messages, show the "unread badge."
// The "message status" indicators is redundant.
self.unreadBadge.hidden = NO;
self.messageStatusView.hidden = YES;
self.unreadLabel.text = [OWSFormat formatInt:(int)unreadCount];
self.unreadLabel.font = self.unreadFont;
const int unreadBadgeHeight = (int)ceil(self.unreadLabel.font.lineHeight * 1.5f);
self.unreadBadge.layer.cornerRadius = unreadBadgeHeight / 2;
self.unreadBadge.layer.borderColor = Theme.backgroundColor.CGColor;
self.unreadBadge.layer.borderWidth = 1.f;
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh
forConstraints:^{
// This is a bit arbitrary, but it should scale with the size of dynamic text
CGFloat minMargin = CeilEven(unreadBadgeHeight * .5f);
// Spec check. Should be 12pts (6pt on each side) when using default font size.
OWSAssertDebug(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12);
[self.viewConstraints addObjectsFromArray:@[
// badge sizing
[self.unreadBadge autoMatchDimension:ALDimensionWidth
toDimension:ALDimensionWidth
ofView:self.unreadLabel
withOffset:minMargin
relation:NSLayoutRelationGreaterThanOrEqual],
[self.unreadBadge autoSetDimension:ALDimensionWidth
toSize:unreadBadgeHeight
relation:NSLayoutRelationGreaterThanOrEqual],
[self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight],
[self.unreadBadge autoPinEdge:ALEdgeTrailing
toEdge:ALEdgeTrailing
ofView:self.avatarView
withOffset:6.f],
]];
}];
} else {
UIImage *_Nullable statusIndicatorImage = nil;
// TODO: Theme, Review with design.
UIColor *messageStatusViewTintColor
= (Theme.isDarkThemeEnabled ? [UIColor ows_gray25Color] : [UIColor ows_gray45Color]);
BOOL shouldAnimateStatusIcon = NO;
if ([self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.thread.lastMessageForInbox;
MessageReceiptStatus messageStatus =
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
switch (messageStatus) {
case MessageReceiptStatusCalculatingPoW:
statusIndicatorImage = [UIImage imageNamed:@"Cog"];
shouldAnimateStatusIcon = YES;
break;
case MessageReceiptStatusUploading:
case MessageReceiptStatusSending:
statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"];
shouldAnimateStatusIcon = YES;
break;
case MessageReceiptStatusSent:
case MessageReceiptStatusSkipped:
statusIndicatorImage = [UIImage imageNamed:@"message_status_sent"];
break;
case MessageReceiptStatusDelivered:
statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"];
break;
case MessageReceiptStatusRead:
statusIndicatorImage = [UIImage imageNamed:@"message_status_read"];
break;
case MessageReceiptStatusFailed:
statusIndicatorImage = [UIImage imageNamed:@"message_status_failed"];
messageStatusViewTintColor = [UIColor ows_destructiveRedColor];
break;
}
}
self.messageStatusView.image = [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.messageStatusView.tintColor = messageStatusViewTintColor;
self.messageStatusView.hidden = statusIndicatorImage == nil;
self.unreadBadge.hidden = YES;
if (shouldAnimateStatusIcon) {
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.toValue = @(M_PI * 2.0);
const CGFloat kPeriodSeconds = 1.f;
animation.duration = kPeriodSeconds;
animation.cumulative = YES;
animation.repeatCount = HUGE_VALF;
[self.messageStatusView.layer addAnimation:animation forKey:@"animation"];
} else {
[self.messageStatusView.layer removeAllAnimations];
}
}
}
- (void)updateAvatarView
{
ThreadViewModel *thread = self.thread;
if (thread == nil) {
OWSFailDebug(@"thread should not be nil");
self.avatarView.image = nil;
return;
}
self.avatarView.contactID = thread.contactIdentifier;
[self.avatarView updateOnlineStatusIndicator];
self.avatarView.image = [OWSAvatarBuilder buildImageForThread:thread.threadRecord diameter:self.avatarSize];
}
- (NSAttributedString *)attributedSnippetForThread:(ThreadViewModel *)thread isBlocked:(BOOL)isBlocked
{
OWSAssertDebug(thread);
BOOL hasUnreadMessages = thread.hasUnreadMessages;
NSMutableAttributedString *snippetText = [NSMutableAttributedString new];
if (isBlocked) {
// If thread is blocked, don't show a snippet or mute status.
[snippetText appendAttributedString:
[[NSAttributedString alloc]
initWithString:NSLocalizedString(@"HOME_VIEW_BLOCKED_CONVERSATION",
@"Table cell subtitle label for a conversation the user has blocked.")
attributes:@{
NSFontAttributeName : self.snippetFont.ows_mediumWeight,
NSForegroundColorAttributeName : [Theme primaryColor],
}]];
} else {
if (thread.isMuted && !thread.isGroupThread) {
[snippetText appendAttributedString:[[NSAttributedString alloc]
initWithString:LocalizationNotNeeded(@"\ue067 ")
attributes:@{
NSFontAttributeName : [UIFont ows_elegantIconsFont:9.f],
NSForegroundColorAttributeName :
(hasUnreadMessages ? [Theme primaryColor]
: [Theme secondaryColor]),
}]];
}
NSString *displayableText = thread.lastMessageText;
if (displayableText) {
[LKMentionsManager populateUserPublicKeyCacheIfNeededFor:thread.threadRecord.uniqueId in:nil]; // TODO: Terrible place to do this, but okay for now
displayableText = [LKMentionUtilities highlightMentionsIn:displayableText threadID:thread.threadRecord.uniqueId];
[snippetText appendAttributedString:[[NSAttributedString alloc]
initWithString:displayableText
attributes:@{
NSFontAttributeName :
(hasUnreadMessages ? self.snippetFont.ows_mediumWeight
: self.snippetFont),
NSForegroundColorAttributeName :
(hasUnreadMessages ? [Theme primaryColor]
: [Theme secondaryColor]),
}]];
}
}
return snippetText;
}
#pragma mark - Date formatting
- (NSString *)stringForDate:(nullable NSDate *)date
{
if (date == nil) {
OWSFailDebug(@"date was unexpectedly nil");
return @"";
}
return [DateUtil formatDateShort:date];
}
#pragma mark - Constants
- (UIFont *)unreadFont
{
return [UIFont ows_dynamicTypeCaption1Font].ows_mediumWeight;
}
- (UIFont *)dateTimeFont
{
return [UIFont ows_dynamicTypeCaption1Font];
}
- (UIFont *)snippetFont
{
return [UIFont ows_dynamicTypeSubheadlineFont];
}
- (UIFont *)nameFont
{
return [UIFont ows_dynamicTypeBodyFont].ows_mediumWeight;
}
// Used for profile names.
- (UIFont *)nameSecondaryFont
{
return [UIFont ows_dynamicTypeBodyFont].ows_italic;
}
- (NSUInteger)avatarSize
{
return kStandardAvatarSize;
}
- (NSUInteger)avatarHSpacing
{
return 12.f;
}
#pragma mark - Reuse
- (void)prepareForReuse
{
[super prepareForReuse];
[NSLayoutConstraint deactivateConstraints:self.viewConstraints];
[self.viewConstraints removeAllObjects];
self.thread = nil;
self.overrideSnippet = nil;
self.avatarView.image = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Name
- (void)otherUsersProfileDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
NSString *recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId];
if (recipientId.length == 0) {
return;
}
if (![self.thread isKindOfClass:[TSContactThread class]]) {
return;
}
if (![self.thread.contactIdentifier isEqualToString:recipientId]) {
return;
}
[self updateNameLabel];
[self updateAvatarView];
}
- (void)updateNameLabel
{
OWSAssertIsOnMainThread();
self.nameLabel.font = self.nameFont;
self.nameLabel.textColor = [Theme primaryColor];
ThreadViewModel *thread = self.thread;
if (thread == nil) {
OWSFailDebug(@"thread should not be nil");
self.nameLabel.attributedText = nil;
return;
}
NSAttributedString *name;
if (thread.isGroupThread) {
if (thread.name.length == 0) {
name = [[NSAttributedString alloc] initWithString:[MessageStrings newGroupDefaultTitle]];
} else {
name = [[NSAttributedString alloc] initWithString:thread.name];
}
} else {
if (self.thread.threadRecord.isNoteToSelf) {
name = [[NSAttributedString alloc]
initWithString:NSLocalizedString(@"NOTE_TO_SELF", @"Label for 1:1 conversation with yourself.")
attributes:@{
NSFontAttributeName : self.nameFont,
}];
} else {
name = [self.contactsManager attributedContactOrProfileNameForPhoneIdentifier:thread.contactIdentifier
primaryFont:self.nameFont
secondaryFont:self.nameSecondaryFont];
}
}
self.nameLabel.attributedText = name;
}
#pragma mark - Typing Indicators
- (void)updatePreview
{
if ([self.typingIndicators typingRecipientIdForThread:self.thread.threadRecord] != nil) {
// If we hide snippetLabel, our layout will break since UIStackView will remove
// it from the layout. Wrapping the preview views (the snippet label and the
// typing indicator) in a UIStackView proved non-trivial since we're using
// UIStackViewAlignmentLastBaseline. Therefore we hide the _contents_ of the
// snippet label using an empty string.
self.snippetLabel.text = @" ";
self.typingIndicatorView.hidden = NO;
[self.typingIndicatorView startAnimation];
} else {
if (self.overrideSnippet) {
self.snippetLabel.attributedText = self.overrideSnippet;
} else {
self.snippetLabel.attributedText = [self attributedSnippetForThread:self.thread isBlocked:self.isBlocked];
}
self.typingIndicatorView.hidden = YES;
[self.typingIndicatorView stopAnimation];
}
}
- (void)typingIndicatorStateDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSAssertDebug(self.thread);
if (notification.object && ![notification.object isEqual:self.thread.threadRecord.uniqueId]) {
return;
}
[self updatePreview];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,27 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "ConversationViewController.h"
#import <SignalMessaging/OWSViewController.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface HomeViewController : OWSViewController
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action animated:(BOOL)isAnimated;
- (void)presentThread:(TSThread *)thread
action:(ConversationViewAction)action
focusMessageId:(nullable NSString *)focusMessageId
animated:(BOOL)isAnimated;
// Used by force-touch Springboard icon shortcut
- (void)showNewConversationVC;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public class MessageStatusView: UIView {
private let imageView: UIImageView
private let lastBaselineView: UIView
// MessageStatusView is aligned 1pt below it's baseline.
private let kBaselineOverhang: CGFloat = 1
@objc
public var image: UIImage? {
get {
return imageView.image
}
set {
imageView.image = newValue
}
}
public override init(frame: CGRect) {
self.imageView = UIImageView()
self.lastBaselineView = UIView()
super.init(frame: frame)
self.addSubview(imageView)
self.addSubview(lastBaselineView)
imageView.setCompressionResistanceHigh()
imageView.setContentHuggingHigh()
imageView.autoPinEdgesToSuperviewEdges()
lastBaselineView.autoSetDimension(.height, toSize: 1)
lastBaselineView.autoPinEdge(toSuperviewEdge: .left)
lastBaselineView.autoPinEdge(toSuperviewEdge: .right)
lastBaselineView.autoPinEdge(toSuperviewEdge: .bottom, withInset: kBaselineOverhang)
}
public override var forLastBaselineLayout: UIView {
return self.lastBaselineView
}
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
}

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalMessaging/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface NewContactThreadViewController : OWSViewController
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalMessaging/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@interface NewGroupViewController : OWSViewController
@end
NS_ASSUME_NONNULL_END

View File

@ -1,695 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "NewGroupViewController.h"
#import "AddToGroupViewController.h"
#import "AvatarViewHelper.h"
#import "OWSNavigationController.h"
#import "Session-Swift.h"
#import "SignalApp.h"
#import <SessionCoreKit/NSDate+OWS.h>
#import <SessionCoreKit/Randomness.h>
#import <SignalMessaging/BlockListUIUtils.h>
#import <SignalMessaging/ContactTableViewCell.h>
#import <SignalMessaging/ContactsViewHelper.h>
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSTableViewController.h>
#import <SignalMessaging/SignalKeyingStorage.h>
#import <SignalMessaging/UIUtil.h>
#import <SignalMessaging/UIView+OWS.h>
#import <SignalMessaging/UIViewController+OWS.h>
#import <SessionServiceKit/NSString+SSK.h>
#import <SessionServiceKit/OWSMessageSender.h>
#import <SessionServiceKit/SignalAccount.h>
#import <SessionServiceKit/TSGroupModel.h>
#import <SessionServiceKit/TSGroupThread.h>
#import <SessionServiceKit/TSOutgoingMessage.h>
NS_ASSUME_NONNULL_BEGIN
@interface NewGroupViewController () <UIImagePickerControllerDelegate,
UITextFieldDelegate,
ContactsViewHelperDelegate,
AvatarViewHelperDelegate,
AddToGroupViewControllerDelegate,
OWSTableViewControllerDelegate,
UINavigationControllerDelegate,
OWSNavigationView>
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) AvatarImageView *avatarView;
@property (nonatomic, readonly) UITextField *groupNameTextField;
@property (nonatomic, readonly) NSData *groupId;
@property (nonatomic, nullable) UIImage *groupAvatar;
@property (nonatomic) NSMutableSet<NSString *> *memberRecipientIds;
@property (nonatomic) NSMutableSet<NSString* > *adminIds;
@property (nonatomic) GroupType groupType;
@property (nonatomic) BOOL hasUnsavedChanges;
@property (nonatomic) BOOL hasAppeared;
@end
#pragma mark -
@implementation NewGroupViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
_groupId = [LKGroupUtilities getEncodedClosedGroupIDAsData:[[Randomness generateRandomBytes:kGroupIdLength] hexadecimalString]];
_groupType = closedGroup;
_messageSender = SSKEnvironment.shared.messageSender;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_avatarViewHelper = [AvatarViewHelper new];
_avatarViewHelper.delegate = self;
self.memberRecipientIds = [NSMutableSet new];
self.adminIds = [NSMutableSet new];
}
#pragma mark - View Lifecycle
- (void)loadView
{
[super loadView];
self.title = [MessageStrings newGroupDefaultTitle];
self.view.backgroundColor = Theme.backgroundColor;
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"NEW_GROUP_CREATE_BUTTON",
@"The title for the 'create group' button.")
style:UIBarButtonItemStylePlain
target:self
action:@selector(createGroup)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"create")];
self.navigationItem.rightBarButtonItem.imageInsets = UIEdgeInsetsMake(0, -10, 0, 10);
self.navigationItem.rightBarButtonItem.accessibilityLabel
= NSLocalizedString(@"FINISH_GROUP_CREATION_LABEL", @"Accessibility label for finishing new group");
// First section.
UIView *firstSection = [self firstSectionHeader];
[self.view addSubview:firstSection];
[firstSection autoSetDimension:ALDimensionHeight toSize:100.f];
[firstSection autoPinWidthToSuperview];
[firstSection autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f];
_tableViewController = [OWSTableViewController new];
_tableViewController.delegate = self;
[self.view addSubview:self.tableViewController.view];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:firstSection];
[self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view avoidNotch:NO];
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableViewController.tableView.estimatedRowHeight = 60;
[self updateTableContents];
}
- (UIView *)firstSectionHeader
{
UIView *firstSectionHeader = [UIView new];
firstSectionHeader.userInteractionEnabled = YES;
[firstSectionHeader
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(headerWasTapped:)]];
firstSectionHeader.backgroundColor = [Theme backgroundColor];
UIView *threadInfoView = [UIView new];
[firstSectionHeader addSubview:threadInfoView];
[threadInfoView autoPinWidthToSuperviewWithMargin:16.f];
[threadInfoView autoPinHeightToSuperviewWithMargin:16.f];
AvatarImageView *avatarView = [AvatarImageView new];
_avatarView = avatarView;
[threadInfoView addSubview:avatarView];
[avatarView autoVCenterInSuperview];
[avatarView autoPinLeadingToSuperviewMargin];
[avatarView autoSetDimension:ALDimensionWidth toSize:kLargeAvatarSize];
[avatarView autoSetDimension:ALDimensionHeight toSize:kLargeAvatarSize];
[self updateAvatarView];
[avatarView
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTouched:)]];
avatarView.userInteractionEnabled = YES;
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, avatarView);
UITextField *groupNameTextField = [OWSTextField new];
_groupNameTextField = groupNameTextField;
groupNameTextField.textColor = Theme.primaryColor;
groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font];
groupNameTextField.attributedPlaceholder =
[[NSAttributedString alloc] initWithString:NSLocalizedString(@"NEW_GROUP_NAMEGROUP_REQUEST_DEFAULT",
@"Placeholder text for group name field")
attributes:@{
NSForegroundColorAttributeName : Theme.secondaryColor,
}];
groupNameTextField.delegate = self;
[groupNameTextField addTarget:self
action:@selector(groupNameDidChange:)
forControlEvents:UIControlEventEditingChanged];
[threadInfoView addSubview:groupNameTextField];
[groupNameTextField autoVCenterInSuperview];
[groupNameTextField autoPinTrailingToSuperviewMargin];
[groupNameTextField autoPinLeadingToTrailingEdgeOfView:avatarView offset:16.f];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, groupNameTextField);
return firstSectionHeader;
}
- (void)headerWasTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.groupNameTextField becomeFirstResponder];
}
}
- (void)avatarTouched:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self showChangeAvatarUI];
}
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak NewGroupViewController *weakSelf = self;
ContactsViewHelper *contactsViewHelper = self.contactsViewHelper;
NSArray<SignalAccount *> *signalAccounts = self.contactsViewHelper.signalAccounts;
NSMutableSet *nonContactMemberRecipientIds = [self.memberRecipientIds mutableCopy];
for (SignalAccount *signalAccount in signalAccounts) {
[nonContactMemberRecipientIds removeObject:signalAccount.recipientId];
}
// Non-contact Members
if (nonContactMemberRecipientIds.count > 0 || signalAccounts.count < 1) {
OWSTableSection *nonContactsSection = [OWSTableSection new];
nonContactsSection.headerTitle = NSLocalizedString(
@"NEW_GROUP_NON_CONTACTS_SECTION_TITLE", @"a title for the non-contacts section of the 'new group' view.");
[nonContactsSection addItem:[self createAddNonContactItem]];
for (NSString *recipientId in
[nonContactMemberRecipientIds.allObjects sortedArrayUsingSelector:@selector(compare:)]) {
[nonContactsSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
NewGroupViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
ContactTableViewCell *cell = [ContactTableViewCell new];
BOOL isCurrentMember = [strongSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
// In the "contacts" section, we label members as such when editing an existing
// group.
cell.accessoryMessage = NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL",
@"An indicator that a user is a member of the new group.");
} else if (isBlocked) {
cell.accessoryMessage = NSLocalizedString(
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
}
[cell configureWithRecipientId:recipientId];
NSString *cellName = [NSString stringWithFormat:@"non_signal_contact.%@", recipientId];
cell.accessibilityIdentifier
= ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewGroupViewController, cellName);
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
BOOL isCurrentMember = [weakSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
[weakSelf removeRecipientId:recipientId];
} else if (isBlocked) {
[BlockListUIUtils
showUnblockPhoneNumberActionSheet:recipientId
fromViewController:weakSelf
blockingManager:contactsViewHelper.blockingManager
contactsManager:contactsViewHelper.contactsManager
completionBlock:^(BOOL isStillBlocked) {
if (!isStillBlocked) {
[weakSelf addRecipientId:recipientId];
}
}];
} else {
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
presentAlertIfNecessaryWithRecipientId:recipientId
confirmationText:NSLocalizedString(
@"SAFETY_NUMBER_CHANGED_CONFIRM_"
@"ADD_TO_GROUP_ACTION",
@"button title to confirm adding "
@"a recipient to a group when "
@"their safety "
@"number has recently changed")
contactsManager:contactsViewHelper.contactsManager
completion:^(BOOL didConfirmIdentity) {
if (didConfirmIdentity) {
[weakSelf addRecipientId:recipientId];
}
}];
if (didShowSNAlert) {
return;
}
[weakSelf addRecipientId:recipientId];
}
}]];
}
[contents addSection:nonContactsSection];
}
// Contacts
OWSTableSection *signalAccountSection = [OWSTableSection new];
signalAccountSection.headerTitle = NSLocalizedString(
@"EDIT_GROUP_CONTACTS_SECTION_TITLE", @"a title for the contacts section of the 'new/update group' view.");
if (signalAccounts.count > 0) {
if (nonContactMemberRecipientIds.count < 1) {
// If the group contains any non-contacts or has not contacts,
// the "add non-contact user" will show up in the previous section
// of the table. However, it's more attractive to hide that section
// for the common case where people want to create a group from just
// their contacts. Therefore, when that section is hidden, we want
// to allow people to add non-contacts.
[signalAccountSection addItem:[self createAddNonContactItem]];
}
for (SignalAccount *signalAccount in signalAccounts) {
[signalAccountSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
NewGroupViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
ContactTableViewCell *cell = [ContactTableViewCell new];
NSString *recipientId = signalAccount.recipientId;
BOOL isCurrentMember = [strongSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
// In the "contacts" section, we label members as such when editing an existing
// group.
cell.accessoryMessage = NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL",
@"An indicator that a user is a member of the new group.");
} else if (isBlocked) {
cell.accessoryMessage = NSLocalizedString(
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
}
[cell configureWithRecipientId:signalAccount.recipientId];
NSString *cellName =
[NSString stringWithFormat:@"signal_contact.%@", signalAccount.recipientId];
cell.accessibilityIdentifier
= ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewGroupViewController, cellName);
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
NSString *recipientId = signalAccount.recipientId;
BOOL isCurrentMember = [weakSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
[weakSelf removeRecipientId:recipientId];
} else if (isBlocked) {
[BlockListUIUtils
showUnblockSignalAccountActionSheet:signalAccount
fromViewController:weakSelf
blockingManager:contactsViewHelper.blockingManager
contactsManager:contactsViewHelper.contactsManager
completionBlock:^(BOOL isStillBlocked) {
if (!isStillBlocked) {
[weakSelf addRecipientId:recipientId];
}
}];
} else {
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
presentAlertIfNecessaryWithRecipientId:signalAccount.recipientId
confirmationText:NSLocalizedString(
@"SAFETY_NUMBER_CHANGED_CONFIRM_"
@"ADD_TO_GROUP_ACTION",
@"button title to confirm adding "
@"a recipient to a group when "
@"their safety "
@"number has recently changed")
contactsManager:contactsViewHelper.contactsManager
completion:^(BOOL didConfirmIdentity) {
if (didConfirmIdentity) {
[weakSelf addRecipientId:recipientId];
}
}];
if (didShowSNAlert) {
return;
}
[weakSelf addRecipientId:recipientId];
}
}]];
}
} else {
[signalAccountSection
addItem:[OWSTableItem
softCenterLabelItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_NO_CONTACTS",
@"A label that indicates the user has no Signal contacts.")]];
}
[contents addSection:signalAccountSection];
self.tableViewController.contents = contents;
}
- (OWSTableItem *)createAddNonContactItem
{
__weak NewGroupViewController *weakSelf = self;
return [OWSTableItem
disclosureItemWithText:NSLocalizedString(@"NEW_GROUP_ADD_NON_CONTACT",
@"A label for the cell that lets you add a new non-contact member to a group.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewGroupViewController, @"add_non_contact")
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
AddToGroupViewController *viewController = [AddToGroupViewController new];
viewController.addToGroupDelegate = weakSelf;
viewController.hideContacts = YES;
[weakSelf.navigationController pushViewController:viewController animated:YES];
}];
}
- (void)removeRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[self.memberRecipientIds removeObject:recipientId];
[self updateTableContents];
}
- (void)addRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[self.memberRecipientIds addObject:recipientId];
self.hasUnsavedChanges = YES;
[self updateTableContents];
}
#pragma mark - Methods
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.hasAppeared) {
[self.groupNameTextField becomeFirstResponder];
self.hasAppeared = YES;
}
}
#pragma mark - Actions
- (void)createGroup
{
OWSAssertIsOnMainThread();
TSGroupModel *model = [self makeGroup];
__block TSGroupThread *thread;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:model transaction:transaction];
} error:nil];
OWSAssertDebug(thread);
[OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread];
void (^successHandler)(void) = ^{
OWSLogError(@"Group creation successful.");
dispatch_async(dispatch_get_main_queue(), ^{
[SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
});
};
void (^failureHandler)(NSError *error) = ^(NSError *error) {
OWSLogError(@"Group creation failed: %@", error);
// Add an error message to the new group indicating
// that group creation didn't succeed.
// MJK TODO should be safe to remove senderTimestamp and just save immediately
TSErrorMessage *errorMessage = [[TSErrorMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
failedMessageType:TSErrorMessageGroupCreationFailed];
[errorMessage save];
dispatch_async(dispatch_get_main_queue(), ^{
[SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
});
};
[ModalActivityIndicatorViewController
presentFromViewController:self
canCancel:NO
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
groupMetaMessage:TSGroupMetaMessageNew
expiresInSeconds:0];
[message updateWithCustomMessage:NSLocalizedString(@"GROUP_CREATED", nil)];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (model.groupImage) {
NSData *data = UIImagePNGRepresentation(model.groupImage);
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:data fileExtension:@"png"];
// CLEANUP DURABLE - Replace with a durable operation e.g. `GroupCreateJob`, which creates
// an error in the thread if group creation fails
[self.messageSender sendTemporaryAttachment:dataSource
contentType:OWSMimeTypeImagePng
inMessage:message
success:successHandler
failure:failureHandler];
} else {
// CLEANUP DURABLE - Replace with a durable operation e.g. `GroupCreateJob`, which creates
// an error in the thread if group creation fails
[self.messageSender sendMessage:message success:successHandler failure:failureHandler];
}
});
}];
}
- (TSGroupModel *)makeGroup
{
NSString *groupName = [self.groupNameTextField.text ows_stripped];
NSMutableArray<NSString *> *recipientIds = [self.memberRecipientIds.allObjects mutableCopy];
NSMutableArray<NSString *> *adminIds = [self.adminIds.allObjects mutableCopy];
//Test: Add Ryan to a new group. Should be deleted!!!!!
[recipientIds addObject:@"057fffb55430abb2df5be80fab693ffe4db26a8b76e590c1748a47baef7c483604"];
[recipientIds addObject:@"050c159f0d46c40ec6306bb0b8470972af3f67b5cb1e24b6460a6f692275c8b57f"];
[recipientIds addObject:[self.contactsViewHelper localNumber]];
//Loki - Add the creator as the admin
[adminIds addObject:[self.contactsViewHelper localNumber]];
TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:groupName
memberIds:recipientIds
image:self.groupAvatar
groupId:self.groupId
groupType:self.groupType
adminIds:adminIds];
return group;
}
#pragma mark - Group Avatar
- (void)showChangeAvatarUI
{
[self.avatarViewHelper showChangeAvatarUI];
}
- (void)setGroupAvatar:(nullable UIImage *)groupAvatar
{
OWSAssertIsOnMainThread();
_groupAvatar = groupAvatar;
self.hasUnsavedChanges = YES;
[self updateAvatarView];
}
- (void)updateAvatarView
{
UIImage *_Nullable groupAvatar = self.groupAvatar;
if (!groupAvatar) {
NSString *conversationColorName = [TSGroupThread defaultConversationColorNameForGroupId:self.groupId];
groupAvatar = [OWSGroupAvatarBuilder defaultAvatarForGroupId:self.groupId
conversationColorName:conversationColorName
diameter:kLargeAvatarSize];
}
self.avatarView.image = groupAvatar;
}
#pragma mark - Event Handling
- (void)backButtonPressed
{
[self.groupNameTextField resignFirstResponder];
if (!self.hasUnsavedChanges) {
// If user made no changes, return to conversation settings view.
[self.navigationController popViewControllerAnimated:YES];
return;
}
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
@"The alert title if user tries to exit the new group view without saving changes.")
message:
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
@"The alert message if user tries to exit the new group view without saving changes.")
preferredStyle:UIAlertControllerStyleAlert];
[alert
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DISCARD_BUTTON",
@"The label for the 'discard' button in alerts and action sheets.")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"discard")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self.navigationController popViewControllerAnimated:YES];
}]];
[alert addAction:[OWSAlerts cancelAction]];
[self presentAlert:alert];
}
- (void)groupNameDidChange:(id)sender
{
self.hasUnsavedChanges = YES;
}
#pragma mark - Text Field Delegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.groupNameTextField resignFirstResponder];
return NO;
}
#pragma mark - OWSTableViewControllerDelegate
- (void)tableViewWillBeginDragging
{
[self.groupNameTextField resignFirstResponder];
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
- (BOOL)shouldHideLocalNumber
{
return YES;
}
#pragma mark - AvatarViewHelperDelegate
- (nullable NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
OWSAssertIsOnMainThread();
OWSAssertDebug(image);
self.groupAvatar = image;
}
- (UIViewController *)fromViewController
{
return self;
}
- (BOOL)hasClearAvatarAction
{
return NO;
}
#pragma mark - AddToGroupViewControllerDelegate
- (void)recipientIdWasAdded:(NSString *)recipientId
{
[self addRecipientId:recipientId];
}
- (BOOL)isRecipientGroupMember:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
return [self.memberRecipientIds containsObject:recipientId];
}
#pragma mark - OWSNavigationView
- (BOOL)shouldCancelNavigationBack
{
BOOL result = self.hasUnsavedChanges;
if (self.hasUnsavedChanges) {
[self backButtonPressed];
}
return result;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,117 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class OWS2FAReminderViewController: UIViewController, PinEntryViewDelegate {
private var ows2FAManager: OWS2FAManager {
return OWS2FAManager.shared()
}
var pinEntryView: PinEntryView!
@objc
public class func wrappedInNavController() -> OWSNavigationController {
let navController = OWSNavigationController()
navController.pushViewController(OWS2FAReminderViewController(), animated: false)
return navController
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
pinEntryView.makePinTextFieldFirstResponder()
}
override public func loadView() {
assert(ows2FAManager.pinCode != nil)
self.navigationItem.title = NSLocalizedString("REMINDER_2FA_NAV_TITLE", comment: "Navbar title for when user is periodically prompted to enter their registration lock PIN")
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(didPressCloseButton))
let view = UIView()
self.view = view
view.backgroundColor = Theme.backgroundColor
let pinEntryView = PinEntryView()
self.pinEntryView = pinEntryView
pinEntryView.delegate = self
let instructionsTextHeader = NSLocalizedString("REMINDER_2FA_BODY_HEADER", comment: "Body header for when user is periodically prompted to enter their registration lock PIN")
let instructionsTextBody = NSLocalizedString("REMINDER_2FA_BODY", comment: "Body text for when user is periodically prompted to enter their registration lock PIN")
let attributes = [NSAttributedString.Key.font: pinEntryView.boldLabelFont]
let attributedInstructionsText = NSAttributedString(string: instructionsTextHeader, attributes: attributes).rtlSafeAppend(" ").rtlSafeAppend(instructionsTextBody)
pinEntryView.attributedInstructionsText = attributedInstructionsText
view.addSubview(pinEntryView)
pinEntryView.autoPinWidthToSuperview(withMargin: 20)
pinEntryView.autoPinEdge(.top, to: .top, of: view, withOffset: ScaleFromIPhone5(16))
pinEntryView.autoPinEdge(.bottom, to: .bottom, of: view)
}
// MARK: PinEntryViewDelegate
public func pinEntryView(_ entryView: PinEntryView, submittedPinCode pinCode: String) {
Logger.info("")
if checkResult(pinCode: pinCode) {
didSubmitCorrectPin()
} else {
didSubmitWrongPin()
}
}
//textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
public func pinEntryView(_ entryView: PinEntryView, pinCodeDidChange pinCode: String) {
// optimistically match, without having to press "done"
if checkResult(pinCode: pinCode) {
didSubmitCorrectPin()
}
}
public func pinEntryViewForgotPinLinkTapped(_ entryView: PinEntryView) {
Logger.info("")
let alertBody = NSLocalizedString("REMINDER_2FA_FORGOT_PIN_ALERT_MESSAGE",
comment: "Alert message explaining what happens if you forget your 'two-factor auth pin'")
OWSAlerts.showAlert(title: nil, message: alertBody)
}
// MARK: Helpers
@objc
private func didPressCloseButton(sender: UIButton) {
Logger.info("")
// We'll ask again next time they launch
self.dismiss(animated: true)
}
private func checkResult(pinCode: String) -> Bool {
return pinCode == ows2FAManager.pinCode
}
private func didSubmitCorrectPin() {
Logger.info("noWrongGuesses: \(noWrongGuesses)")
self.dismiss(animated: true)
OWS2FAManager.shared().updateRepetitionInterval(withWasSuccessful: noWrongGuesses)
}
var noWrongGuesses = true
private func didSubmitWrongPin() {
noWrongGuesses = false
Logger.info("")
let alertTitle = NSLocalizedString("REMINDER_2FA_WRONG_PIN_ALERT_TITLE",
comment: "Alert title after wrong guess for 'two-factor auth pin' reminder activity")
let alertBody = NSLocalizedString("REMINDER_2FA_WRONG_PIN_ALERT_BODY",
comment: "Alert body after wrong guess for 'two-factor auth pin' reminder activity")
OWSAlerts.showAlert(title: alertTitle, message: alertBody)
self.pinEntryView.clearText()
}
}

View File

@ -1,25 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalMessaging/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, Enable2FAMode) {
OWS2FASettingsMode_Status = 0,
OWS2FASettingsMode_SelectPIN,
OWS2FASettingsMode_ConfirmPIN,
};
@interface OWS2FASettingsViewController : OWSViewController
@property (nonatomic) Enable2FAMode mode;
// When confirming the PIN, this is the PIN that was
// initially entered by the user.
@property (nonatomic, nullable) NSString *candidatePin;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,455 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWS2FASettingsViewController.h"
#import "OWSTableViewController.h"
#import "Session-Swift.h"
#import "SignalMessaging.h"
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalMessaging/SignalMessaging.h>
#import <SignalMessaging/UIColor+OWS.h>
#import <SignalMessaging/UIFont+OWS.h>
#import <SignalMessaging/UIView+OWS.h>
#import <SessionServiceKit/NSString+SSK.h>
#import <SessionServiceKit/OWS2FAManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWS2FASettingsViewController () <UITextFieldDelegate>
@property (nonatomic, weak) UIViewController *root2FAViewController;
@property (nonatomic) UITextField *pinTextfield;
@property (nonatomic) OWSTableViewController *tableViewController;
@end
#pragma mark -
@implementation OWS2FASettingsViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [Theme backgroundColor];
self.title = NSLocalizedString(@"ENABLE_2FA_VIEW_TITLE", @"Title for the 'enable two factor auth PIN' views.");
[self createContents];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(stateDidChange:)
name:NSNotificationName_2FAStateDidChange
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)createContents
{
for (UIView *subview in self.view.subviews) {
[subview removeFromSuperview];
}
switch (self.mode) {
case OWS2FASettingsMode_Status:
[self createStatusContents];
break;
case OWS2FASettingsMode_SelectPIN:
[self createSelectCodeContents];
break;
case OWS2FASettingsMode_ConfirmPIN:
[self createConfirmCodeContents];
break;
}
}
- (void)viewWillAppear:(BOOL)animated
{
switch (self.mode) {
case OWS2FASettingsMode_Status:
break;
case OWS2FASettingsMode_SelectPIN:
case OWS2FASettingsMode_ConfirmPIN:
OWSAssertDebug(![OWS2FAManager.sharedManager is2FAEnabled]);
break;
}
[super viewWillAppear:animated];
// If we're using a table, refresh its contents.
[self updateTableContents];
[self updateNavigationItems];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// If we're using a PIN textfield, select it.
[self.pinTextfield becomeFirstResponder];
}
- (UILabel *)createLabelWithText:(NSString *)text
{
UILabel *label = [UILabel new];
label.textColor = [Theme primaryColor];
label.text = text;
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:label];
return label;
}
- (void)createPinTextfield
{
self.pinTextfield = [OWSTextField new];
self.pinTextfield.textColor = [Theme primaryColor];
self.pinTextfield.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(30.f, 36.f)];
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
self.pinTextfield.keyboardType = UIKeyboardTypeNumberPad;
self.pinTextfield.delegate = self;
self.pinTextfield.secureTextEntry = YES;
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
[self.pinTextfield addTarget:self
action:@selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _pinTextfield);
[self.view addSubview:self.pinTextfield];
}
- (void)createTableView
{
self.tableViewController = [OWSTableViewController new];
[self.view addSubview:self.tableViewController.view];
}
- (void)createStatusContents
{
const CGFloat kVSpacing = 30.f;
// TODO: Add hero image?
// TODO: Tweak background color?
NSString *instructions = ([OWS2FAManager.sharedManager is2FAEnabled]
? NSLocalizedString(@"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS",
@"Indicates that user has 'two factor auth pin' enabled.")
: NSLocalizedString(@"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS",
@"Indicates that user has 'two factor auth pin' disabled."));
UILabel *instructionsLabel = [self createLabelWithText:instructions];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, instructionsLabel);
[self createTableView];
[instructionsLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:kVSpacing];
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
[self.tableViewController.view autoPinEdge:ALEdgeTop
toEdge:ALEdgeBottom
ofView:instructionsLabel
withOffset:kVSpacing];
[self.tableViewController.view autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.view withOffset:0.0f];
[self updateTableContents];
}
- (void)createSelectCodeContents
{
[self createEnterPINContentsWithInstructions:NSLocalizedString(@"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS",
@"Indicates that user should select a 'two factor auth pin'.")];
}
- (void)createConfirmCodeContents
{
[self
createEnterPINContentsWithInstructions:NSLocalizedString(@"ENABLE_2FA_VIEW_CONFIRM_PIN_INSTRUCTIONS",
@"Indicates that user should confirm their 'two factor auth pin'.")];
}
- (CGFloat)hMargin
{
return 20.f;
}
- (void)createEnterPINContentsWithInstructions:(NSString *)instructionsText
{
const CGFloat kVSpacing = 30.f;
UILabel *instructionsLabel = [self createLabelWithText:instructionsText];
[self createPinTextfield];
[instructionsLabel autoPinTopToSuperviewMarginWithInset:kVSpacing];
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, instructionsLabel);
[self.pinTextfield autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:instructionsLabel withOffset:kVSpacing];
[self.pinTextfield autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
[self.pinTextfield autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
UIView *underscoreView = [UIView new];
underscoreView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1.f];
[self.view addSubview:underscoreView];
[underscoreView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.pinTextfield withOffset:3];
[underscoreView autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
[underscoreView autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
[underscoreView autoSetDimension:ALDimensionHeight toSize:1.f];
[self updateNavigationItems];
}
- (void)updateTableContents
{
__weak OWS2FASettingsViewController *weakSelf = self;
// Only some modes use a table.
switch (self.mode) {
case OWS2FASettingsMode_Status: {
OWSTableContents *contents = [OWSTableContents new];
OWSTableSection *section = [OWSTableSection new];
if ([OWS2FAManager.sharedManager is2FAEnabled]) {
[section
addItem:[OWSTableItem disclosureItemWithText:
NSLocalizedString(@"ENABLE_2FA_VIEW_DISABLE_2FA",
@"Label for the 'enable two-factor auth' item in the settings view")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"enable_2fa")
actionBlock:^{
[weakSelf tryToDisable2FA];
}]];
} else {
[section addItem:[OWSTableItem
disclosureItemWithText:
NSLocalizedString(@"ENABLE_2FA_VIEW_ENABLE_2FA",
@"Label for the 'enable two-factor auth' item in the settings view")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"disable_2fa")
actionBlock:^{
[weakSelf showEnable2FAWorkUI];
}]];
}
[contents addSection:section];
self.tableViewController.contents = contents;
break;
}
case OWS2FASettingsMode_SelectPIN:
case OWS2FASettingsMode_ConfirmPIN:
return;
}
}
- (BOOL)shouldHaveNextButton
{
switch (self.mode) {
case OWS2FASettingsMode_Status:
return NO;
case OWS2FASettingsMode_SelectPIN:
case OWS2FASettingsMode_ConfirmPIN:
return [self hasValidPin];
}
}
- (void)updateNavigationItems
{
// Note: This affects how the "back" button will look if another
// view is pushed on top of this one, not how the "back"
// button looks when this view is visible.
UIBarButtonItem *backButton =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BACK_BUTTON", @"button text for back button")
style:UIBarButtonItemStylePlain
target:self
action:@selector(backButtonWasPressed)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"back")];
self.navigationItem.backBarButtonItem = backButton;
if (self.shouldHaveNextButton) {
UIBarButtonItem *nextButton = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(@"ENABLE_2FA_VIEW_NEXT_BUTTON",
@"Label for the 'next' button in the 'enable two factor auth' views.")
style:UIBarButtonItemStylePlain
target:self
action:@selector(nextButtonWasPressed)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"next")];
self.navigationItem.rightBarButtonItem = nextButton;
} else {
self.navigationItem.rightBarButtonItem = nil;
}
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
{
[ViewControllerUtils ows2FAPINTextField:textField
shouldChangeCharactersInRange:range
replacementString:insertionText];
[self updateNavigationItems];
return NO;
}
- (void)textFieldDidChange:(id)sender
{
[self updateNavigationItems];
}
#pragma mark - Events
- (void)nextButtonWasPressed
{
switch (self.mode) {
case OWS2FASettingsMode_Status:
OWSFailDebug(@"status mode should not have a next button.");
return;
case OWS2FASettingsMode_SelectPIN: {
OWSAssertDebug(self.hasValidPin);
OWS2FASettingsViewController *vc = [OWS2FASettingsViewController new];
vc.mode = OWS2FASettingsMode_ConfirmPIN;
vc.candidatePin = self.pinTextfield.text;
OWSAssertDebug(self.root2FAViewController);
vc.root2FAViewController = self.root2FAViewController;
[self.navigationController pushViewController:vc animated:YES];
break;
}
case OWS2FASettingsMode_ConfirmPIN: {
OWSAssertDebug(self.hasValidPin);
if ([self.pinTextfield.text isEqualToString:self.candidatePin]) {
[self tryToEnable2FA];
} else {
// Clear the PIN so that the user can try again.
self.pinTextfield.text = nil;
[OWSAlerts showErrorAlertWithMessage:
NSLocalizedString(@"ENABLE_2FA_VIEW_PIN_DOES_NOT_MATCH",
@"Error indicating that the entered 'two-factor auth PINs' do not match.")];
}
break;
}
}
}
- (BOOL)hasValidPin
{
return self.pinTextfield.text.length >= kMin2FAPinLength;
}
- (void)showEnable2FAWorkUI
{
OWSAssertDebug(![OWS2FAManager.sharedManager is2FAEnabled]);
OWSLogInfo(@"");
OWS2FASettingsViewController *vc = [OWS2FASettingsViewController new];
vc.mode = OWS2FASettingsMode_SelectPIN;
vc.root2FAViewController = self;
[self.navigationController pushViewController:vc animated:YES];
}
- (void)tryToDisable2FA
{
OWSLogInfo(@"");
__weak OWS2FASettingsViewController *weakSelf = self;
[ModalActivityIndicatorViewController
presentFromViewController:self
canCancel:NO
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
[OWS2FAManager.sharedManager disable2FAWithSuccess:^{
[modalActivityIndicator dismissWithCompletion:^{
// TODO: Should we show an alert?
[weakSelf updateTableContents];
}];
}
failure:^(NSError *error) {
[modalActivityIndicator dismissWithCompletion:^{
[weakSelf updateTableContents];
[OWSAlerts showErrorAlertWithMessage:
NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA",
@"Error indicating that attempt to disable 'two-factor "
@"auth' failed.")];
}];
}];
}];
}
- (void)tryToEnable2FA
{
OWSAssertDebug(self.candidatePin.length > 0);
OWSLogInfo(@"");
__weak OWS2FASettingsViewController *weakSelf = self;
[ModalActivityIndicatorViewController
presentFromViewController:self
canCancel:NO
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
[OWS2FAManager.sharedManager requestEnable2FAWithPin:self.candidatePin
success:^{
[modalActivityIndicator dismissWithCompletion:^{
[weakSelf showCompleteUI];
}];
}
failure:^(NSError *error) {
[modalActivityIndicator dismissWithCompletion:^{
// The client may have fallen out of sync with the service.
// Try to get back to a known good state by disabling 2FA
// whenever enabling it fails.
[OWS2FAManager.sharedManager disable2FAWithSuccess:nil failure:nil];
[weakSelf updateTableContents];
[OWSAlerts showErrorAlertWithMessage:
NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA",
@"Error indicating that attempt to enable 'two-factor "
@"auth' failed.")];
}];
}];
}];
}
- (void)showCompleteUI
{
OWSAssertDebug([OWS2FAManager.sharedManager is2FAEnabled]);
OWSAssertDebug(self.root2FAViewController);
OWSLogInfo(@"");
[self.navigationController popToViewController:self.root2FAViewController animated:YES];
}
- (void)backButtonWasPressed
{
[self.navigationController popViewControllerAnimated:YES];
}
- (void)stateDidChange:(NSNotification *)notification
{
OWSLogInfo(@"");
if (self.mode == OWS2FASettingsMode_Status) {
[self createContents];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -5,7 +5,6 @@
#import "ProfileViewController.h"
#import "AppDelegate.h"
#import "AvatarViewHelper.h"
#import "HomeViewController.h"
#import "OWSNavigationController.h"
#import "Session-Swift.h"
#import "SignalsNavigationController.h"
@ -586,11 +585,7 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
+ (void)presentForUpgradeOrNag:(HomeViewController *)fromViewController
{
OWSAssertDebug(fromViewController);
ProfileViewController *vc = [[ProfileViewController alloc] initWithMode:ProfileViewMode_UpgradeOrNag];
OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:vc];
[fromViewController presentViewController:navigationController animated:YES completion:nil];
}
#pragma mark - AvatarViewHelperDelegate

View File

@ -1,181 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class Onboarding2FAViewController: OnboardingBaseViewController {
private let pinTextField = UITextField()
private var pinStrokeNormal: UIView?
private var pinStrokeError: UIView?
private let validationWarningLabel = UILabel()
private var isPinInvalid = false {
didSet {
updateValidationWarnings()
}
}
override public func loadView() {
super.loadView()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_2FA_TITLE", comment: "Title of the 'onboarding 2FA' view."))
let explanationLabel1 = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_2FA_EXPLANATION_1",
comment: "The first explanation in the 'onboarding 2FA' view."))
let explanationLabel2 = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_2FA_EXPLANATION_2",
comment: "The first explanation in the 'onboarding 2FA' view."))
explanationLabel1.font = UIFont.ows_dynamicTypeCaption1
explanationLabel2.font = UIFont.ows_dynamicTypeCaption1
explanationLabel1.accessibilityIdentifier = "onboarding.2fa." + "explanationLabel1"
explanationLabel2.accessibilityIdentifier = "onboarding.2fa." + "explanationLabel2"
pinTextField.textAlignment = .center
pinTextField.delegate = self
pinTextField.keyboardType = .numberPad
pinTextField.textColor = Theme.primaryColor
pinTextField.font = UIFont.ows_dynamicTypeBodyClamped
pinTextField.setContentHuggingHorizontalLow()
pinTextField.setCompressionResistanceHorizontalLow()
pinTextField.autoSetDimension(.height, toSize: 40)
pinTextField.accessibilityIdentifier = "onboarding.2fa." + "pinTextField"
pinStrokeNormal = pinTextField.addBottomStroke()
pinStrokeError = pinTextField.addBottomStroke(color: .ows_destructiveRed, strokeWidth: 2)
validationWarningLabel.text = NSLocalizedString("ONBOARDING_PHONE_NUMBER_VALIDATION_WARNING",
comment: "Label indicating that the phone number is invalid in the 'onboarding phone number' view.")
validationWarningLabel.textColor = .ows_destructiveRed
validationWarningLabel.font = UIFont.ows_dynamicTypeSubheadlineClamped
validationWarningLabel.textAlignment = .center
validationWarningLabel.accessibilityIdentifier = "onboarding.2fa." + "validationWarningLabel"
let validationWarningRow = UIView()
validationWarningRow.addSubview(validationWarningLabel)
validationWarningLabel.ows_autoPinToSuperviewEdges()
validationWarningRow.autoSetDimension(.height, toSize: validationWarningLabel.font.lineHeight)
let forgotPinLink = self.createLinkButton(title: NSLocalizedString("ONBOARDING_2FA_FORGOT_PIN_LINK",
comment: "Label for the 'forgot 2FA PIN' link in the 'onboarding 2FA' view."),
selector: #selector(forgotPinLinkTapped))
forgotPinLink.accessibilityIdentifier = "onboarding.2fa." + "forgotPinLink"
let nextButton = self.createButton(title: NSLocalizedString("BUTTON_NEXT",
comment: "Label for the 'next' button."),
selector: #selector(nextPressed))
nextButton.accessibilityIdentifier = "onboarding.2fa." + "nextButton"
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
UIView.spacer(withHeight: 10),
explanationLabel1,
UIView.spacer(withHeight: 10),
explanationLabel2,
topSpacer,
pinTextField,
UIView.spacer(withHeight: 10),
validationWarningRow,
bottomSpacer,
forgotPinLink,
UIView.spacer(withHeight: 10),
nextButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPinEdge(.top, to: .top, of: view)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
// Ensure whitespace is balanced, so inputs are vertically centered.
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
updateValidationWarnings()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_ = pinTextField.becomeFirstResponder()
}
// MARK: - Events
@objc func forgotPinLinkTapped() {
Logger.info("")
OWSAlerts.showAlert(title: nil, message: NSLocalizedString("REGISTER_2FA_FORGOT_PIN_ALERT_MESSAGE",
comment: "Alert message explaining what happens if you forget your 'two-factor auth pin'."))
}
@objc func nextPressed() {
Logger.info("")
tryToVerify()
}
private func tryToVerify() {
Logger.info("")
guard let pin = pinTextField.text?.ows_stripped(),
pin.count > 0 else {
isPinInvalid = true
return
}
isPinInvalid = false
onboardingController.update(twoFAPin: pin)
onboardingController.tryToVerify(fromViewController: self, completion: { (outcome) in
if outcome == .invalid2FAPin {
self.isPinInvalid = true
} else if outcome == .invalidVerificationCode {
owsFailDebug("Invalid verification code in 2FA view.")
}
})
}
private func updateValidationWarnings() {
AssertIsOnMainThread()
pinStrokeNormal?.isHidden = isPinInvalid
pinStrokeError?.isHidden = !isPinInvalid
validationWarningLabel.isHidden = !isPinInvalid
}
}
// MARK: -
extension Onboarding2FAViewController: UITextFieldDelegate {
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString = string.digitsOnly
var oldText = ""
if let textFieldText = textField.text {
oldText = textFieldText
}
let left = oldText.substring(to: range.location)
let right = oldText.substring(from: range.location + range.length)
textField.text = left + newString + right
isPinInvalid = false
// Inform our caller that we took care of performing the change.
return false
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
tryToVerify()
return false
}
}

View File

@ -1,116 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
import PromiseKit
@objc
public class OnboardingBaseViewController: OWSViewController {
// Unlike a delegate, we can and should retain a strong reference to the OnboardingController.
let onboardingController: OnboardingController
@objc
public init(onboardingController: OnboardingController) {
self.onboardingController = onboardingController
super.init(nibName: nil, bundle: nil)
self.shouldUseTheme = false
}
@available(*, unavailable, message: "use other init() instead.")
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: - Factory Methods
func createTitleLabel(text: String) -> UILabel {
let titleLabel = UILabel()
titleLabel.text = text
titleLabel.textColor = Theme.primaryColor
titleLabel.font = UIFont.ows_dynamicTypeTitle1Clamped.ows_mediumWeight()
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.textAlignment = .center
return titleLabel
}
func createExplanationLabel(text: String) -> UILabel {
let explanationLabel = UILabel()
explanationLabel.textColor = Theme.secondaryColor
explanationLabel.font = UIFont.ows_dynamicTypeSubheadlineClamped
explanationLabel.text = text
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
return explanationLabel
}
func createButton(title: String, selector: Selector) -> OWSFlatButton {
return button(title: title, selector: selector, titleColor: .white, backgroundColor: .ows_materialBlue)
}
func createLinkButton(title: String, selector: Selector) -> OWSFlatButton {
return button(title: title, selector: selector, titleColor: .ows_materialBlue, backgroundColor: .white)
}
private func button(title: String, selector: Selector, titleColor: UIColor, backgroundColor: UIColor) -> OWSFlatButton {
let font = UIFont.ows_dynamicTypeBodyClamped.ows_mediumWeight()
// Button height should be 48pt if the font is 17pt.
let buttonHeight = font.pointSize * 48 / 17
let button = OWSFlatButton.button(title: title,
font: font,
titleColor: titleColor,
backgroundColor: backgroundColor,
target: self,
selector: selector)
button.autoSetDimension(.height, toSize: buttonHeight)
return button
}
// MARK: - View Lifecycle
public override func viewDidLoad() {
super.viewDidLoad()
self.shouldBottomViewReserveSpaceForKeyboard = true
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
// Disable "back" gesture.
self.navigationController?.navigationItem.backBarButtonItem?.isEnabled = false
// TODO: Is there a better way to do this?
if let navigationController = self.navigationController as? OWSNavigationController {
SignalApp.shared().signUpFlowNavigationController = navigationController
} else {
owsFailDebug("Missing or invalid navigationController")
}
view.layoutIfNeeded()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.isNavigationBarHidden = true
// Disable "back" gesture.
self.navigationController?.navigationItem.backBarButtonItem?.isEnabled = false
}
// MARK: - Orientation
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
public override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}

View File

@ -2,6 +2,7 @@
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
/*
import UIKit
import WebKit
@ -196,3 +197,5 @@ extension OnboardingCaptchaViewController: WKNavigationDelegate {
Logger.verbose("")
}
}
*/

View File

@ -1,550 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class OnboardingCountryState: NSObject {
public let countryName: String
public let callingCode: String
public let countryCode: String
@objc
public init(countryName: String,
callingCode: String,
countryCode: String) {
self.countryName = countryName
self.callingCode = callingCode
self.countryCode = countryCode
}
public static var defaultValue: OnboardingCountryState {
AssertIsOnMainThread()
var countryCode: String = PhoneNumber.defaultCountryCode()
if let lastRegisteredCountryCode = OnboardingController.lastRegisteredCountryCode(),
lastRegisteredCountryCode.count > 0 {
countryCode = lastRegisteredCountryCode
}
let callingCodeNumber: NSNumber = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(forRegion: countryCode)
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumber)"
var countryName = NSLocalizedString("UNKNOWN_COUNTRY_NAME", comment: "Label for unknown countries.")
if let countryNameDerived = PhoneNumberUtil.countryName(fromCountryCode: countryCode) {
countryName = countryNameDerived
}
return OnboardingCountryState(countryName: countryName, callingCode: callingCode, countryCode: countryCode)
}
}
// MARK: -
@objc
public class OnboardingPhoneNumber: NSObject {
public let e164: String
public let userInput: String
@objc
public init(e164: String,
userInput: String) {
self.e164 = e164
self.userInput = userInput
}
}
// MARK: -
@objc
public class OnboardingController: NSObject {
// MARK: - Dependencies
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
private var accountManager: AccountManager {
return AppEnvironment.shared.accountManager
}
private var contactsManager: OWSContactsManager {
return Environment.shared.contactsManager
}
private var backup: OWSBackup {
return AppEnvironment.shared.backup
}
// MARK: -
@objc
public override init() {
super.init()
}
// MARK: - Factory Methods
@objc
public func initialViewController() -> UIViewController {
AssertIsOnMainThread()
return LandingVC()
}
// MARK: - Transitions
public func onboardingSplashDidComplete(viewController: UIViewController) {
pushSeedVC(from: viewController)
}
public func onboardingPermissionsWasSkipped(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
pushSeedVC(from: viewController)
}
public func onboardingPermissionsDidComplete(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
pushSeedVC(from: viewController)
}
public func pushSeedVC(from viewController: UIViewController) {
// AssertIsOnMainThread()
// let seedVC = SeedVC(onboardingController: self)
// viewController.navigationController?.pushViewController(seedVC, animated: true)
}
public func pushDisplayNameVC(from viewController: UIViewController) {
// AssertIsOnMainThread()
// let displayNameVC = DisplayNameVC(onboardingController: self)
// viewController.navigationController?.pushViewController(displayNameVC, animated: true)
}
public func onboardingRegistrationSucceeded(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
let view = OnboardingVerificationViewController(onboardingController: self)
viewController.navigationController?.pushViewController(view, animated: true)
}
public func onboardingDidRequireCaptcha(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
guard let navigationController = viewController.navigationController else {
owsFailDebug("Missing navigationController.")
return
}
// The service could demand CAPTCHA from the "phone number" view or later
// from the "code verification" view. The "Captcha" view should always appear
// immediately after the "phone number" view.
while navigationController.viewControllers.count > 1 &&
!(navigationController.topViewController is DisplayNameVC) {
navigationController.popViewController(animated: false)
}
let view = OnboardingCaptchaViewController(onboardingController: self)
navigationController.pushViewController(view, animated: true)
}
@objc
public func verificationDidComplete(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
// At this point, the user has been prompted for contact access
// and has valid service credentials.
// We start the contact fetch/intersection now so that by the time
// they get to HomeView we can show meaningful contact in the suggested
// contact bubble.
contactsManager.fetchSystemContactsOnceIfAlreadyAuthorized()
if tsAccountManager.isReregistering() {
showHomeView(view: view)
} else {
checkCanImportBackup(fromView: view)
}
}
private func showProfileView(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
guard let navigationController = view.navigationController else {
owsFailDebug("Missing navigationController")
return
}
ProfileViewController.present(forRegistration: navigationController)
}
private func showBackupRestoreView(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
guard let navigationController = view.navigationController else {
owsFailDebug("Missing navigationController")
return
}
let restoreView = BackupRestoreViewController()
navigationController.setViewControllers([restoreView], animated: true)
}
private func checkCanImportBackup(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
backup.checkCanImport({ (canImport) in
Logger.info("canImport: \(canImport)")
if (canImport) {
self.backup.setHasPendingRestoreDecision(true)
self.showBackupRestoreView(fromView: view)
} else {
self.showHomeView(view: view)
}
}, failure: { (_) in
self.showBackupCheckFailedAlert(fromView: view)
})
}
private func showBackupCheckFailedAlert(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
let alert = UIAlertController(title: NSLocalizedString("CHECK_FOR_BACKUP_FAILED_TITLE",
comment: "Title for alert shown when the app failed to check for an existing backup."),
message: NSLocalizedString("CHECK_FOR_BACKUP_FAILED_MESSAGE",
comment: "Message for alert shown when the app failed to check for an existing backup."),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("REGISTER_FAILED_TRY_AGAIN", comment: ""),
style: .default) { (_) in
self.checkCanImportBackup(fromView: view)
})
alert.addAction(UIAlertAction(title: NSLocalizedString("CHECK_FOR_BACKUP_DO_NOT_RESTORE", comment: "The label for the 'do not restore backup' button."),
style: .destructive) { (_) in
self.showHomeView(view: view)
})
view.presentAlert(alert)
}
public func onboardingDidRequire2FAPin(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
guard let navigationController = viewController.navigationController else {
owsFailDebug("Missing navigationController")
return
}
let view = Onboarding2FAViewController(onboardingController: self)
navigationController.pushViewController(view, animated: true)
}
@objc
public func profileWasSkipped(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
showHomeView(view: view)
}
@objc
public func profileDidComplete(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
showHomeView(view: view)
}
private func showHomeView(view: UIViewController) {
AssertIsOnMainThread()
guard let navigationController = view.navigationController else {
owsFailDebug("Missing navigationController")
return
}
// In production, this view will never be presented in a modal.
// During testing (debug UI, etc.), it may be a modal.
let isModal = navigationController.presentingViewController != nil
if isModal {
view.dismiss(animated: true, completion: {
SignalApp.shared().showHomeView()
})
} else {
SignalApp.shared().showHomeView()
}
}
// MARK: - State
public private(set) var countryState: OnboardingCountryState = .defaultValue
public private(set) var phoneNumber: OnboardingPhoneNumber?
public private(set) var captchaToken: String?
public private(set) var verificationCode: String?
public private(set) var twoFAPin: String?
@objc
public func update(countryState: OnboardingCountryState) {
AssertIsOnMainThread()
self.countryState = countryState
}
@objc
public func update(phoneNumber: OnboardingPhoneNumber) {
AssertIsOnMainThread()
self.phoneNumber = phoneNumber
}
@objc
public func update(captchaToken: String) {
AssertIsOnMainThread()
self.captchaToken = captchaToken
}
@objc
public func update(verificationCode: String) {
AssertIsOnMainThread()
self.verificationCode = verificationCode
}
@objc
public func update(twoFAPin: String) {
AssertIsOnMainThread()
self.twoFAPin = twoFAPin
}
// MARK: - Debug
private static let kKeychainService_LastRegistered = "kKeychainService_LastRegistered"
private static let kKeychainKey_LastRegisteredCountryCode = "kKeychainKey_LastRegisteredCountryCode"
private static let kKeychainKey_LastRegisteredPhoneNumber = "kKeychainKey_LastRegisteredPhoneNumber"
private class func debugValue(forKey key: String) -> String? {
AssertIsOnMainThread()
guard OWSIsDebugBuild() else {
return nil
}
do {
let value = try CurrentAppContext().keychainStorage().string(forService: kKeychainService_LastRegistered, key: key)
return value
} catch {
// The value may not be present in the keychain.
return nil
}
}
private class func setDebugValue(_ value: String, forKey key: String) {
AssertIsOnMainThread()
guard OWSIsDebugBuild() else {
return
}
do {
try CurrentAppContext().keychainStorage().set(string: value, service: kKeychainService_LastRegistered, key: key)
} catch {
owsFailDebug("Error: \(error)")
}
}
public class func lastRegisteredCountryCode() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredCountryCode)
}
private class func setLastRegisteredCountryCode(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredCountryCode)
}
public class func lastRegisteredPhoneNumber() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
private class func setLastRegisteredPhoneNumber(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
// MARK: - Registration
public func tryToRegister(fromViewController: UIViewController,
smsVerification: Bool) {
guard let phoneNumber = phoneNumber else {
owsFailDebug("Missing phoneNumber.")
return
}
// We eagerly update this state, regardless of whether or not the
// registration request succeeds.
OnboardingController.setLastRegisteredCountryCode(value: countryState.countryCode)
OnboardingController.setLastRegisteredPhoneNumber(value: phoneNumber.userInput)
let captchaToken = self.captchaToken
ModalActivityIndicatorViewController.present(fromViewController: fromViewController,
canCancel: true) { (modal) in
self.tsAccountManager.register(withPhoneNumber: phoneNumber.e164,
captchaToken: captchaToken,
success: {
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationSucceeded(viewController: fromViewController)
})
}
}, failure: { (error) in
Logger.error("Error: \(error)")
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationFailed(viewController: fromViewController, error: error as NSError)
})
}
}, smsVerification: smsVerification)
}
}
private func registrationSucceeded(viewController: UIViewController) {
onboardingRegistrationSucceeded(viewController: viewController)
}
private func registrationFailed(viewController: UIViewController, error: NSError) {
if error.code == 402 {
Logger.info("Captcha requested.")
onboardingDidRequireCaptcha(viewController: viewController)
} else if error.code == 400 {
OWSAlerts.showAlert(title: NSLocalizedString("REGISTRATION_ERROR", comment: ""),
message: NSLocalizedString("REGISTRATION_NON_VALID_NUMBER", comment: ""))
} else {
OWSAlerts.showAlert(title: error.localizedDescription,
message: error.localizedRecoverySuggestion)
}
}
// MARK: - Verification
public enum VerificationOutcome {
case success
case invalidVerificationCode
case invalid2FAPin
}
public func tryToVerify(fromViewController: UIViewController,
completion : @escaping (VerificationOutcome) -> Void) {
AssertIsOnMainThread()
guard let phoneNumber = phoneNumber else {
owsFailDebug("Missing phoneNumber.")
return
}
guard let verificationCode = verificationCode else {
completion(.invalidVerificationCode)
return
}
// Ensure the account manager state is up-to-date.
//
// TODO: We could skip this in production.
tsAccountManager.phoneNumberAwaitingVerification = phoneNumber.e164
let twoFAPin = self.twoFAPin
ModalActivityIndicatorViewController.present(fromViewController: fromViewController,
canCancel: true) { (modal) in
self.accountManager.register(verificationCode: verificationCode, pin: twoFAPin)
.done { (_) in
DispatchQueue.main.async {
modal.dismiss(completion: {
self.verificationDidComplete(fromView: fromViewController)
})
}
}.catch({ (error) in
Logger.error("Error: \(error)")
DispatchQueue.main.async {
modal.dismiss(completion: {
self.verificationFailed(fromViewController: fromViewController,
error: error as NSError,
completion: completion)
})
}
}).retainUntilComplete()
}
}
private func verificationFailed(fromViewController: UIViewController, error: NSError,
completion : @escaping (VerificationOutcome) -> Void) {
AssertIsOnMainThread()
if error.domain == OWSSignalServiceKitErrorDomain &&
error.code == OWSErrorCode.registrationMissing2FAPIN.rawValue {
Logger.info("Missing 2FA PIN.")
completion(.invalid2FAPin)
onboardingDidRequire2FAPin(viewController: fromViewController)
} else {
if error.domain == OWSSignalServiceKitErrorDomain &&
error.code == OWSErrorCode.userError.rawValue {
completion(.invalidVerificationCode)
}
Logger.verbose("error: \(error.domain) \(error.code)")
OWSAlerts.showAlert(title: NSLocalizedString("REGISTRATION_VERIFICATION_FAILED_TITLE", comment: "Alert view title"),
message: error.localizedDescription,
fromViewController: fromViewController)
}
}
}
// MARK: -
public extension UIView {
public func addBottomStroke() -> UIView {
return addBottomStroke(color: Theme.middleGrayColor, strokeWidth: CGHairlineWidth())
}
public func addBottomStroke(color: UIColor, strokeWidth: CGFloat) -> UIView {
let strokeView = UIView()
strokeView.backgroundColor = color
addSubview(strokeView)
strokeView.autoSetDimension(.height, toSize: strokeWidth)
strokeView.autoPinWidthToSuperview()
strokeView.autoPinEdge(toSuperviewEdge: .bottom)
return strokeView
}
}

View File

@ -2,6 +2,7 @@
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
/*
import UIKit
import PromiseKit
import Contacts
@ -107,3 +108,4 @@ public class OnboardingPermissionsViewController: OnboardingBaseViewController {
onboardingController.onboardingPermissionsWasSkipped(viewController: self)
}
}
*/

View File

@ -1,257 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class OnboardingProfileViewController: OnboardingBaseViewController {
// MARK: - Dependencies
var profileManager: OWSProfileManager {
return OWSProfileManager.shared()
}
// MARK: -
private let avatarView = AvatarImageView()
private let nameTextfield = UITextField()
private var avatar: UIImage?
private let cameraCircle = UIView.container()
private let avatarViewHelper = AvatarViewHelper()
override public func loadView() {
super.loadView()
avatarViewHelper.delegate = self
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_PROFILE_TITLE", comment: "Title of the 'onboarding profile' view."))
titleLabel.accessibilityIdentifier = "onboarding.profile." + "titleLabel"
let explanationLabel = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_PROFILE_EXPLANATION",
comment: "Explanation in the 'onboarding profile' view."))
explanationLabel.accessibilityIdentifier = "onboarding.profile." + "explanationLabel"
let nextButton = self.createButton(title: NSLocalizedString("BUTTON_NEXT",
comment: "Label for the 'next' button."),
selector: #selector(nextPressed))
nextButton.accessibilityIdentifier = "onboarding.profile." + "nextButton"
avatarView.autoSetDimensions(to: CGSize(width: CGFloat(avatarSize), height: CGFloat(avatarSize)))
let cameraImageView = UIImageView()
cameraImageView.image = UIImage(named: "settings-avatar-camera-2")?.withRenderingMode(.alwaysTemplate)
cameraImageView.tintColor = Theme.secondaryColor
cameraCircle.backgroundColor = Theme.backgroundColor
cameraCircle.addSubview(cameraImageView)
let cameraCircleDiameter: CGFloat = 40
cameraCircle.autoSetDimensions(to: CGSize(width: cameraCircleDiameter, height: cameraCircleDiameter))
cameraCircle.layer.shadowColor = UIColor(white: 0, alpha: 0.15).cgColor
cameraCircle.layer.shadowRadius = 5
cameraCircle.layer.shadowOffset = CGSize(width: 1, height: 1)
cameraCircle.layer.shadowOpacity = 1
cameraCircle.layer.cornerRadius = cameraCircleDiameter * 0.5
cameraCircle.clipsToBounds = false
cameraImageView.autoCenterInSuperview()
let avatarWrapper = UIView.container()
avatarWrapper.isUserInteractionEnabled = true
avatarWrapper.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(avatarTapped)))
avatarWrapper.addSubview(avatarView)
avatarView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4))
avatarWrapper.addSubview(cameraCircle)
cameraCircle.autoPinEdge(toSuperviewEdge: .trailing)
cameraCircle.autoPinEdge(toSuperviewEdge: .bottom)
avatarWrapper.accessibilityIdentifier = "onboarding.profile." + "avatarWrapper"
nameTextfield.textAlignment = .left
nameTextfield.delegate = self
nameTextfield.returnKeyType = .done
nameTextfield.textColor = Theme.primaryColor
nameTextfield.font = UIFont.ows_dynamicTypeBodyClamped
nameTextfield.placeholder = NSLocalizedString("ONBOARDING_PROFILE_NAME_PLACEHOLDER",
comment: "Placeholder text for the profile name in the 'onboarding profile' view.")
nameTextfield.setContentHuggingHorizontalLow()
nameTextfield.setCompressionResistanceHorizontalLow()
nameTextfield.accessibilityIdentifier = "onboarding.profile." + "nameTextfield"
let nameWrapper = UIView.container()
nameWrapper.setCompressionResistanceHorizontalLow()
nameWrapper.setContentHuggingHorizontalLow()
nameWrapper.addSubview(nameTextfield)
nameTextfield.autoPinWidthToSuperview()
nameTextfield.autoPinEdge(toSuperviewEdge: .top, withInset: 8)
nameTextfield.autoPinEdge(toSuperviewEdge: .bottom, withInset: 8)
_ = nameWrapper.addBottomStroke()
let profileRow = UIStackView(arrangedSubviews: [
avatarWrapper,
nameWrapper
])
profileRow.axis = .horizontal
profileRow.alignment = .center
profileRow.spacing = 8
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
profileRow,
UIView.spacer(withHeight: 25),
explanationLabel,
bottomSpacer,
nextButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPinEdge(.top, to: .top, of: view)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
// Ensure whitespace is balanced, so inputs are vertically centered.
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
updateAvatarView()
}
private let avatarSize: UInt = 80
private func updateAvatarView() {
if let avatar = avatar {
avatarView.image = avatar
cameraCircle.isHidden = true
return
}
let defaultAvatar = OWSContactAvatarBuilder(forLocalUserWithDiameter: avatarSize).buildDefaultImage()
avatarView.image = defaultAvatar
cameraCircle.isHidden = false
}
// MARK: -
private func normalizedProfileName() -> String? {
return nameTextfield.text?.ows_stripped()
}
private func tryToComplete() {
let profileName = self.normalizedProfileName()
let profileAvatar = self.avatar
if profileName == nil, profileAvatar == nil {
onboardingController.profileWasSkipped(fromView: self)
return
}
if let name = profileName,
profileManager.isProfileNameTooLong(name) {
OWSAlerts.showErrorAlert(message: NSLocalizedString("PROFILE_VIEW_ERROR_PROFILE_NAME_TOO_LONG",
comment: "Error message shown when user tries to update profile with a profile name that is too long."))
return
}
ModalActivityIndicatorViewController.present(fromViewController: self,
canCancel: true) { (modal) in
self.profileManager.updateLocalProfileName(profileName, avatarImage: profileAvatar, success: {
DispatchQueue.main.async {
modal.dismiss(completion: {
self.onboardingController.profileDidComplete(fromView: self)
})
}
}, failure: { _ in
DispatchQueue.main.async {
modal.dismiss(completion: {
OWSAlerts.showErrorAlert(message: NSLocalizedString("PROFILE_VIEW_ERROR_UPDATE_FAILED",
comment: "Error message shown when a profile update fails."))
})
}
}, requiresSync: false)
}
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_ = nameTextfield.becomeFirstResponder()
}
// MARK: - Events
@objc func avatarTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
showAvatarActionSheet()
}
@objc func nextPressed() {
Logger.info("")
tryToComplete()
}
private func showAvatarActionSheet() {
AssertIsOnMainThread()
Logger.info("")
avatarViewHelper.showChangeAvatarUI()
}
}
// MARK: -
extension OnboardingProfileViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
tryToComplete()
return false
}
}
// MARK: -
extension OnboardingProfileViewController: AvatarViewHelperDelegate {
public func avatarActionSheetTitle() -> String? {
return nil
}
public func avatarDidChange(_ image: UIImage) {
AssertIsOnMainThread()
let maxDiameter = CGFloat(kOWSProfileManager_MaxAvatarDiameter)
avatar = image.resizedImage(toFillPixelSize: CGSize(width: maxDiameter,
height: maxDiameter))
updateAvatarView()
}
public func fromViewController() -> UIViewController {
return self
}
public func hasClearAvatarAction() -> Bool {
return avatar != nil
}
public func clearAvatar() {
avatar = nil
updateAvatarView()
}
public func clearAvatarActionLabel() -> String {
return NSLocalizedString("PROFILE_VIEW_CLEAR_AVATAR", comment: "Label for action that clear's the user's profile avatar")
}
}

View File

@ -1,103 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
import PromiseKit
@objc
public class OnboardingSplashViewController: OnboardingBaseViewController {
override public func loadView() {
super.loadView()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.createTitleLabel(text: NSLocalizedString("Loki Messenger", comment: ""))
view.addSubview(titleLabel)
titleLabel.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
titleLabel.accessibilityIdentifier = "onboarding.splash." + "titleLabel"
let lokiLogo = UIImage(named: "Loki")
let lokiLogoImageView = UIImageView(image: lokiLogo)
lokiLogoImageView.accessibilityIdentifier = "onboarding.splash." + "lokiLogoImageView"
lokiLogoImageView.autoSetDimension(.height, toSize: 71)
lokiLogoImageView.contentMode = .scaleAspectFit
let lokiLogoContainer = UIView()
view.setContentHuggingVerticalLow()
view.setCompressionResistanceVerticalLow()
lokiLogoContainer.addSubview(lokiLogoImageView)
let betaTermsLabel = UILabel()
betaTermsLabel.text = NSLocalizedString("Loki Messenger is currently in beta. For development purposes the beta version collects basic usage statistics and crash logs. In addition, the beta version doesn't yet provide full privacy and shouldn't be used to transmit sensitive information.", comment: "")
betaTermsLabel.textColor = .white
let font = UIFont.ows_dynamicTypeCaption1Clamped
betaTermsLabel.font = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
betaTermsLabel.numberOfLines = 0
betaTermsLabel.textAlignment = .center
betaTermsLabel.lineBreakMode = .byWordWrapping
betaTermsLabel.accessibilityIdentifier = "onboarding.splash." + "betaTermsLabel"
let privacyPolicyLabel = UILabel()
privacyPolicyLabel.text = NSLocalizedString("Privacy Policy", comment: "")
privacyPolicyLabel.textColor = .ows_materialBlue
privacyPolicyLabel.font = .ows_dynamicTypeSubheadlineClamped
privacyPolicyLabel.numberOfLines = 0
privacyPolicyLabel.textAlignment = .center
privacyPolicyLabel.lineBreakMode = .byWordWrapping
privacyPolicyLabel.isUserInteractionEnabled = true
privacyPolicyLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(explanationLabelTapped)))
privacyPolicyLabel.accessibilityIdentifier = "onboarding.splash." + "privacyPolicyLabel"
let continueButton = self.createButton(title: NSLocalizedString("BUTTON_CONTINUE", comment: "Label for 'continue' button."), selector: #selector(continuePressed))
view.addSubview(continueButton)
continueButton.accessibilityIdentifier = "onboarding.splash." + "continueButton"
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
lokiLogoContainer,
betaTermsLabel,
UIView.spacer(withHeight: 24),
privacyPolicyLabel,
UIView.spacer(withHeight: 24),
continueButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPinEdge(.top, to: .top, of: view)
stackView.autoPinEdge(.bottom, to: .bottom, of: view)
lokiLogoImageView.autoCenterInSuperview()
if UserDefaults.standard.bool(forKey: "wasUnlinked") {
let alert = UIAlertController(title: NSLocalizedString("Device Unlinked", comment: ""), message: NSLocalizedString("Your device was unlinked successfully", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
present(alert, animated: true, completion: nil)
UserDefaults.removeAll()
}
}
// MARK: - Events
@objc func explanationLabelTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
guard let url = URL(string: kLegalTermsUrlString) else {
owsFailDebug("Invalid URL.")
return
}
UIApplication.shared.openURL(url)
}
@objc func continuePressed() {
Logger.info("")
onboardingController.onboardingSplashDidComplete(viewController: self)
}
}

View File

@ -1,549 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
import PromiseKit
private protocol OnboardingCodeViewTextFieldDelegate {
func textFieldDidDeletePrevious()
}
// MARK: -
// Editing a code should feel seamless, as even though
// the UITextField only lets you edit a single digit at
// a time. For deletes to work properly, we need to
// detect delete events that would affect the _previous_
// digit.
private class OnboardingCodeViewTextField: UITextField {
fileprivate var codeDelegate: OnboardingCodeViewTextFieldDelegate?
override func deleteBackward() {
var isDeletePrevious = false
if let selectedTextRange = selectedTextRange {
let cursorPosition = offset(from: beginningOfDocument, to: selectedTextRange.start)
if cursorPosition == 0 {
isDeletePrevious = true
}
}
super.deleteBackward()
if isDeletePrevious {
codeDelegate?.textFieldDidDeletePrevious()
}
}
}
// MARK: -
protocol OnboardingCodeViewDelegate {
func codeViewDidChange()
}
// MARK: -
// The OnboardingCodeView is a special "verification code"
// editor that should feel like editing a single piece
// of text (ala UITextField) even though the individual
// digits of the code are visually separated.
//
// We use a separate UILabel for each digit, and move
// around a single UITextfield to let the user edit the
// last/next digit.
private class OnboardingCodeView: UIView {
var delegate: OnboardingCodeViewDelegate?
public init() {
super.init(frame: .zero)
createSubviews()
updateViewState()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let digitCount = 6
private var digitLabels = [UILabel]()
private var digitStrokes = [UIView]()
// We use a single text field to edit the "current" digit.
// The "current" digit is usually the "last"
fileprivate let textfield = OnboardingCodeViewTextField()
private var currentDigitIndex = 0
private var textfieldConstraints = [NSLayoutConstraint]()
// The current complete text - the "model" for this view.
private var digitText = ""
var isComplete: Bool {
return digitText.count == digitCount
}
var verificationCode: String {
return digitText
}
private func createSubviews() {
textfield.textAlignment = .left
textfield.delegate = self
textfield.keyboardType = .numberPad
textfield.textColor = Theme.primaryColor
textfield.font = UIFont.ows_dynamicTypeLargeTitle1Clamped
textfield.codeDelegate = self
var digitViews = [UIView]()
(0..<digitCount).forEach { (_) in
let (digitView, digitLabel, digitStroke) = makeCellView(text: "", hasStroke: true)
digitLabels.append(digitLabel)
digitStrokes.append(digitStroke)
digitViews.append(digitView)
}
let (hyphenView, _, _) = makeCellView(text: "-", hasStroke: false)
digitViews.insert(hyphenView, at: 3)
let stackView = UIStackView(arrangedSubviews: digitViews)
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = 8
addSubview(stackView)
stackView.autoPinHeightToSuperview()
stackView.autoHCenterInSuperview()
self.addSubview(textfield)
}
private func makeCellView(text: String, hasStroke: Bool) -> (UIView, UILabel, UIView) {
let digitView = UIView()
let digitLabel = UILabel()
digitLabel.text = text
digitLabel.font = UIFont.ows_dynamicTypeLargeTitle1Clamped
digitLabel.textColor = Theme.primaryColor
digitLabel.textAlignment = .center
digitView.addSubview(digitLabel)
digitLabel.autoCenterInSuperview()
let strokeColor = (hasStroke ? Theme.primaryColor : UIColor.clear)
let strokeView = digitView.addBottomStroke(color: strokeColor, strokeWidth: 1)
let vMargin: CGFloat = 4
let cellHeight: CGFloat = digitLabel.font.lineHeight + vMargin * 2
let cellWidth: CGFloat = cellHeight * 2 / 3
digitView.autoSetDimensions(to: CGSize(width: cellWidth, height: cellHeight))
return (digitView, digitLabel, strokeView)
}
private func digit(at index: Int) -> String {
guard index < digitText.count else {
return ""
}
return digitText.substring(from: index).substring(to: 1)
}
// Ensure that all labels are displaying the correct
// digit (if any) and that the UITextField has replaced
// the "current" digit.
private func updateViewState() {
currentDigitIndex = min(digitCount - 1,
digitText.count)
(0..<digitCount).forEach { (index) in
let digitLabel = digitLabels[index]
digitLabel.text = digit(at: index)
digitLabel.isHidden = index == currentDigitIndex
}
NSLayoutConstraint.deactivate(textfieldConstraints)
textfieldConstraints.removeAll()
let digitLabelToReplace = digitLabels[currentDigitIndex]
textfield.text = digit(at: currentDigitIndex)
textfieldConstraints.append(textfield.autoAlignAxis(.horizontal, toSameAxisOf: digitLabelToReplace))
textfieldConstraints.append(textfield.autoAlignAxis(.vertical, toSameAxisOf: digitLabelToReplace))
// Move cursor to end of text.
let newPosition = textfield.endOfDocument
textfield.selectedTextRange = textfield.textRange(from: newPosition, to: newPosition)
}
public override func becomeFirstResponder() -> Bool {
return textfield.becomeFirstResponder()
}
func setHasError(_ hasError: Bool) {
let backgroundColor = (hasError ? UIColor.ows_destructiveRed : Theme.primaryColor)
for digitStroke in digitStrokes {
digitStroke.backgroundColor = backgroundColor
}
}
fileprivate func set(verificationCode: String) {
digitText = verificationCode
updateViewState()
self.delegate?.codeViewDidChange()
}
}
// MARK: -
extension OnboardingCodeView: UITextFieldDelegate {
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString newString: String) -> Bool {
var oldText = ""
if let textFieldText = textField.text {
oldText = textFieldText
}
let left = oldText.substring(to: range.location)
let right = oldText.substring(from: range.location + range.length)
let unfiltered = left + newString + right
let characterSet = CharacterSet(charactersIn: "0123456789")
let filtered = unfiltered.components(separatedBy: characterSet.inverted).joined()
let filteredAndTrimmed = filtered.substring(to: 1)
textField.text = filteredAndTrimmed
digitText = digitText.substring(to: currentDigitIndex) + filteredAndTrimmed
updateViewState()
self.delegate?.codeViewDidChange()
// Inform our caller that we took care of performing the change.
return false
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.delegate?.codeViewDidChange()
return false
}
}
// MARK: -
extension OnboardingCodeView: OnboardingCodeViewTextFieldDelegate {
public func textFieldDidDeletePrevious() {
guard digitText.count > 0 else {
return
}
digitText = digitText.substring(to: currentDigitIndex - 1)
updateViewState()
}
}
// MARK: -
@objc
public class OnboardingVerificationViewController: OnboardingBaseViewController {
private enum CodeState {
case sent
case readyForResend
case resent
}
// MARK: -
private var codeState = CodeState.sent
private var titleLabel: UILabel?
private var backLink: UIView?
private let onboardingCodeView = OnboardingCodeView()
private var codeStateLink: OWSFlatButton?
private let errorLabel = UILabel()
@objc
public func hideBackLink() {
backLink?.isHidden = true
}
override public func loadView() {
super.loadView()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.createTitleLabel(text: "")
self.titleLabel = titleLabel
titleLabel.accessibilityIdentifier = "onboarding.verification." + "titleLabel"
let backLink = self.createLinkButton(title: NSLocalizedString("ONBOARDING_VERIFICATION_BACK_LINK",
comment: "Label for the link that lets users change their phone number in the onboarding views."),
selector: #selector(backLinkTapped))
self.backLink = backLink
backLink.accessibilityIdentifier = "onboarding.verification." + "backLink"
onboardingCodeView.delegate = self
errorLabel.text = NSLocalizedString("ONBOARDING_VERIFICATION_INVALID_CODE",
comment: "Label indicating that the verification code is incorrect in the 'onboarding verification' view.")
errorLabel.textColor = .ows_destructiveRed
errorLabel.font = UIFont.ows_dynamicTypeBodyClamped.ows_mediumWeight()
errorLabel.textAlignment = .center
errorLabel.autoSetDimension(.height, toSize: errorLabel.font.lineHeight)
errorLabel.accessibilityIdentifier = "onboarding.verification." + "errorLabel"
// Wrap the error label in a row so that we can show/hide it without affecting view layout.
let errorRow = UIView()
errorRow.addSubview(errorLabel)
errorLabel.autoPinEdgesToSuperviewEdges()
let codeStateLink = self.createLinkButton(title: "",
selector: #selector(resendCodeLinkTapped))
codeStateLink.enableMultilineLabel()
self.codeStateLink = codeStateLink
codeStateLink.accessibilityIdentifier = "onboarding.verification." + "codeStateLink"
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
UIView.spacer(withHeight: 12),
backLink,
topSpacer,
onboardingCodeView,
UIView.spacer(withHeight: 12),
errorRow,
bottomSpacer,
codeStateLink
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPinEdge(.top, to: .top, of: view)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
// Ensure whitespace is balanced, so inputs are vertically centered.
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
startCodeCountdown()
updateCodeState()
setHasInvalidCode(false)
}
// MARK: - Code State
private let countdownDuration: TimeInterval = 60
private var codeCountdownTimer: Timer?
private var codeCountdownStart: NSDate?
deinit {
codeCountdownTimer?.invalidate()
}
private func startCodeCountdown() {
codeCountdownStart = NSDate()
codeCountdownTimer = Timer.weakScheduledTimer(withTimeInterval: 0.25, target: self, selector: #selector(codeCountdownTimerFired), userInfo: nil, repeats: true)
}
@objc
public func codeCountdownTimerFired() {
guard let codeCountdownStart = codeCountdownStart else {
owsFailDebug("Missing codeCountdownStart.")
return
}
guard let codeCountdownTimer = codeCountdownTimer else {
owsFailDebug("Missing codeCountdownTimer.")
return
}
let countdownInterval = abs(codeCountdownStart.timeIntervalSinceNow)
guard countdownInterval < countdownDuration else {
// Countdown complete.
codeCountdownTimer.invalidate()
self.codeCountdownTimer = nil
if codeState != .sent {
owsFailDebug("Unexpected codeState: \(codeState)")
}
codeState = .readyForResend
updateCodeState()
return
}
// Update the "code state" UI to reflect the countdown.
updateCodeState()
}
private func updateCodeState() {
AssertIsOnMainThread()
guard let codeCountdownStart = codeCountdownStart else {
owsFailDebug("Missing codeCountdownStart.")
return
}
guard let titleLabel = titleLabel else {
owsFailDebug("Missing titleLabel.")
return
}
guard let codeStateLink = codeStateLink else {
owsFailDebug("Missing codeStateLink.")
return
}
var e164PhoneNumber = ""
if let phoneNumber = onboardingController.phoneNumber {
e164PhoneNumber = phoneNumber.e164
}
let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: e164PhoneNumber)
// Update titleLabel
switch codeState {
case .sent, .readyForResend:
titleLabel.text = String(format: NSLocalizedString("ONBOARDING_VERIFICATION_TITLE_DEFAULT_FORMAT",
comment: "Format for the title of the 'onboarding verification' view. Embeds {{the user's phone number}}."),
formattedPhoneNumber)
case .resent:
titleLabel.text = String(format: NSLocalizedString("ONBOARDING_VERIFICATION_TITLE_RESENT_FORMAT",
comment: "Format for the title of the 'onboarding verification' view after the verification code has been resent. Embeds {{the user's phone number}}."),
formattedPhoneNumber)
}
// Update codeStateLink
switch codeState {
case .sent:
let countdownInterval = abs(codeCountdownStart.timeIntervalSinceNow)
let countdownRemaining = max(0, countdownDuration - countdownInterval)
let formattedCountdown = OWSFormat.formatDurationSeconds(Int(round(countdownRemaining)))
let text = String(format: NSLocalizedString("ONBOARDING_VERIFICATION_CODE_COUNTDOWN_FORMAT",
comment: "Format for the label of the 'sent code' label of the 'onboarding verification' view. Embeds {{the time until the code can be resent}}."),
formattedCountdown)
codeStateLink.setTitle(title: text, font: .ows_dynamicTypeBodyClamped, titleColor: Theme.secondaryColor)
case .readyForResend:
codeStateLink.setTitle(title: NSLocalizedString("ONBOARDING_VERIFICATION_ORIGINAL_CODE_MISSING_LINK",
comment: "Label for link that can be used when the original code did not arrive."),
font: .ows_dynamicTypeBodyClamped,
titleColor: .ows_materialBlue)
case .resent:
codeStateLink.setTitle(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESENT_CODE_MISSING_LINK",
comment: "Label for link that can be used when the resent code did not arrive."),
font: .ows_dynamicTypeBodyClamped,
titleColor: .ows_materialBlue)
}
}
// MARK: - View Lifecycle
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_ = onboardingCodeView.becomeFirstResponder()
}
// MARK: - Events
@objc func backLinkTapped() {
Logger.info("")
self.navigationController?.popViewController(animated: true)
}
@objc func resendCodeLinkTapped() {
Logger.info("")
switch codeState {
case .sent:
// Ignore taps until the countdown expires.
break
case .readyForResend, .resent:
showResendActionSheet()
}
}
private func showResendActionSheet() {
Logger.info("")
let actionSheet = UIAlertController(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_ALERT_TITLE",
comment: "Title for the 'resend code' alert in the 'onboarding verification' view."),
message: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_ALERT_MESSAGE",
comment: "Message for the 'resend code' alert in the 'onboarding verification' view."),
preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_BY_SMS_BUTTON",
comment: "Label for the 'resend code by SMS' button in the 'onboarding verification' view."),
style: .default) { _ in
self.onboardingController.tryToRegister(fromViewController: self, smsVerification: true)
})
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_BY_VOICE_BUTTON",
comment: "Label for the 'resend code by voice' button in the 'onboarding verification' view."),
style: .default) { _ in
self.onboardingController.tryToRegister(fromViewController: self, smsVerification: false)
})
actionSheet.addAction(OWSAlerts.cancelAction)
self.presentAlert(actionSheet)
}
private func tryToVerify() {
Logger.info("")
guard onboardingCodeView.isComplete else {
self.setHasInvalidCode(false)
return
}
setHasInvalidCode(false)
onboardingController.update(verificationCode: onboardingCodeView.verificationCode)
// Temporarily hide the "resend link" button during the verification attempt.
codeStateLink?.layer.opacity = 0.05
onboardingController.tryToVerify(fromViewController: self, completion: { (outcome) in
self.codeStateLink?.layer.opacity = 1
if outcome == .invalidVerificationCode {
self.setHasInvalidCode(true)
}
})
}
private func setHasInvalidCode(_ value: Bool) {
onboardingCodeView.setHasError(value)
errorLabel.isHidden = !value
}
@objc
public func setVerificationCodeAndTryToVerify(_ verificationCode: String) {
AssertIsOnMainThread()
let filteredCode = verificationCode.digitsOnly
guard filteredCode.count > 0 else {
owsFailDebug("Invalid code: \(verificationCode)")
return
}
onboardingCodeView.set(verificationCode: filteredCode)
}
}
// MARK: -
extension OnboardingVerificationViewController: OnboardingCodeViewDelegate {
public func codeViewDidChange() {
AssertIsOnMainThread()
setHasInvalidCode(false)
tryToVerify()
}
}

View File

@ -1,81 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
public class RegistrationController: NSObject {
// MARK: - Dependencies
private static var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
private static var backup: OWSBackup {
return AppEnvironment.shared.backup
}
// MARK: -
private override init() {}
// MARK: -
private class func showBackupRestoreView(fromView view: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
guard let navigationController = view.navigationController else {
owsFailDebug("Missing navigationController")
return
}
let restoreView = BackupRestoreViewController()
navigationController.setViewControllers([restoreView], animated: true)
}
// TODO: OnboardingController will eventually need to do something like this.
// private class func checkCanImportBackup(fromView view: UIViewController) {
// AssertIsOnMainThread()
//
// Logger.info("")
//
// self.backup.checkCanImport({ (canImport) in
// Logger.info("canImport: \(canImport)")
//
// if (canImport) {
// self.backup.setHasPendingRestoreDecision(true)
//
// self.showBackupRestoreView(fromView: view)
// } else {
// self.showProfileView(fromView: view)
// }
// }) { (_) in
// self.showBackupCheckFailedAlert(fromView: view)
// }
// }
//
// private class func showBackupCheckFailedAlert(fromView view: UIViewController) {
// AssertIsOnMainThread()
//
// Logger.info("")
//
// let alert = UIAlertController(title: NSLocalizedString("CHECK_FOR_BACKUP_FAILED_TITLE",
// comment: "Title for alert shown when the app failed to check for an existing backup."),
// message: NSLocalizedString("CHECK_FOR_BACKUP_FAILED_MESSAGE",
// comment: "Message for alert shown when the app failed to check for an existing backup."),
// preferredStyle: .alert)
// alert.addAction(UIAlertAction(title: NSLocalizedString("REGISTER_FAILED_TRY_AGAIN", comment: ""),
// style: .default) { (_) in
// self.checkCanImportBackup(fromView: view)
// })
// alert.addAction(UIAlertAction(title: NSLocalizedString("CHECK_FOR_BACKUP_DO_NOT_RESTORE", comment: "The label for the 'do not restore backup' button."),
// style: .destructive) { (_) in
// self.showProfileView(fromView: view)
// })
// view.presentAlert(alert)
// }
}

View File

@ -115,8 +115,9 @@ class AppUpdateNag: NSObject {
return
}
/*
switch frontmostViewController {
case is HomeViewController, is OnboardingSplashViewController:
case is OnboardingSplashViewController:
self.setLastNagDate(Date())
self.clearFirstHeardOfNewVersionDate()
presentUpgradeNag(appStoreRecord: appStoreRecord)
@ -124,6 +125,7 @@ class AppUpdateNag: NSObject {
Logger.debug("not presenting alert due to frontmostViewController: \(frontmostViewController)")
break
}
*/
}
func presentUpgradeNag(appStoreRecord: AppStoreRecord) {

View File

@ -1,15 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface RegistrationUtils : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (void)showReregistrationUIFromViewController:(UIViewController *)fromViewController;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,110 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "RegistrationUtils.h"
#import "OWSNavigationController.h"
#import "Session-Swift.h"
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SessionServiceKit/TSAccountManager.h>
NS_ASSUME_NONNULL_BEGIN
@implementation RegistrationUtils
#pragma mark - Dependencies
+ (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark -
+ (void)showReregistrationUIFromViewController:(UIViewController *)fromViewController
{
UIAlertController *actionSheet =
[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[actionSheet
addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(@"DEREGISTRATION_REREGISTER_WITH_SAME_PHONE_NUMBER",
@"Label for button that lets users re-register using the same phone number.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[RegistrationUtils reregisterWithFromViewController:fromViewController];
}]];
[actionSheet addAction:[OWSAlerts cancelAction]];
[fromViewController presentAlert:actionSheet];
}
+ (void)reregisterWithFromViewController:(UIViewController *)fromViewController
{
OWSLogInfo(@"reregisterWithSamePhoneNumber.");
if (![self.tsAccountManager resetForReregistration]) {
OWSFailDebug(@"could not reset for re-registration.");
return;
}
[Environment.shared.preferences unsetRecordedAPNSTokens];
[ModalActivityIndicatorViewController
presentFromViewController:fromViewController
canCancel:NO
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
NSString *phoneNumber = self.tsAccountManager.reregisterationPhoneNumber;
[self.tsAccountManager registerWithPhoneNumber:phoneNumber
captchaToken:nil
success:^{
OWSLogInfo(@"re-registering: send verification code succeeded.");
dispatch_async(dispatch_get_main_queue(), ^{
[modalActivityIndicator dismissWithCompletion:^{
OnboardingController *onboardingController = [OnboardingController new];
OnboardingPhoneNumber *onboardingPhoneNumber =
[[OnboardingPhoneNumber alloc] initWithE164:phoneNumber
userInput:phoneNumber];
[onboardingController updateWithPhoneNumber:onboardingPhoneNumber];
OnboardingVerificationViewController *viewController =
[[OnboardingVerificationViewController alloc]
initWithOnboardingController:onboardingController];
[viewController hideBackLink];
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:viewController];
navigationController.navigationBarHidden = YES;
[UIApplication sharedApplication].delegate.window.rootViewController
= navigationController;
}];
});
}
failure:^(NSError *error) {
OWSLogError(@"re-registering: send verification code failed.");
dispatch_async(dispatch_get_main_queue(), ^{
[modalActivityIndicator dismissWithCompletion:^{
if (error.code == 400) {
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"REGISTRATION_ERROR", nil)
message:NSLocalizedString(
@"REGISTRATION_NON_VALID_NUMBER", nil)];
} else {
[OWSAlerts showAlertWithTitle:error.localizedDescription
message:error.localizedRecoverySuggestion];
}
}];
});
}
smsVerification:YES];
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -17,7 +17,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[];
#import <SignalMessaging/ContactCellView.h>
#import <SignalMessaging/ContactTableViewCell.h>
#import <SignalMessaging/ContactsViewHelper.h>
#import <SignalMessaging/CountryCodeViewController.h>
#import <SignalMessaging/DebugLogger.h>
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/NSAttributedString+OWS.h>

View File

@ -1,32 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@class CountryCodeViewController;
@protocol CountryCodeViewControllerDelegate <NSObject>
- (void)countryCodeViewController:(CountryCodeViewController *)vc
didSelectCountryCode:(NSString *)countryCode
countryName:(NSString *)countryName
callingCode:(NSString *)callingCode;
@end
#pragma mark -
@interface CountryCodeViewController : OWSTableViewController
@property (nonatomic, weak) id<CountryCodeViewControllerDelegate> countryCodeDelegate;
@property (nonatomic) BOOL isPresentedInNavigationController;
@property (nonatomic) UIInterfaceOrientationMask interfaceOrientationMask;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,176 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "CountryCodeViewController.h"
#import "OWSSearchBar.h"
#import "PhoneNumberUtil.h"
#import "Theme.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SessionServiceKit/NSString+SSK.h>
NS_ASSUME_NONNULL_BEGIN
@interface CountryCodeViewController () <OWSTableViewControllerDelegate, UISearchBarDelegate>
@property (nonatomic, readonly) UISearchBar *searchBar;
@property (nonatomic) NSArray<NSString *> *countryCodes;
@end
#pragma mark -
@implementation CountryCodeViewController
- (void)loadView
{
[super loadView];
self.shouldUseTheme = NO;
self.interfaceOrientationMask = DefaultUIInterfaceOrientationMask();
self.view.backgroundColor = [UIColor whiteColor];
self.title = NSLocalizedString(@"COUNTRYCODE_SELECT_TITLE", @"");
self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:nil];
if (!self.isPresentedInNavigationController) {
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissWasPressed:)];
}
[self createViews];
}
- (void)createViews
{
// Search
UISearchBar *searchBar = [OWSSearchBar new];
_searchBar = searchBar;
searchBar.delegate = self;
searchBar.placeholder = NSLocalizedString(@"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT", @"");
[searchBar sizeToFit];
self.tableView.tableHeaderView = searchBar;
[self updateTableContents];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak CountryCodeViewController *weakSelf = self;
OWSTableSection *section = [OWSTableSection new];
for (NSString *countryCode in self.countryCodes) {
OWSAssertDebug(countryCode.length > 0);
OWSAssertDebug([PhoneNumberUtil countryNameFromCountryCode:countryCode].length > 0);
OWSAssertDebug([PhoneNumberUtil callingCodeFromCountryCode:countryCode].length > 0);
OWSAssertDebug(![[PhoneNumberUtil callingCodeFromCountryCode:countryCode] isEqualToString:@"+0"]);
[section addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell = [OWSTableItem newCell];
[OWSTableItem configureCell:cell];
cell.textLabel.text = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
UILabel *countryCodeLabel = [UILabel new];
countryCodeLabel.text = [PhoneNumberUtil callingCodeFromCountryCode:countryCode];
countryCodeLabel.font = [UIFont ows_regularFontWithSize:16.f];
countryCodeLabel.textColor = Theme.secondaryColor;
[countryCodeLabel sizeToFit];
cell.accessoryView = countryCodeLabel;
return cell;
}
actionBlock:^{
[weakSelf countryCodeWasSelected:countryCode];
}]];
}
[contents addSection:section];
self.contents = contents;
}
- (void)countryCodeWasSelected:(NSString *)countryCode
{
OWSAssertDebug(countryCode.length > 0);
NSString *callingCodeSelected = [PhoneNumberUtil callingCodeFromCountryCode:countryCode];
NSString *countryNameSelected = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
NSString *countryCodeSelected = countryCode;
[self.countryCodeDelegate countryCodeViewController:self
didSelectCountryCode:countryCodeSelected
countryName:countryNameSelected
callingCode:callingCodeSelected];
[self.searchBar resignFirstResponder];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dismissWasPressed:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - UISearchBarDelegate
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self searchTextDidChange];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[self searchTextDidChange];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
[self searchTextDidChange];
}
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar
{
[self searchTextDidChange];
}
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
{
[self searchTextDidChange];
}
- (void)searchTextDidChange
{
NSString *searchText = [self.searchBar.text ows_stripped];
self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:searchText];
[self updateTableContents];
}
#pragma mark - OWSTableViewControllerDelegate
- (void)tableViewWillBeginDragging
{
[self.searchBar resignFirstResponder];
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return self.interfaceOrientationMask;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3,7 +3,6 @@
//
#import "SelectRecipientViewController.h"
#import "CountryCodeViewController.h"
#import "PhoneNumber.h"
#import "ViewControllerUtils.h"
#import <SignalMessaging/ContactTableViewCell.h>
@ -26,7 +25,7 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
#pragma mark -
@interface SelectRecipientViewController () <CountryCodeViewControllerDelegate,
@interface SelectRecipientViewController () </*CountryCodeViewControllerDelegate,*/
ContactsViewHelperDelegate,
OWSTableViewControllerDelegate,
UITextFieldDelegate>
@ -267,16 +266,7 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
- (void)showCountryCodeView:(nullable id)sender
{
CountryCodeViewController *countryCodeController = [CountryCodeViewController new];
countryCodeController.countryCodeDelegate = self;
countryCodeController.isPresentedInNavigationController = self.isPresentedInNavigationController;
if (self.isPresentedInNavigationController) {
[self.navigationController pushViewController:countryCodeController animated:YES];
} else {
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:countryCodeController];
[self presentViewController:navigationController animated:YES completion:nil];
}
}
- (void)phoneNumberButtonPressed
@ -401,20 +391,20 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
#pragma mark - CountryCodeViewControllerDelegate
- (void)countryCodeViewController:(CountryCodeViewController *)vc
didSelectCountryCode:(NSString *)countryCode
countryName:(NSString *)countryName
callingCode:(NSString *)callingCode
{
OWSAssertDebug(countryCode.length > 0);
OWSAssertDebug(countryName.length > 0);
OWSAssertDebug(callingCode.length > 0);
[self updateCountryWithName:countryName callingCode:callingCode countryCode:countryCode];
// Trigger the formatting logic with a no-op edit.
[self textField:self.phoneNumberTextField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
}
//- (void)countryCodeViewController:(CountryCodeViewController *)vc
// didSelectCountryCode:(NSString *)countryCode
// countryName:(NSString *)countryName
// callingCode:(NSString *)callingCode
//{
// OWSAssertDebug(countryCode.length > 0);
// OWSAssertDebug(countryName.length > 0);
// OWSAssertDebug(callingCode.length > 0);
//
// [self updateCountryWithName:countryName callingCode:callingCode countryCode:countryCode];
//
// // Trigger the formatting logic with a no-op edit.
// [self textField:self.phoneNumberTextField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
//}
#pragma mark - UITextFieldDelegate

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS100RemoveTSRecipientsMigration : OWSDatabaseMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,31 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS100RemoveTSRecipientsMigration.h"
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS100RemoveTSRecipientsMigrationId = @"100";
@implementation OWS100RemoveTSRecipientsMigration
+ (NSString *)migrationId
{
return OWS100RemoveTSRecipientsMigrationId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
NSUInteger legacyRecipientCount = [transaction numberOfKeysInCollection:@"TSRecipient"];
OWSLogWarn(@"Removing %lu objects from TSRecipient collection", (unsigned long)legacyRecipientCount);
[transaction removeAllObjectsInCollection:@"TSRecipient"];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS101ExistingUsersBlockOnIdentityChange : OWSDatabaseMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,34 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS101ExistingUsersBlockOnIdentityChange.h"
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS101ExistingUsersBlockOnIdentityChangeMigrationId = @"101";
/**
* This migration is no longer necessary, but deleting a class is complicated in Yap
* and involves writing another migration to remove the previous. It seemed like the
* simplest/safest thing to do would be to just leave it.
*/
@implementation OWS101ExistingUsersBlockOnIdentityChange
+ (NSString *)migrationId
{
return OWS101ExistingUsersBlockOnIdentityChangeMigrationId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
OWSFailDebug(@"[OWS101ExistingUsersBlockOnIdentityChange] has been obviated.");
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,9 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
@interface OWS102MoveLoggingPreferenceToUserDefaults : OWSDatabaseMigration
@end

View File

@ -1,47 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS102MoveLoggingPreferenceToUserDefaults.h"
#import "DebugLogger.h"
#import "Environment.h"
#import "OWSPreferences.h"
#import <YapDatabase/YapDatabase.h>
// Increment a similar constant for every future DBMigration
static NSString *const OWS102MoveLoggingPreferenceToUserDefaultsMigrationId = @"102";
@implementation OWS102MoveLoggingPreferenceToUserDefaults
+ (NSString *)migrationId
{
return OWS102MoveLoggingPreferenceToUserDefaultsMigrationId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
OWSLogWarn(@"[OWS102MoveLoggingPreferenceToUserDefaultsMigrationId] copying existing logging preference to "
@"NSUserDefaults");
NSNumber *existingValue =
[transaction objectForKey:OWSPreferencesKeyEnableDebugLog inCollection:OWSPreferencesSignalDatabaseCollection];
if (existingValue) {
OWSLogInfo(@"assigning existing value: %@", existingValue);
[OWSPreferences setIsLoggingEnabled:[existingValue boolValue]];
if (![existingValue boolValue]) {
OWSLogInfo(@"Disabling file logger after one-time log settings migration.");
// Since we're migrating, we didn't have the appropriate value on startup, and incorrectly started logging.
[DebugLogger.sharedLogger disableFileLogging];
} else {
OWSLogInfo(@"Continuing to log after one-time log settings migration.");
}
} else {
OWSLogInfo(@"not assigning any value, since no previous value was stored.");
}
}
@end

View File

@ -1,9 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
@interface OWS103EnableVideoCalling : OWSDatabaseMigration
@end

View File

@ -1,67 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS103EnableVideoCalling.h"
#import <SessionServiceKit/OWSRequestFactory.h>
#import <SessionServiceKit/SSKEnvironment.h>
#import <SessionServiceKit/TSAccountManager.h>
#import <SessionServiceKit/TSNetworkManager.h>
// Increment a similar constant for every future DBMigration
static NSString *const OWS103EnableVideoCallingMigrationId = @"103";
@implementation OWS103EnableVideoCalling
#pragma mark - Dependencies
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark -
+ (NSString *)migrationId
{
return OWS103EnableVideoCallingMigrationId;
}
// Override parent migration
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(completion);
OWSLogWarn(@"running migration...");
if ([self.tsAccountManager isRegistered]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
TSRequest *request = [OWSRequestFactory updateAttributesRequest];
[[TSNetworkManager sharedManager] makeRequest:request
success:^(NSURLSessionDataTask *task, id responseObject) {
OWSLogInfo(@"successfully ran");
[self save];
completion();
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents errorEnableVideoCallingRequestFailed]);
}
OWSLogError(@"failed with error: %@", error);
completion();
}];
});
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSLogInfo(@"skipping; not registered");
[self save];
completion();
});
}
}
@end

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS104CreateRecipientIdentities : OWSDatabaseMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,61 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS104CreateRecipientIdentities.h"
#import <SessionServiceKit/OWSIdentityManager.h>
#import <SessionServiceKit/OWSRecipientIdentity.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS104CreateRecipientIdentitiesMigrationId = @"104";
/**
* New SN behavior requires tracking additional state - not just the identity key data.
* So we wrap the key, along with the new meta-data in an OWSRecipientIdentity.
*/
@implementation OWS104CreateRecipientIdentities
+ (NSString *)migrationId
{
return OWS104CreateRecipientIdentitiesMigrationId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
NSMutableDictionary<NSString *, NSData *> *identityKeys = [NSMutableDictionary new];
[transaction
enumerateKeysAndObjectsInCollection:OWSPrimaryStorageTrustedKeysCollection
usingBlock:^(NSString *_Nonnull recipientId, id _Nonnull object, BOOL *_Nonnull stop) {
if (![object isKindOfClass:[NSData class]]) {
OWSFailDebug(
@"Unexpected object in trusted keys collection key: %@ object: %@",
recipientId,
[object class]);
return;
}
NSData *identityKey = (NSData *)object;
[identityKeys setObject:identityKey forKey:recipientId];
}];
[identityKeys enumerateKeysAndObjectsUsingBlock:^(
NSString *_Nonnull recipientId, NSData *_Nonnull identityKey, BOOL *_Nonnull stop) {
OWSLogInfo(@"Migrating identity key for recipient: %@", recipientId);
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:NO
createdAt:[NSDate dateWithTimeIntervalSince1970:0]
verificationState:OWSVerificationStateDefault]
saveWithTransaction:transaction];
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS105AttachmentFilePaths : OWSDatabaseMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,47 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS105AttachmentFilePaths.h"
#import <SessionServiceKit/TSAttachmentStream.h>
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS105AttachmentFilePathsMigrationId = @"105";
@implementation OWS105AttachmentFilePaths
+ (NSString *)migrationId
{
return OWS105AttachmentFilePathsMigrationId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray new];
[transaction enumerateKeysAndObjectsInCollection:TSAttachmentStream.collection
usingBlock:^(NSString *key, TSAttachment *attachment, BOOL *stop) {
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
return;
}
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
[attachmentStreams addObject:attachmentStream];
}];
OWSLogInfo(@"Saving %lu attachment streams.", (unsigned long)attachmentStreams.count);
// Persist the new localRelativeFilePath property of TSAttachmentStream.
// For performance, we want to upgrade all existing attachment streams in
// a single transaction.
for (TSAttachmentStream *attachmentStream in attachmentStreams) {
[attachmentStream saveWithTransaction:transaction];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,123 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
import SessionServiceKit
@objc
public class OWS106EnsureProfileComplete: OWSDatabaseMigration {
private static var sharedCompleteRegistrationFixerJob: CompleteRegistrationFixerJob?
// increment a similar constant for each migration.
@objc
class func migrationId() -> String {
return "106"
}
// Overriding runUp since we have some specific completion criteria which
// is more likely to fail since it involves network requests.
override public func runUp(completion:@escaping () -> Void) {
let job = CompleteRegistrationFixerJob(completionHandler: { (didSucceed) in
if (didSucceed) {
Logger.info("Completed. Saving.")
self.save()
} else {
Logger.error("Failed.")
}
completion()
})
type(of: self).sharedCompleteRegistrationFixerJob = job
job.start()
}
/**
* A previous client bug made it possible for re-registering users to register their new account
* but never upload new pre-keys. The symptom is that there will be accounts with no uploaded
* identity key. We detect that here and fix the situation
*/
private class CompleteRegistrationFixerJob: NSObject {
// MARK: - Dependencies
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
// MARK: -
// Duration between retries if update fails.
let kRetryInterval: TimeInterval = 5
let completionHandler: (Bool) -> Void
init(completionHandler: @escaping (Bool) -> Void) {
self.completionHandler = completionHandler
}
func start() {
guard tsAccountManager.isRegistered() else {
self.completionHandler(true)
return
}
self.ensureProfileComplete().done {
Logger.info("complete. Canceling timer and saving.")
self.completionHandler(true)
}.catch { error in
let nserror = error as NSError
if nserror.domain == TSNetworkManagerErrorDomain {
// Don't retry if we had an unrecoverable error.
// In particular, 401 (invalid auth) is unrecoverable.
let isUnrecoverableError = nserror.code == 401
if isUnrecoverableError {
Logger.error("failed due to unrecoverable error: \(error). Aborting.")
self.completionHandler(true)
return
}
}
Logger.error("failed with \(error).")
self.completionHandler(false)
}.retainUntilComplete()
}
func ensureProfileComplete() -> Promise<Void> {
guard let localRecipientId = TSAccountManager.localNumber() else {
// local app doesn't think we're registered, so nothing to worry about.
return Promise.value(())
}
return firstly {
ProfileFetcherJob().getProfile(recipientId: localRecipientId)
}.done { _ in
Logger.info("verified recipient profile is in good shape: \(localRecipientId)")
}.recover { error -> Promise<Void> in
switch error {
case SignalServiceProfile.ValidationError.invalidIdentityKey(let description):
Logger.warn("detected incomplete profile for \(localRecipientId) error: \(description)")
let (promise, resolver) = Promise<Void>.pending()
// This is the error condition we're looking for. Update prekeys to properly set the identity key, completing registration.
TSPreKeyManager.createPreKeys(success: {
Logger.info("successfully uploaded pre-keys. Profile should be fixed.")
resolver.fulfill(())
},
failure: { _ in
resolver.reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.logTag) Unknown error"))
})
return promise
default:
throw error
}
}
}
}
}

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS107LegacySounds : OWSDatabaseMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,30 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS107LegacySounds.h"
#import "OWSSounds.h"
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS107LegacySoundsMigrationId = @"107";
@implementation OWS107LegacySounds
+ (NSString *)migrationId
{
return OWS107LegacySoundsMigrationId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
[OWSSounds setGlobalNotificationSound:OWSSound_SignalClassic transaction:transaction];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS108CallLoggingPreference : OWSDatabaseMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,31 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS108CallLoggingPreference.h"
#import "Environment.h"
#import "OWSPreferences.h"
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS108CallLoggingPreferenceId = @"108";
@implementation OWS108CallLoggingPreference
+ (NSString *)migrationId
{
return OWS108CallLoggingPreferenceId;
}
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
[Environment.shared.preferences applyCallLoggingSettingsForLegacyUsersWithTransaction:transaction];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSResaveCollectionDBMigration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWS109OutgoingMessageState : OWSResaveCollectionDBMigration
@end
NS_ASSUME_NONNULL_END

View File

@ -1,46 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWS109OutgoingMessageState.h"
#import <SessionServiceKit/OWSPrimaryStorage.h>
#import <SessionServiceKit/TSOutgoingMessage.h>
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
// Increment a similar constant for every future DBMigration
static NSString *const OWS109OutgoingMessageStateMigrationId = @"109";
@implementation OWS109OutgoingMessageState
+ (NSString *)migrationId
{
return OWS109OutgoingMessageStateMigrationId;
}
// Override parent migration
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion
{
OWSAssertDebug(completion);
OWSDatabaseConnection *dbConnection = (OWSDatabaseConnection *)self.primaryStorage.newDatabaseConnection;
[self resaveDBCollection:TSOutgoingMessage.collection
filter:^(id entity) {
return [entity isKindOfClass:[TSOutgoingMessage class]];
}
dbConnection:dbConnection
completion:^{
OWSLogInfo(@"Completed migration %@", self.uniqueId);
[self save];
completion();
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,118 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
public class OWS110SortIdMigration: OWSDatabaseMigration {
// increment a similar constant for each migration.
@objc
class func migrationId() -> String {
// append char "x" because we want to rerun on some internal devices which
// have already run this migration.
return "110x"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
Logger.debug("")
BenchAsync(title: "Sort Migration") { completeBenchmark in
self.doMigration {
completeBenchmark()
completion()
}
}
}
private func doMigration(completion: @escaping OWSDatabaseMigrationCompletion) {
// TODO batch this?
try! Storage.writeSync { transaction in
var archivedThreads: [TSThread] = []
// get archived threads before migration
TSThread.enumerateCollectionObjects(with: transaction) { (object, _) in
guard let thread = object as? TSThread else {
owsFailDebug("unexpected object: \(type(of: object))")
return
}
if thread.isArchivedByLegacyTimestampForSorting {
archivedThreads.append(thread)
}
}
guard let legacySorting: YapDatabaseAutoViewTransaction = transaction.extension(TSMessageDatabaseViewExtensionName_Legacy) as? YapDatabaseAutoViewTransaction else {
owsFailDebug("legacySorting was unexpectedly nil")
return
}
let totalCount: UInt = legacySorting.numberOfItemsInAllGroups()
var completedCount: UInt = 0
var allGroups = [String]()
legacySorting.enumerateGroups { group, _ in
allGroups.append(group)
}
var seenGroups: Set<String> = Set()
for group in allGroups {
autoreleasepool {
// Sanity Check #1
// Make sure our enumeration is monotonically increasing.
// Note: sortIds increase monotonically WRT timestampForLegacySorting, but only WRT the interaction's thread.
//
// e.g. When we migrate the **next** thread, we start with that thread's oldest interaction. So it's possible and expected
// that thread2's oldest interaction will have a smaller timestampForLegacySorting, but a greater sortId then an interaction
// in thread1. That's OK because we only sort messages with respect to the thread they belong in. We don't have any sort of
// "global sort" of messages across all threads.
var previousTimestampForLegacySorting: UInt64 = 0
// Sanity Check #2
// Ensure we only process a DB View's group (i.e. threadId) once.
guard !seenGroups.contains(group) else {
owsFail("unexpectedly seeing a repeated group: \(group)")
}
seenGroups.insert(group)
var groupKeys = [String]()
legacySorting.enumerateKeys(inGroup: group, using: { (_, key, _, _) in
groupKeys.append(key)
})
let groupKeyBatchSize: Int = 1024
for batch in groupKeys.chunked(by: groupKeyBatchSize) {
autoreleasepool {
for uniqueId in batch {
guard let interaction = TSInteraction.fetch(uniqueId: uniqueId, transaction: transaction) else {
owsFailDebug("Could not load interaction: \(uniqueId)")
return
}
if interaction.timestampForLegacySorting() < previousTimestampForLegacySorting {
owsFailDebug("unexpected object ordering previousTimestampForLegacySorting: \(previousTimestampForLegacySorting) interaction.timestampForLegacySorting: \(interaction.timestampForLegacySorting())")
}
previousTimestampForLegacySorting = interaction.timestampForLegacySorting()
interaction.saveNextSortId(transaction: transaction)
completedCount += 1
if completedCount % 100 == 0 {
// Legit usage of legacy sorting for migration to new sorting
Logger.info("thread: \(interaction.uniqueThreadId), timestampForLegacySorting:\(interaction.timestampForLegacySorting()), sortId: \(interaction.sortId) totalCount: \(totalCount), completedcount: \(completedCount)")
}
}
}
}
}
}
Logger.info("re-archiving \(archivedThreads.count) threads which were previously archived")
for archivedThread in archivedThreads {
archivedThread.archiveThread(with: transaction)
}
self.save(with: transaction)
}
completion()
}
}

View File

@ -1,42 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SessionServiceKit
@objc
public class OWS111UDAttributesMigration: OWSDatabaseMigration {
// MARK: - Dependencies
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
// MARK: -
// increment a similar constant for each migration.
@objc
class func migrationId() -> String {
// NOTE: Changes were made to the service after this migration was initially
// merged, so we need to re-migrate any developer devices.
return "111.1"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
Logger.debug("")
Bench(title: "UD Attributes Migration") {
self.doMigration()
}
completion()
}
private func doMigration() {
tsAccountManager.updateAccountAttributes().retainUntilComplete()
try! Storage.writeSync { transaction in
self.save(with: transaction)
}
}
}

View File

@ -1,50 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SessionServiceKit
@objc
public class OWS112TypingIndicatorsMigration: OWSDatabaseMigration {
// MARK: - Dependencies
private var typingIndicators: TypingIndicators {
return SSKEnvironment.shared.typingIndicators
}
// MARK: -
// Increment a similar constant for each migration.
@objc
class func migrationId() -> String {
return "112"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
Logger.debug("")
BenchAsync(title: "Typing Indicators Migration") { (benchCompletion) in
self.doMigrationAsync(completion:{
benchCompletion()
completion()
})
}
}
private func doMigrationAsync(completion : @escaping OWSDatabaseMigrationCompletion) {
DispatchQueue.main.async {
// Typing indicators should be disabled by default for
// legacy users.
self.typingIndicators.setTypingIndicatorsEnabled(value: false)
DispatchQueue.global().async {
try! Storage.writeSync { transaction in
self.save(with: transaction)
}
completion()
}
}
}
}

View File

@ -1,74 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SessionServiceKit
@objc
public class OWS113MultiAttachmentMediaMessages: OWSDatabaseMigration {
// MARK: - Dependencies
// MARK: -
// Increment a similar constant for each migration.
@objc
class func migrationId() -> String {
// NOTE: that we use .1 since there was a bug in the logic to
// set albumMessageId.
return "113.1"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
Logger.debug("")
BenchAsync(title: "\(self.logTag)") { (benchCompletion) in
self.doMigrationAsync(completion: {
benchCompletion()
completion()
})
}
}
private func doMigrationAsync(completion : @escaping OWSDatabaseMigrationCompletion) {
DispatchQueue.global().async {
var legacyAttachments: [(attachmentId: String, messageId: String)] = []
self.dbReadWriteConnection().read { transaction in
TSMessage.enumerateCollectionObjects(with: transaction) { object, _ in
autoreleasepool {
guard let message: TSMessage = object as? TSMessage else {
Logger.debug("ignoring message with type: \(object)")
return
}
guard let messageId = message.uniqueId else {
owsFailDebug("messageId was unexpectedly nil")
return
}
for attachmentId in message.attachmentIds {
legacyAttachments.append((attachmentId: attachmentId as! String, messageId: messageId))
}
}
}
}
try! Storage.writeSync { transaction in
for (attachmentId, messageId) in legacyAttachments {
autoreleasepool {
guard let attachment = TSAttachment.fetch(uniqueId: attachmentId, transaction: transaction) else {
Logger.warn("missing attachment for messageId: \(messageId)")
return
}
attachment.migrateAlbumMessageId(messageId)
attachment.save(with: transaction)
}
}
self.save(with: transaction)
}
completion()
}
}
}

View File

@ -1,65 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SessionServiceKit
@objc
public class OWS114RemoveDynamicInteractions: OWSDatabaseMigration {
// MARK: - Dependencies
// MARK: -
// Increment a similar constant for each migration.
@objc
class func migrationId() -> String {
return "114"
}
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
Logger.debug("")
BenchAsync(title: "\(self.logTag)") { (benchCompletion) in
self.doMigrationAsync(completion: {
benchCompletion()
completion()
})
}
}
private func doMigrationAsync(completion : @escaping OWSDatabaseMigrationCompletion) {
DispatchQueue.global().async {
try! Storage.writeSync { transaction in
guard let dbView = TSDatabaseView.threadSpecialMessagesDatabaseView(transaction) as? YapDatabaseViewTransaction else {
owsFailDebug("Couldn't load db view.")
return
}
var interactionsToDelete = [TSInteraction]()
let groupIds = dbView.allGroups()
for groupId in groupIds {
dbView.enumerateKeysAndObjects(inGroup: groupId) { (_: String, _: String, object: Any, _: UInt, _: UnsafeMutablePointer<ObjCBool>) in
guard let interaction = object as? TSInteraction else {
owsFailDebug("Invalid database entity: \(type(of: object)).")
return
}
guard interaction.isDynamicInteraction() else {
return
}
interactionsToDelete.append(interaction)
}
}
for interaction in interactionsToDelete {
Logger.debug("Cleaning up interaction: \(type(of: interaction)).")
interaction.remove(with: transaction)
}
self.save(with: transaction)
}
completion()
}
}
}

View File

@ -3,14 +3,6 @@
//
#import "OWSDatabaseMigrationRunner.h"
#import "OWS100RemoveTSRecipientsMigration.h"
#import "OWS102MoveLoggingPreferenceToUserDefaults.h"
#import "OWS103EnableVideoCalling.h"
#import "OWS104CreateRecipientIdentities.h"
#import "OWS105AttachmentFilePaths.h"
#import "OWS107LegacySounds.h"
#import "OWS108CallLoggingPreference.h"
#import "OWS109OutgoingMessageState.h"
#import "OWSDatabaseMigration.h"
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SessionServiceKit/AppContext.h>
@ -34,20 +26,6 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<OWSDatabaseMigration *> *)allMigrations
{
return @[
[[OWS100RemoveTSRecipientsMigration alloc] init],
[[OWS102MoveLoggingPreferenceToUserDefaults alloc] init],
[[OWS103EnableVideoCalling alloc] init],
[[OWS104CreateRecipientIdentities alloc] init],
[[OWS105AttachmentFilePaths alloc] init],
[[OWS106EnsureProfileComplete alloc] init],
[[OWS107LegacySounds alloc] init],
[[OWS108CallLoggingPreference alloc] init],
[[OWS109OutgoingMessageState alloc] init],
[OWS110SortIdMigration new],
[[OWS111UDAttributesMigration alloc] init],
[[OWS112TypingIndicatorsMigration alloc] init],
[[OWS113MultiAttachmentMediaMessages alloc] init],
[[OWS114RemoveDynamicInteractions alloc] init],
[[LK002RemoveFriendRequests alloc] init]
];
}