diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 2e05cc7c6..02faca95d 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 340FC880204DAC8C007AEB0F /* AppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSettingsViewController.h; sourceTree = ""; }; 340FC881204DAC8C007AEB0F /* AdvancedSettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AdvancedSettingsTableViewController.h; sourceTree = ""; }; - 340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkedDevicesTableViewController.m; sourceTree = ""; }; 340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSoundSettingsViewController.m; sourceTree = ""; }; 340FC884204DAC8C007AEB0F /* AboutTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutTableViewController.h; sourceTree = ""; }; - 340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkDeviceViewController.m; sourceTree = ""; }; 340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = ""; }; 340FC887204DAC8C007AEB0F /* BlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockListViewController.m; sourceTree = ""; }; 340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = ""; }; @@ -790,11 +742,9 @@ 340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupSettingsViewController.m; sourceTree = ""; }; 340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = ""; }; 340FC890204DAC8C007AEB0F /* BlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockListViewController.h; sourceTree = ""; }; - 340FC891204DAC8C007AEB0F /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = ""; }; 340FC892204DAC8C007AEB0F /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = ""; }; 340FC893204DAC8C007AEB0F /* AboutTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutTableViewController.m; sourceTree = ""; }; 340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSoundSettingsViewController.h; sourceTree = ""; }; - 340FC895204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = ""; }; 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = ""; }; 340FC898204DAC8D007AEB0F /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = ""; }; 340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = ""; }; @@ -836,10 +786,6 @@ 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = ""; }; 34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = ""; }; 34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = ""; }; - 34386A4D207D0C01009F5D9C /* HomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewController.m; sourceTree = ""; }; - 34386A4E207D0C01009F5D9C /* HomeViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewCell.h; sourceTree = ""; }; - 34386A4F207D0C01009F5D9C /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = ""; }; - 34386A50207D0C01009F5D9C /* HomeViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewCell.m; sourceTree = ""; }; 34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = ""; }; 343A65931FC47D5D000477A1 /* DebugUISyncMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUISyncMessages.h; sourceTree = ""; }; 343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUISyncMessages.m; sourceTree = ""; }; @@ -866,14 +812,9 @@ 344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOrphanDataCleaner.h; sourceTree = ""; }; 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleaner.m; sourceTree = ""; }; 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = ""; }; - 3448E15D221333F5004B052E /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = ""; }; - 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSplashViewController.swift; sourceTree = ""; }; - 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBaseViewController.swift; sourceTree = ""; }; 3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCaptchaViewController.swift; sourceTree = ""; }; 34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = ""; }; 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = ""; }; - 345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FASettingsViewController.h; sourceTree = ""; }; - 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS2FASettingsViewController.m; sourceTree = ""; }; 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAELoadViewController.swift; sourceTree = ""; }; 346129371FD1B47200532771 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSPreferences.h; sourceTree = ""; }; 346129381FD1B47200532771 /* OWSPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSPreferences.m; sourceTree = ""; }; @@ -905,19 +846,6 @@ 346129E11FD5C0BE00532771 /* VersionMigrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VersionMigrations.m; sourceTree = ""; }; 346129E41FD5C0C600532771 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseMigrationRunner.m; sourceTree = ""; }; 346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDatabaseMigrationRunner.h; sourceTree = ""; }; - 346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS102MoveLoggingPreferenceToUserDefaults.m; sourceTree = ""; }; - 346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS103EnableVideoCalling.h; sourceTree = ""; }; - 346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS105AttachmentFilePaths.m; sourceTree = ""; }; - 346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS100RemoveTSRecipientsMigration.m; sourceTree = ""; }; - 346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS104CreateRecipientIdentities.m; sourceTree = ""; }; - 346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS100RemoveTSRecipientsMigration.h; sourceTree = ""; }; - 346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS101ExistingUsersBlockOnIdentityChange.m; sourceTree = ""; }; - 346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS101ExistingUsersBlockOnIdentityChange.h; sourceTree = ""; }; - 346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS102MoveLoggingPreferenceToUserDefaults.h; sourceTree = ""; }; - 346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS106EnsureProfileComplete.swift; sourceTree = ""; }; - 346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS103EnableVideoCalling.m; sourceTree = ""; }; - 346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS105AttachmentFilePaths.h; sourceTree = ""; }; - 346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS104CreateRecipientIdentities.h; sourceTree = ""; }; 34612A041FD7238500532771 /* OWSSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSyncManager.h; sourceTree = ""; }; 34612A051FD7238500532771 /* OWSSyncManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSyncManager.m; sourceTree = ""; }; 34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWindowManager.m; sourceTree = ""; }; @@ -933,7 +861,6 @@ 346941A0215D2EE400B5BFAD /* OWSConversationColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationColor.h; sourceTree = ""; }; 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = ""; }; 346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+OWS.swift"; sourceTree = ""; }; - 346E9D5321B040B600562252 /* RegistrationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = ""; }; 347850561FD86544007B8332 /* SAEFailedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEFailedViewController.swift; sourceTree = ""; }; 3478505A1FD999D5007B8332 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = translations/et.lproj/Localizable.strings; sourceTree = ""; }; 3478505C1FD99A1F007B8332 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = translations/zh_TW.lproj/Localizable.strings; sourceTree = ""; }; @@ -973,11 +900,7 @@ 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupAPI.swift; sourceTree = ""; }; 3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportJob.h; sourceTree = ""; }; 3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = ""; }; - 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS111UDAttributesMigration.swift; sourceTree = ""; }; - 349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Onboarding2FAViewController.swift; sourceTree = ""; }; 349ED991221EE80D008045B0 /* AppPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = ""; }; - 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingVerificationViewController.swift; sourceTree = ""; }; - 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingProfileViewController.swift; sourceTree = ""; }; 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = ""; }; 34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumCellView.swift; sourceTree = ""; }; 34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSResaveCollectionDBMigration.m; sourceTree = ""; }; @@ -1007,8 +930,6 @@ 34AC09D6211B39B100997B47 /* SelectThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectThreadViewController.h; sourceTree = ""; }; 34AC09D7211B39B100997B47 /* SharingThreadPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharingThreadPickerViewController.h; sourceTree = ""; }; 34AC09D9211B39B100997B47 /* MediaMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageView.swift; sourceTree = ""; }; - 34AC09DA211B39B100997B47 /* CountryCodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryCodeViewController.m; sourceTree = ""; }; - 34AC09DB211B39B100997B47 /* CountryCodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryCodeViewController.h; sourceTree = ""; }; 34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharingThreadPickerViewController.m; sourceTree = ""; }; 34AC09FB211B39E700997B47 /* ContactsViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactsViewHelper.h; sourceTree = ""; }; 34AC09FC211B39E700997B47 /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactTableViewCell.h; sourceTree = ""; }; @@ -1038,18 +959,12 @@ 34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentSharing.m; sourceTree = ""; }; 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = ""; }; 34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsPicker.swift; sourceTree = ""; }; - 34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExperienceUpgradesPageViewController.swift; sourceTree = ""; }; 34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteFlow.swift; sourceTree = ""; }; - 34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewContactThreadViewController.h; sourceTree = ""; }; - 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewContactThreadViewController.m; sourceTree = ""; }; - 34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewGroupViewController.h; sourceTree = ""; }; - 34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewGroupViewController.m; sourceTree = ""; }; 34B3F86D1E8DF1700035BE1A /* SignalsNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalsNavigationController.h; sourceTree = ""; }; 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalsNavigationController.m; sourceTree = ""; }; 34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = ""; }; 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorInteraction.swift; sourceTree = ""; }; 34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; - 34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS112TypingIndicatorsMigration.swift; sourceTree = ""; }; 34B6A90A218BA1D0007C4606 /* typing-animation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation.gif"; sourceTree = ""; }; 34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicator.h; sourceTree = ""; }; 34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUnreadIndicator.m; sourceTree = ""; }; @@ -1069,7 +984,6 @@ 34BECE2A1F74C12700D7438D /* DebugUIStress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIStress.m; sourceTree = ""; }; 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerViewController.swift; sourceTree = ""; }; 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerLayout.swift; sourceTree = ""; }; - 34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS114RemoveDynamicInteractions.swift; sourceTree = ""; }; 34BEDB0D21C405B0007B0EAE /* ImageEditorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorModel.swift; sourceTree = ""; }; 34BEDB1221C43F69007B0EAE /* ImageEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorView.swift; sourceTree = ""; }; 34BEDB1421C80BC9007B0EAE /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAnyTouchGestureRecognizer.h; sourceTree = ""; }; @@ -1132,8 +1046,6 @@ 34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAction.h; sourceTree = ""; }; 34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAssetLoader.h; sourceTree = ""; }; 34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesUtils.h; sourceTree = ""; }; - 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS109OutgoingMessageState.m; sourceTree = ""; }; - 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS109OutgoingMessageState.h; sourceTree = ""; }; 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = ""; }; 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = ""; }; 34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = ""; }; @@ -1161,8 +1073,6 @@ 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIDiskUsage.m; sourceTree = ""; }; 34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIPage.h; sourceTree = ""; }; 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIPage.m; sourceTree = ""; }; - 34E5DC8020D8050D00C08145 /* RegistrationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegistrationUtils.h; sourceTree = ""; }; - 34E5DC8120D8050D00C08145 /* RegistrationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegistrationUtils.m; sourceTree = ""; }; 34E88D252098C5AE00A608F4 /* ContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewController.swift; sourceTree = ""; }; 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProtoParsingTest.m; sourceTree = ""; }; 34EA693F2194933900702471 /* MediaDownloadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaDownloadView.swift; sourceTree = ""; }; @@ -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 = ""; }; 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "classic-quiet.aifc"; sourceTree = ""; }; 4503F1BC20470A5B00CEE724 /* classic.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = classic.aifc; sourceTree = ""; }; - 4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS107LegacySounds.m; sourceTree = ""; }; - 4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS107LegacySounds.h; sourceTree = ""; }; 4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ExperienceUpgrade.swift; path = ExperienceUpgrades/ExperienceUpgrade.swift; sourceTree = ""; }; 4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = ThirdParty/WebRTC/Build/WebRTC.framework; sourceTree = ""; }; 450D19111F85236600970622 /* RemoteVideoView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemoteVideoView.h; sourceTree = ""; }; @@ -1245,8 +1153,6 @@ 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDeviceTableViewCell.m; sourceTree = ""; }; 4597E94E1D8313C100040CDE /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = translations/sq.lproj/Localizable.strings; sourceTree = ""; }; 4597E94F1D8313CB00040CDE /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = translations/bg.lproj/Localizable.strings; sourceTree = ""; }; - 4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWS108CallLoggingPreference.h; sourceTree = ""; }; - 4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWS108CallLoggingPreference.m; sourceTree = ""; }; 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSQuotedReplyModel.h; sourceTree = ""; }; 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedReplyModel.m; sourceTree = ""; }; 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 = ""; }; 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionResetJob.swift; sourceTree = ""; }; - 45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS2FAReminderViewController.swift; sourceTree = ""; }; 45D308AB2049A439000189E4 /* PinEntryView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PinEntryView.h; sourceTree = ""; }; 45D308AC2049A439000189E4 /* PinEntryView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PinEntryView.m; sourceTree = ""; }; 45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationHeaderView.swift; sourceTree = ""; }; @@ -1312,7 +1217,6 @@ 4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceNoteLock.swift; sourceTree = ""; }; 4C04F58321C860C50090D0BB /* MantlePerfTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MantlePerfTest.swift; path = Models/MantlePerfTest.swift; sourceTree = ""; }; 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = ""; }; - 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = ""; }; 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoGridViewCell.swift; sourceTree = ""; }; 4C1D2333218B692800A0598F /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = translations/ko.lproj/Localizable.strings; sourceTree = ""; }; @@ -1323,7 +1227,6 @@ 4C1D2339218B6C6D00A0598F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = translations/sv.lproj/Localizable.strings; sourceTree = ""; }; 4C1D233A218B6CDB00A0598F /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = translations/th.lproj/Localizable.strings; sourceTree = ""; }; 4C1D233B218B6D3100A0598F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = translations/tr.lproj/Localizable.strings; sourceTree = ""; }; - 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = ""; }; 4C21D5D5223A9DC500EF8A77 /* UIAlerts+iOS9.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIAlerts+iOS9.m"; sourceTree = ""; }; 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCapture.swift; sourceTree = ""; }; 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = ""; }; @@ -1332,14 +1235,12 @@ 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = ""; }; 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMediaNavigationController.swift; sourceTree = ""; }; 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = ""; }; - 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidator.swift; sourceTree = ""; }; 4C586924224FAB83003FD070 /* AVAudioSession+OWS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "AVAudioSession+OWS.h"; path = "util/UI Categories/AVAudioSession+OWS.h"; sourceTree = ""; }; 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "AVAudioSession+OWS.m"; path = "util/UI Categories/AVAudioSession+OWS.m"; sourceTree = ""; }; 4C618198219DF03A009BD6B5 /* OWSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSButton.swift; sourceTree = ""; }; 4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation-dark.gif"; sourceTree = ""; }; 4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = ""; }; 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = ""; }; - 4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS113MultiAttachmentMediaMessages.swift; sourceTree = ""; }; 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+OWS.swift"; sourceTree = ""; }; 4C948FF62146EB4800349F0D /* BlockListCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListCache.swift; sourceTree = ""; }; 4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = ""; }; @@ -1349,7 +1250,6 @@ 4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = ""; }; 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = ""; }; - 4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS110SortIdMigration.swift; sourceTree = ""; }; 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; @@ -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 = ""; }; - 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 = ""; - }; 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 = ""; @@ -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 */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 46fa9bb73..7764ebf85 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -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); diff --git a/Signal/src/Models/PhoneNumberValidator.swift b/Signal/src/Models/PhoneNumberValidator.swift deleted file mode 100644 index 7b66dcdf2..000000000 --- a/Signal/src/Models/PhoneNumberValidator.swift +++ /dev/null @@ -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) - } -} diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 2a761e6c2..bbe069ec8 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -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 #import #import -#import "NewGroupViewController.h" diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m index 37b9d4603..9b0464a59 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m @@ -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 #import @@ -559,7 +557,7 @@ - (void)reregisterUser { - [RegistrationUtils showReregistrationUIFromViewController:self]; + } #pragma mark - Dark Theme diff --git a/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.h b/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.h deleted file mode 100644 index e03c97d50..000000000 --- a/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSQRCodeScanningViewController.h" -#import - -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 diff --git a/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.m b/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.m deleted file mode 100644 index 9370e3ba7..000000000 --- a/Signal/src/ViewControllers/AppSettings/OWSLinkDeviceViewController.m +++ /dev/null @@ -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 -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSLinkDeviceViewController () - -@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)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 diff --git a/Signal/src/ViewControllers/AppSettings/OWSLinkedDevicesTableViewController.h b/Signal/src/ViewControllers/AppSettings/OWSLinkedDevicesTableViewController.h deleted file mode 100644 index cb8352ec4..000000000 --- a/Signal/src/ViewControllers/AppSettings/OWSLinkedDevicesTableViewController.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -@interface OWSLinkedDevicesTableViewController : UITableViewController - -/** - * This is used to show the user there is a device provisioning in-progress. - */ -- (void)expectMoreDevices; - -@end diff --git a/Signal/src/ViewControllers/AppSettings/OWSLinkedDevicesTableViewController.m b/Signal/src/ViewControllers/AppSettings/OWSLinkedDevicesTableViewController.m deleted file mode 100644 index cb7c7f5c3..000000000 --- a/Signal/src/ViewControllers/AppSettings/OWSLinkedDevicesTableViewController.m +++ /dev/null @@ -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 -#import -#import -#import -#import -#import -#import -#import - -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 diff --git a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m index 1972b6d52..d0ae86c79 100644 --- a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m @@ -4,7 +4,6 @@ #import "PrivacySettingsTableViewController.h" #import "BlockListViewController.h" -#import "OWS2FASettingsViewController.h" #import "Session-Swift.h" #import #import @@ -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 diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index c289c058c..51b7d009e 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -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" diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m index ac8b33d2f..fa84c6e08 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m @@ -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 diff --git a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift deleted file mode 100644 index 9eba6c0f1..000000000 --- a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift +++ /dev/null @@ -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 - } -} diff --git a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift b/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift deleted file mode 100644 index 82699c9c3..000000000 --- a/Signal/src/ViewControllers/HomeView/ConversationSearchViewController.swift +++ /dev/null @@ -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 - } -} diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.h b/Signal/src/ViewControllers/HomeView/HomeViewCell.h deleted file mode 100644 index 4509205f4..000000000 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.h +++ /dev/null @@ -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 diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m deleted file mode 100644 index 3a6d716c7..000000000 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ /dev/null @@ -1,581 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "HomeViewCell.h" -#import "OWSAvatarBuilder.h" -#import "Session-Swift.h" -#import -#import -#import -#import -#import -#import -#import - -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 *viewConstraints; - -@end - -#pragma mark - - -@implementation HomeViewCell - -#pragma mark - Dependencies - -- (OWSContactsManager *)contactsManager -{ - OWSAssertDebug(Environment.shared.contactsManager); - - return Environment.shared.contactsManager; -} - -- (id)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 diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.h b/Signal/src/ViewControllers/HomeView/HomeViewController.h deleted file mode 100644 index 2e4f3185f..000000000 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ConversationViewController.h" -#import -#import - -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 diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m deleted file mode 100644 index ad9188a32..000000000 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ /dev/null @@ -1,1792 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "HomeViewController.h" -#import "AppDelegate.h" -#import "AppSettingsViewController.h" -#import "HomeViewCell.h" -#import "NewContactThreadViewController.h" -#import "OWSNavigationController.h" -#import "OWSPrimaryStorage.h" -#import "ProfileViewController.h" -#import "RegistrationUtils.h" -#import "Session-Swift.h" -#import "SignalApp.h" -#import "TSAccountManager.h" -#import "TSDatabaseView.h" -#import "TSGroupThread.h" -#import "ViewControllerUtils.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversationsReuseIdentifier"; - -typedef NS_ENUM(NSInteger, HomeViewMode) { - HomeViewMode_Archive, - HomeViewMode_Inbox, -}; - -// The bulk of the content in this view is driven by a YapDB view/mapping. -// However, we also want to optionally include ReminderView's at the top -// and an "Archived Conversations" button at the bottom. Rather than introduce -// index-offsets into the Mapping calculation, we introduce two pseudo groups -// to add a top and bottom section to the content, and create cells for those -// sections without consulting the YapMapping. -// This is a bit of a hack, but it consolidates the hacks into the Reminder/Archive section -// and allows us to leaves the bulk of the content logic on the happy path. -NSString *const kReminderViewPseudoGroup = @"kReminderViewPseudoGroup"; -NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup"; - -typedef NS_ENUM(NSInteger, HomeViewControllerSection) { - HomeViewControllerSectionReminders, - HomeViewControllerSectionConversations, - HomeViewControllerSectionArchiveButton, -}; - -@interface HomeViewController () - -@property (nonatomic) UITableView *tableView; -@property (nonatomic) UIView *emptyInboxView; - -@property (nonatomic) UIView *firstConversationCueView; -@property (nonatomic) UILabel *firstConversationLabel; - -@property (nonatomic) YapDatabaseConnection *editingDbConnection; -@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; -@property (nonatomic) YapDatabaseViewMappings *threadMappings; -@property (nonatomic) HomeViewMode homeViewMode; -@property (nonatomic) id previewingContext; -@property (nonatomic, readonly) NSCache *threadViewModelCache; -@property (nonatomic) BOOL isViewVisible; -@property (nonatomic) BOOL shouldObserveDBModifications; -@property (nonatomic) BOOL hasEverAppeared; - -// Mark: Search - -@property (nonatomic, readonly) UISearchBar *searchBar; -@property (nonatomic) ConversationSearchViewController *searchResultsController; - -// Dependencies - -@property (nonatomic, readonly) AccountManager *accountManager; -@property (nonatomic, readonly) OWSContactsManager *contactsManager; -@property (nonatomic, readonly) OWSMessageSender *messageSender; -@property (nonatomic, readonly) OWSBlockListCache *blocklistCache; - -// Views - -@property (nonatomic, readonly) UIStackView *reminderStackView; -@property (nonatomic, readonly) UITableViewCell *reminderViewCell; -@property (nonatomic, readonly) UIView *deregisteredView; -@property (nonatomic, readonly) UIView *outageView; -@property (nonatomic, readonly) UIView *archiveReminderView; -@property (nonatomic, readonly) UIView *missingContactsPermissionView; - -@property (nonatomic) TSThread *lastThread; - -@property (nonatomic) BOOL hasArchivedThreadsRow; -@property (nonatomic) BOOL hasThemeChanged; -@property (nonatomic) BOOL hasVisibleReminders; - -@end - -#pragma mark - - -@implementation HomeViewController - -#pragma mark - Init - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _homeViewMode = HomeViewMode_Inbox; - - [self commonInit]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder -{ - OWSFailDebug(@"Do not load this from the storyboard."); - - self = [super initWithCoder:aDecoder]; - if (!self) { - return self; - } - - [self commonInit]; - - return self; -} - -- (void)commonInit -{ - _accountManager = AppEnvironment.shared.accountManager; - _contactsManager = Environment.shared.contactsManager; - _messageSender = SSKEnvironment.shared.messageSender; - _blocklistCache = [OWSBlockListCache new]; - [_blocklistCache startObservingAndSyncStateWithDelegate:self]; - _threadViewModelCache = [NSCache new]; - - // Ensure ExperienceUpgradeFinder has been initialized. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" - [ExperienceUpgradeFinder sharedManager]; -#pragma GCC diagnostic pop -} - -- (void)observeNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(signalAccountsDidChange:) - name:OWSContactsManagerSignalAccountsDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:OWSApplicationWillEnterForegroundNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive:) - name:OWSApplicationWillResignActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModified:) - name:YapDatabaseModifiedNotification - object:OWSPrimaryStorage.sharedManager.dbNotificationObject]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModifiedExternally:) - name:YapDatabaseModifiedExternallyNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStateDidChange:) - name:RegistrationStateDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(outageStateDidChange:) - name:OutageDetection.outageStateDidChange - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(themeDidChange:) - name:ThemeDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(localProfileDidChange:) - name:kNSNotificationName_LocalProfileDidChange - object:nil]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Notifications - -- (void)signalAccountsDidChange:(id)notification -{ - OWSAssertIsOnMainThread(); - - [self reloadTableViewData]; - - if (!self.firstConversationCueView.isHidden) { - [self updateFirstConversationLabel]; - } -} - -- (void)registrationStateDidChange:(id)notification -{ - OWSAssertIsOnMainThread(); - - [self updateReminderViews]; -} - -- (void)outageStateDidChange:(id)notification -{ - OWSAssertIsOnMainThread(); - - [self updateReminderViews]; -} - -- (void)localProfileDidChange:(id)notification -{ - OWSAssertIsOnMainThread(); - - [self updateBarButtonItems]; -} - -#pragma mark - Theme - -- (void)themeDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self applyTheme]; - [self.tableView reloadData]; - - self.hasThemeChanged = YES; -} - -- (void)applyTheme -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(self.tableView); - OWSAssertDebug(self.searchBar); - - self.view.backgroundColor = Theme.backgroundColor; - self.tableView.backgroundColor = Theme.backgroundColor; -} - -#pragma mark - View Life Cycle - -- (void)loadView -{ - [super loadView]; - - // TODO: Remove this. - if (self.homeViewMode == HomeViewMode_Inbox) { -// [SignalApp.sharedApp setHomeViewController:self]; - } - - UIStackView *reminderStackView = [UIStackView new]; - _reminderStackView = reminderStackView; - reminderStackView.axis = UILayoutConstraintAxisVertical; - reminderStackView.spacing = 0; - _reminderViewCell = [UITableViewCell new]; - self.reminderViewCell.selectionStyle = UITableViewCellSelectionStyleNone; - [self.reminderViewCell.contentView addSubview:reminderStackView]; - [reminderStackView autoPinEdgesToSuperviewEdges]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _reminderViewCell); - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, reminderStackView); - - __weak HomeViewController *weakSelf = self; - ReminderView *deregisteredView = - [ReminderView nagWithText:NSLocalizedString(@"DEREGISTRATION_WARNING", - @"Label warning the user that they have been de-registered.") - tapAction:^{ - HomeViewController *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [RegistrationUtils showReregistrationUIFromViewController:strongSelf]; - }]; - _deregisteredView = deregisteredView; - [reminderStackView addArrangedSubview:deregisteredView]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, deregisteredView); - - ReminderView *outageView = [ReminderView - nagWithText:NSLocalizedString(@"OUTAGE_WARNING", @"Label warning the user that the Signal service may be down.") - tapAction:nil]; - _outageView = outageView; - [reminderStackView addArrangedSubview:outageView]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, outageView); - - ReminderView *archiveReminderView = - [ReminderView explanationWithText:NSLocalizedString(@"INBOX_VIEW_ARCHIVE_MODE_REMINDER", - @"Label reminding the user that they are in archive mode.")]; - _archiveReminderView = archiveReminderView; - [reminderStackView addArrangedSubview:archiveReminderView]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, archiveReminderView); - - ReminderView *missingContactsPermissionView = [ReminderView - nagWithText:NSLocalizedString(@"INBOX_VIEW_MISSING_CONTACTS_PERMISSION", - @"Multi-line label explaining how to show names instead of phone numbers in your inbox") - tapAction:^{ - [[UIApplication sharedApplication] openSystemSettings]; - }]; - _missingContactsPermissionView = missingContactsPermissionView; - [reminderStackView addArrangedSubview:missingContactsPermissionView]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, missingContactsPermissionView); - - self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; - self.tableView.delegate = self; - self.tableView.dataSource = self; - self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; - self.tableView.separatorColor = Theme.cellSeparatorColor; - [self.tableView registerClass:[HomeViewCell class] forCellReuseIdentifier:HomeViewCell.cellReuseIdentifier]; - [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kArchivedConversationsReuseIdentifier]; - [self.view addSubview:self.tableView]; - [self.tableView autoPinEdgesToSuperviewEdges]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _tableView); - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _searchBar); - - self.tableView.rowHeight = UITableViewAutomaticDimension; - self.tableView.estimatedRowHeight = 60; - - self.emptyInboxView = [self createEmptyInboxView]; - [self.view addSubview:self.emptyInboxView]; - [self.emptyInboxView autoPinWidthToSuperviewMargins]; - [self.emptyInboxView autoVCenterInSuperview]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _emptyInboxView); - - [self createFirstConversationCueView]; - [self.view addSubview:self.firstConversationCueView]; - [self.firstConversationCueView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f]; - // This inset bakes in assumptions about UINavigationBar layout, but I'm not sure - // there's a better way to do it, since it isn't safe to use iOS auto layout with - // UINavigationBar contents. - [self.firstConversationCueView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:6.f]; - [self.firstConversationCueView autoPinEdgeToSuperviewEdge:ALEdgeLeading - withInset:10 - relation:NSLayoutRelationGreaterThanOrEqual]; - [self.firstConversationCueView autoPinEdgeToSuperviewMargin:ALEdgeBottom - relation:NSLayoutRelationGreaterThanOrEqual]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _firstConversationCueView); - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _firstConversationLabel); - - UIRefreshControl *pullToRefreshView = [UIRefreshControl new]; - pullToRefreshView.tintColor = [UIColor grayColor]; - [pullToRefreshView addTarget:self - action:@selector(pullToRefreshPerformed:) - forControlEvents:UIControlEventValueChanged]; - [self.tableView insertSubview:pullToRefreshView atIndex:0]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, pullToRefreshView); -} - -- (UIView *)createEmptyInboxView -{ - /* - NSArray *emptyInboxImageNames = @[ - @"home_empty_splash_1", - @"home_empty_splash_2", - @"home_empty_splash_3", - @"home_empty_splash_4", - @"home_empty_splash_5", - ]; - NSString *emptyInboxImageName = emptyInboxImageNames[arc4random_uniform((uint32_t) emptyInboxImageNames.count)]; - UIImageView *emptyInboxImageView = [UIImageView new]; - emptyInboxImageView.image = [UIImage imageNamed:emptyInboxImageName]; - emptyInboxImageView.layer.minificationFilter = kCAFilterTrilinear; - emptyInboxImageView.layer.magnificationFilter = kCAFilterTrilinear; - [emptyInboxImageView autoPinToAspectRatioWithSize:emptyInboxImageView.image.size]; - CGSize screenSize = UIScreen.mainScreen.bounds.size; - CGFloat emptyInboxImageSize = MIN(screenSize.width, screenSize.height) * 0.65f; - [emptyInboxImageView autoSetDimension:ALDimensionWidth toSize:emptyInboxImageSize]; - */ - - UILabel *emptyInboxLabel = [UILabel new]; - emptyInboxLabel.text = NSLocalizedString(@"Looks like you don't have any conversations yet. Get started by messaging a friend.", @""); - emptyInboxLabel.font = UIFont.ows_dynamicTypeBodyClampedFont; - emptyInboxLabel.textColor = UIColor.whiteColor; - emptyInboxLabel.textAlignment = NSTextAlignmentCenter; - emptyInboxLabel.numberOfLines = 0; - emptyInboxLabel.lineBreakMode = NSLineBreakByWordWrapping; - - UIStackView *emptyInboxStack = [[UIStackView alloc] initWithArrangedSubviews:@[ - /*emptyInboxImageView,*/ - emptyInboxLabel, - ]]; - emptyInboxStack.axis = UILayoutConstraintAxisVertical; - emptyInboxStack.alignment = UIStackViewAlignmentCenter; - emptyInboxStack.spacing = 12; - emptyInboxStack.layoutMargins = UIEdgeInsetsMake(50, 50, 50, 50); - emptyInboxStack.layoutMarginsRelativeArrangement = YES; - return emptyInboxStack; -} - -- (void)createFirstConversationCueView -{ - const CGFloat kTailWidth = 16.f; - const CGFloat kTailHeight = 8.f; - const CGFloat kTailHMargin = 12.f; - - UILabel *label = [UILabel new]; - label.textColor = UIColor.ows_whiteColor; - label.font = UIFont.ows_dynamicTypeBodyClampedFont; - label.numberOfLines = 0; - label.lineBreakMode = NSLineBreakByWordWrapping; - - OWSLayerView *layerView = [OWSLayerView new]; - layerView.layoutMargins = UIEdgeInsetsMake(11 + kTailHeight, 16, 11, 16); - CAShapeLayer *shapeLayer = [CAShapeLayer new]; - shapeLayer.fillColor = UIColor.lokiGreen.CGColor; - [layerView.layer addSublayer:shapeLayer]; - layerView.layoutCallback = ^(UIView *view) { - UIBezierPath *bezierPath = [UIBezierPath new]; - - // Bubble - CGRect bubbleBounds = view.bounds; - bubbleBounds.origin.y += kTailHeight; - bubbleBounds.size.height -= kTailHeight; - [bezierPath appendPath:[UIBezierPath bezierPathWithRoundedRect:bubbleBounds cornerRadius:8]]; - - // Tail - CGPoint tailTop = CGPointMake(kTailHMargin + kTailWidth * 0.5f, 0.f); - CGPoint tailLeft = CGPointMake(kTailHMargin, kTailHeight); - CGPoint tailRight = CGPointMake(kTailHMargin + kTailWidth, kTailHeight); - if (!CurrentAppContext().isRTL) { - tailTop.x = view.width - tailTop.x; - tailLeft.x = view.width - tailLeft.x; - tailRight.x = view.width - tailRight.x; - } - [bezierPath moveToPoint:tailTop]; - [bezierPath addLineToPoint:tailLeft]; - [bezierPath addLineToPoint:tailRight]; - [bezierPath addLineToPoint:tailTop]; - shapeLayer.path = bezierPath.CGPath; - shapeLayer.frame = view.bounds; - }; - - [layerView addSubview:label]; - [label autoPinEdgesToSuperviewMargins]; - - layerView.userInteractionEnabled = YES; - [layerView - addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(firstConversationCueWasTapped:)]]; - - self.firstConversationCueView = layerView; - self.firstConversationLabel = label; -} - -- (void)firstConversationCueWasTapped:(UITapGestureRecognizer *)gestureRecognizer -{ - OWSLogInfo(@""); - - AppPreferences.hasDimissedFirstConversationCue = YES; - - [self updateViewState]; -} - -- (NSArray *)suggestedAccountsForFirstContact -{ - NSMutableArray *accounts = [NSMutableArray new]; - NSString *_Nullable localNumber = [TSAccountManager localNumber]; - if (localNumber == nil) { - OWSFailDebug(@"localNumber was unexepectedly nil"); - return @[]; - } - - for (SignalAccount *account in self.contactsManager.signalAccounts) { - if ([localNumber isEqual:account.recipientId]) { - continue; - } - if (accounts.count >= 3) { - break; - } - [accounts addObject:account]; - } - - return [accounts copy]; -} - -- (void)updateFirstConversationLabel -{ - - NSArray *signalAccounts = self.suggestedAccountsForFirstContact; - - NSString *formatString = @""; - NSMutableArray *contactNames = [NSMutableArray new]; - if (signalAccounts.count >= 3) { - [contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[0]]]; - [contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[1]]]; - [contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[2]]]; - - formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_3_CONTACTS_FORMAT", - @"Format string for a label offering to start a new conversation with your contacts, if you have at least " - @"3 Signal contacts. Embeds {{The names of 3 of your Signal contacts}}."); - } else if (signalAccounts.count == 2) { - [contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[0]]]; - [contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[1]]]; - - formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_2_CONTACTS_FORMAT", - @"Format string for a label offering to start a new conversation with your contacts, if you have 2 Signal " - @"contacts. Embeds {{The names of 2 of your Signal contacts}}."); - } else if (signalAccounts.count == 1) { - [contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[0]]]; - - formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_1_CONTACT_FORMAT", - @"Format string for a label offering to start a new conversation with your contacts, if you have 1 Signal " - @"contact. Embeds {{The name of 1 of your Signal contacts}}."); - } - - NSString *embedToken = @"%@"; - NSArray *formatSplits = [formatString componentsSeparatedByString:embedToken]; - // We need to use a complicated format string that possibly embeds multiple contact names. - // Translator error could easily lead to an invalid format string. - // We need to verify that it was translated properly. - BOOL isValidFormatString = (contactNames.count > 0 && formatSplits.count == contactNames.count + 1); - for (NSString *contactName in contactNames) { - if ([contactName containsString:embedToken]) { - isValidFormatString = NO; - } - } - - NSMutableAttributedString *_Nullable attributedString = nil; - if (isValidFormatString) { - attributedString = [[NSMutableAttributedString alloc] initWithString:formatString]; - while (contactNames.count > 0) { - NSString *contactName = contactNames.firstObject; - [contactNames removeObjectAtIndex:0]; - - NSRange range = [attributedString.string rangeOfString:embedToken]; - if (range.location == NSNotFound) { - // Error - attributedString = nil; - break; - } - - NSAttributedString *formattedName = [[NSAttributedString alloc] - initWithString:contactName - attributes:@{ - NSFontAttributeName : self.firstConversationLabel.font.ows_mediumWeight, - }]; - [attributedString replaceCharactersInRange:range withAttributedString:formattedName]; - } - } - - if (!attributedString) { - // The default case handles the no-contacts scenario and all error cases. - NSString *defaultText = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_NO_CONTACTS", - @"A label offering to start a new conversation with your contacts, if you have no Signal contacts."); - attributedString = [[NSMutableAttributedString alloc] initWithString:defaultText]; - } - - self.firstConversationLabel.attributedText = [attributedString copy]; -} - -- (void)updateReminderViews -{ - self.archiveReminderView.hidden = self.homeViewMode != HomeViewMode_Archive; - // App is killed and restarted when the user changes their contact permissions, so need need to "observe" anything - // to re-render this. - self.missingContactsPermissionView.hidden = !self.contactsManager.isSystemContactsDenied; - self.deregisteredView.hidden = !TSAccountManager.sharedInstance.isDeregistered; - self.outageView.hidden = !OutageDetection.sharedManager.hasOutage; - - self.hasVisibleReminders = !self.archiveReminderView.isHidden || !self.missingContactsPermissionView.isHidden - || !self.deregisteredView.isHidden || !self.outageView.isHidden; -} - -- (void)setHasVisibleReminders:(BOOL)hasVisibleReminders -{ - if (_hasVisibleReminders == hasVisibleReminders) { - return; - } - _hasVisibleReminders = hasVisibleReminders; - // If the reminders show/hide, reload the table. - [self.tableView reloadData]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.editingDbConnection = OWSPrimaryStorage.sharedManager.newDatabaseConnection; - - // Create the database connection. - [self uiDatabaseConnection]; - - [self updateMappings]; - [self updateViewState]; - [self updateReminderViews]; - [self observeNotifications]; - - // because this uses the table data source, `tableViewSetup` must happen - // after mappings have been set up in `showInboxGrouping` - [self tableViewSetUp]; - - switch (self.homeViewMode) { - case HomeViewMode_Inbox: - // TODO: Should our app name be translated? Probably not. - self.title = NSLocalizedString(@"Loki Messenger", @""); - break; - case HomeViewMode_Archive: - self.title = NSLocalizedString(@"HOME_VIEW_TITLE_ARCHIVE", @"Title for the home view's 'archive' mode."); - break; - } - - [self applyDefaultBackButton]; - - if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)] - && (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)) { - [self registerForPreviewingWithDelegate:self sourceView:self.tableView]; - } - - // Search - - UISearchBar *searchBar = [OWSSearchBar new]; - _searchBar = searchBar; - searchBar.placeholder = NSLocalizedString(@"HOME_VIEW_CONVERSATION_SEARCHBAR_PLACEHOLDER", - @"Placeholder text for search bar which filters conversations."); - searchBar.delegate = self; - [searchBar sizeToFit]; - - // Setting tableHeader calls numberOfSections, which must happen after updateMappings has been called at least once. - OWSAssertDebug(self.tableView.tableHeaderView == nil); - self.tableView.tableHeaderView = self.searchBar; - // Hide search bar by default. User can pull down to search. - self.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(searchBar.frame)); - - ConversationSearchViewController *searchResultsController = [ConversationSearchViewController new]; - searchResultsController.delegate = self; - self.searchResultsController = searchResultsController; - [self addChildViewController:searchResultsController]; - [self.view addSubview:searchResultsController.view]; - [searchResultsController.view autoPinEdgeToSuperviewEdge:ALEdgeBottom]; - [searchResultsController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading]; - [searchResultsController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing]; - if (@available(iOS 11, *)) { - [searchResultsController.view autoPinTopToSuperviewMarginWithInset:56]; - } else { - [searchResultsController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:40.0f]; - } - searchResultsController.view.hidden = YES; - - [self updateReminderViews]; - [self updateBarButtonItems]; - - [self applyTheme]; - - NSString *buildNumberAsString = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; - NSInteger buildNumber = buildNumberAsString.integerValue; - BOOL didUpdateForMainnet = [NSUserDefaults.standardUserDefaults boolForKey:@"didUpdateForMainnet"]; - if ((buildNumber == 8 || buildNumber == 9 || buildNumber == 10 || buildNumber == 11) && !didUpdateForMainnet) { - NSString *title = NSLocalizedString(@"Update Required", @""); - NSString *message = NSLocalizedString(@"This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version.", @""); - UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [ThreadUtil deleteAllContent]; - [SSKEnvironment.shared.identityManager clearIdentityKey]; - [LKSnodeAPI clearSnodePool]; - AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate; - [appDelegate stopPoller]; - [appDelegate stopClosedGroupPoller]; - [appDelegate stopOpenGroupPollers]; - [SSKEnvironment.shared.tsAccountManager resetForReregistration]; - UIViewController *rootViewController = [[OnboardingController new] initialViewController]; - OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:rootViewController]; - navigationController.navigationBarHidden = YES; - UIApplication.sharedApplication.keyWindow.rootViewController = navigationController; - }]]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { /* Do nothing */ }]]; - [self presentAlert:alert]; - } - if (OWSIdentityManager.sharedManager.identityKeyPair != nil) { - AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate; - [appDelegate startPollerIfNeeded]; - [LKPublicChatManager.shared startPollersIfNeeded]; - } -} - -- (void)applyDefaultBackButton -{ - // We don't show any text for the back button, so there's no need to localize it. But because we left align the - // conversation title view, we add a little tappable padding after the back button, by having a title of spaces. - // Admittedly this is kind of a hack and not super fine grained, but it's simple and results in the interactive pop - // gesture animating our title view nicely vs. creating our own back button bar item with custom padding, which does - // not properly animate with the "swipe to go back" or "swipe left for info" gestures. - NSUInteger paddingLength = 3; - NSString *paddingString = [@"" stringByPaddingToLength:paddingLength withString:@" " startingAtIndex:0]; - - self.navigationItem.backBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:paddingString style:UIBarButtonItemStylePlain target:nil action:nil]; -} - -- (void)applyArchiveBackButton -{ - self.navigationItem.backBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BACK_BUTTON", @"button text for back button") - style:UIBarButtonItemStylePlain - target:nil - action:nil]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - [self displayAnyUnseenUpgradeExperience]; - [self applyDefaultBackButton]; - - if (self.hasThemeChanged) { - [self.tableView reloadData]; - self.hasThemeChanged = NO; - } - - [self requestReviewIfAppropriate]; - - [self.searchResultsController viewDidAppear:animated]; - - self.hasEverAppeared = YES; -} - -- (void)viewDidDisappear:(BOOL)animated -{ - [super viewDidDisappear:animated]; - - [self.searchResultsController viewDidDisappear:animated]; -} - -- (void)updateBarButtonItems -{ - if (self.homeViewMode != HomeViewMode_Inbox) { - return; - } - - // Settings button. - UIBarButtonItem *settingsButton; - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0)) { - const NSUInteger kAvatarSize = 28; - NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; - NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey ?: TSAccountManager.localNumber; - UIImage *_Nullable localProfileAvatarImage = [OWSProfileManager.sharedManager profileAvatarForRecipientId:hexEncodedPublicKey]; - UIImage *avatarImage = (localProfileAvatarImage - ?: [[[OWSContactAvatarBuilder alloc] initForLocalUserWithDiameter:kAvatarSize] buildDefaultImage]); - OWSAssertDebug(avatarImage); - - UIButton *avatarButton = [AvatarImageButton buttonWithType:UIButtonTypeCustom]; - [avatarButton addTarget:self - action:@selector(settingsButtonPressed:) - forControlEvents:UIControlEventTouchUpInside]; - [avatarButton setImage:avatarImage forState:UIControlStateNormal]; - [avatarButton autoSetDimension:ALDimensionWidth toSize:kAvatarSize]; - [avatarButton autoSetDimension:ALDimensionHeight toSize:kAvatarSize]; - - settingsButton = [[UIBarButtonItem alloc] initWithCustomView:avatarButton]; - } else { - // iOS 9 and 10 have a bug around layout of custom views in UIBarButtonItem, - // so we just use a simple icon. - UIImage *image = [UIImage imageNamed:@"button_settings_white"]; - settingsButton = [[UIBarButtonItem alloc] initWithImage:image - style:UIBarButtonItemStylePlain - target:self - action:@selector(settingsButtonPressed:) - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"settings")]; - } - settingsButton.accessibilityLabel = CommonStrings.openSettingsButton; - self.navigationItem.leftBarButtonItem = settingsButton; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, settingsButton); - - UIBarButtonItem *newPrivateChatButton = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem:UIBarButtonSystemItemCompose - target:self - action:@selector(showNewConversationVC) - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"compose")]; - - UIBarButtonItem *newGroupChatButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"group-avatar"] style:UIBarButtonItemStylePlain target:self action:@selector(showNewPublicChatVC)]; - - self.navigationItem.rightBarButtonItems = @[ newPrivateChatButton, newGroupChatButton ]; -} - -- (void)settingsButtonPressed:(id)sender -{ - OWSNavigationController *navigationController = [AppSettingsViewController inModalNavigationController]; - [self presentViewController:navigationController animated:YES completion:nil]; -} - -- (nullable UIViewController *)previewingContext:(id)previewingContext - viewControllerForLocation:(CGPoint)location -{ - NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location]; - - if (!indexPath) { - return nil; - } - - if (indexPath.section != HomeViewControllerSectionConversations) { - return nil; - } - - [previewingContext setSourceRect:[self.tableView rectForRowAtIndexPath:indexPath]]; - - ConversationViewController *vc = [ConversationViewController new]; - TSThread *thread = [self threadForIndexPath:indexPath]; - self.lastThread = thread; - [vc configureForThread:thread action:ConversationViewActionNone focusMessageId:nil]; - [vc peekSetup]; - - return vc; -} - -- (void)previewingContext:(id)previewingContext - commitViewController:(UIViewController *)viewControllerToCommit -{ - ConversationViewController *vc = (ConversationViewController *)viewControllerToCommit; - [vc popped]; - - [self.navigationController pushViewController:vc animated:NO]; -} - -- (void)showNewConversationVC -{ -// LKNewConversationVC *newConversationVC = [LKNewConversationVC new]; -// OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:newConversationVC]; -// [self.navigationController presentViewController:navigationController animated:YES completion:nil]; - - /** - OWSAssertIsOnMainThread(); - - OWSLogInfo(@""); - - NewContactThreadViewController *viewController = [NewContactThreadViewController new]; - - [self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) { - if (error) { - OWSLogError(@"Error when requesting contacts: %@", error); - } - // Even if there is an error fetching contacts we proceed to the next screen. - // As the compose view will present the proper thing depending on contact access. - // - // We just want to make sure contact access is *complete* before showing the compose - // screen to avoid flicker. - OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:viewController]; - [self.navigationController presentViewController:modal animated:YES completion:nil]; - }]; - */ -} - -- (void)showNewPublicChatVC -{ -// LKJoinPublicChatVC *joinPublicChatVC = [LKJoinPublicChatVC new]; -// OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:joinPublicChatVC]; -// [self.navigationController presentViewController:navigationController animated:YES completion:nil]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - __block BOOL hasAnyMessages; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { - hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; - }]; - if (hasAnyMessages) { - [self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self updateReminderViews]; - }); - }]; - } - - self.isViewVisible = YES; - - BOOL isShowingSearchResults = !self.searchResultsController.view.hidden; - if (isShowingSearchResults) { - OWSAssertDebug(self.searchBar.text.ows_stripped.length > 0); - [self scrollSearchBarToTopAnimated:NO]; - } else if (self.lastThread) { - OWSAssertDebug(self.searchBar.text.ows_stripped.length == 0); - - // When returning to home view, try to ensure that the "last" thread is still - // visible. The threads often change ordering while in conversation view due - // to incoming & outgoing messages. - __block NSIndexPath *indexPathOfLastThread = nil; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - indexPathOfLastThread = - [[transaction extension:TSThreadDatabaseViewExtensionName] indexPathForKey:self.lastThread.uniqueId - inCollection:[TSThread collection] - withMappings:self.threadMappings]; - }]; - - if (indexPathOfLastThread) { - [self.tableView scrollToRowAtIndexPath:indexPathOfLastThread - atScrollPosition:UITableViewScrollPositionNone - animated:NO]; - } - } - - [self updateViewState]; - [self applyDefaultBackButton]; - if ([self updateHasArchivedThreadsRow]) { - [self.tableView reloadData]; - } - - [self.searchResultsController viewWillAppear:animated]; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - self.isViewVisible = NO; - - [self.searchResultsController viewWillDisappear:animated]; -} - -- (void)setIsViewVisible:(BOOL)isViewVisible -{ - _isViewVisible = isViewVisible; - - [self updateShouldObserveDBModifications]; -} - -- (void)updateShouldObserveDBModifications -{ - BOOL isAppForegroundAndActive = CurrentAppContext().isAppForegroundAndActive; - self.shouldObserveDBModifications = self.isViewVisible && isAppForegroundAndActive; -} - -- (void)setShouldObserveDBModifications:(BOOL)shouldObserveDBModifications -{ - if (_shouldObserveDBModifications == shouldObserveDBModifications) { - return; - } - - _shouldObserveDBModifications = shouldObserveDBModifications; - - if (self.shouldObserveDBModifications) { - [self resetMappings]; - } -} - -- (void)reloadTableViewData -{ - // PERF: come up with a more nuanced cache clearing scheme - [self.threadViewModelCache removeAllObjects]; - [self.tableView reloadData]; -} - -- (void)resetMappings -{ - // If we're entering "active" mode (e.g. view is visible and app is in foreground), - // reset all state updated by yapDatabaseModified:. - if (self.threadMappings != nil) { - // Before we begin observing database modifications, make sure - // our mapping and table state is up-to-date. - // - // We need to `beginLongLivedReadTransaction` before we update our - // mapping in order to jump to the most recent commit. - [self.uiDatabaseConnection beginLongLivedReadTransaction]; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [self.threadMappings updateWithTransaction:transaction]; - }]; - } - - [self updateHasArchivedThreadsRow]; - [self reloadTableViewData]; - - [self updateViewState]; - - // If the user hasn't already granted contact access - // we don't want to request until they receive a message. - __block BOOL hasAnyMessages; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { - hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; - }]; - if (hasAnyMessages) { - [self.contactsManager requestSystemContactsOnce]; - } -} - -- (void)applicationWillEnterForeground:(NSNotification *)notification -{ - [self updateViewState]; -} - -- (BOOL)hasAnyMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [TSThread numberOfKeysInCollectionWithTransaction:transaction] > 0; -} - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - [self updateShouldObserveDBModifications]; - - // It's possible a thread was created while we where in the background. But since we don't honor contact - // requests unless the app is in the foregrond, we must check again here upon becoming active. - __block BOOL hasAnyMessages; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { - hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; - }]; - - if (hasAnyMessages) { - [self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self updateReminderViews]; - }); - }]; - } -} - -- (void)applicationWillResignActive:(NSNotification *)notification -{ - [self updateShouldObserveDBModifications]; -} - -#pragma mark - startup - -- (NSArray *)unseenUpgradeExperiences -{ - OWSAssertIsOnMainThread(); - - __block NSArray *unseenUpgrades; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - unseenUpgrades = [ExperienceUpgradeFinder.sharedManager allUnseenWithTransaction:transaction]; - }]; - return unseenUpgrades; -} - -- (void)displayAnyUnseenUpgradeExperience -{ - OWSAssertIsOnMainThread(); - - NSArray *unseenUpgrades = [self unseenUpgradeExperiences]; - - if (unseenUpgrades.count > 0) { - ExperienceUpgradesPageViewController *experienceUpgradeViewController = - [[ExperienceUpgradesPageViewController alloc] initWithExperienceUpgrades:unseenUpgrades]; - [self presentViewController:experienceUpgradeViewController animated:YES completion:nil]; - } else { - [OWSAlerts showIOSUpgradeNagIfNecessary]; - } -} - -- (void)tableViewSetUp -{ - self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; -} - -#pragma mark - Table View Data Source - -// Returns YES IFF this value changes. -- (BOOL)updateHasArchivedThreadsRow -{ - BOOL hasArchivedThreadsRow = (self.homeViewMode == HomeViewMode_Inbox && self.numberOfArchivedThreads > 0); - if (self.hasArchivedThreadsRow == hasArchivedThreadsRow) { - return NO; - } - self.hasArchivedThreadsRow = hasArchivedThreadsRow; - - return YES; -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return (NSInteger)[self.threadMappings numberOfSections]; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)aSection -{ - HomeViewControllerSection section = (HomeViewControllerSection)aSection; - switch (section) { - case HomeViewControllerSectionReminders: { - return self.hasVisibleReminders ? 1 : 0; - } - case HomeViewControllerSectionConversations: { - NSInteger result = (NSInteger)[self.threadMappings numberOfItemsInSection:(NSUInteger)section]; - return result; - } - case HomeViewControllerSectionArchiveButton: { - return self.hasArchivedThreadsRow ? 1 : 0; - } - } - - OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)section); - return 0; -} - -- (ThreadViewModel *)threadViewModelForIndexPath:(NSIndexPath *)indexPath -{ - TSThread *threadRecord = [self threadForIndexPath:indexPath]; - OWSAssertDebug(threadRecord); - - ThreadViewModel *_Nullable cachedThreadViewModel = [self.threadViewModelCache objectForKey:threadRecord.uniqueId]; - if (cachedThreadViewModel) { - return cachedThreadViewModel; - } - - __block ThreadViewModel *_Nullable newThreadViewModel; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - newThreadViewModel = [[ThreadViewModel alloc] initWithThread:threadRecord transaction:transaction]; - }]; - [self.threadViewModelCache setObject:newThreadViewModel forKey:threadRecord.uniqueId]; - return newThreadViewModel; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; - switch (section) { - case HomeViewControllerSectionReminders: { - OWSAssert(self.reminderStackView); - - return self.reminderViewCell; - } - case HomeViewControllerSectionConversations: { - return [self tableView:tableView cellForConversationAtIndexPath:indexPath]; - } - case HomeViewControllerSectionArchiveButton: { - return [self cellForArchivedConversationsRow:tableView]; - } - } - - OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)section); - return [UITableViewCell new]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForConversationAtIndexPath:(NSIndexPath *)indexPath -{ - HomeViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:HomeViewCell.cellReuseIdentifier]; - OWSAssertDebug(cell); - - ThreadViewModel *thread = [self threadViewModelForIndexPath:indexPath]; - - BOOL isBlocked = [self.blocklistCache isThreadBlocked:thread.threadRecord]; - [cell configureWithThread:thread isBlocked:isBlocked]; - - // TODO: Work with Nancy to confirm that this is accessible via Appium. - NSString *cellName = [NSString stringWithFormat:@"conversation-%@", NSUUID.UUID.UUIDString]; - cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, cellName); - - return cell; -} - -- (UITableViewCell *)cellForArchivedConversationsRow:(UITableView *)tableView -{ - UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kArchivedConversationsReuseIdentifier]; - OWSAssertDebug(cell); - [OWSTableItem configureCell:cell]; - - for (UIView *subview in cell.contentView.subviews) { - [subview removeFromSuperview]; - } - - UIImage *disclosureImage = [UIImage imageNamed:(CurrentAppContext().isRTL ? @"NavBarBack" : @"NavBarBackRTL")]; - OWSAssertDebug(disclosureImage); - UIImageView *disclosureImageView = [UIImageView new]; - disclosureImageView.image = [disclosureImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - disclosureImageView.tintColor = [UIColor colorWithRGBHex:0xd1d1d6]; - [disclosureImageView setContentHuggingHigh]; - [disclosureImageView setCompressionResistanceHigh]; - - UILabel *label = [UILabel new]; - label.text = NSLocalizedString(@"HOME_VIEW_ARCHIVED_CONVERSATIONS", @"Label for 'archived conversations' button."); - label.textAlignment = NSTextAlignmentCenter; - label.font = [UIFont ows_dynamicTypeBodyFont]; - label.textColor = Theme.primaryColor; - - UIStackView *stackView = [UIStackView new]; - stackView.axis = UILayoutConstraintAxisHorizontal; - stackView.spacing = 5; - // If alignment isn't set, UIStackView uses the height of - // disclosureImageView, even if label has a higher desired height. - stackView.alignment = UIStackViewAlignmentCenter; - [stackView addArrangedSubview:label]; - [stackView addArrangedSubview:disclosureImageView]; - [cell.contentView addSubview:stackView]; - [stackView autoCenterInSuperview]; - // Constrain to cell margins. - [stackView autoPinEdgeToSuperviewMargin:ALEdgeLeading relation:NSLayoutRelationGreaterThanOrEqual]; - [stackView autoPinEdgeToSuperviewMargin:ALEdgeTrailing relation:NSLayoutRelationGreaterThanOrEqual]; - [stackView autoPinEdgeToSuperviewMargin:ALEdgeTop]; - [stackView autoPinEdgeToSuperviewMargin:ALEdgeBottom]; - - cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"archived_conversations"); - - return cell; -} - -- (TSThread *)threadForIndexPath:(NSIndexPath *)indexPath -{ - __block TSThread *thread = nil; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - thread = [[transaction extension:TSThreadDatabaseViewExtensionName] objectAtIndexPath:indexPath - withMappings:self.threadMappings]; - }]; - - if (![thread isKindOfClass:[TSThread class]]) { - OWSLogError(@"Invalid object in thread view: %@", [thread class]); - [OWSStorage incrementVersionOfDatabaseExtension:TSThreadDatabaseViewExtensionName]; - } - - return thread; -} - -- (void)pullToRefreshPerformed:(UIRefreshControl *)refreshControl -{ - OWSAssertIsOnMainThread(); - OWSLogInfo(@"beggining refreshing."); - [[AppEnvironment.shared.messageFetcherJob run].ensure(^{ - OWSLogInfo(@"ending refreshing."); - [refreshControl endRefreshing]; - }) retainUntilComplete]; -} - -#pragma mark - Edit Actions - -- (void)tableView:(UITableView *)tableView - commitEditingStyle:(UITableViewCellEditingStyle)editingStyle - forRowAtIndexPath:(NSIndexPath *)indexPath -{ - return; -} - -- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath -{ - HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; - switch (section) { - case HomeViewControllerSectionReminders: { - return @[]; - } - case HomeViewControllerSectionConversations: { - UITableViewRowAction *deleteAction = - [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault - title:NSLocalizedString(@"TXT_DELETE_TITLE", nil) - handler:^(UITableViewRowAction *action, NSIndexPath *swipedIndexPath) { - [self tableViewCellTappedDelete:swipedIndexPath]; - }]; - - /** - UITableViewRowAction *archiveAction; - if (self.homeViewMode == HomeViewMode_Inbox) { - archiveAction = [UITableViewRowAction - rowActionWithStyle:UITableViewRowActionStyleNormal - title:NSLocalizedString(@"ARCHIVE_ACTION", - @"Pressing this button moves a thread from the inbox to the archive") - handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { - [self archiveIndexPath:tappedIndexPath]; - }]; - - } else { - archiveAction = [UITableViewRowAction - rowActionWithStyle:UITableViewRowActionStyleNormal - title:NSLocalizedString(@"UNARCHIVE_ACTION", - @"Pressing this button moves an archived thread from the archive back to " - @"the inbox") - handler:^(UITableViewRowAction *_Nonnull action, NSIndexPath *_Nonnull tappedIndexPath) { - [self archiveIndexPath:tappedIndexPath]; - }]; - } - */ - - // The first action will be auto-performed for "very long swipes". - return @[ - /*archiveAction,*/ - deleteAction, - ]; - } - case HomeViewControllerSectionArchiveButton: { - return @[]; - } - } -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; - switch (section) { - case HomeViewControllerSectionReminders: { - return NO; - } - case HomeViewControllerSectionConversations: { - return YES; - } - case HomeViewControllerSectionArchiveButton: { - return NO; - } - } -} - -#pragma mark - UISearchBarDelegate - -- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar -{ - [self scrollSearchBarToTopAnimated:NO]; - - [self updateSearchResultsVisibility]; - - [self ensureSearchBarCancelButton]; -} - -- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar -{ - [self updateSearchResultsVisibility]; - - [self ensureSearchBarCancelButton]; -} - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText -{ - [self updateSearchResultsVisibility]; - - [self ensureSearchBarCancelButton]; -} - -- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar -{ - [self updateSearchResultsVisibility]; -} - -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar -{ - self.searchBar.text = nil; - - [self.searchBar resignFirstResponder]; - OWSAssertDebug(!self.searchBar.isFirstResponder); - - [self updateSearchResultsVisibility]; - - [self ensureSearchBarCancelButton]; -} - -- (void)ensureSearchBarCancelButton -{ - self.searchBar.showsCancelButton = (self.searchBar.isFirstResponder || self.searchBar.text.length > 0); -} - -- (void)updateSearchResultsVisibility -{ - OWSAssertIsOnMainThread(); - - NSString *searchText = self.searchBar.text.ows_stripped; - self.searchResultsController.searchText = searchText; - BOOL isSearching = searchText.length > 0; - self.searchResultsController.view.hidden = !isSearching; - - if (isSearching) { - [self scrollSearchBarToTopAnimated:NO]; - self.tableView.scrollEnabled = NO; - } else { - self.tableView.scrollEnabled = YES; - } -} - -- (void)scrollSearchBarToTopAnimated:(BOOL)isAnimated -{ - CGFloat topInset = self.topLayoutGuide.length; - [self.tableView setContentOffset:CGPointMake(0, -topInset) animated:isAnimated]; -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - [self.searchBar resignFirstResponder]; - OWSAssertDebug(!self.searchBar.isFirstResponder); -} - -#pragma mark - ConversationSearchViewDelegate - -- (void)conversationSearchViewWillBeginDragging -{ - [self.searchBar resignFirstResponder]; - OWSAssertDebug(!self.searchBar.isFirstResponder); -} - -#pragma mark - HomeFeedTableViewCellDelegate - -- (void)tableViewCellTappedDelete:(NSIndexPath *)indexPath -{ - if (indexPath.section != HomeViewControllerSectionConversations) { - OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)indexPath.section); - return; - } - - TSThread *thread = [self threadForIndexPath:indexPath]; - - __weak HomeViewController *weakSelf = self; - UIAlertController *alert = - [UIAlertController alertControllerWithTitle:NSLocalizedString(@"CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE", - @"Title for the 'conversation delete confirmation' alert.") - message:NSLocalizedString(@"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE", - @"Message for the 'conversation delete confirmation' alert.") - preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", nil) - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction *action) { - [weakSelf deleteThread:thread]; - }]]; - [alert addAction:[OWSAlerts cancelAction]]; - - [self presentAlert:alert]; -} - -- (void)deleteThread:(TSThread *)thread -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - /* Loki: Orignal Code - ===================== - if ([thread isKindOfClass:[TSGroupThread class]]) { - TSGroupThread *groupThread = (TSGroupThread *)thread; - if (groupThread.isLocalUserInGroup) { - [groupThread softDeleteGroupThreadWithTransaction:transaction]; - return; - } - } - */ - - // Loki: For now hard delete all groups - [thread removeWithTransaction:transaction]; - } error:nil]; - - // Loki: Post notification - [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.threadDeleted object:nil userInfo:@{ @"threadId": thread.uniqueId }]; - - [self updateViewState]; -} - -- (void)archiveIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section != HomeViewControllerSectionConversations) { - OWSFailDebug(@"failure: unexpected section: %lu", (unsigned long)indexPath.section); - return; - } - - TSThread *thread = [self threadForIndexPath:indexPath]; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - switch (self.homeViewMode) { - case HomeViewMode_Inbox: - [thread archiveThreadWithTransaction:transaction]; - break; - case HomeViewMode_Archive: - [thread unarchiveThreadWithTransaction:transaction]; - break; - } - } error:nil]; - [self updateViewState]; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - OWSLogInfo(@"%ld %ld", (long)indexPath.row, (long)indexPath.section); - - [self.searchBar resignFirstResponder]; - HomeViewControllerSection section = (HomeViewControllerSection)indexPath.section; - switch (section) { - case HomeViewControllerSectionReminders: { - break; - } - case HomeViewControllerSectionConversations: { - TSThread *thread = [self threadForIndexPath:indexPath]; - [self presentThread:thread action:ConversationViewActionNone animated:YES]; - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - break; - } - case HomeViewControllerSectionArchiveButton: { - [self showArchivedConversations]; - break; - } - } -} - -- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action animated:(BOOL)isAnimated -{ - [self presentThread:thread action:action focusMessageId:nil animated:isAnimated]; -} - -- (void)presentThread:(TSThread *)thread - action:(ConversationViewAction)action - focusMessageId:(nullable NSString *)focusMessageId - animated:(BOOL)isAnimated -{ - if (thread == nil) { - OWSFailDebug(@"Thread unexpectedly nil"); - return; - } - - DispatchMainThreadSafe(^{ - ConversationViewController *conversationVC = [ConversationViewController new]; - [conversationVC configureForThread:thread action:action focusMessageId:focusMessageId]; - self.lastThread = thread; - - if (self.homeViewMode == HomeViewMode_Archive) { - [self.navigationController pushViewController:conversationVC animated:isAnimated]; - } else { - [self.navigationController setViewControllers:@[ self, conversationVC ] animated:isAnimated]; - } - }); -} - -#pragma mark - Groupings - -- (YapDatabaseViewMappings *)threadMappings -{ - OWSAssertDebug(_threadMappings != nil); - return _threadMappings; -} - -- (void)showInboxGrouping -{ - OWSAssertDebug(self.homeViewMode == HomeViewMode_Archive); - - [self.navigationController popToRootViewControllerAnimated:YES]; -} - -- (void)showArchivedConversations -{ - OWSAssertDebug(self.homeViewMode == HomeViewMode_Inbox); - - // When showing archived conversations, we want to use a conventional "back" button - // to return to the "inbox" home view. - [self applyArchiveBackButton]; - - // Push a separate instance of this view using "archive" mode. - HomeViewController *homeView = [HomeViewController new]; - homeView.homeViewMode = HomeViewMode_Archive; - [self.navigationController pushViewController:homeView animated:YES]; -} - -- (NSString *)currentGrouping -{ - switch (self.homeViewMode) { - case HomeViewMode_Inbox: - return TSInboxGroup; - case HomeViewMode_Archive: - return TSArchiveGroup; - } -} - -- (void)updateMappings -{ - OWSAssertIsOnMainThread(); - - self.threadMappings = [[YapDatabaseViewMappings alloc] - initWithGroups:@[ kReminderViewPseudoGroup, self.currentGrouping, kArchiveButtonPseudoGroup ] - view:TSThreadDatabaseViewExtensionName]; - [self.threadMappings setIsReversed:YES forGroup:self.currentGrouping]; - - [self resetMappings]; - - [self reloadTableViewData]; - [self updateViewState]; - [self updateReminderViews]; -} - -#pragma mark - Database delegates - -- (YapDatabaseConnection *)uiDatabaseConnection -{ - OWSAssertIsOnMainThread(); - - if (!_uiDatabaseConnection) { - _uiDatabaseConnection = [OWSPrimaryStorage.sharedManager newDatabaseConnection]; - // default is 250 - _uiDatabaseConnection.objectCacheLimit = 500; - [_uiDatabaseConnection beginLongLivedReadTransaction]; - } - return _uiDatabaseConnection; -} - -- (void)yapDatabaseModifiedExternally:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - OWSLogVerbose(@""); - - if (self.shouldObserveDBModifications) { - // 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. - // - // We don't need to do this if we're not observing db modifications since we'll - // do it when we resume. - [self resetMappings]; - } -} - -- (void)yapDatabaseModified:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - if (!self.shouldObserveDBModifications) { - return; - } - - NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction]; - - if (![[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] hasChangesForGroup:self.currentGrouping - inNotifications:notifications]) { - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [self.threadMappings updateWithTransaction:transaction]; - }]; - [self updateViewState]; - - return; - } - - // If the user hasn't already granted contact access - // we don't want to request until they receive a message. - __block BOOL hasAnyMessages; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - hasAnyMessages = [self hasAnyMessagesWithTransaction:transaction]; - }]; - - if (hasAnyMessages) { - [self.contactsManager requestSystemContactsOnce]; - } - - NSArray *sectionChanges = nil; - NSArray *rowChanges = nil; - [[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] getSectionChanges:§ionChanges - rowChanges:&rowChanges - forNotifications:notifications - withMappings:self.threadMappings]; - - // We want this regardless of if we're currently viewing the archive. - // So we run it before the early return - [self updateViewState]; - - if ([sectionChanges count] == 0 && [rowChanges count] == 0) { - return; - } - - if ([self updateHasArchivedThreadsRow]) { - [self.tableView reloadData]; - return; - } - - [self.tableView beginUpdates]; - - for (YapDatabaseViewSectionChange *sectionChange in sectionChanges) { - switch (sectionChange.type) { - case YapDatabaseViewChangeDelete: { - [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionChange.index] - withRowAnimation:UITableViewRowAnimationAutomatic]; - break; - } - case YapDatabaseViewChangeInsert: { - [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionChange.index] - withRowAnimation:UITableViewRowAnimationAutomatic]; - break; - } - case YapDatabaseViewChangeUpdate: - case YapDatabaseViewChangeMove: - break; - } - } - - for (YapDatabaseViewRowChange *rowChange in rowChanges) { - NSString *key = rowChange.collectionKey.key; - OWSAssertDebug(key); - [self.threadViewModelCache removeObjectForKey:key]; - - 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]; -} - -- (NSUInteger)numberOfThreadsInGroup:(NSString *)group -{ - // We need to consult the db view, not the mapping since the mapping only knows about - // the current group. - __block NSUInteger result; - [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSThreadDatabaseViewExtensionName]; - result = [viewTransaction numberOfItemsInGroup:group]; - }]; - return result; -} - -- (NSUInteger)numberOfInboxThreads -{ - return [self numberOfThreadsInGroup:TSInboxGroup]; -} - -- (NSUInteger)numberOfArchivedThreads -{ - return [self numberOfThreadsInGroup:TSArchiveGroup]; -} - -- (void)updateViewState -{ - if (self.shouldShowFirstConversationCue) { - [_tableView setHidden:YES]; - [self.emptyInboxView setHidden:NO]; - [self.firstConversationCueView setHidden:NO]; - [self updateFirstConversationLabel]; - } else { - [_tableView setHidden:NO]; - [self.emptyInboxView setHidden:YES]; - [self.firstConversationCueView setHidden:YES]; - } -} - -- (BOOL)shouldShowFirstConversationCue -{ - return (self.homeViewMode == HomeViewMode_Inbox && self.numberOfInboxThreads == 0 - && self.numberOfArchivedThreads == 0 && !AppPreferences.hasDimissedFirstConversationCue - && !SSKPreferences.hasSavedThread); -} - -// We want to delay asking for a review until an opportune time. -// If the user has *just* launched Signal they intend to do something, we don't want to interrupt them. -// If the user hasn't sent a message, we don't want to ask them for a review yet. -- (void)requestReviewIfAppropriate -{ - if (self.hasEverAppeared && Environment.shared.preferences.hasSentAMessage) { - OWSLogDebug(@"requesting review"); - if (@available(iOS 10, *)) { - // In Debug this pops up *every* time, which is helpful, but annoying. - // In Production this will pop up at most 3 times per 365 days. -#ifndef DEBUG - static dispatch_once_t onceToken; - // Despite `SKStoreReviewController` docs, some people have reported seeing the "request review" prompt - // repeatedly after first installation. Let's make sure it only happens at most once per launch. - dispatch_once(&onceToken, ^{ - [SKStoreReviewController requestReview]; - }); -#endif - } - } else { - OWSLogDebug(@"not requesting review"); - } -} - -#pragma mark - OWSBlockListCacheDelegate - -- (void)blockListCacheDidUpdate:(OWSBlockListCache *_Nonnull)blocklistCache -{ - OWSLogVerbose(@""); - [self reloadTableViewData]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/HomeView/MessageStatusView.swift b/Signal/src/ViewControllers/HomeView/MessageStatusView.swift deleted file mode 100644 index cfe750d7f..000000000 --- a/Signal/src/ViewControllers/HomeView/MessageStatusView.swift +++ /dev/null @@ -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() - } -} diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.h b/Signal/src/ViewControllers/NewContactThreadViewController.h deleted file mode 100644 index 1fb32376b..000000000 --- a/Signal/src/ViewControllers/NewContactThreadViewController.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NewContactThreadViewController : OWSViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m deleted file mode 100644 index 907db165e..000000000 --- a/Signal/src/ViewControllers/NewContactThreadViewController.m +++ /dev/null @@ -1,1151 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "NewContactThreadViewController.h" -#import "ContactTableViewCell.h" -#import "ContactsViewHelper.h" -#import "NewGroupViewController.h" -#import "NewNonContactConversationViewController.h" -#import "OWSTableViewController.h" -#import "Session-Swift.h" -#import "SignalApp.h" -#import "UIColor+OWS.h" -#import "UIView+OWS.h" -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SignalAccount (Collation) - -- (NSString *)stringForCollation; - -@end - -@implementation SignalAccount (Collation) - -- (NSString *)stringForCollation -{ - OWSContactsManager *contactsManager = Environment.shared.contactsManager; - return [contactsManager comparableNameForSignalAccount:self]; -} - -@end - -@interface NewContactThreadViewController () - -@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper; -@property (nonatomic, readonly) FullTextSearcher *fullTextSearcher; - -@property (nonatomic, readonly) UIView *noSignalContactsView; - -@property (nonatomic, readonly) OWSTableViewController *tableViewController; - -@property (nonatomic, readonly) UILocalizedIndexedCollation *collation; - -@property (nonatomic, readonly) UISearchBar *searchBar; -@property (nonatomic) ComposeScreenSearchResultSet *searchResults; - -// A list of possible phone numbers parsed from the search text as -// E164 values. -@property (nonatomic) NSArray *searchPhoneNumbers; - -// This set is used to cache the set of non-contact phone numbers -// which are known to correspond to Signal accounts. -@property (nonatomic, readonly) NSMutableSet *nonContactAccountSet; - -@property (nonatomic) BOOL isNoContactsModeActive; - -@end - -#pragma mark - - -@implementation NewContactThreadViewController - - -#pragma mark - Dependencies - -- (FullTextSearcher *)fullTextSearcher -{ - return FullTextSearcher.shared; -} - -- (YapDatabaseConnection *)uiDatabaseConnection -{ - return OWSPrimaryStorage.sharedManager.uiDatabaseConnection; -} - -- (OWSContactsManager *)contactsManager -{ - return Environment.shared.contactsManager; -} - -#pragma mark - - -- (void)loadView -{ - [super loadView]; - - _searchResults = ComposeScreenSearchResultSet.empty; - _contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self]; - _nonContactAccountSet = [NSMutableSet set]; - _collation = [UILocalizedIndexedCollation currentCollation]; - - self.navigationItem.leftBarButtonItem = - [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop - target:self - action:@selector(dismissPressed) - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"stop")]; - // Loki: Original code - // ======== -// // TODO: We should use separate RTL and LTR flavors of this asset. -// UIImage *newGroupImage = [UIImage imageNamed:@"btnGroup--white"]; -// OWSAssertDebug(newGroupImage); -// UIBarButtonItem *newGroupButton = -// [[UIBarButtonItem alloc] initWithImage:newGroupImage -// style:UIBarButtonItemStylePlain -// target:self -// action:@selector(showNewGroupView:) -// accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"new_group")]; -// newGroupButton.accessibilityLabel -// = NSLocalizedString(@"CREATE_NEW_GROUP", @"Accessibility label for the create group new group button"); -// self.navigationItem.rightBarButtonItem = newGroupButton; - // ======== - - // Search - UISearchBar *searchBar = [OWSSearchBar new]; - _searchBar = searchBar; - searchBar.delegate = self; - searchBar.placeholder = NSLocalizedString(@"Search by public key", @""); - searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; - [searchBar sizeToFit]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, searchBar); - - _tableViewController = [OWSTableViewController new]; - _tableViewController.delegate = self; - _tableViewController.tableViewStyle = UITableViewStylePlain; - - // To automatically adjust our content inset appropriately on iOS9/10 - // 1. the tableViewController must be a childView - // 2. the scrollable view (tableView in this case) must be at index 0. - [self addChildViewController:self.tableViewController]; - [self.view insertSubview:self.tableViewController.view atIndex:0]; - - [self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading]; - [self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing]; - [_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeTop]; - - self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension; - self.tableViewController.tableView.estimatedRowHeight = 60; - - [self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view avoidNotch:NO]; - _tableViewController.tableView.tableHeaderView = searchBar; - - _noSignalContactsView = [self createNoSignalContactsView]; - self.noSignalContactsView.hidden = YES; - [self.view addSubview:self.noSignalContactsView]; - [self.noSignalContactsView autoPinWidthToSuperview]; - [self.noSignalContactsView autoPinEdgeToSuperviewEdge:ALEdgeTop]; - [self.noSignalContactsView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.view withOffset:0.0f]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _noSignalContactsView); - - UIRefreshControl *pullToRefreshView = [UIRefreshControl new]; - pullToRefreshView.tintColor = [UIColor grayColor]; - [pullToRefreshView addTarget:self - action:@selector(pullToRefreshPerformed:) - forControlEvents:UIControlEventValueChanged]; - [self.tableViewController.tableView insertSubview:pullToRefreshView atIndex:0]; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, pullToRefreshView); - - [self updateTableContents]; - - [self applyTheme]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(themeDidChange:) - name:ThemeDidChangeNotification - object:nil]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)pullToRefreshPerformed:(UIRefreshControl *)refreshControl -{ - OWSAssertIsOnMainThread(); - - [self.contactsManager userRequestedSystemContactsRefreshWithCompletion:^(NSError *_Nullable error) { - if (error) { - OWSLogError(@"refreshing contacts failed with error: %@", error); - } - [refreshControl endRefreshing]; - }]; -} - -- (void)showSearchBar:(BOOL)isVisible -{ - if (isVisible) { - self.tableViewController.tableView.tableHeaderView = self.searchBar; - } else { - self.tableViewController.tableView.tableHeaderView = nil; - } -} - -- (UIView *)createNoSignalContactsView -{ - UIView *view = [UIView new]; - view.backgroundColor = [Theme backgroundColor]; - - UIView *contents = [UIView new]; - [view addSubview:contents]; - [contents autoCenterInSuperview]; - - UIImage *heroImage = [UIImage imageNamed:@"uiEmptyContact"]; - OWSAssertDebug(heroImage); - UIImageView *heroImageView = [[UIImageView alloc] initWithImage:heroImage]; - heroImageView.layer.minificationFilter = kCAFilterTrilinear; - heroImageView.layer.magnificationFilter = kCAFilterTrilinear; - [contents addSubview:heroImageView]; - [heroImageView autoHCenterInSuperview]; - [heroImageView autoPinEdgeToSuperviewEdge:ALEdgeTop]; - const CGFloat kHeroSize = ScaleFromIPhone5To7Plus(100, 150); - [heroImageView autoSetDimension:ALDimensionWidth toSize:kHeroSize]; - [heroImageView autoSetDimension:ALDimensionHeight toSize:kHeroSize]; - UIView *lastSubview = heroImageView; - - UILabel *titleLabel = [UILabel new]; - titleLabel.text = NSLocalizedString( - @"EMPTY_CONTACTS_LABEL_LINE1", "Full width label displayed when attempting to compose message"); - titleLabel.textColor = [Theme primaryColor]; - titleLabel.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(17.f, 20.f)]; - titleLabel.textAlignment = NSTextAlignmentCenter; - titleLabel.lineBreakMode = NSLineBreakByWordWrapping; - titleLabel.numberOfLines = 0; - [contents addSubview:titleLabel]; - [titleLabel autoPinWidthToSuperview]; - [titleLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:30]; - lastSubview = titleLabel; - - UILabel *subtitleLabel = [UILabel new]; - subtitleLabel.text = NSLocalizedString( - @"EMPTY_CONTACTS_LABEL_LINE2", "Full width label displayed when attempting to compose message"); - subtitleLabel.textColor = [Theme secondaryColor]; - subtitleLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(12.f, 14.f)]; - subtitleLabel.textAlignment = NSTextAlignmentCenter; - subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping; - subtitleLabel.numberOfLines = 0; - [contents addSubview:subtitleLabel]; - [subtitleLabel autoPinWidthToSuperview]; - [subtitleLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:15]; - lastSubview = subtitleLabel; - - UIButton *inviteContactsButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [inviteContactsButton setTitle:NSLocalizedString(@"INVITE_FRIENDS_CONTACT_TABLE_BUTTON", - "Label for the cell that presents the 'invite contacts' workflow.") - forState:UIControlStateNormal]; - [inviteContactsButton setTitleColor:[UIColor ows_materialBlueColor] forState:UIControlStateNormal]; - [inviteContactsButton.titleLabel setFont:[UIFont ows_regularFontWithSize:17.f]]; - [contents addSubview:inviteContactsButton]; - [inviteContactsButton autoHCenterInSuperview]; - [inviteContactsButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:50]; - [inviteContactsButton addTarget:self - action:@selector(presentInviteFlow) - forControlEvents:UIControlEventTouchUpInside]; - lastSubview = inviteContactsButton; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, inviteContactsButton); - - UIButton *searchByPhoneNumberButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [searchByPhoneNumberButton setTitle:NSLocalizedString(@"NO_CONTACTS_SEARCH_BY_PHONE_NUMBER", - @"Label for a button that lets users search for contacts by phone number") - forState:UIControlStateNormal]; - [searchByPhoneNumberButton setTitleColor:[UIColor ows_materialBlueColor] forState:UIControlStateNormal]; - [searchByPhoneNumberButton.titleLabel setFont:[UIFont ows_regularFontWithSize:17.f]]; - [contents addSubview:searchByPhoneNumberButton]; - [searchByPhoneNumberButton autoHCenterInSuperview]; - [searchByPhoneNumberButton autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:20]; - [searchByPhoneNumberButton addTarget:self - action:@selector(hideBackgroundView) - forControlEvents:UIControlEventTouchUpInside]; - lastSubview = searchByPhoneNumberButton; - SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, searchByPhoneNumberButton); - - [lastSubview autoPinEdgeToSuperviewMargin:ALEdgeBottom]; - - return view; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - [self.contactsViewHelper warmNonSignalContactsCacheAsync]; - - self.title = NSLocalizedString(@"MESSAGE_COMPOSEVIEW_TITLE", @""); -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Make sure we have requested contact access at this point if, e.g. - // the user has no messages in their inbox and they choose to compose - // a message. - [self.contactsManager requestSystemContactsOnce]; - - [self showContactAppropriateViews]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - [OWSAlerts showIOSUpgradeNagIfNecessary]; -} - -#pragma mark - Table Contents - -- (void)updateTableContents -{ - OWSTableContents *contents = [OWSTableContents new]; - - if (self.isNoContactsModeActive) { - self.tableViewController.contents = contents; - return; - } - - __weak NewContactThreadViewController *weakSelf = self; - - // App is killed and restarted when the user changes their contact permissions, so need need to "observe" anything - // to re-render this. - if (self.contactsManager.isSystemContactsDenied) { - OWSTableItem *contactReminderItem = [OWSTableItem - itemWithCustomCellBlock:^{ - UITableViewCell *cell = [OWSTableItem newCell]; - - ReminderView *reminderView = [ReminderView - nagWithText:NSLocalizedString(@"COMPOSE_SCREEN_MISSING_CONTACTS_PERMISSION", - @"Multi-line label explaining why compose-screen contact picker is empty.") - tapAction:^{ - [[UIApplication sharedApplication] openSystemSettings]; - }]; - [cell.contentView addSubview:reminderView]; - [reminderView autoPinEdgesToSuperviewEdges]; - - cell.accessibilityIdentifier - = ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewContactThreadViewController, @"missing_contacts"); - - return cell; - } - customRowHeight:UITableViewAutomaticDimension - actionBlock:nil]; - - OWSTableSection *reminderSection = [OWSTableSection new]; - [reminderSection addItem:contactReminderItem]; - [contents addSection:reminderSection]; - } - - // Loki: Original code - // ======== -// OWSTableSection *staticSection = [OWSTableSection new]; -// -// // Find Non-Contacts by Phone Number -// [staticSection -// addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"NEW_CONVERSATION_FIND_BY_PHONE_NUMBER", -// @"A label the cell that lets you add a new member to a group.") -// accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( -// NewContactThreadViewController, @"find_by_phone") -// customRowHeight:UITableViewAutomaticDimension -// actionBlock:^{ -// NewNonContactConversationViewController *viewController = -// [NewNonContactConversationViewController new]; -// viewController.nonContactConversationDelegate = weakSelf; -// [weakSelf.navigationController pushViewController:viewController -// animated:YES]; -// }]]; -// -// if (self.contactsManager.isSystemContactsAuthorized) { -// // Invite Contacts -// [staticSection -// addItem:[OWSTableItem -// disclosureItemWithText:NSLocalizedString(@"INVITE_FRIENDS_CONTACT_TABLE_BUTTON", -// @"Label for the cell that presents the 'invite contacts' workflow.") -// accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( -// NewContactThreadViewController, @"invite_contacts") -// customRowHeight:UITableViewAutomaticDimension -// actionBlock:^{ -// [weakSelf presentInviteFlow]; -// }]]; -// } -// [contents addSection:staticSection]; - // ======== - - BOOL hasSearchText = self.searchText.length > 0; - - if (hasSearchText) { - // Loki: - // ======== - NSString *publicKey = self.searchBar.text; - BOOL isValid = [ECKeyPair isValidHexEncodedPublicKeyWithCandidate:publicKey]; - if (!isValid) { - OWSTableSection *invalidPublicKeySection = [OWSTableSection new]; - [invalidPublicKeySection - addItem:[OWSTableItem softCenterLabelItemWithText:NSLocalizedString(@"Invalid public key", @"") - customRowHeight:UITableViewAutomaticDimension]]; - [contents addSection:invalidPublicKeySection]; - } else { - OWSTableSection *newConversationSection = [OWSTableSection new]; - [newConversationSection - addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"Start a Conversation", @"") - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"start_conversation") - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf newConversationWithRecipientId:publicKey]; - }]]; - [contents addSection:newConversationSection]; - } - // ======== - for (OWSTableSection *section in [self contactsSectionsForSearch]) { - [contents addSection:section]; - } - } else { - // Count the none collated sections, before we add our collated sections. - // Later we'll need to offset which sections our collation indexes reference - // by this amount. e.g. otherwise the "B" index will reference names starting with "A" - // And the "A" index will reference the static non-collated section(s). - NSInteger noncollatedSections = (NSInteger)contents.sections.count; - for (OWSTableSection *section in [self collatedContactsSections]) { - [contents addSection:section]; - } - contents.sectionForSectionIndexTitleBlock = ^NSInteger(NSString *_Nonnull title, NSInteger index) { - typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return 0; - } - - // Offset the collation section to account for the noncollated sections. - NSInteger sectionIndex = - [strongSelf.collation sectionForSectionIndexTitleAtIndex:index] + noncollatedSections; - if (sectionIndex < 0) { - // Sentinal in case we change our section ordering in a surprising way. - OWSCFailDebug(@"Unexpected negative section index"); - return 0; - } - if (sectionIndex >= (NSInteger)contents.sections.count) { - // Sentinal in case we change our section ordering in a surprising way. - OWSCFailDebug(@"Unexpectedly large index"); - return 0; - } - - return sectionIndex; - }; - /** Loki: Original code - contents.sectionIndexTitlesForTableViewBlock = ^NSArray *_Nonnull - { - typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return @[]; - } - - return strongSelf.collation.sectionTitles; - }; - */ - } - - self.tableViewController.contents = contents; -} - -- (NSArray *)collatedContactsSections -{ - if (self.contactsViewHelper.signalAccounts.count < 1) { - // No Contacts - OWSTableSection *contactsSection = [OWSTableSection new]; - - // Loki: Original code - // ======== -// if (self.contactsManager.isSystemContactsAuthorized) { - // ======== - if (self.contactsViewHelper.hasUpdatedContactsAtLeastOnce) { - - /** - * Loki: Original code - * ======== - [contactsSection - addItem:[OWSTableItem softCenterLabelItemWithText: - NSLocalizedString(@"SETTINGS_BLOCK_LIST_NO_CONTACTS", - @"A label that indicates the user has no Signal contacts.") - customRowHeight:UITableViewAutomaticDimension]]; - * ======== - */ - } else { - UITableViewCell *loadingCell = [OWSTableItem newCell]; - OWSAssertDebug(loadingCell.contentView); - - UIActivityIndicatorView *activityIndicatorView = - [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - [loadingCell.contentView addSubview:activityIndicatorView]; - [activityIndicatorView startAnimating]; - - [activityIndicatorView autoCenterInSuperview]; - [activityIndicatorView setCompressionResistanceHigh]; - [activityIndicatorView setContentHuggingHigh]; - - // hide separator for loading cell. The loading cell doesn't really feel like a cell - loadingCell.backgroundView = [UIView new]; - - loadingCell.accessibilityIdentifier - = ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewContactThreadViewController, @"loading"); - - OWSTableItem *loadingItem = - [OWSTableItem itemWithCustomCell:loadingCell customRowHeight:40 actionBlock:nil]; - [contactsSection addItem:loadingItem]; - } - // Loki: Original code - // ======== -// } - // ======== - - return @[ contactsSection ]; - } - __weak NewContactThreadViewController *weakSelf = self; - - NSMutableArray *contactSections = [NSMutableArray new]; - - NSMutableArray *> *collatedSignalAccounts = [NSMutableArray new]; - for (NSUInteger i = 0; i < self.collation.sectionTitles.count; i++) { - collatedSignalAccounts[i] = [NSMutableArray new]; - } - for (SignalAccount *signalAccount in self.contactsViewHelper.signalAccounts) { - NSInteger section = - [self.collation sectionForObject:signalAccount collationStringSelector:@selector(stringForCollation)]; - - if (section < 0) { - OWSFailDebug(@"Unexpected collation for name:%@", signalAccount.stringForCollation); - continue; - } - NSUInteger sectionIndex = (NSUInteger)section; - - [collatedSignalAccounts[sectionIndex] addObject:signalAccount]; - } - - for (NSUInteger i = 0; i < collatedSignalAccounts.count; i++) { - NSArray *signalAccounts = collatedSignalAccounts[i]; - NSMutableArray *contactItems = [NSMutableArray new]; - for (SignalAccount *signalAccount in signalAccounts) { - [contactItems addObject:[OWSTableItem - itemWithCustomCellBlock:^{ - ContactTableViewCell *cell = [ContactTableViewCell new]; - BOOL isBlocked = [self.contactsViewHelper - isRecipientIdBlocked:signalAccount.recipientId]; - 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( - NewContactThreadViewController, cellName); - - return cell; - } - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf newConversationWithRecipientId:signalAccount.recipientId]; - }]]; - } - - // Don't show empty sections. - // To accomplish this we add a section with a blank title rather than omitting the section altogether, - // in order for section indexes to match up correctly - NSString *sectionTitle = contactItems.count > 0 ? self.collation.sectionTitles[i] : nil; - [contactSections addObject:[OWSTableSection sectionWithTitle:sectionTitle items:contactItems]]; - } - - return [contactSections copy]; -} - -- (NSArray *)contactsSectionsForSearch -{ - __weak NewContactThreadViewController *weakSelf = self; - - NSMutableArray *sections = [NSMutableArray new]; - - ContactsViewHelper *helper = self.contactsViewHelper; - - OWSTableSection *phoneNumbersSection = [OWSTableSection new]; - // FIXME we should make sure "invite via SMS" cells appear *below* any matching signal-account cells. - // - // If the search string looks like a phone number, show either "new conversation..." cells and/or - // "invite via SMS..." cells. - NSArray *searchPhoneNumbers = [self parsePossibleSearchPhoneNumbers]; - for (NSString *phoneNumber in searchPhoneNumbers) { - OWSAssertDebug(phoneNumber.length > 0); - - if ([self.nonContactAccountSet containsObject:phoneNumber]) { - [phoneNumbersSection - addItem:[OWSTableItem - itemWithCustomCellBlock:^{ - ContactTableViewCell *cell = [ContactTableViewCell new]; - BOOL isBlocked = [helper isRecipientIdBlocked:phoneNumber]; - if (isBlocked) { - cell.accessoryMessage = NSLocalizedString( - @"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); - } - [cell configureWithRecipientId:phoneNumber]; - - NSString *cellName = [NSString stringWithFormat:@"non_signal_contact.%@", phoneNumber]; - cell.accessibilityIdentifier - = ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewContactThreadViewController, cellName); - - return cell; - } - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf newConversationWithRecipientId:phoneNumber]; - }]]; - } else { - NSString *text = [NSString stringWithFormat:NSLocalizedString(@"SEND_INVITE_VIA_SMS_BUTTON_FORMAT", - @"Text for button to send a Signal invite via SMS. %@ is " - @"placeholder for the recipient's phone number."), - phoneNumber]; - [phoneNumbersSection - addItem:[OWSTableItem disclosureItemWithText:text - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"invite_via_sms") - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf sendTextToPhoneNumber:phoneNumber]; - }]]; - } - } - if (searchPhoneNumbers.count > 0) { - [sections addObject:phoneNumbersSection]; - } - - // Contacts, filtered with the search text. - NSArray *filteredSignalAccounts = [self filteredSignalAccounts]; - BOOL hasSearchResults = NO; - - OWSTableSection *contactsSection = [OWSTableSection new]; - contactsSection.headerTitle = NSLocalizedString(@"COMPOSE_MESSAGE_CONTACT_SECTION_TITLE", - @"Table section header for contact listing when composing a new message"); - for (SignalAccount *signalAccount in filteredSignalAccounts) { - hasSearchResults = YES; - - if ([searchPhoneNumbers containsObject:signalAccount.recipientId]) { - // Don't show a contact if they already appear in the "search phone numbers" - // results. - continue; - } - [contactsSection - addItem:[OWSTableItem - itemWithCustomCellBlock:^{ - ContactTableViewCell *cell = [ContactTableViewCell new]; - BOOL isBlocked = [helper isRecipientIdBlocked:signalAccount.recipientId]; - 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(NewContactThreadViewController, cellName); - - return cell; - } - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf newConversationWithRecipientId:signalAccount.recipientId]; - }]]; - } - if (filteredSignalAccounts.count > 0) { - [sections addObject:contactsSection]; - } - - // When searching, we include matching groups - OWSTableSection *groupSection = [OWSTableSection new]; - groupSection.headerTitle = NSLocalizedString( - @"COMPOSE_MESSAGE_GROUP_SECTION_TITLE", @"Table section header for group listing when composing a new message"); - NSArray *filteredGroupThreads = [self filteredGroupThreads]; - for (TSGroupThread *thread in filteredGroupThreads) { - hasSearchResults = YES; - - [groupSection addItem:[OWSTableItem - itemWithCustomCellBlock:^{ - GroupTableViewCell *cell = [GroupTableViewCell new]; - [cell configureWithThread:thread]; - - // TODO: We need to verify that UUIDs will work for Nancy. - NSString *cellName = - [NSString stringWithFormat:@"group.%@", NSUUID.UUID.UUIDString]; - cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME( - NewContactThreadViewController, cellName); - - return cell; - } - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf newConversationWithThread:thread]; - }]]; - } - if (filteredGroupThreads.count > 0) { - [sections addObject:groupSection]; - } - - // Invitation offers for non-signal contacts - OWSTableSection *inviteeSection = [OWSTableSection new]; - inviteeSection.headerTitle = NSLocalizedString(@"COMPOSE_MESSAGE_INVITE_SECTION_TITLE", - @"Table section header for invite listing when composing a new message"); - NSArray *invitees = [helper nonSignalContactsMatchingSearchString:[self.searchBar text]]; - for (Contact *contact in invitees) { - hasSearchResults = YES; - - OWSAssertDebug(contact.parsedPhoneNumbers.count > 0); - // TODO: Should we invite all of their phone numbers? - PhoneNumber *phoneNumber = contact.parsedPhoneNumbers[0]; - NSString *displayName = contact.fullName; - if (displayName.length < 1) { - displayName = phoneNumber.toE164; - } - - NSString *text = [NSString stringWithFormat:NSLocalizedString(@"SEND_INVITE_VIA_SMS_BUTTON_FORMAT", - @"Text for button to send a Signal invite via SMS. %@ is " - @"placeholder for the recipient's phone number."), - displayName]; - NSString *accessibilityIdentifier = [NSString stringWithFormat:@"invite_via_sms.%@", phoneNumber.toE164]; - [inviteeSection addItem:[OWSTableItem disclosureItemWithText:text - accessibilityIdentifier:accessibilityIdentifier - customRowHeight:UITableViewAutomaticDimension - actionBlock:^{ - [weakSelf sendTextToPhoneNumber:phoneNumber.toE164]; - }]]; - } - if (invitees.count > 0) { - [sections addObject:inviteeSection]; - } - - /** - * Loki: Original code - * ======== - if (isValidPublicKey && !hasSearchResults) { - // No Search Results - OWSTableSection *noResultsSection = [OWSTableSection new]; - [noResultsSection - addItem:[OWSTableItem softCenterLabelItemWithText: - NSLocalizedString(@"No search results", - @"A label that indicates the user's search has no matching results.") - customRowHeight:UITableViewAutomaticDimension]]; - - [sections addObject:noResultsSection]; - } - * ======== - */ - - return [sections copy]; -} - -- (NSArray *)filteredSignalAccounts -{ - return self.searchResults.signalAccounts; -} - -- (NSArray *)filteredGroupThreads -{ - return self.searchResults.groupThreads; -} - -#pragma mark - No Contacts Mode - -- (void)hideBackgroundView -{ - [Environment.shared.preferences setHasDeclinedNoContactsView:YES]; - - [self showContactAppropriateViews]; -} - -- (void)presentInviteFlow -{ - OWSInviteFlow *inviteFlow = [[OWSInviteFlow alloc] initWithPresentingViewController:self]; - [self presentViewController:inviteFlow.actionSheetController animated:YES completion:nil]; -} - -- (void)showContactAppropriateViews -{ - // Loki: Original code - // ======== -// if (self.contactsManager.isSystemContactsAuthorized) { - // ======== - if (self.contactsViewHelper.hasUpdatedContactsAtLeastOnce && self.contactsViewHelper.signalAccounts.count < 1 - && ![Environment.shared.preferences hasDeclinedNoContactsView]) { - self.isNoContactsModeActive = YES; - } else { - self.isNoContactsModeActive = NO; - } - - [self showSearchBar:YES]; - // Loki: Original code - // ======== -// } else { -// // don't show "no signal contacts", show "no contact access" -// self.isNoContactsModeActive = NO; -// [self showSearchBar:NO]; -// } - // ======== -} - -- (void)setIsNoContactsModeActive:(BOOL)isNoContactsModeActive -{ - if (isNoContactsModeActive == _isNoContactsModeActive) { - return; - } - - _isNoContactsModeActive = NO; - -// if (isNoContactsModeActive) { -// self.tableViewController.tableView.hidden = YES; -// self.searchBar.hidden = YES; -// self.noSignalContactsView.hidden = NO; -// } else { - self.tableViewController.tableView.hidden = NO; - self.searchBar.hidden = NO; - self.noSignalContactsView.hidden = YES; -// } - - [self updateTableContents]; -} - -#pragma mark - Send Invite By SMS - -- (void)sendTextToPhoneNumber:(NSString *)phoneNumber -{ - OWSInviteFlow *inviteFlow = [[OWSInviteFlow alloc] initWithPresentingViewController:self]; - - OWSAssertDebug([phoneNumber length] > 0); - NSString *confirmMessage = NSLocalizedString(@"SEND_SMS_CONFIRM_TITLE", @""); - if ([phoneNumber length] > 0) { - confirmMessage = [[NSLocalizedString(@"SEND_SMS_INVITE_TITLE", @"") stringByAppendingString:phoneNumber] - stringByAppendingString:NSLocalizedString(@"QUESTIONMARK_PUNCTUATION", @"")]; - } - - UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"CONFIRMATION_TITLE", @"") - message:confirmMessage - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction *okAction = [UIAlertAction - actionWithTitle:NSLocalizedString(@"OK", @"") - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"ok") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *action) { - [self.searchBar resignFirstResponder]; - if ([MFMessageComposeViewController canSendText]) { - [inviteFlow sendSMSToPhoneNumbers:@[ phoneNumber ]]; - } else { - [OWSAlerts - showErrorAlertWithMessage:NSLocalizedString(@"UNSUPPORTED_FEATURE_ERROR", @"")]; - } - }]; - - [alert addAction:[OWSAlerts cancelAction]]; - [alert addAction:okAction]; - self.searchBar.text = @""; - [self searchTextDidChange]; - - // must dismiss search controller before presenting alert. - if ([self presentedViewController]) { - [self dismissViewControllerAnimated:YES - completion:^{ - [self presentAlert:alert]; - }]; - } else { - [self presentAlert:alert]; - } -} - -#pragma mark - SMS Composer Delegate - -// called on completion of message screen -- (void)messageComposeViewController:(MFMessageComposeViewController *)controller - didFinishWithResult:(MessageComposeResult)result -{ - switch (result) { - case MessageComposeResultCancelled: - break; - case MessageComposeResultFailed: { - [OWSAlerts showErrorAlertWithMessage:NSLocalizedString(@"SEND_INVITE_FAILURE", @"")]; - break; - } - case MessageComposeResultSent: { - [self dismissViewControllerAnimated:NO - completion:^{ - OWSLogDebug(@"view controller dismissed"); - }]; - [OWSAlerts - showAlertWithTitle:NSLocalizedString(@"SEND_INVITE_SUCCESS", @"Alert body after invite succeeded")]; - break; - } - default: - break; - } - - [self dismissViewControllerAnimated:YES completion:nil]; -} - -#pragma mark - Methods - -- (void)dismissPressed -{ - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)newConversationWithRecipientId:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId]; - [self newConversationWithThread:thread]; -} - -- (void)newConversationWithThread:(TSThread *)thread -{ - OWSAssertDebug(thread != nil); - [SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO]; - [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)showNewGroupView:(id)sender -{ - NewGroupViewController *newGroupViewController = [NewGroupViewController new]; - [self.navigationController pushViewController:newGroupViewController animated:YES]; -} - -#pragma mark - OWSTableViewControllerDelegate - -- (void)tableViewWillBeginDragging -{ - [self.searchBar resignFirstResponder]; -} - -#pragma mark - ContactsViewHelperDelegate - -- (void)contactsViewHelperDidUpdateContacts -{ - [self updateTableContents]; - - [self showContactAppropriateViews]; -} - -- (BOOL)shouldHideLocalNumber -{ - return NO; -} - -#pragma mark - NewNonContactConversationViewControllerDelegate - -- (void)recipientIdWasSelected:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - - [self newConversationWithRecipientId:recipientId]; -} - -#pragma mark - UISearchBarDelegate - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText -{ - [BenchManager startEventWithTitle:@"Compose Search" eventId:@"Compose Search"]; - [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.searchText; - - __weak __typeof(self) weakSelf = self; - - [self.uiDatabaseConnection - asyncReadWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - self.searchResults = [self.fullTextSearcher searchForComposeScreenWithSearchText:searchText - transaction:transaction - contactsManager:self.contactsManager]; - } - completionBlock:^{ - __typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - [strongSelf updateSearchPhoneNumbers]; - [strongSelf updateTableContents]; - [BenchManager completeEventWithEventId:@"Compose Search"]; - }]; -} - -#pragma mark - - -- (NSDictionary *)callingCodesToCountryCodeMap -{ - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSMutableDictionary *map = [NSMutableDictionary new]; - for (NSString *countryCode in [PhoneNumberUtil countryCodesForSearchTerm:nil]) { - OWSAssertDebug(countryCode.length > 0); - NSString *callingCode = [PhoneNumberUtil callingCodeFromCountryCode:countryCode]; - OWSAssertDebug(callingCode.length > 0); - OWSAssertDebug([callingCode hasPrefix:@"+"]); - OWSAssertDebug(![callingCode isEqualToString:@"+0"]); - - map[callingCode] = countryCode; - } - result = [map copy]; - }); - return result; -} - -- (nullable NSString *)callingCodeForPossiblePhoneNumber:(NSString *)phoneNumber -{ - OWSAssertDebug([phoneNumber hasPrefix:@"+"]); - - for (NSString *callingCode in [self callingCodesToCountryCodeMap].allKeys) { - if ([phoneNumber hasPrefix:callingCode]) { - return callingCode; - } - } - return nil; -} - -- (NSString *)searchText -{ - NSString *rawText = self.searchBar.text; - return rawText.ows_stripped ?: @""; -} - -- (NSArray *)parsePossibleSearchPhoneNumbers -{ - NSString *searchText = self.searchText; - - if (searchText.length < 8) { - return @[]; - } - - NSMutableSet *parsedPhoneNumbers = [NSMutableSet new]; - for (PhoneNumber *phoneNumber in - [PhoneNumber tryParsePhoneNumbersFromsUserSpecifiedText:searchText - clientPhoneNumber:[TSAccountManager localNumber]]) { - - NSString *phoneNumberString = phoneNumber.toE164; - - // Ignore phone numbers with an unrecognized calling code. - NSString *_Nullable callingCode = [self callingCodeForPossiblePhoneNumber:phoneNumberString]; - if (!callingCode) { - continue; - } - - // Ignore phone numbers which are too long. - NSString *phoneNumberWithoutCallingCode = [phoneNumberString substringFromIndex:callingCode.length]; - if (phoneNumberWithoutCallingCode.length < 1 || phoneNumberWithoutCallingCode.length > 15) { - continue; - } - [parsedPhoneNumbers addObject:phoneNumberString]; - } - - return [parsedPhoneNumbers.allObjects sortedArrayUsingSelector:@selector(compare:)]; -} - -- (void)updateSearchPhoneNumbers -{ - [self checkForAccountsForPhoneNumbers:[self parsePossibleSearchPhoneNumbers]]; -} - -- (void)checkForAccountsForPhoneNumbers:(NSArray *)phoneNumbers -{ - NSMutableArray *unknownPhoneNumbers = [NSMutableArray new]; - for (NSString *phoneNumber in phoneNumbers) { - if (![self.nonContactAccountSet containsObject:phoneNumber]) { - [unknownPhoneNumbers addObject:phoneNumber]; - } - } - if ([unknownPhoneNumbers count] < 1) { - return; - } - - __weak NewContactThreadViewController *weakSelf = self; - [[ContactsUpdater sharedUpdater] lookupIdentifiers:unknownPhoneNumbers - success:^(NSArray *recipients) { - [weakSelf updateNonContactAccountSet:recipients]; - } - failure:^(NSError *error){ - // Ignore. - }]; -} - -- (void)updateNonContactAccountSet:(NSArray *)recipients -{ - BOOL didUpdate = NO; - for (SignalRecipient *recipient in recipients) { - if ([self.nonContactAccountSet containsObject:recipient.recipientId]) { - continue; - } - [self.nonContactAccountSet addObject:recipient.recipientId]; - didUpdate = YES; - } - if (didUpdate) { - [self updateTableContents]; - } -} - -#pragma mark - Theme - -- (void)themeDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self applyTheme]; -} - -- (void)applyTheme -{ - OWSAssertIsOnMainThread(); - - self.view.backgroundColor = Theme.backgroundColor; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/NewGroupViewController.h b/Signal/src/ViewControllers/NewGroupViewController.h deleted file mode 100644 index a5dfa1b19..000000000 --- a/Signal/src/ViewControllers/NewGroupViewController.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NewGroupViewController : OWSViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m deleted file mode 100644 index 208f0b1d2..000000000 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ /dev/null @@ -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 -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NewGroupViewController () - -@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 *memberRecipientIds; -@property (nonatomic) NSMutableSet *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 *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 *recipientIds = [self.memberRecipientIds.allObjects mutableCopy]; - NSMutableArray *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 diff --git a/Signal/src/ViewControllers/OWS2FAReminderViewController.swift b/Signal/src/ViewControllers/OWS2FAReminderViewController.swift deleted file mode 100644 index 3bb8e61e8..000000000 --- a/Signal/src/ViewControllers/OWS2FAReminderViewController.swift +++ /dev/null @@ -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() - } -} diff --git a/Signal/src/ViewControllers/OWS2FASettingsViewController.h b/Signal/src/ViewControllers/OWS2FASettingsViewController.h deleted file mode 100644 index c5afc92ee..000000000 --- a/Signal/src/ViewControllers/OWS2FASettingsViewController.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -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 diff --git a/Signal/src/ViewControllers/OWS2FASettingsViewController.m b/Signal/src/ViewControllers/OWS2FASettingsViewController.m deleted file mode 100644 index e21fe0c63..000000000 --- a/Signal/src/ViewControllers/OWS2FASettingsViewController.m +++ /dev/null @@ -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 -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWS2FASettingsViewController () - -@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 diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 6a33d8f1d..218e805f8 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -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 diff --git a/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift b/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift deleted file mode 100644 index f33d1bda1..000000000 --- a/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift +++ /dev/null @@ -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 - } -} diff --git a/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift deleted file mode 100644 index c68ae4d25..000000000 --- a/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift +++ /dev/null @@ -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 - } -} diff --git a/Signal/src/ViewControllers/Registration/OnboardingCaptchaViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingCaptchaViewController.swift index 1c9ceec24..a9f76abcc 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingCaptchaViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingCaptchaViewController.swift @@ -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("") } } + */ + diff --git a/Signal/src/ViewControllers/Registration/OnboardingController.swift b/Signal/src/ViewControllers/Registration/OnboardingController.swift deleted file mode 100644 index d7ce7a46d..000000000 --- a/Signal/src/ViewControllers/Registration/OnboardingController.swift +++ /dev/null @@ -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 - } -} diff --git a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift index 7674485ae..fabf45256 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift @@ -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) } } + */ diff --git a/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift deleted file mode 100644 index 6d04d4861..000000000 --- a/Signal/src/ViewControllers/Registration/OnboardingProfileViewController.swift +++ /dev/null @@ -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") - } -} diff --git a/Signal/src/ViewControllers/Registration/OnboardingSplashViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingSplashViewController.swift deleted file mode 100644 index 8d1912bf1..000000000 --- a/Signal/src/ViewControllers/Registration/OnboardingSplashViewController.swift +++ /dev/null @@ -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) - } -} diff --git a/Signal/src/ViewControllers/Registration/OnboardingVerificationViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingVerificationViewController.swift deleted file mode 100644 index a4132a379..000000000 --- a/Signal/src/ViewControllers/Registration/OnboardingVerificationViewController.swift +++ /dev/null @@ -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.. (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.. 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() - } -} diff --git a/Signal/src/ViewControllers/Registration/RegistrationController.swift b/Signal/src/ViewControllers/Registration/RegistrationController.swift deleted file mode 100644 index a4b0d953e..000000000 --- a/Signal/src/ViewControllers/Registration/RegistrationController.swift +++ /dev/null @@ -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) -// } -} diff --git a/Signal/src/util/AppUpdateNag.swift b/Signal/src/util/AppUpdateNag.swift index f1c3cb4e9..6694966ec 100644 --- a/Signal/src/util/AppUpdateNag.swift +++ b/Signal/src/util/AppUpdateNag.swift @@ -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) { diff --git a/Signal/src/util/RegistrationUtils.h b/Signal/src/util/RegistrationUtils.h deleted file mode 100644 index 5777c22a8..000000000 --- a/Signal/src/util/RegistrationUtils.h +++ /dev/null @@ -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 diff --git a/Signal/src/util/RegistrationUtils.m b/Signal/src/util/RegistrationUtils.m deleted file mode 100644 index 8c0272d6a..000000000 --- a/Signal/src/util/RegistrationUtils.m +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "RegistrationUtils.h" -#import "OWSNavigationController.h" -#import "Session-Swift.h" -#import -#import -#import -#import - -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 diff --git a/SignalMessaging/SignalMessaging.h b/SignalMessaging/SignalMessaging.h index 092dcdeb1..624a38429 100644 --- a/SignalMessaging/SignalMessaging.h +++ b/SignalMessaging/SignalMessaging.h @@ -17,7 +17,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[]; #import #import #import -#import #import #import #import diff --git a/SignalMessaging/ViewControllers/CountryCodeViewController.h b/SignalMessaging/ViewControllers/CountryCodeViewController.h deleted file mode 100644 index 7f9a70352..000000000 --- a/SignalMessaging/ViewControllers/CountryCodeViewController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSTableViewController.h" - -NS_ASSUME_NONNULL_BEGIN - -@class CountryCodeViewController; - -@protocol CountryCodeViewControllerDelegate - -- (void)countryCodeViewController:(CountryCodeViewController *)vc - didSelectCountryCode:(NSString *)countryCode - countryName:(NSString *)countryName - callingCode:(NSString *)callingCode; - -@end - -#pragma mark - - -@interface CountryCodeViewController : OWSTableViewController - -@property (nonatomic, weak) id countryCodeDelegate; - -@property (nonatomic) BOOL isPresentedInNavigationController; - -@property (nonatomic) UIInterfaceOrientationMask interfaceOrientationMask; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/ViewControllers/CountryCodeViewController.m b/SignalMessaging/ViewControllers/CountryCodeViewController.m deleted file mode 100644 index 011114d9b..000000000 --- a/SignalMessaging/ViewControllers/CountryCodeViewController.m +++ /dev/null @@ -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 - -NS_ASSUME_NONNULL_BEGIN - -@interface CountryCodeViewController () - -@property (nonatomic, readonly) UISearchBar *searchBar; - -@property (nonatomic) NSArray *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 diff --git a/SignalMessaging/ViewControllers/SelectRecipientViewController.m b/SignalMessaging/ViewControllers/SelectRecipientViewController.m index 7147b216d..8020cca15 100644 --- a/SignalMessaging/ViewControllers/SelectRecipientViewController.m +++ b/SignalMessaging/ViewControllers/SelectRecipientViewController.m @@ -3,7 +3,6 @@ // #import "SelectRecipientViewController.h" -#import "CountryCodeViewController.h" #import "PhoneNumber.h" #import "ViewControllerUtils.h" #import @@ -26,7 +25,7 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien #pragma mark - -@interface SelectRecipientViewController () @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS100RemoveTSRecipientsMigration.h b/SignalMessaging/environment/migrations/OWS100RemoveTSRecipientsMigration.h deleted file mode 100644 index ba5006949..000000000 --- a/SignalMessaging/environment/migrations/OWS100RemoveTSRecipientsMigration.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS100RemoveTSRecipientsMigration.m b/SignalMessaging/environment/migrations/OWS100RemoveTSRecipientsMigration.m deleted file mode 100644 index 471da53fe..000000000 --- a/SignalMessaging/environment/migrations/OWS100RemoveTSRecipientsMigration.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS100RemoveTSRecipientsMigration.h" -#import - -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 diff --git a/SignalMessaging/environment/migrations/OWS101ExistingUsersBlockOnIdentityChange.h b/SignalMessaging/environment/migrations/OWS101ExistingUsersBlockOnIdentityChange.h deleted file mode 100644 index c63d20ca2..000000000 --- a/SignalMessaging/environment/migrations/OWS101ExistingUsersBlockOnIdentityChange.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS101ExistingUsersBlockOnIdentityChange.m b/SignalMessaging/environment/migrations/OWS101ExistingUsersBlockOnIdentityChange.m deleted file mode 100644 index 9e414b652..000000000 --- a/SignalMessaging/environment/migrations/OWS101ExistingUsersBlockOnIdentityChange.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS101ExistingUsersBlockOnIdentityChange.h" -#import - -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 diff --git a/SignalMessaging/environment/migrations/OWS102MoveLoggingPreferenceToUserDefaults.h b/SignalMessaging/environment/migrations/OWS102MoveLoggingPreferenceToUserDefaults.h deleted file mode 100644 index 02029a5ec..000000000 --- a/SignalMessaging/environment/migrations/OWS102MoveLoggingPreferenceToUserDefaults.h +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "OWSDatabaseMigration.h" - -@interface OWS102MoveLoggingPreferenceToUserDefaults : OWSDatabaseMigration - -@end diff --git a/SignalMessaging/environment/migrations/OWS102MoveLoggingPreferenceToUserDefaults.m b/SignalMessaging/environment/migrations/OWS102MoveLoggingPreferenceToUserDefaults.m deleted file mode 100644 index edfefa37a..000000000 --- a/SignalMessaging/environment/migrations/OWS102MoveLoggingPreferenceToUserDefaults.m +++ /dev/null @@ -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 - -// 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 diff --git a/SignalMessaging/environment/migrations/OWS103EnableVideoCalling.h b/SignalMessaging/environment/migrations/OWS103EnableVideoCalling.h deleted file mode 100644 index 9730100f5..000000000 --- a/SignalMessaging/environment/migrations/OWS103EnableVideoCalling.h +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "OWSDatabaseMigration.h" - -@interface OWS103EnableVideoCalling : OWSDatabaseMigration - -@end diff --git a/SignalMessaging/environment/migrations/OWS103EnableVideoCalling.m b/SignalMessaging/environment/migrations/OWS103EnableVideoCalling.m deleted file mode 100644 index ac570e586..000000000 --- a/SignalMessaging/environment/migrations/OWS103EnableVideoCalling.m +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS103EnableVideoCalling.h" -#import -#import -#import -#import - -// 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 diff --git a/SignalMessaging/environment/migrations/OWS104CreateRecipientIdentities.h b/SignalMessaging/environment/migrations/OWS104CreateRecipientIdentities.h deleted file mode 100644 index 46d1d6c2f..000000000 --- a/SignalMessaging/environment/migrations/OWS104CreateRecipientIdentities.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS104CreateRecipientIdentities.m b/SignalMessaging/environment/migrations/OWS104CreateRecipientIdentities.m deleted file mode 100644 index 007687325..000000000 --- a/SignalMessaging/environment/migrations/OWS104CreateRecipientIdentities.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS104CreateRecipientIdentities.h" -#import -#import -#import -#import - -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 *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 diff --git a/SignalMessaging/environment/migrations/OWS105AttachmentFilePaths.h b/SignalMessaging/environment/migrations/OWS105AttachmentFilePaths.h deleted file mode 100644 index a9fbfb6d1..000000000 --- a/SignalMessaging/environment/migrations/OWS105AttachmentFilePaths.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS105AttachmentFilePaths.m b/SignalMessaging/environment/migrations/OWS105AttachmentFilePaths.m deleted file mode 100644 index 54d22e1bf..000000000 --- a/SignalMessaging/environment/migrations/OWS105AttachmentFilePaths.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS105AttachmentFilePaths.h" -#import -#import - -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 *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 diff --git a/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift b/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift deleted file mode 100644 index cff73b023..000000000 --- a/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift +++ /dev/null @@ -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 { - 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 in - switch error { - case SignalServiceProfile.ValidationError.invalidIdentityKey(let description): - Logger.warn("detected incomplete profile for \(localRecipientId) error: \(description)") - - let (promise, resolver) = Promise.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 - } - } - } - } -} diff --git a/SignalMessaging/environment/migrations/OWS107LegacySounds.h b/SignalMessaging/environment/migrations/OWS107LegacySounds.h deleted file mode 100644 index f6e75f382..000000000 --- a/SignalMessaging/environment/migrations/OWS107LegacySounds.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS107LegacySounds.m b/SignalMessaging/environment/migrations/OWS107LegacySounds.m deleted file mode 100644 index 00314d0fd..000000000 --- a/SignalMessaging/environment/migrations/OWS107LegacySounds.m +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS107LegacySounds.h" -#import "OWSSounds.h" -#import - -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 diff --git a/SignalMessaging/environment/migrations/OWS108CallLoggingPreference.h b/SignalMessaging/environment/migrations/OWS108CallLoggingPreference.h deleted file mode 100644 index dce3b59e7..000000000 --- a/SignalMessaging/environment/migrations/OWS108CallLoggingPreference.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS108CallLoggingPreference.m b/SignalMessaging/environment/migrations/OWS108CallLoggingPreference.m deleted file mode 100644 index 1a479fe0d..000000000 --- a/SignalMessaging/environment/migrations/OWS108CallLoggingPreference.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS108CallLoggingPreference.h" -#import "Environment.h" -#import "OWSPreferences.h" -#import - -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 diff --git a/SignalMessaging/environment/migrations/OWS109OutgoingMessageState.h b/SignalMessaging/environment/migrations/OWS109OutgoingMessageState.h deleted file mode 100644 index 6b6b23c5e..000000000 --- a/SignalMessaging/environment/migrations/OWS109OutgoingMessageState.h +++ /dev/null @@ -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 diff --git a/SignalMessaging/environment/migrations/OWS109OutgoingMessageState.m b/SignalMessaging/environment/migrations/OWS109OutgoingMessageState.m deleted file mode 100644 index 602152ee3..000000000 --- a/SignalMessaging/environment/migrations/OWS109OutgoingMessageState.m +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS109OutgoingMessageState.h" -#import -#import -#import - -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 diff --git a/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift b/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift deleted file mode 100644 index a433860f1..000000000 --- a/SignalMessaging/environment/migrations/OWS110SortIdMigration.swift +++ /dev/null @@ -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 = 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() - } - -} diff --git a/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift b/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift deleted file mode 100644 index 631159ff8..000000000 --- a/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift +++ /dev/null @@ -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) - } - } -} diff --git a/SignalMessaging/environment/migrations/OWS112TypingIndicatorsMigration.swift b/SignalMessaging/environment/migrations/OWS112TypingIndicatorsMigration.swift deleted file mode 100644 index 671c5c3ae..000000000 --- a/SignalMessaging/environment/migrations/OWS112TypingIndicatorsMigration.swift +++ /dev/null @@ -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() - } - } - } -} diff --git a/SignalMessaging/environment/migrations/OWS113MultiAttachmentMediaMessages.swift b/SignalMessaging/environment/migrations/OWS113MultiAttachmentMediaMessages.swift deleted file mode 100644 index 601a1d54d..000000000 --- a/SignalMessaging/environment/migrations/OWS113MultiAttachmentMediaMessages.swift +++ /dev/null @@ -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() - } - } -} diff --git a/SignalMessaging/environment/migrations/OWS114RemoveDynamicInteractions.swift b/SignalMessaging/environment/migrations/OWS114RemoveDynamicInteractions.swift deleted file mode 100644 index a953d4958..000000000 --- a/SignalMessaging/environment/migrations/OWS114RemoveDynamicInteractions.swift +++ /dev/null @@ -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) 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() - } - } -} diff --git a/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m b/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m index a6dd79442..e1ec293e8 100644 --- a/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m +++ b/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m @@ -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 #import @@ -34,20 +26,6 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)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] ]; }