mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
commit
4742606343
70 changed files with 44 additions and 10978 deletions
|
@ -34,9 +34,7 @@
|
|||
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
|
||||
340FC8AB204DAC8D007AEB0F /* DomainFrontingCountryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87D204DAC8C007AEB0F /* DomainFrontingCountryViewController.m */; };
|
||||
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */; };
|
||||
340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */; };
|
||||
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */; };
|
||||
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */; };
|
||||
340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */; };
|
||||
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC887204DAC8C007AEB0F /* BlockListViewController.m */; };
|
||||
340FC8B2204DAC8D007AEB0F /* AdvancedSettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC88C204DAC8C007AEB0F /* AdvancedSettingsTableViewController.m */; };
|
||||
|
@ -69,8 +67,6 @@
|
|||
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
|
||||
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
|
||||
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A4D207D0C01009F5D9C /* HomeViewController.m */; };
|
||||
34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34386A50207D0C01009F5D9C /* HomeViewCell.m */; };
|
||||
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
|
||||
343A65951FC47D5E000477A1 /* DebugUISyncMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */; };
|
||||
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */; };
|
||||
|
@ -89,12 +85,8 @@
|
|||
34480B681FD0AA9400BC14EF /* UIFont+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B661FD0AA9400BC14EF /* UIFont+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */; };
|
||||
3448E15C22133274004B052E /* OnboardingPermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */; };
|
||||
3448E15E221333F5004B052E /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15D221333F5004B052E /* OnboardingController.swift */; };
|
||||
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */; };
|
||||
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */; };
|
||||
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */; };
|
||||
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; };
|
||||
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */; };
|
||||
3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; };
|
||||
346129391FD1B47300532771 /* OWSPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129371FD1B47200532771 /* OWSPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3461293A1FD1B47300532771 /* OWSPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129381FD1B47200532771 /* OWSPreferences.m */; };
|
||||
|
@ -123,19 +115,6 @@
|
|||
346129E31FD5C0BE00532771 /* VersionMigrations.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129E11FD5C0BE00532771 /* VersionMigrations.m */; };
|
||||
346129E61FD5C0C600532771 /* OWSDatabaseMigrationRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129E41FD5C0C600532771 /* OWSDatabaseMigrationRunner.m */; };
|
||||
346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */; };
|
||||
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */; };
|
||||
346129F61FD5F31400532771 /* OWS103EnableVideoCalling.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */; };
|
||||
346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */; };
|
||||
346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */; };
|
||||
346129F91FD5F31400532771 /* OWS104CreateRecipientIdentities.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */; };
|
||||
346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */; };
|
||||
346129FB1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */; };
|
||||
346129FC1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */; };
|
||||
346129FD1FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */; };
|
||||
346129FE1FD5F31400532771 /* OWS106EnsureProfileComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */; };
|
||||
346129FF1FD5F31400532771 /* OWS103EnableVideoCalling.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */; };
|
||||
34612A001FD5F31400532771 /* OWS105AttachmentFilePaths.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */; };
|
||||
34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */; };
|
||||
34612A061FD7238600532771 /* OWSSyncManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 34612A041FD7238500532771 /* OWSSyncManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34612A071FD7238600532771 /* OWSSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 34612A051FD7238500532771 /* OWSSyncManager.m */; };
|
||||
34641E182088D7E900E2EDE5 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */; };
|
||||
|
@ -148,7 +127,6 @@
|
|||
346941A4215D2EE400B5BFAD /* OWSConversationColor.h in Headers */ = {isa = PBXBuildFile; fileRef = 346941A0215D2EE400B5BFAD /* OWSConversationColor.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
|
||||
346E35BE224283B100E55D5F /* UIAlertController+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */; };
|
||||
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E9D5321B040B600562252 /* RegistrationController.swift */; };
|
||||
347850311FD7494A007B8332 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
|
||||
347850321FD7494A007B8332 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
|
||||
347850331FD7494A007B8332 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
|
||||
|
@ -180,11 +158,7 @@
|
|||
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956921A301A100DCFE74 /* OWSBackup.m */; };
|
||||
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956A21A301A100DCFE74 /* OWSBackupJob.m */; };
|
||||
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; };
|
||||
349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */; };
|
||||
349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */; };
|
||||
349ED992221EE80D008045B0 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349ED991221EE80D008045B0 /* AppPreferences.swift */; };
|
||||
34A4C61E221613D00042EF2E /* OnboardingVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */; };
|
||||
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */; };
|
||||
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; };
|
||||
34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */; };
|
||||
34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */; };
|
||||
|
@ -214,8 +188,6 @@
|
|||
34AC09F4211B39B100997B47 /* SelectThreadViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09D6211B39B100997B47 /* SelectThreadViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34AC09F5211B39B100997B47 /* SharingThreadPickerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09D7211B39B100997B47 /* SharingThreadPickerViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34AC09F7211B39B100997B47 /* MediaMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34AC09D9211B39B100997B47 /* MediaMessageView.swift */; };
|
||||
34AC09F8211B39B100997B47 /* CountryCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34AC09DA211B39B100997B47 /* CountryCodeViewController.m */; };
|
||||
34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09DB211B39B100997B47 /* CountryCodeViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34AC09FA211B39B100997B47 /* SharingThreadPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */; };
|
||||
34AC0A0E211B39EA00997B47 /* ContactsViewHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09FB211B39E700997B47 /* ContactsViewHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34AC0A0F211B39EA00997B47 /* ContactTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 34AC09FC211B39E700997B47 /* ContactTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -240,15 +212,11 @@
|
|||
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B0796B1FCF46B000E248C2 /* MainAppContext.m */; };
|
||||
34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */; };
|
||||
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */; };
|
||||
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */; };
|
||||
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */; };
|
||||
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */; };
|
||||
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */; };
|
||||
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */; };
|
||||
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */; };
|
||||
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */; };
|
||||
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */; };
|
||||
34B6A909218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */; };
|
||||
34B6A90B218BA1D1007C4606 /* typing-animation.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34B6A90A218BA1D0007C4606 /* typing-animation.gif */; };
|
||||
34B6D27420F664C900765BE2 /* OWSUnreadIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */; };
|
||||
|
@ -267,7 +235,6 @@
|
|||
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2A1F74C12700D7438D /* DebugUIStress.m */; };
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; };
|
||||
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */; };
|
||||
34BEDB0B21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */; };
|
||||
34BEDB0E21C405B0007B0EAE /* ImageEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB0D21C405B0007B0EAE /* ImageEditorModel.swift */; };
|
||||
34BEDB1321C43F6A007B0EAE /* ImageEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BEDB1221C43F69007B0EAE /* ImageEditorView.swift */; };
|
||||
34BEDB1621C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 34BEDB1421C80BC9007B0EAE /* OWSAnyTouchGestureRecognizer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -306,8 +273,6 @@
|
|||
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; };
|
||||
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */; };
|
||||
34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */; };
|
||||
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */; };
|
||||
34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */ = {isa = PBXBuildFile; fileRef = 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */; };
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
|
||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
|
||||
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
|
||||
|
@ -323,7 +288,6 @@
|
|||
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
|
||||
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; };
|
||||
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; };
|
||||
34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E5DC8120D8050D00C08145 /* RegistrationUtils.m */; };
|
||||
34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E88D252098C5AE00A608F4 /* ContactViewController.swift */; };
|
||||
34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */; };
|
||||
34EA69402194933900702471 /* MediaDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EA693F2194933900702471 /* MediaDownloadView.swift */; };
|
||||
|
@ -333,8 +297,6 @@
|
|||
390650A6D345BFE01E006DB0 /* Pods_LokiPushNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04912E453971FB16E5E78EC6 /* Pods_LokiPushNotificationService.framework */; };
|
||||
4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */; };
|
||||
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BC20470A5B00CEE724 /* classic.aifc */; };
|
||||
4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */; };
|
||||
4503F1C4204711D300CEE724 /* OWS107LegacySounds.h in Headers */ = {isa = PBXBuildFile; fileRef = 4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */; };
|
||||
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */; };
|
||||
450998651FD8A34D00D89EB3 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; };
|
||||
450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */; };
|
||||
|
@ -411,8 +373,6 @@
|
|||
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */; };
|
||||
458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38391D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m */; };
|
||||
459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */; };
|
||||
4598198E204E2F28009414F2 /* OWS108CallLoggingPreference.h in Headers */ = {isa = PBXBuildFile; fileRef = 4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */; };
|
||||
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */ = {isa = PBXBuildFile; fileRef = 4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */; };
|
||||
459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */; };
|
||||
459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
45A2F005204473A3002E978A /* NewMessage.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45A2F004204473A3002E978A /* NewMessage.aifc */; };
|
||||
|
@ -453,7 +413,6 @@
|
|||
45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */; };
|
||||
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */; };
|
||||
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D231761DC7E8F10034FA89 /* SessionResetJob.swift */; };
|
||||
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */; };
|
||||
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */ = {isa = PBXBuildFile; fileRef = 45D308AC2049A439000189E4 /* PinEntryView.m */; };
|
||||
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */; };
|
||||
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; };
|
||||
|
@ -473,11 +432,9 @@
|
|||
4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */; };
|
||||
4C04F58421C860C50090D0BB /* MantlePerfTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C04F58321C860C50090D0BB /* MantlePerfTest.swift */; };
|
||||
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; };
|
||||
4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */; };
|
||||
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
|
||||
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */; };
|
||||
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
|
||||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
|
||||
4C21D5D6223A9DC500EF8A77 /* UIAlerts+iOS9.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D5223A9DC500EF8A77 /* UIAlerts+iOS9.m */; };
|
||||
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */; };
|
||||
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */; };
|
||||
|
@ -488,13 +445,11 @@
|
|||
4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; };
|
||||
4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */; };
|
||||
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; };
|
||||
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */; };
|
||||
4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */; };
|
||||
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C618198219DF03A009BD6B5 /* OWSButton.swift */; };
|
||||
4C61819F219E1796009BD6B5 /* typing-animation-dark.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */; };
|
||||
4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; };
|
||||
4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; };
|
||||
4C7537892193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */; };
|
||||
4C858A52212DC5E1001B45D3 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */; };
|
||||
4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C948FF62146EB4800349F0D /* BlockListCache.swift */; };
|
||||
4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9CA25C217E676900607C63 /* ZXingObjC.framework */; };
|
||||
|
@ -504,7 +459,6 @@
|
|||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; };
|
||||
4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */; };
|
||||
4CBBCA6321714B4500EEB37D /* OWS110SortIdMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */; };
|
||||
4CC0B59C20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */; };
|
||||
4CC1ECF9211A47CE00CC13BE /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; };
|
||||
|
@ -775,10 +729,8 @@
|
|||
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupSettingsViewController.h; sourceTree = "<group>"; };
|
||||
340FC880204DAC8C007AEB0F /* AppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSettingsViewController.h; sourceTree = "<group>"; };
|
||||
340FC881204DAC8C007AEB0F /* AdvancedSettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AdvancedSettingsTableViewController.h; sourceTree = "<group>"; };
|
||||
340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkedDevicesTableViewController.m; sourceTree = "<group>"; };
|
||||
340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSoundSettingsViewController.m; sourceTree = "<group>"; };
|
||||
340FC884204DAC8C007AEB0F /* AboutTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutTableViewController.h; sourceTree = "<group>"; };
|
||||
340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkDeviceViewController.m; sourceTree = "<group>"; };
|
||||
340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = "<group>"; };
|
||||
340FC887204DAC8C007AEB0F /* BlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockListViewController.m; sourceTree = "<group>"; };
|
||||
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = "<group>"; };
|
||||
|
@ -790,11 +742,9 @@
|
|||
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupSettingsViewController.m; sourceTree = "<group>"; };
|
||||
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = "<group>"; };
|
||||
340FC890204DAC8C007AEB0F /* BlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockListViewController.h; sourceTree = "<group>"; };
|
||||
340FC891204DAC8C007AEB0F /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = "<group>"; };
|
||||
340FC892204DAC8C007AEB0F /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = "<group>"; };
|
||||
340FC893204DAC8C007AEB0F /* AboutTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutTableViewController.m; sourceTree = "<group>"; };
|
||||
340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSoundSettingsViewController.h; sourceTree = "<group>"; };
|
||||
340FC895204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = "<group>"; };
|
||||
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = "<group>"; };
|
||||
340FC898204DAC8D007AEB0F /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = "<group>"; };
|
||||
340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = "<group>"; };
|
||||
|
@ -836,10 +786,6 @@
|
|||
34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = "<group>"; };
|
||||
34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = "<group>"; };
|
||||
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
|
||||
34386A4D207D0C01009F5D9C /* HomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewController.m; sourceTree = "<group>"; };
|
||||
34386A4E207D0C01009F5D9C /* HomeViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewCell.h; sourceTree = "<group>"; };
|
||||
34386A4F207D0C01009F5D9C /* HomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeViewController.h; sourceTree = "<group>"; };
|
||||
34386A50207D0C01009F5D9C /* HomeViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeViewCell.m; sourceTree = "<group>"; };
|
||||
34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = "<group>"; };
|
||||
343A65931FC47D5D000477A1 /* DebugUISyncMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUISyncMessages.h; sourceTree = "<group>"; };
|
||||
343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUISyncMessages.m; sourceTree = "<group>"; };
|
||||
|
@ -866,14 +812,9 @@
|
|||
344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOrphanDataCleaner.h; sourceTree = "<group>"; };
|
||||
344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleaner.m; sourceTree = "<group>"; };
|
||||
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = "<group>"; };
|
||||
3448E15D221333F5004B052E /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = "<group>"; };
|
||||
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSplashViewController.swift; sourceTree = "<group>"; };
|
||||
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBaseViewController.swift; sourceTree = "<group>"; };
|
||||
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCaptchaViewController.swift; sourceTree = "<group>"; };
|
||||
34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
344F248C2007CCD600CFB4F4 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = "<group>"; };
|
||||
345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FASettingsViewController.h; sourceTree = "<group>"; };
|
||||
345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS2FASettingsViewController.m; sourceTree = "<group>"; };
|
||||
3461284A1FD0B93F00532771 /* SAELoadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAELoadViewController.swift; sourceTree = "<group>"; };
|
||||
346129371FD1B47200532771 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSPreferences.h; sourceTree = "<group>"; };
|
||||
346129381FD1B47200532771 /* OWSPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSPreferences.m; sourceTree = "<group>"; };
|
||||
|
@ -905,19 +846,6 @@
|
|||
346129E11FD5C0BE00532771 /* VersionMigrations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VersionMigrations.m; sourceTree = "<group>"; };
|
||||
346129E41FD5C0C600532771 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseMigrationRunner.m; sourceTree = "<group>"; };
|
||||
346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDatabaseMigrationRunner.h; sourceTree = "<group>"; };
|
||||
346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS102MoveLoggingPreferenceToUserDefaults.m; sourceTree = "<group>"; };
|
||||
346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS103EnableVideoCalling.h; sourceTree = "<group>"; };
|
||||
346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS105AttachmentFilePaths.m; sourceTree = "<group>"; };
|
||||
346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS100RemoveTSRecipientsMigration.m; sourceTree = "<group>"; };
|
||||
346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS104CreateRecipientIdentities.m; sourceTree = "<group>"; };
|
||||
346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS100RemoveTSRecipientsMigration.h; sourceTree = "<group>"; };
|
||||
346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS101ExistingUsersBlockOnIdentityChange.m; sourceTree = "<group>"; };
|
||||
346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS101ExistingUsersBlockOnIdentityChange.h; sourceTree = "<group>"; };
|
||||
346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS102MoveLoggingPreferenceToUserDefaults.h; sourceTree = "<group>"; };
|
||||
346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS106EnsureProfileComplete.swift; sourceTree = "<group>"; };
|
||||
346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS103EnableVideoCalling.m; sourceTree = "<group>"; };
|
||||
346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS105AttachmentFilePaths.h; sourceTree = "<group>"; };
|
||||
346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS104CreateRecipientIdentities.h; sourceTree = "<group>"; };
|
||||
34612A041FD7238500532771 /* OWSSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSyncManager.h; sourceTree = "<group>"; };
|
||||
34612A051FD7238500532771 /* OWSSyncManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSyncManager.m; sourceTree = "<group>"; };
|
||||
34641E1020878FAF00E2EDE5 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWindowManager.m; sourceTree = "<group>"; };
|
||||
|
@ -933,7 +861,6 @@
|
|||
346941A0215D2EE400B5BFAD /* OWSConversationColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationColor.h; sourceTree = "<group>"; };
|
||||
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
|
||||
346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+OWS.swift"; sourceTree = "<group>"; };
|
||||
346E9D5321B040B600562252 /* RegistrationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = "<group>"; };
|
||||
347850561FD86544007B8332 /* SAEFailedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEFailedViewController.swift; sourceTree = "<group>"; };
|
||||
3478505A1FD999D5007B8332 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = translations/et.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3478505C1FD99A1F007B8332 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = translations/zh_TW.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -973,11 +900,7 @@
|
|||
3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupAPI.swift; sourceTree = "<group>"; };
|
||||
3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportJob.h; sourceTree = "<group>"; };
|
||||
3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = "<group>"; };
|
||||
349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS111UDAttributesMigration.swift; sourceTree = "<group>"; };
|
||||
349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Onboarding2FAViewController.swift; sourceTree = "<group>"; };
|
||||
349ED991221EE80D008045B0 /* AppPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = "<group>"; };
|
||||
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingVerificationViewController.swift; sourceTree = "<group>"; };
|
||||
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingProfileViewController.swift; sourceTree = "<group>"; };
|
||||
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = "<group>"; };
|
||||
34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumCellView.swift; sourceTree = "<group>"; };
|
||||
34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSResaveCollectionDBMigration.m; sourceTree = "<group>"; };
|
||||
|
@ -1007,8 +930,6 @@
|
|||
34AC09D6211B39B100997B47 /* SelectThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectThreadViewController.h; sourceTree = "<group>"; };
|
||||
34AC09D7211B39B100997B47 /* SharingThreadPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharingThreadPickerViewController.h; sourceTree = "<group>"; };
|
||||
34AC09D9211B39B100997B47 /* MediaMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageView.swift; sourceTree = "<group>"; };
|
||||
34AC09DA211B39B100997B47 /* CountryCodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryCodeViewController.m; sourceTree = "<group>"; };
|
||||
34AC09DB211B39B100997B47 /* CountryCodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryCodeViewController.h; sourceTree = "<group>"; };
|
||||
34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharingThreadPickerViewController.m; sourceTree = "<group>"; };
|
||||
34AC09FB211B39E700997B47 /* ContactsViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactsViewHelper.h; sourceTree = "<group>"; };
|
||||
34AC09FC211B39E700997B47 /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactTableViewCell.h; sourceTree = "<group>"; };
|
||||
|
@ -1038,18 +959,12 @@
|
|||
34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AttachmentSharing.m; sourceTree = "<group>"; };
|
||||
34B3F83B1E8DF1700035BE1A /* CallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = "<group>"; };
|
||||
34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsPicker.swift; sourceTree = "<group>"; };
|
||||
34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExperienceUpgradesPageViewController.swift; sourceTree = "<group>"; };
|
||||
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteFlow.swift; sourceTree = "<group>"; };
|
||||
34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewContactThreadViewController.h; sourceTree = "<group>"; };
|
||||
34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewContactThreadViewController.m; sourceTree = "<group>"; };
|
||||
34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewGroupViewController.h; sourceTree = "<group>"; };
|
||||
34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewGroupViewController.m; sourceTree = "<group>"; };
|
||||
34B3F86D1E8DF1700035BE1A /* SignalsNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalsNavigationController.h; sourceTree = "<group>"; };
|
||||
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalsNavigationController.m; sourceTree = "<group>"; };
|
||||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = "<group>"; };
|
||||
34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorInteraction.swift; sourceTree = "<group>"; };
|
||||
34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = "<group>"; };
|
||||
34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS112TypingIndicatorsMigration.swift; sourceTree = "<group>"; };
|
||||
34B6A90A218BA1D0007C4606 /* typing-animation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation.gif"; sourceTree = "<group>"; };
|
||||
34B6D27220F664C800765BE2 /* OWSUnreadIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUnreadIndicator.h; sourceTree = "<group>"; };
|
||||
34B6D27320F664C800765BE2 /* OWSUnreadIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUnreadIndicator.m; sourceTree = "<group>"; };
|
||||
|
@ -1069,7 +984,6 @@
|
|||
34BECE2A1F74C12700D7438D /* DebugUIStress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIStress.m; sourceTree = "<group>"; };
|
||||
34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerViewController.swift; sourceTree = "<group>"; };
|
||||
34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerLayout.swift; sourceTree = "<group>"; };
|
||||
34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS114RemoveDynamicInteractions.swift; sourceTree = "<group>"; };
|
||||
34BEDB0D21C405B0007B0EAE /* ImageEditorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorModel.swift; sourceTree = "<group>"; };
|
||||
34BEDB1221C43F69007B0EAE /* ImageEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageEditorView.swift; sourceTree = "<group>"; };
|
||||
34BEDB1421C80BC9007B0EAE /* OWSAnyTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAnyTouchGestureRecognizer.h; sourceTree = "<group>"; };
|
||||
|
@ -1132,8 +1046,6 @@
|
|||
34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAction.h; sourceTree = "<group>"; };
|
||||
34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAssetLoader.h; sourceTree = "<group>"; };
|
||||
34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesUtils.h; sourceTree = "<group>"; };
|
||||
34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS109OutgoingMessageState.m; sourceTree = "<group>"; };
|
||||
34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS109OutgoingMessageState.h; sourceTree = "<group>"; };
|
||||
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
|
||||
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
|
||||
34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = "<group>"; };
|
||||
|
@ -1161,8 +1073,6 @@
|
|||
34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIDiskUsage.m; sourceTree = "<group>"; };
|
||||
34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIPage.h; sourceTree = "<group>"; };
|
||||
34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIPage.m; sourceTree = "<group>"; };
|
||||
34E5DC8020D8050D00C08145 /* RegistrationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegistrationUtils.h; sourceTree = "<group>"; };
|
||||
34E5DC8120D8050D00C08145 /* RegistrationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegistrationUtils.m; sourceTree = "<group>"; };
|
||||
34E88D252098C5AE00A608F4 /* ContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewController.swift; sourceTree = "<group>"; };
|
||||
34E8A8D02085238900B272B1 /* ProtoParsingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProtoParsingTest.m; sourceTree = "<group>"; };
|
||||
34EA693F2194933900702471 /* MediaDownloadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaDownloadView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1173,8 +1083,6 @@
|
|||
435EAC2E5E22D3F087EB3192 /* Pods-SignalShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "classic-quiet.aifc"; sourceTree = "<group>"; };
|
||||
4503F1BC20470A5B00CEE724 /* classic.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = classic.aifc; sourceTree = "<group>"; };
|
||||
4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS107LegacySounds.m; sourceTree = "<group>"; };
|
||||
4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS107LegacySounds.h; sourceTree = "<group>"; };
|
||||
4505C2BE1E648EA300CEBF41 /* ExperienceUpgrade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ExperienceUpgrade.swift; path = ExperienceUpgrades/ExperienceUpgrade.swift; sourceTree = "<group>"; };
|
||||
4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = ThirdParty/WebRTC/Build/WebRTC.framework; sourceTree = "<group>"; };
|
||||
450D19111F85236600970622 /* RemoteVideoView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemoteVideoView.h; sourceTree = "<group>"; };
|
||||
|
@ -1245,8 +1153,6 @@
|
|||
459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDeviceTableViewCell.m; sourceTree = "<group>"; };
|
||||
4597E94E1D8313C100040CDE /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = translations/sq.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4597E94F1D8313CB00040CDE /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = translations/bg.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWS108CallLoggingPreference.h; sourceTree = "<group>"; };
|
||||
4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWS108CallLoggingPreference.m; sourceTree = "<group>"; };
|
||||
459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSQuotedReplyModel.h; sourceTree = "<group>"; };
|
||||
459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedReplyModel.m; sourceTree = "<group>"; };
|
||||
45A2F004204473A3002E978A /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; name = NewMessage.aifc; path = Signal/AudioFiles/NewMessage.aifc; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -1291,7 +1197,6 @@
|
|||
45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Signal/src/util/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; };
|
||||
45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = "<group>"; };
|
||||
45D231761DC7E8F10034FA89 /* SessionResetJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionResetJob.swift; sourceTree = "<group>"; };
|
||||
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS2FAReminderViewController.swift; sourceTree = "<group>"; };
|
||||
45D308AB2049A439000189E4 /* PinEntryView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PinEntryView.h; sourceTree = "<group>"; };
|
||||
45D308AC2049A439000189E4 /* PinEntryView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PinEntryView.m; sourceTree = "<group>"; };
|
||||
45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationHeaderView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1312,7 +1217,6 @@
|
|||
4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceNoteLock.swift; sourceTree = "<group>"; };
|
||||
4C04F58321C860C50090D0BB /* MantlePerfTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MantlePerfTest.swift; path = Models/MantlePerfTest.swift; sourceTree = "<group>"; };
|
||||
4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = "<group>"; };
|
||||
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
|
||||
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
|
||||
4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoGridViewCell.swift; sourceTree = "<group>"; };
|
||||
4C1D2333218B692800A0598F /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = translations/ko.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -1323,7 +1227,6 @@
|
|||
4C1D2339218B6C6D00A0598F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = translations/sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4C1D233A218B6CDB00A0598F /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = translations/th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4C1D233B218B6D3100A0598F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = translations/tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
|
||||
4C21D5D5223A9DC500EF8A77 /* UIAlerts+iOS9.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIAlerts+iOS9.m"; sourceTree = "<group>"; };
|
||||
4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCapture.swift; sourceTree = "<group>"; };
|
||||
4C23A5F1215C4ADE00534937 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1332,14 +1235,12 @@
|
|||
4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = "<group>"; };
|
||||
4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMediaNavigationController.swift; sourceTree = "<group>"; };
|
||||
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = "<group>"; };
|
||||
4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidator.swift; sourceTree = "<group>"; };
|
||||
4C586924224FAB83003FD070 /* AVAudioSession+OWS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "AVAudioSession+OWS.h"; path = "util/UI Categories/AVAudioSession+OWS.h"; sourceTree = "<group>"; };
|
||||
4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "AVAudioSession+OWS.m"; path = "util/UI Categories/AVAudioSession+OWS.m"; sourceTree = "<group>"; };
|
||||
4C618198219DF03A009BD6B5 /* OWSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSButton.swift; sourceTree = "<group>"; };
|
||||
4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation-dark.gif"; sourceTree = "<group>"; };
|
||||
4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = "<group>"; };
|
||||
4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = "<group>"; };
|
||||
4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWS113MultiAttachmentMediaMessages.swift; sourceTree = "<group>"; };
|
||||
4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+OWS.swift"; sourceTree = "<group>"; };
|
||||
4C948FF62146EB4800349F0D /* BlockListCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListCache.swift; sourceTree = "<group>"; };
|
||||
4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = "<group>"; };
|
||||
|
@ -1349,7 +1250,6 @@
|
|||
4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; };
|
||||
4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = "<group>"; };
|
||||
4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS110SortIdMigration.swift; sourceTree = "<group>"; };
|
||||
4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = "<group>"; };
|
||||
4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateNag.swift; sourceTree = "<group>"; };
|
||||
|
@ -1701,15 +1601,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */,
|
||||
349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */,
|
||||
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */,
|
||||
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */,
|
||||
3448E15D221333F5004B052E /* OnboardingController.swift */,
|
||||
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */,
|
||||
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */,
|
||||
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */,
|
||||
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */,
|
||||
346E9D5321B040B600562252 /* RegistrationController.swift */,
|
||||
);
|
||||
path = Registration;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1735,10 +1628,6 @@
|
|||
340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */,
|
||||
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */,
|
||||
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */,
|
||||
340FC891204DAC8C007AEB0F /* OWSLinkDeviceViewController.h */,
|
||||
340FC885204DAC8C007AEB0F /* OWSLinkDeviceViewController.m */,
|
||||
340FC895204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.h */,
|
||||
340FC882204DAC8C007AEB0F /* OWSLinkedDevicesTableViewController.m */,
|
||||
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */,
|
||||
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */,
|
||||
340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */,
|
||||
|
@ -1782,19 +1671,6 @@
|
|||
path = Fonts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
34386A4C207D0C01009F5D9C /* HomeView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
34386A4E207D0C01009F5D9C /* HomeViewCell.h */,
|
||||
34386A50207D0C01009F5D9C /* HomeViewCell.m */,
|
||||
34386A4F207D0C01009F5D9C /* HomeViewController.h */,
|
||||
34386A4D207D0C01009F5D9C /* HomeViewController.m */,
|
||||
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */,
|
||||
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */,
|
||||
);
|
||||
path = HomeView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
34480B2F1FD0921000BC14EF /* utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1923,30 +1799,6 @@
|
|||
346129921FD1E30000532771 /* migrations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
346129ED1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.h */,
|
||||
346129EB1FD5F31300532771 /* OWS100RemoveTSRecipientsMigration.m */,
|
||||
346129EF1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h */,
|
||||
346129EE1FD5F31300532771 /* OWS101ExistingUsersBlockOnIdentityChange.m */,
|
||||
346129F01FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h */,
|
||||
346129E81FD5F31200532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m */,
|
||||
346129E91FD5F31300532771 /* OWS103EnableVideoCalling.h */,
|
||||
346129F21FD5F31400532771 /* OWS103EnableVideoCalling.m */,
|
||||
346129F41FD5F31400532771 /* OWS104CreateRecipientIdentities.h */,
|
||||
346129EC1FD5F31300532771 /* OWS104CreateRecipientIdentities.m */,
|
||||
346129F31FD5F31400532771 /* OWS105AttachmentFilePaths.h */,
|
||||
346129EA1FD5F31300532771 /* OWS105AttachmentFilePaths.m */,
|
||||
346129F11FD5F31400532771 /* OWS106EnsureProfileComplete.swift */,
|
||||
4503F1C2204711D200CEE724 /* OWS107LegacySounds.h */,
|
||||
4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */,
|
||||
4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */,
|
||||
4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */,
|
||||
34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */,
|
||||
34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */,
|
||||
4CBBCA6221714B4500EEB37D /* OWS110SortIdMigration.swift */,
|
||||
349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */,
|
||||
34B6A908218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift */,
|
||||
4C7537882193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift */,
|
||||
34BEDB0A21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift */,
|
||||
346129931FD1E30000532771 /* OWSDatabaseMigration.h */,
|
||||
346129941FD1E30000532771 /* OWSDatabaseMigration.m */,
|
||||
346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */,
|
||||
|
@ -2086,9 +1938,7 @@
|
|||
3448BFC01EDF0EA7005B2D69 /* ConversationView */,
|
||||
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */,
|
||||
34D8C0221ED3673300188D7C /* DebugUI */,
|
||||
34B3F8441E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift */,
|
||||
34BECE2C1F7ABCE000D7438D /* GifPicker */,
|
||||
34386A4C207D0C01009F5D9C /* HomeView */,
|
||||
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */,
|
||||
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */,
|
||||
3496744E2076ACCE00080B5F /* LongTextViewController.swift */,
|
||||
|
@ -2099,13 +1949,6 @@
|
|||
454A84032059C787008B8C75 /* MediaTileViewController.swift */,
|
||||
4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */,
|
||||
34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */,
|
||||
34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */,
|
||||
34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */,
|
||||
34B3F8541E8DF1700035BE1A /* NewGroupViewController.h */,
|
||||
34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */,
|
||||
45D2AC01204885170033C692 /* OWS2FAReminderViewController.swift */,
|
||||
345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */,
|
||||
345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */,
|
||||
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */,
|
||||
34969558219B605E00DCFE74 /* Photos */,
|
||||
34CE88E51F2FB9A10098030F /* ProfileViewController.h */,
|
||||
|
@ -2349,8 +2192,6 @@
|
|||
340872C22239563500CB25B0 /* AttachmentApproval */,
|
||||
34AC09CF211B39B000997B47 /* ContactFieldView.swift */,
|
||||
34AC09CD211B39B000997B47 /* ContactShareApprovalViewController.swift */,
|
||||
34AC09DB211B39B100997B47 /* CountryCodeViewController.h */,
|
||||
34AC09DA211B39B100997B47 /* CountryCodeViewController.m */,
|
||||
34AC09D0211B39B000997B47 /* EditContactShareNameViewController.swift */,
|
||||
34AC09D9211B39B100997B47 /* MediaMessageView.swift */,
|
||||
34AC09C9211B39AF00997B47 /* MessageApprovalViewController.swift */,
|
||||
|
@ -2469,7 +2310,6 @@
|
|||
458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */,
|
||||
458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */,
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
|
||||
4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2605,8 +2445,6 @@
|
|||
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */,
|
||||
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */,
|
||||
450DF2041E0D74AC003D14BE /* Platform.swift */,
|
||||
34E5DC8020D8050D00C08145 /* RegistrationUtils.h */,
|
||||
34E5DC8120D8050D00C08145 /* RegistrationUtils.m */,
|
||||
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
|
||||
FCFA64B11A24F29E0007FB87 /* UI Categories */,
|
||||
);
|
||||
|
@ -3089,18 +2927,13 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */,
|
||||
34AC09F9211B39B100997B47 /* CountryCodeViewController.h in Headers */,
|
||||
34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */,
|
||||
459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */,
|
||||
34612A001FD5F31400532771 /* OWS105AttachmentFilePaths.h in Headers */,
|
||||
346129F61FD5F31400532771 /* OWS103EnableVideoCalling.h in Headers */,
|
||||
346129A91FD1F0E000532771 /* OWSFormat.h in Headers */,
|
||||
34480B551FD0A7A400BC14EF /* DebugLogger.h in Headers */,
|
||||
4503F1C4204711D300CEE724 /* OWS107LegacySounds.h in Headers */,
|
||||
346129711FD1D74C00532771 /* SignalKeyingStorage.h in Headers */,
|
||||
34AC0A20211B39EA00997B47 /* ThreadViewHelper.h in Headers */,
|
||||
34AC09DE211B39B100997B47 /* OWSNavigationController.h in Headers */,
|
||||
34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */,
|
||||
450998691FD8C10200D89EB3 /* AttachmentSharing.h in Headers */,
|
||||
34BEDB1621C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.h in Headers */,
|
||||
34AC09EC211B39B100997B47 /* OWSTableViewController.h in Headers */,
|
||||
|
@ -3108,7 +2941,6 @@
|
|||
346129D61FD20ADC00532771 /* UIViewController+OWS.h in Headers */,
|
||||
34612A061FD7238600532771 /* OWSSyncManager.h in Headers */,
|
||||
34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */,
|
||||
346129FC1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h in Headers */,
|
||||
34AC09F2211B39B100997B47 /* OWSViewController.h in Headers */,
|
||||
451F8A491FD715CF005CB9DA /* OWSAvatarBuilder.h in Headers */,
|
||||
346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */,
|
||||
|
@ -3117,7 +2949,6 @@
|
|||
346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */,
|
||||
342950892124CB0A0000B063 /* OWSSearchBar.h in Headers */,
|
||||
346941A1215D2EE400B5BFAD /* Theme.h in Headers */,
|
||||
346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */,
|
||||
346129E21FD5C0BE00532771 /* VersionMigrations.h in Headers */,
|
||||
34AC09E8211B39B100997B47 /* NewNonContactConversationViewController.h in Headers */,
|
||||
34480B611FD0A98800BC14EF /* UIColor+OWS.h in Headers */,
|
||||
|
@ -3125,8 +2956,6 @@
|
|||
346941A4215D2EE400B5BFAD /* OWSConversationColor.h in Headers */,
|
||||
3461295A1FD1D74C00532771 /* Environment.h in Headers */,
|
||||
450C801020AD1AE400F3A091 /* OWSWindowManager.h in Headers */,
|
||||
34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */,
|
||||
4598198E204E2F28009414F2 /* OWS108CallLoggingPreference.h in Headers */,
|
||||
34AC0A0F211B39EA00997B47 /* ContactTableViewCell.h in Headers */,
|
||||
34480B631FD0A98800BC14EF /* UIView+OWS.h in Headers */,
|
||||
451F8A4B1FD715E1005CB9DA /* OWSGroupAvatarBuilder.h in Headers */,
|
||||
|
@ -3150,7 +2979,6 @@
|
|||
342950852124C9750000B063 /* OWSTextField.h in Headers */,
|
||||
45194F901FD7200000333B2C /* ThreadUtil.h in Headers */,
|
||||
346129CC1FD2072E00532771 /* NSAttributedString+OWS.h in Headers */,
|
||||
346129FD1FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.h in Headers */,
|
||||
34074F62203D0CBE004596AE /* OWSSounds.h in Headers */,
|
||||
34AC0A1D211B39EA00997B47 /* ContactCellView.h in Headers */,
|
||||
);
|
||||
|
@ -3221,7 +3049,6 @@
|
|||
buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Signal" */;
|
||||
buildPhases = (
|
||||
1460156AE01E0DB0949D61FE /* [CP] Check Pods Manifest.lock */,
|
||||
45AE48531E073428004D96C2 /* Swift Lint */,
|
||||
D221A085169C9E5E00537ABF /* Sources */,
|
||||
D221A086169C9E5E00537ABF /* Frameworks */,
|
||||
34C239432180B01B00B6108F /* Run Script: update_list_info */,
|
||||
|
@ -3630,20 +3457,6 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "/usr/local/bin/carthage copy-frameworks\n";
|
||||
};
|
||||
45AE48531E073428004D96C2 /* Swift Lint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Swift Lint";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n# disabled for now. too many lint errors outside of the scope of this branch\n#(cd Signal && swiftlint)\n# never fail.\nexit 0\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
4B4609DACEC6E462A2394D2F /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -3856,20 +3669,16 @@
|
|||
34AC0A17211B39EA00997B47 /* VideoPlayerView.swift in Sources */,
|
||||
34BEDB1321C43F6A007B0EAE /* ImageEditorView.swift in Sources */,
|
||||
34AC09EE211B39B100997B47 /* EditContactShareNameViewController.swift in Sources */,
|
||||
346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */,
|
||||
45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */,
|
||||
450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */,
|
||||
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */,
|
||||
346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */,
|
||||
34AC09DF211B39B100997B47 /* OWSNavigationController.m in Sources */,
|
||||
34074F61203D0CBE004596AE /* OWSSounds.m in Sources */,
|
||||
34BEDB1721C80BCA007B0EAE /* OWSAnyTouchGestureRecognizer.m in Sources */,
|
||||
340872C92239563500CB25B0 /* AttachmentItemCollection.swift in Sources */,
|
||||
34080EFE2225F96D0087E99F /* ImageEditorPaletteView.swift in Sources */,
|
||||
34B6A909218B8824007C4606 /* OWS112TypingIndicatorsMigration.swift in Sources */,
|
||||
4C3E245D21F2B395000AE092 /* DirectionalPanGestureRecognizer.swift in Sources */,
|
||||
346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */,
|
||||
4CBBCA6321714B4500EEB37D /* OWS110SortIdMigration.swift in Sources */,
|
||||
342950832124C9750000B063 /* OWSTextView.m in Sources */,
|
||||
452EC6E1205FF5DC000E787C /* Bench.swift in Sources */,
|
||||
B8C9689923FA1B72005F64E0 /* Values.swift in Sources */,
|
||||
|
@ -3904,7 +3713,6 @@
|
|||
451F8A461FD715BA005CB9DA /* OWSGroupAvatarBuilder.m in Sources */,
|
||||
34BBC85B220C7ADA00857249 /* OrderedDictionary.swift in Sources */,
|
||||
346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */,
|
||||
346129FB1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */,
|
||||
B8544E3523D5201400299F14 /* UIView+Constraints.swift in Sources */,
|
||||
34AC09EA211B39B100997B47 /* ModalActivityIndicatorViewController.swift in Sources */,
|
||||
B8C9689523FA1B72005F64E0 /* AppMode.swift in Sources */,
|
||||
|
@ -3917,19 +3725,14 @@
|
|||
340872CA2239563500CB25B0 /* AttachmentApprovalViewController.swift in Sources */,
|
||||
346129AD1FD1F34E00532771 /* ImageCache.swift in Sources */,
|
||||
452C7CA72037628B003D51A5 /* Weak.swift in Sources */,
|
||||
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */,
|
||||
340872D02239787F00CB25B0 /* AttachmentTextToolbar.swift in Sources */,
|
||||
34AC09F8211B39B100997B47 /* CountryCodeViewController.m in Sources */,
|
||||
451F8A341FD710C3005CB9DA /* FullTextSearcher.swift in Sources */,
|
||||
34080F04222858DC0087E99F /* OWSViewController+ImageEditor.swift in Sources */,
|
||||
346129FE1FD5F31400532771 /* OWS106EnsureProfileComplete.swift in Sources */,
|
||||
34AC0A10211B39EA00997B47 /* TappableView.swift in Sources */,
|
||||
346129F91FD5F31400532771 /* OWS104CreateRecipientIdentities.m in Sources */,
|
||||
B8C9689A23FA1B95005F64E0 /* DeviceUtilities.swift in Sources */,
|
||||
346129B61FD1F7E800532771 /* ProfileFetcherJob.swift in Sources */,
|
||||
34AC09E9211B39B100997B47 /* OWSTableViewController.m in Sources */,
|
||||
340872CE2239596100CB25B0 /* AttachmentApprovalInputAccessoryView.swift in Sources */,
|
||||
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */,
|
||||
45194F8F1FD71FF500333B2C /* ThreadUtil.m in Sources */,
|
||||
34BEDB0E21C405B0007B0EAE /* ImageEditorModel.swift in Sources */,
|
||||
451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */,
|
||||
|
@ -3948,13 +3751,10 @@
|
|||
34080F02222853E30087E99F /* ImageEditorBrushViewController.swift in Sources */,
|
||||
34AC0A14211B39EA00997B47 /* ContactCellView.m in Sources */,
|
||||
34AC0A15211B39EA00997B47 /* ContactsViewHelper.m in Sources */,
|
||||
346129FF1FD5F31400532771 /* OWS103EnableVideoCalling.m in Sources */,
|
||||
346129E31FD5C0BE00532771 /* VersionMigrations.m in Sources */,
|
||||
4C7537892193779700DF5E37 /* OWS113MultiAttachmentMediaMessages.swift in Sources */,
|
||||
C3638C0524C7F0B500AF29BC /* LK002RemoveFriendRequests.swift in Sources */,
|
||||
340872CB2239563500CB25B0 /* AttachmentPrepViewController.swift in Sources */,
|
||||
34AC0A16211B39EA00997B47 /* OWSNavigationBar.swift in Sources */,
|
||||
34BEDB0B21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift in Sources */,
|
||||
34AC0A1A211B39EA00997B47 /* CommonStrings.swift in Sources */,
|
||||
34AC0A19211B39EA00997B47 /* OWSAlerts.swift in Sources */,
|
||||
340872DA22397FEB00CB25B0 /* AttachmentTextView.swift in Sources */,
|
||||
|
@ -3962,14 +3762,12 @@
|
|||
34BBC859220C7ADA00857249 /* ImageEditorStrokeItem.swift in Sources */,
|
||||
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */,
|
||||
451F8A481FD715BA005CB9DA /* OWSContactAvatarBuilder.m in Sources */,
|
||||
4503F1C3204711D300CEE724 /* OWS107LegacySounds.m in Sources */,
|
||||
34AC0A18211B39EA00997B47 /* TappableStackView.swift in Sources */,
|
||||
34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */,
|
||||
346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */,
|
||||
4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */,
|
||||
4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */,
|
||||
241C6314231F64C000B4198E /* JazzIcon.swift in Sources */,
|
||||
4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */,
|
||||
34AC09F3211B39B100997B47 /* NewNonContactConversationViewController.m in Sources */,
|
||||
4C3E245C21F29FCE000AE092 /* Toast.swift in Sources */,
|
||||
34BBC84B220B2CB200857249 /* ImageEditorTextViewController.swift in Sources */,
|
||||
|
@ -3987,7 +3785,6 @@
|
|||
34641E182088D7E900E2EDE5 /* OWSScreenLock.swift in Sources */,
|
||||
346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */,
|
||||
241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */,
|
||||
349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */,
|
||||
34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */,
|
||||
459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */,
|
||||
340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */,
|
||||
|
@ -4025,7 +3822,6 @@
|
|||
34C4E2582118957600BEA353 /* WebRTCProto.swift in Sources */,
|
||||
34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */,
|
||||
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */,
|
||||
34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */,
|
||||
34DC9BD921543E0C00FDDCEC /* DebugContactsUtils.m in Sources */,
|
||||
34DBF007206C3CB200025978 /* OWSBubbleShapeView.m in Sources */,
|
||||
4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */,
|
||||
|
@ -4044,7 +3840,6 @@
|
|||
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
|
||||
B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */,
|
||||
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
|
||||
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */,
|
||||
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */,
|
||||
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
|
||||
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
|
||||
|
@ -4061,9 +3856,7 @@
|
|||
B8BB82B92394911B00BA5194 /* Separator.swift in Sources */,
|
||||
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
|
||||
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */,
|
||||
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */,
|
||||
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */,
|
||||
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */,
|
||||
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */,
|
||||
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
|
||||
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,
|
||||
|
@ -4072,7 +3865,6 @@
|
|||
45794E861E00620000066731 /* CallUIAdapter.swift in Sources */,
|
||||
340FC8BA204DAC8D007AEB0F /* FingerprintViewScanController.m in Sources */,
|
||||
4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */,
|
||||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */,
|
||||
B85357C123A1B81900AAF6CD /* SeedReminderViewDelegate.swift in Sources */,
|
||||
450D19131F85236600970622 /* RemoteVideoView.m in Sources */,
|
||||
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
|
||||
|
@ -4083,8 +3875,6 @@
|
|||
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */,
|
||||
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
|
||||
4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */,
|
||||
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */,
|
||||
340FC8AD204DAC8D007AEB0F /* OWSLinkedDevicesTableViewController.m in Sources */,
|
||||
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */,
|
||||
B80C6B592384C4E700FDBC8B /* DeviceNameModal.swift in Sources */,
|
||||
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */,
|
||||
|
@ -4106,7 +3896,6 @@
|
|||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */,
|
||||
3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */,
|
||||
B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */,
|
||||
349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */,
|
||||
C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */,
|
||||
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
|
||||
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */,
|
||||
|
@ -4116,15 +3905,11 @@
|
|||
D221A09A169C9E5E00537ABF /* main.m in Sources */,
|
||||
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */,
|
||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */,
|
||||
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */,
|
||||
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */,
|
||||
34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */,
|
||||
45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */,
|
||||
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
|
||||
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */,
|
||||
340FC8BC204DAC8D007AEB0F /* FingerprintViewController.m in Sources */,
|
||||
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */,
|
||||
34A4C61E221613D00042EF2E /* OnboardingVerificationViewController.swift in Sources */,
|
||||
4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */,
|
||||
340FC8B2204DAC8D007AEB0F /* AdvancedSettingsTableViewController.m in Sources */,
|
||||
452B999020A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift in Sources */,
|
||||
|
@ -4143,7 +3928,6 @@
|
|||
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
|
||||
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
|
||||
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,
|
||||
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
|
||||
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
|
||||
|
@ -4159,7 +3943,6 @@
|
|||
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
|
||||
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */,
|
||||
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
|
||||
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */,
|
||||
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
|
||||
B8CCF639239721E20091D419 /* TabBar.swift in Sources */,
|
||||
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
|
||||
|
@ -4179,13 +3962,11 @@
|
|||
C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */,
|
||||
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
|
||||
B879D44B247E1D9200DB3608 /* PathStatusView.swift in Sources */,
|
||||
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
|
||||
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */,
|
||||
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
|
||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
|
||||
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
|
||||
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
|
||||
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */,
|
||||
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
|
||||
B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */,
|
||||
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */,
|
||||
|
@ -4195,7 +3976,6 @@
|
|||
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */,
|
||||
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
|
||||
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
|
||||
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */,
|
||||
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
|
||||
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */,
|
||||
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
|
||||
|
@ -4211,7 +3991,6 @@
|
|||
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */,
|
||||
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */,
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
|
||||
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
|
||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
|
||||
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
|
||||
|
@ -4254,7 +4033,6 @@
|
|||
B85357C723A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift in Sources */,
|
||||
340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */,
|
||||
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */,
|
||||
4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */,
|
||||
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */,
|
||||
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */,
|
||||
340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */,
|
||||
|
@ -4273,7 +4051,6 @@
|
|||
34D1F0861F8678AA0066283D /* ConversationViewController.m in Sources */,
|
||||
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */,
|
||||
B90418E6183E9DD40038554A /* DateUtil.m in Sources */,
|
||||
3448E15E221333F5004B052E /* OnboardingController.swift in Sources */,
|
||||
340FC8BD204DAC8D007AEB0F /* ShowGroupMembersViewController.m in Sources */,
|
||||
3496956F21A301A100DCFE74 /* OWSBackupLazyRestore.swift in Sources */,
|
||||
459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */,
|
||||
|
|
|
@ -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"
|
||||
|
@ -166,21 +164,13 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application
|
||||
{
|
||||
OWSLogInfo(@"applicationDidEnterBackground");
|
||||
|
||||
[DDLog flushLog];
|
||||
|
||||
// Loki: Stop pollers
|
||||
[self stopPoller];
|
||||
[self stopClosedGroupPoller];
|
||||
[self stopOpenGroupPollers];
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application
|
||||
{
|
||||
OWSLogInfo(@"applicationWillEnterForeground");
|
||||
}
|
||||
|
||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
|
||||
{
|
||||
OWSLogInfo(@"applicationDidReceiveMemoryWarning");
|
||||
|
@ -188,11 +178,8 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application
|
||||
{
|
||||
OWSLogInfo(@"applicationWillTerminate");
|
||||
|
||||
[DDLog flushLog];
|
||||
|
||||
// Loki: Stop pollers
|
||||
[self stopPoller];
|
||||
[self stopClosedGroupPoller];
|
||||
[self stopOpenGroupPollers];
|
||||
|
@ -200,7 +187,7 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
// This should be the first thing we do.
|
||||
// This should be the first thing we do
|
||||
SetCurrentAppContext([MainAppContext new]);
|
||||
|
||||
launchStartedAt = CACurrentMediaTime();
|
||||
|
@ -218,7 +205,6 @@ static NSTimeInterval launchStartedAt;
|
|||
[DebugLogger.sharedLogger enableFileLogging];
|
||||
}
|
||||
|
||||
OWSLogWarn(@"application:didFinishLaunchingWithOptions");
|
||||
[Cryptography seedRandom];
|
||||
|
||||
// XXX - careful when moving this. It must happen before we initialize OWSPrimaryStorage.
|
||||
|
@ -233,18 +219,6 @@ static NSTimeInterval launchStartedAt;
|
|||
}
|
||||
#endif
|
||||
|
||||
// We need to do this _after_ we set up logging, when the keychain is unlocked,
|
||||
// but before we access YapDatabase, files on disk, or NSUserDefaults
|
||||
if (![self ensureIsReadyForAppExtensions]) {
|
||||
// If this method has failed; do nothing.
|
||||
//
|
||||
// ensureIsReadyForAppExtensions will show a failure mode UI that
|
||||
// lets users report this error.
|
||||
OWSLogInfo(@"application:didFinishLaunchingWithOptions failed");
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
[AppVersion sharedInstance];
|
||||
|
||||
[self startupLogging];
|
||||
|
@ -257,7 +231,7 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
[AppSetup
|
||||
setupEnvironmentWithAppSpecificSingletonBlock:^{
|
||||
// Create AppEnvironment.
|
||||
// Create AppEnvironment
|
||||
[AppEnvironment.shared setup];
|
||||
[SignalApp.sharedApp setup];
|
||||
}
|
||||
|
@ -328,11 +302,9 @@ static NSTimeInterval launchStartedAt;
|
|||
*/
|
||||
- (void)verifyDBKeysAvailableBeforeBackgroundLaunch
|
||||
{
|
||||
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
|
||||
return;
|
||||
}
|
||||
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { return; }
|
||||
|
||||
if (![OWSPrimaryStorage isDatabasePasswordAccessible]) {
|
||||
if (!OWSPrimaryStorage.isDatabasePasswordAccessible) {
|
||||
OWSLogInfo(@"Exiting because we are in the background and the database password is not accessible.");
|
||||
|
||||
UILocalNotification *notification = [UILocalNotification new];
|
||||
|
@ -354,72 +326,6 @@ static NSTimeInterval launchStartedAt;
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)ensureIsReadyForAppExtensions
|
||||
{
|
||||
// Given how sensitive this migration is, we verbosely
|
||||
// log the contents of all involved paths before and after.
|
||||
//
|
||||
// TODO: Remove this logging once we have high confidence
|
||||
// in our migration logic.
|
||||
NSArray<NSString *> *paths = @[
|
||||
OWSPrimaryStorage.legacyDatabaseFilePath,
|
||||
OWSPrimaryStorage.legacyDatabaseFilePath_SHM,
|
||||
OWSPrimaryStorage.legacyDatabaseFilePath_WAL,
|
||||
OWSPrimaryStorage.sharedDataDatabaseFilePath,
|
||||
OWSPrimaryStorage.sharedDataDatabaseFilePath_SHM,
|
||||
OWSPrimaryStorage.sharedDataDatabaseFilePath_WAL,
|
||||
];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
for (NSString *path in paths) {
|
||||
if ([fileManager fileExistsAtPath:path]) {
|
||||
OWSLogInfo(@"storage file: %@, %@", path, [OWSFileSystem fileSizeOfPath:path]);
|
||||
}
|
||||
}
|
||||
|
||||
if ([OWSPreferences isReadyForAppExtensions]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
OWSBackgroundTask *_Nullable backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
|
||||
SUPPRESS_DEADSTORE_WARNING(backgroundTask);
|
||||
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:OWSPrimaryStorage.legacyDatabaseFilePath]) {
|
||||
OWSLogInfo(
|
||||
@"Legacy Database file size: %@", [OWSFileSystem fileSizeOfPath:OWSPrimaryStorage.legacyDatabaseFilePath]);
|
||||
OWSLogInfo(@"\t Legacy SHM file size: %@",
|
||||
[OWSFileSystem fileSizeOfPath:OWSPrimaryStorage.legacyDatabaseFilePath_SHM]);
|
||||
OWSLogInfo(@"\t Legacy WAL file size: %@",
|
||||
[OWSFileSystem fileSizeOfPath:OWSPrimaryStorage.legacyDatabaseFilePath_WAL]);
|
||||
}
|
||||
|
||||
NSError *_Nullable error = [self convertDatabaseIfNecessary];
|
||||
|
||||
if (!error) {
|
||||
[NSUserDefaults migrateToSharedUserDefaults];
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
error = [OWSPrimaryStorage migrateToSharedData];
|
||||
}
|
||||
if (!error) {
|
||||
error = [OWSUserProfile migrateToSharedData];
|
||||
}
|
||||
if (!error) {
|
||||
error = [TSAttachmentStream migrateToSharedData];
|
||||
}
|
||||
|
||||
if (error) {
|
||||
OWSFailDebug(@"Database conversion failed: %@", error);
|
||||
[self showLaunchFailureUI:error];
|
||||
return NO;
|
||||
}
|
||||
|
||||
OWSAssertDebug(backgroundTask);
|
||||
backgroundTask = nil;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)showLaunchFailureUI:(NSError *)error
|
||||
{
|
||||
// Disable normal functioning of app.
|
||||
|
@ -455,53 +361,6 @@ static NSTimeInterval launchStartedAt;
|
|||
[fromViewController presentAlert:alert];
|
||||
}
|
||||
|
||||
- (nullable NSError *)convertDatabaseIfNecessary
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
|
||||
NSString *databaseFilePath = [OWSPrimaryStorage legacyDatabaseFilePath];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) {
|
||||
OWSLogVerbose(@"No legacy database file found");
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *_Nullable error;
|
||||
NSData *_Nullable databasePassword = [OWSStorage tryToLoadDatabaseLegacyPassphrase:&error];
|
||||
if (!databasePassword || error) {
|
||||
return (error
|
||||
?: OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeDatabaseConversionFatalError, @"Failed to load database password"));
|
||||
}
|
||||
|
||||
YapRecordDatabaseSaltBlock recordSaltBlock = ^(NSData *saltData) {
|
||||
OWSLogVerbose(@"saltData: %@", saltData.hexadecimalString);
|
||||
|
||||
// Derive and store the raw cipher key spec, to avoid the ongoing tax of future KDF
|
||||
NSData *_Nullable keySpecData =
|
||||
[YapDatabaseCryptoUtils deriveDatabaseKeySpecForPassword:databasePassword saltData:saltData];
|
||||
|
||||
if (!keySpecData) {
|
||||
OWSLogError(@"Failed to derive key spec.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
[OWSStorage storeDatabaseCipherKeySpec:keySpecData];
|
||||
|
||||
return YES;
|
||||
};
|
||||
|
||||
YapDatabaseOptions *dbOptions = [OWSStorage defaultDatabaseOptions];
|
||||
error = [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
|
||||
databasePassword:databasePassword
|
||||
options:dbOptions
|
||||
recordSaltBlock:recordSaltBlock];
|
||||
if (!error) {
|
||||
[OWSStorage removeLegacyPassphrase];
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
- (void)startupLogging
|
||||
{
|
||||
OWSLogInfo(@"iOS Version: %@", [UIDevice currentDevice].systemVersion);
|
||||
|
@ -585,56 +444,9 @@ static NSTimeInterval launchStartedAt;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogInfo(@"Registered legacy notification settings.");
|
||||
[self.notificationPresenter didRegisterLegacyNotificationSettings];
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
annotation:(id)annotation
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (self.didAppLaunchFail) {
|
||||
OWSFailDebug(@"App launch failed");
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!AppReadiness.isAppReady) {
|
||||
OWSLogWarn(@"Ignoring openURL: app not ready.");
|
||||
// We don't need to use [AppReadiness runNowOrWhenAppDidBecomeReady:];
|
||||
// the only URLs we handle in Signal iOS at the moment are used
|
||||
// for resuming the verification step of the registration flow.
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([url.scheme isEqualToString:kURLSchemeSGNLKey]) {
|
||||
if ([url.host hasPrefix:kURLHostVerifyPrefix] && ![self.tsAccountManager isRegistered]) {
|
||||
id signupController = SignalApp.sharedApp.signUpFlowNavigationController;
|
||||
if ([signupController isKindOfClass:[OWSNavigationController class]]) {
|
||||
OWSNavigationController *navController = (OWSNavigationController *)signupController;
|
||||
UIViewController *controller = [navController.childViewControllers lastObject];
|
||||
if ([controller isKindOfClass:[OnboardingVerificationViewController class]]) {
|
||||
OnboardingVerificationViewController *verificationView
|
||||
= (OnboardingVerificationViewController *)controller;
|
||||
NSString *verificationCode = [url.path substringFromIndex:1];
|
||||
[verificationView setVerificationCodeAndTryToVerify:verificationCode];
|
||||
return YES;
|
||||
} else {
|
||||
OWSLogWarn(@"Not the verification view controller we expected. Got %@ instead.",
|
||||
NSStringFromClass(controller.class));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OWSFailDebug(@"Application opened with an unknown URL action: %@", url.host);
|
||||
}
|
||||
} else {
|
||||
OWSFailDebug(@"Application opened with an unknown URL scheme: %@", url.scheme);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
|
@ -643,7 +455,6 @@ static NSTimeInterval launchStartedAt;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogWarn(@"applicationDidBecomeActive");
|
||||
if (CurrentAppContext().isRunningTests) {
|
||||
return;
|
||||
}
|
||||
|
@ -662,27 +473,12 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
// On every activation, clear old temp directories.
|
||||
ClearOldTemporaryDirectories();
|
||||
|
||||
OWSLogInfo(@"applicationDidBecomeActive completed");
|
||||
}
|
||||
|
||||
- (void)enableBackgroundRefreshIfNecessary
|
||||
{
|
||||
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
|
||||
if (isUsingFullAPNs) { return; }
|
||||
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
|
||||
[UIApplication.sharedApplication setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
|
||||
// Loki: Original code
|
||||
// ========
|
||||
// if (OWS2FAManager.sharedManager.is2FAEnabled && [self.tsAccountManager isRegisteredAndReady]) {
|
||||
// // Ping server once a day to keep-alive 2FA clients.
|
||||
// const NSTimeInterval kBackgroundRefreshInterval = 24 * 60 * 60;
|
||||
// [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:kBackgroundRefreshInterval];
|
||||
// } else {
|
||||
// [[UIApplication sharedApplication]
|
||||
// setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
|
||||
// }
|
||||
// ========
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -690,8 +486,6 @@ static NSTimeInterval launchStartedAt;
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
OWSLogWarn(@"handleActivation");
|
||||
|
||||
// Always check prekeys after app launches, and sometimes check on app activation.
|
||||
[TSPreKeyManager checkPreKeysIfNecessary];
|
||||
|
||||
|
@ -744,19 +538,15 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
NSString *userHexEncodedPublicKey = self.tsAccountManager.localNumber;
|
||||
|
||||
// Loki: Start pollers
|
||||
[self startPollerIfNeeded];
|
||||
[self startClosedGroupPollerIfNeeded];
|
||||
[self startOpenGroupPollersIfNeeded];
|
||||
|
||||
// Loki: Get device links
|
||||
[[LKFileServerAPI getDeviceLinksAssociatedWithHexEncodedPublicKey:userHexEncodedPublicKey] retainUntilComplete];
|
||||
|
||||
// Loki: Update profile picture if needed
|
||||
NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
|
||||
NSDate *now = [NSDate new];
|
||||
NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"];
|
||||
if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 14 * 24 * 60 * 60) {
|
||||
if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 4 * 24 * 60 * 60) {
|
||||
OWSProfileManager *profileManager = OWSProfileManager.sharedManager;
|
||||
NSString *displayName = [profileManager profileNameForRecipientWithID:userHexEncodedPublicKey];
|
||||
UIImage *profilePicture = [profileManager profileAvatarForRecipientId:userHexEncodedPublicKey];
|
||||
|
@ -777,22 +567,8 @@ static NSTimeInterval launchStartedAt;
|
|||
[OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager
|
||||
preferences:Environment.shared.preferences];
|
||||
}
|
||||
|
||||
if ([OWS2FAManager sharedManager].isDueForReminder) {
|
||||
if (!self.hasInitialRootViewController || self.window.rootViewController == nil) {
|
||||
OWSLogDebug(@"Skipping 2FA reminder since there isn't yet an initial view controller.");
|
||||
} else {
|
||||
UIViewController *rootViewController = self.window.rootViewController;
|
||||
OWSNavigationController *reminderNavController =
|
||||
[OWS2FAReminderViewController wrappedInNavController];
|
||||
|
||||
[rootViewController presentViewController:reminderNavController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OWSLogInfo(@"handleActivation completed");
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application
|
||||
|
@ -804,8 +580,6 @@ static NSTimeInterval launchStartedAt;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogWarn(@"applicationWillResignActive");
|
||||
|
||||
[self clearAllNotificationsAndRestoreBadgeCount];
|
||||
|
||||
[DDLog flushLog];
|
||||
|
@ -859,7 +633,6 @@ static NSTimeInterval launchStartedAt;
|
|||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application
|
||||
|
@ -868,97 +641,8 @@ static NSTimeInterval launchStartedAt;
|
|||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
- (BOOL)hasCall
|
||||
{
|
||||
return self.windowManager.hasCall;
|
||||
}
|
||||
|
||||
#pragma mark Push Notifications Delegate Methods
|
||||
|
||||
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (self.didAppLaunchFail) {
|
||||
OWSFailDebug(@"App launch failed");
|
||||
return;
|
||||
}
|
||||
if (!(AppReadiness.isAppReady && [self.tsAccountManager isRegisteredAndReady])) {
|
||||
OWSLogInfo(@"Ignoring remote notification; app not ready.");
|
||||
return;
|
||||
}
|
||||
|
||||
[LKLogger print:@"[Loki] Silent push notification received; fetching messages."];
|
||||
|
||||
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
|
||||
fetchMessagesPromise = nil;
|
||||
}).catch(^{
|
||||
fetchMessagesPromise = nil;
|
||||
});
|
||||
[fetchMessagesPromise retainUntilComplete];
|
||||
|
||||
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
|
||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
|
||||
}];
|
||||
for (LKPublicChat *publicChat in publicChats) {
|
||||
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; }
|
||||
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
|
||||
[poller stop];
|
||||
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
|
||||
[fetchGroupMessagesPromise retainUntilComplete];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
didReceiveRemoteNotification:(NSDictionary *)userInfo
|
||||
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (self.didAppLaunchFail) {
|
||||
OWSFailDebug(@"App launch failed");
|
||||
return;
|
||||
}
|
||||
if (!(AppReadiness.isAppReady && [self.tsAccountManager isRegisteredAndReady])) {
|
||||
OWSLogInfo(@"Ignoring remote notification; app not ready.");
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentAppContext().wasWokenUpBySilentPushNotification = true;
|
||||
|
||||
[LKLogger print:@"[Loki] Silent push notification received; fetching messages."];
|
||||
|
||||
NSMutableArray *promises = [NSMutableArray new];
|
||||
|
||||
__block AnyPromise *fetchMessagesPromise = [AppEnvironment.shared.messageFetcherJob run].then(^{
|
||||
fetchMessagesPromise = nil;
|
||||
}).catch(^{
|
||||
fetchMessagesPromise = nil;
|
||||
});
|
||||
[promises addObject:fetchMessagesPromise];
|
||||
[fetchMessagesPromise retainUntilComplete];
|
||||
|
||||
__block NSDictionary<NSString *, LKPublicChat *> *publicChats;
|
||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
|
||||
}];
|
||||
for (LKPublicChat *publicChat in publicChats.allValues) {
|
||||
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } // For some reason publicChat is sometimes a base 64 encoded string...
|
||||
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
|
||||
[poller stop];
|
||||
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
|
||||
[promises addObject:fetchGroupMessagesPromise];
|
||||
[fetchGroupMessagesPromise retainUntilComplete];
|
||||
}
|
||||
|
||||
PMKJoin(promises).then(^(id results) {
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
CurrentAppContext().wasWokenUpBySilentPushNotification = false;
|
||||
}).catch(^(id error) {
|
||||
completionHandler(UIBackgroundFetchResultFailed);
|
||||
CurrentAppContext().wasWokenUpBySilentPushNotification = false;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
|
@ -1022,8 +706,6 @@ static NSTimeInterval launchStartedAt;
|
|||
withResponseInfo:(NSDictionary *)responseInfo
|
||||
completionHandler:(void (^)())completionHandler
|
||||
{
|
||||
OWSLogInfo(@"Handling action with identifier: %@", identifier);
|
||||
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (self.didAppLaunchFail) {
|
||||
|
@ -1055,8 +737,6 @@ static NSTimeInterval launchStartedAt;
|
|||
- (void)application:(UIApplication *)application
|
||||
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
|
||||
{
|
||||
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
|
||||
if (isUsingFullAPNs) { return; }
|
||||
NSLog(@"[Loki] Performing background fetch.");
|
||||
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
|
||||
NSMutableArray *promises = [NSMutableArray new];
|
||||
|
@ -1113,7 +793,7 @@ static NSTimeInterval launchStartedAt;
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// App isn't ready until storage is ready AND all version migrations are complete.
|
||||
// App isn't ready until storage is ready AND all version migrations are complete
|
||||
if (!self.areVersionMigrationsComplete) {
|
||||
return;
|
||||
}
|
||||
|
@ -1121,12 +801,10 @@ static NSTimeInterval launchStartedAt;
|
|||
return;
|
||||
}
|
||||
if ([AppReadiness isAppReady]) {
|
||||
// Only mark the app as ready once.
|
||||
// Only mark the app as ready once
|
||||
return;
|
||||
}
|
||||
|
||||
OWSLogInfo(@"checkIfAppIsReady");
|
||||
|
||||
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
|
||||
[self.profileManager ensureLocalProfileCached];
|
||||
|
||||
|
@ -1134,13 +812,9 @@ static NSTimeInterval launchStartedAt;
|
|||
// it will also run all deferred blocks.
|
||||
[AppReadiness setAppIsReady];
|
||||
|
||||
if (CurrentAppContext().isRunningTests) {
|
||||
OWSLogVerbose(@"Skipping post-launch logic in tests.");
|
||||
return;
|
||||
}
|
||||
if (CurrentAppContext().isRunningTests) { return; }
|
||||
|
||||
if ([self.tsAccountManager isRegistered]) {
|
||||
OWSLogInfo(@"localNumber: %@", [TSAccountManager localNumber]);
|
||||
|
||||
// This should happen at any launch, background or foreground
|
||||
__unused AnyPromise *pushTokenpromise =
|
||||
|
@ -1174,8 +848,8 @@ static NSTimeInterval launchStartedAt;
|
|||
//
|
||||
// TODO: Release to production once we have analytics.
|
||||
// TODO: Orphan cleanup is somewhat expensive - not least in doing a bunch
|
||||
// of disk access. We might want to only run it "once per version"
|
||||
// or something like that in production.
|
||||
// TODO: of disk access. We might want to only run it "once per version"
|
||||
// TODO: or something like that in production.
|
||||
[OWSOrphanDataCleaner auditOnLaunchIfNecessary];
|
||||
#endif
|
||||
|
||||
|
@ -1232,17 +906,9 @@ static NSTimeInterval launchStartedAt;
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
OWSLogInfo(@"registrationStateDidChange");
|
||||
|
||||
[self enableBackgroundRefreshIfNecessary];
|
||||
|
||||
if ([self.tsAccountManager isRegistered]) {
|
||||
OWSLogInfo(@"localNumber: %@", [self.tsAccountManager localNumber]);
|
||||
|
||||
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[ExperienceUpgradeFinder.sharedManager markAllAsSeenWithTransaction:transaction];
|
||||
} error:nil];
|
||||
|
||||
// Start running the disappearing messages job in case the newly registered user
|
||||
// enables this feature
|
||||
[self.disappearingMessagesJob startIfNecessary];
|
||||
|
@ -1250,14 +916,10 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
// For non-legacy users, read receipts are on by default.
|
||||
[self.readReceiptManager setAreReadReceiptsEnabled:YES];
|
||||
|
||||
// Loki: Start pollers
|
||||
|
||||
[self startPollerIfNeeded];
|
||||
[self startClosedGroupPollerIfNeeded];
|
||||
[self startOpenGroupPollersIfNeeded];
|
||||
|
||||
// Loki: Get device links
|
||||
[[LKFileServerAPI getDeviceLinksAssociatedWithHexEncodedPublicKey:self.tsAccountManager.localNumber] retainUntilComplete]; // TODO: Is this even needed?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1270,16 +932,9 @@ static NSTimeInterval launchStartedAt;
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
OWSLogInfo(@"ensureRootViewController");
|
||||
|
||||
if (!AppReadiness.isAppReady || self.hasInitialRootViewController) {
|
||||
return;
|
||||
}
|
||||
if (!AppReadiness.isAppReady || self.hasInitialRootViewController) { return; }
|
||||
self.hasInitialRootViewController = YES;
|
||||
|
||||
NSTimeInterval startupDuration = CACurrentMediaTime() - launchStartedAt;
|
||||
OWSLogInfo(@"Presenting app %.2f seconds after launch started.", startupDuration);
|
||||
|
||||
UIViewController *rootViewController;
|
||||
BOOL navigationBarHidden = NO;
|
||||
if ([self.tsAccountManager isRegistered]) {
|
||||
|
@ -1289,7 +944,7 @@ static NSTimeInterval launchStartedAt;
|
|||
rootViewController = [HomeVC new];
|
||||
}
|
||||
} else {
|
||||
rootViewController = [[OnboardingController new] initialViewController];
|
||||
rootViewController = [LandingVC new];
|
||||
navigationBarHidden = NO;
|
||||
}
|
||||
OWSAssertDebug(rootViewController);
|
||||
|
@ -1298,8 +953,6 @@ static NSTimeInterval launchStartedAt;
|
|||
navigationController.navigationBarHidden = navigationBarHidden;
|
||||
self.window.rootViewController = navigationController;
|
||||
|
||||
[AppUpdateNag.sharedInstance showAppUpgradeNagIfNecessary];
|
||||
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
}
|
||||
|
||||
|
@ -1311,7 +964,6 @@ static NSTimeInterval launchStartedAt;
|
|||
CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
|
||||
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
|
||||
if (CGRectContainsPoint(statusBarFrame, location)) {
|
||||
OWSLogDebug(@"touched status bar");
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:TappedStatusBarNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
@ -1327,7 +979,6 @@ static NSTimeInterval launchStartedAt;
|
|||
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
|
||||
__IOS_AVAILABLE(10.0)__TVOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)__OSX_AVAILABLE(10.14)
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
if (notification.request.content.userInfo[@"remote"]) {
|
||||
OWSLogInfo(@"[Loki] Ignoring remote notifications while the app is in the foreground.");
|
||||
return;
|
||||
|
@ -1352,7 +1003,6 @@ static NSTimeInterval launchStartedAt;
|
|||
withCompletionHandler:(void (^)(void))completionHandler __IOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)
|
||||
__OSX_AVAILABLE(10.14)__TVOS_PROHIBITED
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
[AppReadiness runNowOrWhenAppDidBecomeReady:^() {
|
||||
[self.userNotificationActionHandler handleNotificationResponse:response completionHandler:completionHandler];
|
||||
}];
|
||||
|
@ -1366,7 +1016,7 @@ static NSTimeInterval launchStartedAt;
|
|||
openSettingsForNotification:(nullable UNNotification *)notification __IOS_AVAILABLE(12.0)
|
||||
__OSX_AVAILABLE(10.14)__WATCHOS_PROHIBITED __TVOS_PROHIBITED
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Loki
|
||||
|
|
|
@ -31,40 +31,31 @@ class SyncPushTokensJob: NSObject {
|
|||
}
|
||||
|
||||
func run() -> Promise<Void> {
|
||||
Logger.info("Starting.")
|
||||
|
||||
let runPromise = firstly {
|
||||
return self.pushRegistrationManager.requestPushTokens()
|
||||
}.then { (pushToken: String, voipToken: String) -> Promise<Void> in
|
||||
Logger.info("finished: requesting push tokens")
|
||||
var shouldUploadTokens = false
|
||||
|
||||
if self.preferences.getPushToken() != pushToken || self.preferences.getVoipToken() != voipToken {
|
||||
Logger.debug("Push tokens changed.")
|
||||
shouldUploadTokens = true
|
||||
} else if !self.uploadOnlyIfStale {
|
||||
Logger.debug("Forced uploading, even though tokens didn't change.")
|
||||
shouldUploadTokens = true
|
||||
}
|
||||
|
||||
if AppVersion.sharedInstance().lastAppVersion != AppVersion.sharedInstance().currentAppVersion {
|
||||
Logger.info("Uploading due to fresh install or app upgrade.")
|
||||
shouldUploadTokens = true
|
||||
}
|
||||
|
||||
guard shouldUploadTokens else {
|
||||
Logger.info("No reason to upload pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
Logger.warn("uploading tokens to account servers. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
||||
return firstly {
|
||||
self.accountManager.updatePushTokens(pushToken: pushToken, voipToken: voipToken, isForcedUpdate: shouldUploadTokens)
|
||||
}.done { _ in
|
||||
self.recordPushTokensLocally(pushToken: pushToken, voipToken: voipToken)
|
||||
}
|
||||
}.done {
|
||||
Logger.info("completed successfully.")
|
||||
}
|
||||
|
||||
runPromise.retainUntilComplete()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@
|
|||
#import "DebugUIPage.h"
|
||||
#import "DebugUITableViewController.h"
|
||||
#import "FingerprintViewController.h"
|
||||
#import "HomeViewCell.h"
|
||||
#import "HomeViewController.h"
|
||||
#import "MediaDetailViewController.h"
|
||||
#import "NotificationSettingsViewController.h"
|
||||
#import "OWSAddToContactViewController.h"
|
||||
|
@ -129,4 +127,3 @@
|
|||
#import <WebRTC/RTCAudioSession.h>
|
||||
#import <WebRTC/RTCCameraPreviewView.h>
|
||||
#import <YYImage/YYImage.h>
|
||||
#import "NewGroupViewController.h"
|
||||
|
|
|
@ -10,11 +10,9 @@
|
|||
#import "NotificationSettingsViewController.h"
|
||||
#import "OWSBackup.h"
|
||||
#import "OWSBackupSettingsViewController.h"
|
||||
#import "OWSLinkedDevicesTableViewController.h"
|
||||
#import "OWSNavigationController.h"
|
||||
#import "PrivacySettingsTableViewController.h"
|
||||
#import "ProfileViewController.h"
|
||||
#import "RegistrationUtils.h"
|
||||
#import "Session-Swift.h"
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/OWSContactsManager.h>
|
||||
|
@ -559,7 +557,7 @@
|
|||
|
||||
- (void)reregisterUser
|
||||
{
|
||||
[RegistrationUtils showReregistrationUIFromViewController:self];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Dark Theme
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSQRCodeScanningViewController.h"
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class OWSLinkedDevicesTableViewController;
|
||||
|
||||
@interface OWSLinkDeviceViewController : OWSViewController
|
||||
|
||||
@property (nonatomic, weak) OWSLinkedDevicesTableViewController *linkedDevicesTableViewController;
|
||||
|
||||
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithString:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,279 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSLinkDeviceViewController.h"
|
||||
#import "OWSDeviceProvisioningURLParser.h"
|
||||
#import "OWSLinkedDevicesTableViewController.h"
|
||||
#import "Session-Swift.h"
|
||||
#import <SessionCoreKit/Cryptography.h>
|
||||
#import <SignalMessaging/OWSProfileManager.h>
|
||||
#import <SessionServiceKit/OWSDevice.h>
|
||||
#import <SessionServiceKit/OWSDeviceProvisioner.h>
|
||||
#import <SessionServiceKit/OWSIdentityManager.h>
|
||||
#import <SessionServiceKit/OWSReadReceiptManager.h>
|
||||
#import <SessionServiceKit/TSAccountManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSLinkDeviceViewController () <OWSQRScannerDelegate>
|
||||
|
||||
@property (nonatomic) YapDatabaseConnection *dbConnection;
|
||||
@property (nonatomic) UIView *qrScanningView;
|
||||
@property (nonatomic) UILabel *scanningInstructionsLabel;
|
||||
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
|
||||
@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSLinkDeviceViewController
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = Theme.backgroundColor;
|
||||
|
||||
self.dbConnection = [[OWSPrimaryStorage sharedManager] newDatabaseConnection];
|
||||
|
||||
UIImage *heroImage = [UIImage imageNamed:@"ic_devices_ios"];
|
||||
OWSAssertDebug(heroImage);
|
||||
UIImageView *heroImageView = [[UIImageView alloc] initWithImage:heroImage];
|
||||
[heroImageView autoSetDimensionsToSize:heroImage.size];
|
||||
|
||||
self.scanningInstructionsLabel = [UILabel new];
|
||||
self.scanningInstructionsLabel.text = NSLocalizedString(@"LINK_DEVICE_SCANNING_INSTRUCTIONS",
|
||||
@"QR Scanning screen instructions, placed alongside a camera view for scanning QR Codes");
|
||||
self.scanningInstructionsLabel.textColor = Theme.primaryColor;
|
||||
self.scanningInstructionsLabel.font = UIFont.ows_dynamicTypeCaption1Font;
|
||||
self.scanningInstructionsLabel.numberOfLines = 0;
|
||||
self.scanningInstructionsLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
self.scanningInstructionsLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
self.qrScanningController = [OWSQRCodeScanningViewController new];
|
||||
self.qrScanningController.scanDelegate = self;
|
||||
[self.view addSubview:self.qrScanningController.view];
|
||||
[self.qrScanningController.view autoPinEdgeToSuperviewEdge:ALEdgeLeading];
|
||||
[self.qrScanningController.view autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
|
||||
[self.qrScanningController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view];
|
||||
[self.qrScanningController.view autoPinToSquareAspectRatio];
|
||||
|
||||
UIView *bottomView = [UIView new];
|
||||
[self.view addSubview:bottomView];
|
||||
[bottomView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.qrScanningController.view];
|
||||
[bottomView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
|
||||
[bottomView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
|
||||
[bottomView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
|
||||
UIStackView *bottomStack = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
heroImageView,
|
||||
self.scanningInstructionsLabel,
|
||||
]];
|
||||
bottomStack.axis = UILayoutConstraintAxisVertical;
|
||||
bottomStack.alignment = UIStackViewAlignmentCenter;
|
||||
bottomStack.spacing = 2;
|
||||
bottomStack.layoutMarginsRelativeArrangement = YES;
|
||||
bottomStack.layoutMargins = UIEdgeInsetsMake(20, 20, 20, 20);
|
||||
[bottomView addSubview:bottomStack];
|
||||
[bottomStack autoPinWidthToSuperview];
|
||||
[bottomStack autoVCenterInSuperview];
|
||||
|
||||
self.title
|
||||
= NSLocalizedString(@"LINK_NEW_DEVICE_TITLE", "Navigation title when scanning QR code to add new device.");
|
||||
}
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (OWSProfileManager *)profileManager
|
||||
{
|
||||
return [OWSProfileManager sharedManager];
|
||||
}
|
||||
|
||||
- (OWSReadReceiptManager *)readReceiptManager
|
||||
{
|
||||
return [OWSReadReceiptManager sharedManager];
|
||||
}
|
||||
|
||||
- (id<OWSUDManager>)udManager
|
||||
{
|
||||
return SSKEnvironment.shared.udManager;
|
||||
}
|
||||
|
||||
- (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
return TSAccountManager.sharedInstance;
|
||||
}
|
||||
|
||||
- (TSSocketManager *)socketManager
|
||||
{
|
||||
return SSKEnvironment.shared.socketManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[UIDevice.currentDevice ows_setOrientation:UIInterfaceOrientationPortrait];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.qrScanningController startCapture];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - OWSQRScannerDelegate
|
||||
|
||||
- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithString:(NSString *)string
|
||||
{
|
||||
OWSDeviceProvisioningURLParser *parser = [[OWSDeviceProvisioningURLParser alloc] initWithProvisioningURL:string];
|
||||
if (!parser.isValid) {
|
||||
OWSLogError(@"Unable to parse provisioning params from QRCode: %@", string);
|
||||
|
||||
NSString *title = NSLocalizedString(@"LINK_DEVICE_INVALID_CODE_TITLE", @"report an invalid linking code");
|
||||
NSString *body = NSLocalizedString(@"LINK_DEVICE_INVALID_CODE_BODY", @"report an invalid linking code");
|
||||
|
||||
UIAlertController *alert =
|
||||
[UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *action) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self popToLinkedDeviceList];
|
||||
});
|
||||
}];
|
||||
[alert addAction:cancelAction];
|
||||
|
||||
UIAlertAction *proceedAction =
|
||||
[UIAlertAction actionWithTitle:NSLocalizedString(@"LINK_DEVICE_RESTART", @"attempt another linking")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self.qrScanningController startCapture];
|
||||
}];
|
||||
[alert addAction:proceedAction];
|
||||
|
||||
[self presentAlert:alert];
|
||||
} else {
|
||||
NSString *title = NSLocalizedString(
|
||||
@"LINK_DEVICE_PERMISSION_ALERT_TITLE", @"confirm the users intent to link a new device");
|
||||
NSString *linkingDescription
|
||||
= NSLocalizedString(@"LINK_DEVICE_PERMISSION_ALERT_BODY", @"confirm the users intent to link a new device");
|
||||
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
|
||||
message:linkingDescription
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *action) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self popToLinkedDeviceList];
|
||||
});
|
||||
}];
|
||||
[alert addAction:cancelAction];
|
||||
|
||||
UIAlertAction *proceedAction =
|
||||
[UIAlertAction actionWithTitle:NSLocalizedString(@"CONFIRM_LINK_NEW_DEVICE_ACTION", @"Button text")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self provisionWithParser:parser];
|
||||
}];
|
||||
[alert addAction:proceedAction];
|
||||
|
||||
[self presentAlert:alert];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)provisionWithParser:(OWSDeviceProvisioningURLParser *)parser
|
||||
{
|
||||
// Optimistically set this flag.
|
||||
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
|
||||
|
||||
ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
|
||||
OWSAssertDebug(identityKeyPair);
|
||||
NSData *myPublicKey = identityKeyPair.publicKey;
|
||||
NSData *myPrivateKey = identityKeyPair.privateKey;
|
||||
NSString *accountIdentifier = [TSAccountManager localNumber];
|
||||
NSData *myProfileKeyData = self.profileManager.localProfileKey.keyData;
|
||||
BOOL areReadReceiptsEnabled = self.readReceiptManager.areReadReceiptsEnabled;
|
||||
|
||||
OWSDeviceProvisioner *provisioner = [[OWSDeviceProvisioner alloc] initWithMyPublicKey:myPublicKey
|
||||
myPrivateKey:myPrivateKey
|
||||
theirPublicKey:parser.publicKey
|
||||
theirEphemeralDeviceId:parser.ephemeralDeviceId
|
||||
accountIdentifier:accountIdentifier
|
||||
profileKey:myProfileKeyData
|
||||
readReceiptsEnabled:areReadReceiptsEnabled];
|
||||
|
||||
[provisioner
|
||||
provisionWithSuccess:^{
|
||||
OWSLogInfo(@"Successfully provisioned device.");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.linkedDevicesTableViewController expectMoreDevices];
|
||||
[self popToLinkedDeviceList];
|
||||
|
||||
// The service implementation of the socket connection caches the linked device state,
|
||||
// so all sync message sends will fail on the socket until it is cycled.
|
||||
[TSSocketManager.shared cycleSocket];
|
||||
|
||||
// Fetch the local profile to determine if all
|
||||
// linked devices support UD.
|
||||
[self.profileManager fetchLocalUsersProfile];
|
||||
});
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
OWSLogError(@"Failed to provision device with error: %@", error);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self presentAlert:[self retryAlertControllerWithError:error
|
||||
retryBlock:^{
|
||||
[self provisionWithParser:parser];
|
||||
}]];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIAlertController *)retryAlertControllerWithError:(NSError *)error retryBlock:(void (^)(void))retryBlock
|
||||
{
|
||||
NSString *title = NSLocalizedString(@"LINKING_DEVICE_FAILED_TITLE", @"Alert Title");
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
|
||||
message:error.localizedDescription
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
retryBlock();
|
||||
}];
|
||||
[alertController addAction:retryAction];
|
||||
|
||||
UIAlertAction *cancelAction =
|
||||
[UIAlertAction actionWithTitle:CommonStrings.cancelButton
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *action) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
});
|
||||
}];
|
||||
[alertController addAction:cancelAction];
|
||||
return alertController;
|
||||
}
|
||||
|
||||
- (void)popToLinkedDeviceList
|
||||
{
|
||||
[self.navigationController popViewControllerWithAnimated:YES
|
||||
completion:^{
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface OWSLinkedDevicesTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
|
||||
|
||||
/**
|
||||
* This is used to show the user there is a device provisioning in-progress.
|
||||
*/
|
||||
- (void)expectMoreDevices;
|
||||
|
||||
@end
|
|
@ -1,445 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSLinkedDevicesTableViewController.h"
|
||||
#import "OWSDeviceTableViewCell.h"
|
||||
#import "OWSLinkDeviceViewController.h"
|
||||
#import "Session-Swift.h"
|
||||
#import "UIViewController+Permissions.h"
|
||||
#import <SessionServiceKit/NSTimer+OWS.h>
|
||||
#import <SessionServiceKit/OWSDevice.h>
|
||||
#import <SessionServiceKit/OWSDevicesService.h>
|
||||
#import <SessionServiceKit/OWSPrimaryStorage.h>
|
||||
#import <SessionServiceKit/TSDatabaseView.h>
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabaseViewConnection.h>
|
||||
#import <YapDatabase/YapDatabaseViewMappings.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSLinkedDevicesTableViewController ()
|
||||
|
||||
@property (nonatomic) YapDatabaseConnection *dbConnection;
|
||||
@property (nonatomic) YapDatabaseViewMappings *deviceMappings;
|
||||
@property (nonatomic) NSTimer *pollingRefreshTimer;
|
||||
@property (nonatomic) BOOL isExpectingMoreDevices;
|
||||
|
||||
@end
|
||||
|
||||
int const OWSLinkedDevicesTableViewControllerSectionExistingDevices = 0;
|
||||
int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
|
||||
|
||||
@implementation OWSLinkedDevicesTableViewController
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
OWSLogVerbose(@"");
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = Theme.backgroundColor;
|
||||
|
||||
self.title = NSLocalizedString(@"LINKED_DEVICES_TITLE", @"Menu item and navbar title for the device manager");
|
||||
|
||||
self.isExpectingMoreDevices = NO;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
self.tableView.estimatedRowHeight = 60;
|
||||
self.tableView.separatorColor = Theme.cellSeparatorColor;
|
||||
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"AddNewDevice"];
|
||||
[self.tableView registerClass:[OWSDeviceTableViewCell class] forCellReuseIdentifier:@"ExistingDevice"];
|
||||
[self.tableView applyScrollViewInsetsFix];
|
||||
|
||||
self.dbConnection = [[OWSPrimaryStorage sharedManager] newDatabaseConnection];
|
||||
[self.dbConnection beginLongLivedReadTransaction];
|
||||
self.deviceMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[ TSSecondaryDevicesGroup ]
|
||||
view:TSSecondaryDevicesDatabaseViewExtensionName];
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
[self.deviceMappings updateWithTransaction:transaction];
|
||||
}];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(yapDatabaseModified:)
|
||||
name:YapDatabaseModifiedNotification
|
||||
object:OWSPrimaryStorage.sharedManager.dbNotificationObject];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(yapDatabaseModifiedExternally:)
|
||||
name:YapDatabaseModifiedExternallyNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deviceListUpdateSucceeded:)
|
||||
name:NSNotificationName_DeviceListUpdateSucceeded
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deviceListUpdateFailed:)
|
||||
name:NSNotificationName_DeviceListUpdateFailed
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deviceListUpdateModifiedDeviceList:)
|
||||
name:NSNotificationName_DeviceListUpdateModifiedDeviceList
|
||||
object:nil];
|
||||
|
||||
self.refreshControl = [UIRefreshControl new];
|
||||
[self.refreshControl addTarget:self action:@selector(refreshDevices) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
[self setupEditButton];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
[self refreshDevices];
|
||||
|
||||
NSIndexPath *_Nullable selectedPath = [self.tableView indexPathForSelectedRow];
|
||||
if (selectedPath) {
|
||||
// HACK to unselect rows when swiping back
|
||||
// http://stackoverflow.com/questions/19379510/uitableviewcell-doesnt-get-deselected-when-swiping-back-quickly
|
||||
[self.tableView deselectRowAtIndexPath:selectedPath animated:animated];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear:animated];
|
||||
[self.pollingRefreshTimer invalidate];
|
||||
}
|
||||
|
||||
// Don't show edit button for an empty table
|
||||
- (void)setupEditButton
|
||||
{
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
if ([OWSDevice hasSecondaryDevicesWithTransaction:transaction]) {
|
||||
self.navigationItem.rightBarButtonItem = self.editButtonItem;
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)expectMoreDevices
|
||||
{
|
||||
self.isExpectingMoreDevices = YES;
|
||||
|
||||
// When you delete and re-add a device, you will be returned to this view in editing mode, making your newly
|
||||
// added device appear with a delete icon. Probably not what you want.
|
||||
self.editing = NO;
|
||||
|
||||
__weak typeof(self) wself = self;
|
||||
[self.pollingRefreshTimer invalidate];
|
||||
self.pollingRefreshTimer = [NSTimer weakScheduledTimerWithTimeInterval:(10.0)target:wself
|
||||
selector:@selector(refreshDevices)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
|
||||
NSString *progressText = NSLocalizedString(@"WAITING_TO_COMPLETE_DEVICE_LINK_TEXT",
|
||||
@"Activity indicator title, shown upon returning to the device "
|
||||
@"manager, until you complete the provisioning process on desktop");
|
||||
NSAttributedString *progressTitle = [[NSAttributedString alloc] initWithString:progressText];
|
||||
|
||||
// HACK to get refreshControl title to align properly.
|
||||
self.refreshControl.attributedTitle = progressTitle;
|
||||
[self.refreshControl endRefreshing];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.refreshControl.attributedTitle = progressTitle;
|
||||
[self.refreshControl beginRefreshing];
|
||||
// Needed to show refresh control programatically
|
||||
[self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:NO];
|
||||
});
|
||||
// END HACK to get refreshControl title to align properly.
|
||||
}
|
||||
|
||||
- (void)refreshDevices
|
||||
{
|
||||
[OWSDevicesService refreshDevices];
|
||||
}
|
||||
|
||||
- (void)deviceListUpdateSucceeded:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
[self.refreshControl endRefreshing];
|
||||
}
|
||||
|
||||
- (void)deviceListUpdateFailed:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
NSError *error = notification.object;
|
||||
OWSAssertDebug(error);
|
||||
|
||||
NSString *alertTitle = NSLocalizedString(
|
||||
@"DEVICE_LIST_UPDATE_FAILED_TITLE", @"Alert title that can occur when viewing device manager.");
|
||||
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:alertTitle
|
||||
message:error.localizedDescription
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self refreshDevices];
|
||||
}];
|
||||
[alert addAction:retryAction];
|
||||
|
||||
UIAlertAction *dismissAction =
|
||||
[UIAlertAction actionWithTitle:CommonStrings.dismissButton style:UIAlertActionStyleCancel handler:nil];
|
||||
[alert addAction:dismissAction];
|
||||
|
||||
[self.refreshControl endRefreshing];
|
||||
[self presentAlert:alert];
|
||||
}
|
||||
|
||||
- (void)deviceListUpdateModifiedDeviceList:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// Got our new device, we can stop refreshing.
|
||||
self.isExpectingMoreDevices = NO;
|
||||
[self.pollingRefreshTimer invalidate];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.refreshControl.attributedTitle = nil;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (void)yapDatabaseModifiedExternally:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// External database modifications can't be converted into incremental updates,
|
||||
// so rebuild everything. This is expensive and usually isn't necessary, but
|
||||
// there's no alternative.
|
||||
[self.dbConnection beginLongLivedReadTransaction];
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
[self.deviceMappings updateWithTransaction:transaction];
|
||||
}];
|
||||
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)yapDatabaseModified:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
OWSLogVerbose(@"");
|
||||
|
||||
NSArray *notifications = [self.dbConnection beginLongLivedReadTransaction];
|
||||
[self setupEditButton];
|
||||
|
||||
if ([notifications count] == 0) {
|
||||
return; // already processed commit
|
||||
}
|
||||
|
||||
NSArray *rowChanges;
|
||||
[[self.dbConnection ext:TSSecondaryDevicesDatabaseViewExtensionName] getSectionChanges:nil
|
||||
rowChanges:&rowChanges
|
||||
forNotifications:notifications
|
||||
withMappings:self.deviceMappings];
|
||||
if (rowChanges.count == 0) {
|
||||
// There aren't any changes that affect our tableView!
|
||||
return;
|
||||
}
|
||||
|
||||
[self.tableView beginUpdates];
|
||||
|
||||
for (YapDatabaseViewRowChange *rowChange in rowChanges) {
|
||||
switch (rowChange.type) {
|
||||
case YapDatabaseViewChangeDelete: {
|
||||
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeInsert: {
|
||||
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeMove: {
|
||||
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeUpdate: {
|
||||
[self.tableView reloadRowsAtIndexPaths:@[ rowChange.indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self.tableView endUpdates];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
switch (section) {
|
||||
case OWSLinkedDevicesTableViewControllerSectionExistingDevices:
|
||||
return (NSInteger)[self.deviceMappings numberOfItemsInSection:(NSUInteger)section];
|
||||
case OWSLinkedDevicesTableViewControllerSectionAddDevice:
|
||||
return 1;
|
||||
default:
|
||||
OWSLogError(@"Unknown section: %ld", (long)section);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
|
||||
|
||||
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionAddDevice) {
|
||||
[self ows_askForCameraPermissions:^(BOOL granted) {
|
||||
if (!granted) {
|
||||
return;
|
||||
}
|
||||
[self showLinkNewDeviceView];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showLinkNewDeviceView
|
||||
{
|
||||
OWSLinkDeviceViewController *vc = [OWSLinkDeviceViewController new];
|
||||
vc.linkedDevicesTableViewController = self;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionAddDevice) {
|
||||
UITableViewCell *cell =
|
||||
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"AddNewDevice"];
|
||||
[OWSTableItem configureCell:cell];
|
||||
cell.textLabel.text
|
||||
= NSLocalizedString(@"LINK_NEW_DEVICE_TITLE", @"Navigation title when scanning QR code to add new device.");
|
||||
cell.detailTextLabel.text
|
||||
= NSLocalizedString(@"LINK_NEW_DEVICE_SUBTITLE", @"Subheading for 'Link New Device' navigation");
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
cell.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(OWSLinkedDevicesTableViewController, @"add");
|
||||
return cell;
|
||||
} else if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionExistingDevices) {
|
||||
OWSDeviceTableViewCell *cell =
|
||||
[tableView dequeueReusableCellWithIdentifier:@"ExistingDevice" forIndexPath:indexPath];
|
||||
OWSDevice *device = [self deviceForRowAtIndexPath:indexPath];
|
||||
[cell configureWithDevice:device];
|
||||
return cell;
|
||||
} else {
|
||||
OWSLogError(@"Unknown section: %@", indexPath);
|
||||
return [UITableViewCell new];
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable OWSDevice *)deviceForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionExistingDevices) {
|
||||
__block OWSDevice *device;
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
device = [[transaction extension:TSSecondaryDevicesDatabaseViewExtensionName]
|
||||
objectAtIndexPath:indexPath
|
||||
withMappings:self.deviceMappings];
|
||||
}];
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return indexPath.section == OWSLinkedDevicesTableViewControllerSectionExistingDevices;
|
||||
}
|
||||
|
||||
- (nullable NSString *)tableView:(UITableView *)tableView
|
||||
titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return NSLocalizedString(@"UNLINK_ACTION", "button title for unlinking a device");
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView
|
||||
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
|
||||
forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete) {
|
||||
OWSDevice *device = [self deviceForRowAtIndexPath:indexPath];
|
||||
[self
|
||||
touchedUnlinkControlForDevice:device
|
||||
success:^{
|
||||
OWSLogInfo(@"Removing unlinked device with deviceId: %ld", (long)device.deviceId);
|
||||
[device remove];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchedUnlinkControlForDevice:(OWSDevice *)device success:(void (^)(void))successCallback
|
||||
{
|
||||
NSString *confirmationTitleFormat
|
||||
= NSLocalizedString(@"UNLINK_CONFIRMATION_ALERT_TITLE", @"Alert title for confirming device deletion");
|
||||
NSString *confirmationTitle = [NSString stringWithFormat:confirmationTitleFormat, device.displayName];
|
||||
NSString *confirmationMessage
|
||||
= NSLocalizedString(@"UNLINK_CONFIRMATION_ALERT_BODY", @"Alert message to confirm unlinking a device");
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:confirmationTitle
|
||||
message:confirmationMessage
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[alert addAction:[OWSAlerts cancelAction]];
|
||||
|
||||
UIAlertAction *unlinkAction =
|
||||
[UIAlertAction actionWithTitle:NSLocalizedString(@"UNLINK_ACTION", "button title for unlinking a device")
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *action) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self unlinkDevice:device success:successCallback];
|
||||
});
|
||||
}];
|
||||
[alert addAction:unlinkAction];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self presentAlert:alert];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)unlinkDevice:(OWSDevice *)device success:(void (^)(void))successCallback
|
||||
{
|
||||
[OWSDevicesService unlinkDevice:device
|
||||
success:successCallback
|
||||
failure:^(NSError *error) {
|
||||
NSString *title = NSLocalizedString(
|
||||
@"UNLINKING_FAILED_ALERT_TITLE", @"Alert title when unlinking device fails");
|
||||
UIAlertController *alert =
|
||||
[UIAlertController alertControllerWithTitle:title
|
||||
message:error.localizedDescription
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *retryAction =
|
||||
[UIAlertAction actionWithTitle:[CommonStrings retryButton]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *aaction) {
|
||||
[self unlinkDevice:device success:successCallback];
|
||||
}];
|
||||
[alert addAction:retryAction];
|
||||
[alert addAction:[OWSAlerts cancelAction]];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self presentAlert:alert];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#import "PrivacySettingsTableViewController.h"
|
||||
#import "BlockListViewController.h"
|
||||
#import "OWS2FASettingsViewController.h"
|
||||
#import "Session-Swift.h"
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/OWSPreferences.h>
|
||||
|
@ -555,11 +554,7 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s
|
|||
|
||||
- (void)show2FASettings
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
|
||||
OWS2FASettingsViewController *vc = [OWS2FASettingsViewController new];
|
||||
vc.mode = OWS2FASettingsMode_Status;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
|
||||
}
|
||||
|
||||
- (void)isScreenLockEnabledDidChange:(UISwitch *)sender
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -1,581 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HomeViewCell.h"
|
||||
#import "OWSAvatarBuilder.h"
|
||||
#import "Session-Swift.h"
|
||||
#import <SignalMessaging/OWSFormat.h>
|
||||
#import <SignalMessaging/OWSUserProfile.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SessionServiceKit/OWSMath.h>
|
||||
#import <SessionServiceKit/OWSMessageManager.h>
|
||||
#import <SessionServiceKit/TSContactThread.h>
|
||||
#import <SessionServiceKit/TSGroupThread.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HomeViewCell ()
|
||||
|
||||
@property (nonatomic) AvatarImageView *avatarView;
|
||||
@property (nonatomic) UILabel *nameLabel;
|
||||
@property (nonatomic) UILabel *snippetLabel;
|
||||
@property (nonatomic) UILabel *dateTimeLabel;
|
||||
@property (nonatomic) MessageStatusView *messageStatusView;
|
||||
@property (nonatomic) TypingIndicatorView *typingIndicatorView;
|
||||
|
||||
@property (nonatomic) UIView *unreadBadge;
|
||||
@property (nonatomic) UILabel *unreadLabel;
|
||||
|
||||
@property (nonatomic, nullable) ThreadViewModel *thread;
|
||||
@property (nonatomic, nullable) NSAttributedString *overrideSnippet;
|
||||
@property (nonatomic) BOOL isBlocked;
|
||||
|
||||
@property (nonatomic, readonly) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation HomeViewCell
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (OWSContactsManager *)contactsManager
|
||||
{
|
||||
OWSAssertDebug(Environment.shared.contactsManager);
|
||||
|
||||
return Environment.shared.contactsManager;
|
||||
}
|
||||
|
||||
- (id<OWSTypingIndicators>)typingIndicators
|
||||
{
|
||||
return SSKEnvironment.shared.typingIndicators;
|
||||
}
|
||||
|
||||
- (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
|
||||
return SSKEnvironment.shared.tsAccountManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier
|
||||
{
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self commontInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// `[UIView init]` invokes `[self initWithFrame:...]`.
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self commontInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commontInit
|
||||
{
|
||||
OWSAssertDebug(!self.avatarView);
|
||||
|
||||
self.backgroundColor = Theme.backgroundColor;
|
||||
|
||||
_viewConstraints = [NSMutableArray new];
|
||||
|
||||
self.avatarView = [[AvatarImageView alloc] init];
|
||||
[self.contentView addSubview:self.avatarView];
|
||||
[self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize];
|
||||
[self.avatarView autoSetDimension:ALDimensionHeight toSize:self.avatarSize];
|
||||
[self.avatarView autoPinLeadingToSuperviewMargin];
|
||||
[self.avatarView autoVCenterInSuperview];
|
||||
[self.avatarView setContentHuggingHigh];
|
||||
[self.avatarView setCompressionResistanceHigh];
|
||||
// Ensure that the cell's contents never overflow the cell bounds.
|
||||
[self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual];
|
||||
[self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual];
|
||||
|
||||
self.nameLabel = [UILabel new];
|
||||
self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
self.nameLabel.font = self.nameFont;
|
||||
[self.nameLabel setContentHuggingHorizontalLow];
|
||||
[self.nameLabel setCompressionResistanceHorizontalLow];
|
||||
|
||||
self.dateTimeLabel = [UILabel new];
|
||||
[self.dateTimeLabel setContentHuggingHorizontalHigh];
|
||||
[self.dateTimeLabel setCompressionResistanceHorizontalHigh];
|
||||
|
||||
self.messageStatusView = [MessageStatusView new];
|
||||
[self.messageStatusView setContentHuggingHorizontalHigh];
|
||||
[self.messageStatusView setCompressionResistanceHorizontalHigh];
|
||||
|
||||
UIStackView *topRowView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.nameLabel,
|
||||
self.dateTimeLabel,
|
||||
]];
|
||||
topRowView.axis = UILayoutConstraintAxisHorizontal;
|
||||
topRowView.alignment = UIStackViewAlignmentLastBaseline;
|
||||
topRowView.spacing = 6.f;
|
||||
|
||||
self.snippetLabel = [UILabel new];
|
||||
self.snippetLabel.font = [self snippetFont];
|
||||
self.snippetLabel.numberOfLines = 1;
|
||||
self.snippetLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
[self.snippetLabel setContentHuggingHorizontalLow];
|
||||
[self.snippetLabel setCompressionResistanceHorizontalLow];
|
||||
|
||||
self.typingIndicatorView = [TypingIndicatorView new];
|
||||
[self.contentView addSubview:self.typingIndicatorView];
|
||||
|
||||
UIStackView *bottomRowView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.snippetLabel,
|
||||
self.messageStatusView,
|
||||
]];
|
||||
|
||||
bottomRowView.axis = UILayoutConstraintAxisHorizontal;
|
||||
bottomRowView.alignment = UIStackViewAlignmentLastBaseline;
|
||||
bottomRowView.spacing = 6.f;
|
||||
|
||||
UIStackView *vStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
topRowView,
|
||||
bottomRowView,
|
||||
]];
|
||||
vStackView.axis = UILayoutConstraintAxisVertical;
|
||||
|
||||
[self.contentView addSubview:vStackView];
|
||||
[vStackView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing];
|
||||
[vStackView autoVCenterInSuperview];
|
||||
// Ensure that the cell's contents never overflow the cell bounds.
|
||||
[vStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual];
|
||||
[vStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual];
|
||||
[vStackView autoPinTrailingToSuperviewMargin];
|
||||
|
||||
vStackView.userInteractionEnabled = NO;
|
||||
|
||||
self.unreadLabel = [UILabel new];
|
||||
self.unreadLabel.textColor = [UIColor ows_whiteColor];
|
||||
self.unreadLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
self.unreadLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self.unreadLabel setContentHuggingHigh];
|
||||
[self.unreadLabel setCompressionResistanceHigh];
|
||||
|
||||
self.unreadBadge = [NeverClearView new];
|
||||
self.unreadBadge.backgroundColor = [UIColor ows_materialBlueColor];
|
||||
[self.unreadBadge addSubview:self.unreadLabel];
|
||||
[self.unreadLabel autoCenterInSuperview];
|
||||
[self.unreadBadge setContentHuggingHigh];
|
||||
[self.unreadBadge setCompressionResistanceHigh];
|
||||
|
||||
[self.contentView addSubview:self.unreadBadge];
|
||||
[self.unreadBadge autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.nameLabel];
|
||||
|
||||
[self.typingIndicatorView autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:self.snippetLabel];
|
||||
[self.typingIndicatorView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.snippetLabel];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
+ (NSString *)cellReuseIdentifier
|
||||
{
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (void)initializeLayout
|
||||
{
|
||||
self.selectionStyle = UITableViewCellSelectionStyleDefault;
|
||||
}
|
||||
|
||||
- (nullable NSString *)reuseIdentifier
|
||||
{
|
||||
return NSStringFromClass(self.class);
|
||||
}
|
||||
|
||||
- (void)configureWithThread:(ThreadViewModel *)thread
|
||||
isBlocked:(BOOL)isBlocked
|
||||
{
|
||||
[self configureWithThread:thread
|
||||
isBlocked:isBlocked
|
||||
overrideSnippet:nil
|
||||
overrideDate:nil];
|
||||
}
|
||||
|
||||
- (void)configureWithThread:(ThreadViewModel *)thread
|
||||
isBlocked:(BOOL)isBlocked
|
||||
overrideSnippet:(nullable NSAttributedString *)overrideSnippet
|
||||
overrideDate:(nullable NSDate *)overrideDate
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(thread);
|
||||
|
||||
[OWSTableItem configureCell:self];
|
||||
|
||||
self.thread = thread;
|
||||
self.overrideSnippet = overrideSnippet;
|
||||
self.isBlocked = isBlocked;
|
||||
|
||||
BOOL hasUnreadMessages = thread.hasUnreadMessages;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(otherUsersProfileDidChange:)
|
||||
name:kNSNotificationName_OtherUsersProfileDidChange
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(typingIndicatorStateDidChange:)
|
||||
name:[OWSTypingIndicatorsImpl typingIndicatorStateDidChange]
|
||||
object:nil];
|
||||
[self updateNameLabel];
|
||||
[self updateAvatarView];
|
||||
|
||||
// We update the fonts every time this cell is configured to ensure that
|
||||
// changes to the dynamic type settings are reflected.
|
||||
self.snippetLabel.font = [self snippetFont];
|
||||
|
||||
[self updatePreview];
|
||||
|
||||
self.dateTimeLabel.text
|
||||
= (overrideDate ? [self stringForDate:overrideDate] : [self stringForDate:thread.lastMessageDate]);
|
||||
|
||||
UIColor *textColor = [Theme secondaryColor];
|
||||
if (hasUnreadMessages && overrideSnippet == nil) {
|
||||
textColor = [Theme primaryColor];
|
||||
self.dateTimeLabel.font = self.dateTimeFont.ows_mediumWeight;
|
||||
} else {
|
||||
self.dateTimeLabel.font = self.dateTimeFont;
|
||||
}
|
||||
self.dateTimeLabel.textColor = textColor;
|
||||
|
||||
NSUInteger unreadCount = thread.unreadCount;
|
||||
if (overrideSnippet) {
|
||||
// If we're using the home view cell to render search results,
|
||||
// don't show "unread badge" or "message status" indicator.
|
||||
self.unreadBadge.hidden = YES;
|
||||
self.messageStatusView.hidden = YES;
|
||||
} else if (unreadCount > 0) {
|
||||
// If there are unread messages, show the "unread badge."
|
||||
// The "message status" indicators is redundant.
|
||||
self.unreadBadge.hidden = NO;
|
||||
self.messageStatusView.hidden = YES;
|
||||
|
||||
self.unreadLabel.text = [OWSFormat formatInt:(int)unreadCount];
|
||||
self.unreadLabel.font = self.unreadFont;
|
||||
const int unreadBadgeHeight = (int)ceil(self.unreadLabel.font.lineHeight * 1.5f);
|
||||
self.unreadBadge.layer.cornerRadius = unreadBadgeHeight / 2;
|
||||
self.unreadBadge.layer.borderColor = Theme.backgroundColor.CGColor;
|
||||
self.unreadBadge.layer.borderWidth = 1.f;
|
||||
|
||||
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh
|
||||
forConstraints:^{
|
||||
// This is a bit arbitrary, but it should scale with the size of dynamic text
|
||||
CGFloat minMargin = CeilEven(unreadBadgeHeight * .5f);
|
||||
|
||||
// Spec check. Should be 12pts (6pt on each side) when using default font size.
|
||||
OWSAssertDebug(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12);
|
||||
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
// badge sizing
|
||||
[self.unreadBadge autoMatchDimension:ALDimensionWidth
|
||||
toDimension:ALDimensionWidth
|
||||
ofView:self.unreadLabel
|
||||
withOffset:minMargin
|
||||
relation:NSLayoutRelationGreaterThanOrEqual],
|
||||
[self.unreadBadge autoSetDimension:ALDimensionWidth
|
||||
toSize:unreadBadgeHeight
|
||||
relation:NSLayoutRelationGreaterThanOrEqual],
|
||||
[self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight],
|
||||
[self.unreadBadge autoPinEdge:ALEdgeTrailing
|
||||
toEdge:ALEdgeTrailing
|
||||
ofView:self.avatarView
|
||||
withOffset:6.f],
|
||||
]];
|
||||
}];
|
||||
} else {
|
||||
UIImage *_Nullable statusIndicatorImage = nil;
|
||||
// TODO: Theme, Review with design.
|
||||
UIColor *messageStatusViewTintColor
|
||||
= (Theme.isDarkThemeEnabled ? [UIColor ows_gray25Color] : [UIColor ows_gray45Color]);
|
||||
BOOL shouldAnimateStatusIcon = NO;
|
||||
if ([self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.thread.lastMessageForInbox;
|
||||
|
||||
MessageReceiptStatus messageStatus =
|
||||
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
|
||||
switch (messageStatus) {
|
||||
case MessageReceiptStatusCalculatingPoW:
|
||||
statusIndicatorImage = [UIImage imageNamed:@"Cog"];
|
||||
shouldAnimateStatusIcon = YES;
|
||||
break;
|
||||
case MessageReceiptStatusUploading:
|
||||
case MessageReceiptStatusSending:
|
||||
statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"];
|
||||
shouldAnimateStatusIcon = YES;
|
||||
break;
|
||||
case MessageReceiptStatusSent:
|
||||
case MessageReceiptStatusSkipped:
|
||||
statusIndicatorImage = [UIImage imageNamed:@"message_status_sent"];
|
||||
break;
|
||||
case MessageReceiptStatusDelivered:
|
||||
statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"];
|
||||
break;
|
||||
case MessageReceiptStatusRead:
|
||||
statusIndicatorImage = [UIImage imageNamed:@"message_status_read"];
|
||||
break;
|
||||
case MessageReceiptStatusFailed:
|
||||
statusIndicatorImage = [UIImage imageNamed:@"message_status_failed"];
|
||||
messageStatusViewTintColor = [UIColor ows_destructiveRedColor];
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.messageStatusView.image = [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
self.messageStatusView.tintColor = messageStatusViewTintColor;
|
||||
self.messageStatusView.hidden = statusIndicatorImage == nil;
|
||||
self.unreadBadge.hidden = YES;
|
||||
if (shouldAnimateStatusIcon) {
|
||||
CABasicAnimation *animation;
|
||||
animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
|
||||
animation.toValue = @(M_PI * 2.0);
|
||||
const CGFloat kPeriodSeconds = 1.f;
|
||||
animation.duration = kPeriodSeconds;
|
||||
animation.cumulative = YES;
|
||||
animation.repeatCount = HUGE_VALF;
|
||||
[self.messageStatusView.layer addAnimation:animation forKey:@"animation"];
|
||||
} else {
|
||||
[self.messageStatusView.layer removeAllAnimations];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateAvatarView
|
||||
{
|
||||
ThreadViewModel *thread = self.thread;
|
||||
if (thread == nil) {
|
||||
OWSFailDebug(@"thread should not be nil");
|
||||
self.avatarView.image = nil;
|
||||
return;
|
||||
}
|
||||
self.avatarView.contactID = thread.contactIdentifier;
|
||||
[self.avatarView updateOnlineStatusIndicator];
|
||||
self.avatarView.image = [OWSAvatarBuilder buildImageForThread:thread.threadRecord diameter:self.avatarSize];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedSnippetForThread:(ThreadViewModel *)thread isBlocked:(BOOL)isBlocked
|
||||
{
|
||||
OWSAssertDebug(thread);
|
||||
|
||||
BOOL hasUnreadMessages = thread.hasUnreadMessages;
|
||||
|
||||
NSMutableAttributedString *snippetText = [NSMutableAttributedString new];
|
||||
if (isBlocked) {
|
||||
// If thread is blocked, don't show a snippet or mute status.
|
||||
[snippetText appendAttributedString:
|
||||
[[NSAttributedString alloc]
|
||||
initWithString:NSLocalizedString(@"HOME_VIEW_BLOCKED_CONVERSATION",
|
||||
@"Table cell subtitle label for a conversation the user has blocked.")
|
||||
attributes:@{
|
||||
NSFontAttributeName : self.snippetFont.ows_mediumWeight,
|
||||
NSForegroundColorAttributeName : [Theme primaryColor],
|
||||
}]];
|
||||
} else {
|
||||
if (thread.isMuted && !thread.isGroupThread) {
|
||||
[snippetText appendAttributedString:[[NSAttributedString alloc]
|
||||
initWithString:LocalizationNotNeeded(@"\ue067 ")
|
||||
attributes:@{
|
||||
NSFontAttributeName : [UIFont ows_elegantIconsFont:9.f],
|
||||
NSForegroundColorAttributeName :
|
||||
(hasUnreadMessages ? [Theme primaryColor]
|
||||
: [Theme secondaryColor]),
|
||||
}]];
|
||||
}
|
||||
NSString *displayableText = thread.lastMessageText;
|
||||
if (displayableText) {
|
||||
[LKMentionsManager populateUserPublicKeyCacheIfNeededFor:thread.threadRecord.uniqueId in:nil]; // TODO: Terrible place to do this, but okay for now
|
||||
displayableText = [LKMentionUtilities highlightMentionsIn:displayableText threadID:thread.threadRecord.uniqueId];
|
||||
[snippetText appendAttributedString:[[NSAttributedString alloc]
|
||||
initWithString:displayableText
|
||||
attributes:@{
|
||||
NSFontAttributeName :
|
||||
(hasUnreadMessages ? self.snippetFont.ows_mediumWeight
|
||||
: self.snippetFont),
|
||||
NSForegroundColorAttributeName :
|
||||
(hasUnreadMessages ? [Theme primaryColor]
|
||||
: [Theme secondaryColor]),
|
||||
}]];
|
||||
}
|
||||
}
|
||||
|
||||
return snippetText;
|
||||
}
|
||||
|
||||
#pragma mark - Date formatting
|
||||
|
||||
- (NSString *)stringForDate:(nullable NSDate *)date
|
||||
{
|
||||
if (date == nil) {
|
||||
OWSFailDebug(@"date was unexpectedly nil");
|
||||
return @"";
|
||||
}
|
||||
|
||||
return [DateUtil formatDateShort:date];
|
||||
}
|
||||
|
||||
#pragma mark - Constants
|
||||
|
||||
- (UIFont *)unreadFont
|
||||
{
|
||||
return [UIFont ows_dynamicTypeCaption1Font].ows_mediumWeight;
|
||||
}
|
||||
|
||||
- (UIFont *)dateTimeFont
|
||||
{
|
||||
return [UIFont ows_dynamicTypeCaption1Font];
|
||||
}
|
||||
|
||||
- (UIFont *)snippetFont
|
||||
{
|
||||
return [UIFont ows_dynamicTypeSubheadlineFont];
|
||||
}
|
||||
|
||||
- (UIFont *)nameFont
|
||||
{
|
||||
return [UIFont ows_dynamicTypeBodyFont].ows_mediumWeight;
|
||||
}
|
||||
|
||||
// Used for profile names.
|
||||
- (UIFont *)nameSecondaryFont
|
||||
{
|
||||
return [UIFont ows_dynamicTypeBodyFont].ows_italic;
|
||||
}
|
||||
|
||||
- (NSUInteger)avatarSize
|
||||
{
|
||||
return kStandardAvatarSize;
|
||||
}
|
||||
|
||||
- (NSUInteger)avatarHSpacing
|
||||
{
|
||||
return 12.f;
|
||||
}
|
||||
|
||||
#pragma mark - Reuse
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:self.viewConstraints];
|
||||
[self.viewConstraints removeAllObjects];
|
||||
|
||||
self.thread = nil;
|
||||
self.overrideSnippet = nil;
|
||||
self.avatarView.image = nil;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark - Name
|
||||
|
||||
- (void)otherUsersProfileDidChange:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
NSString *recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId];
|
||||
if (recipientId.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self.thread isKindOfClass:[TSContactThread class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self.thread.contactIdentifier isEqualToString:recipientId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateNameLabel];
|
||||
[self updateAvatarView];
|
||||
}
|
||||
|
||||
- (void)updateNameLabel
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
self.nameLabel.font = self.nameFont;
|
||||
self.nameLabel.textColor = [Theme primaryColor];
|
||||
|
||||
ThreadViewModel *thread = self.thread;
|
||||
if (thread == nil) {
|
||||
OWSFailDebug(@"thread should not be nil");
|
||||
self.nameLabel.attributedText = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
NSAttributedString *name;
|
||||
if (thread.isGroupThread) {
|
||||
if (thread.name.length == 0) {
|
||||
name = [[NSAttributedString alloc] initWithString:[MessageStrings newGroupDefaultTitle]];
|
||||
} else {
|
||||
name = [[NSAttributedString alloc] initWithString:thread.name];
|
||||
}
|
||||
} else {
|
||||
if (self.thread.threadRecord.isNoteToSelf) {
|
||||
name = [[NSAttributedString alloc]
|
||||
initWithString:NSLocalizedString(@"NOTE_TO_SELF", @"Label for 1:1 conversation with yourself.")
|
||||
attributes:@{
|
||||
NSFontAttributeName : self.nameFont,
|
||||
}];
|
||||
} else {
|
||||
name = [self.contactsManager attributedContactOrProfileNameForPhoneIdentifier:thread.contactIdentifier
|
||||
primaryFont:self.nameFont
|
||||
secondaryFont:self.nameSecondaryFont];
|
||||
}
|
||||
}
|
||||
|
||||
self.nameLabel.attributedText = name;
|
||||
}
|
||||
|
||||
#pragma mark - Typing Indicators
|
||||
|
||||
- (void)updatePreview
|
||||
{
|
||||
if ([self.typingIndicators typingRecipientIdForThread:self.thread.threadRecord] != nil) {
|
||||
// If we hide snippetLabel, our layout will break since UIStackView will remove
|
||||
// it from the layout. Wrapping the preview views (the snippet label and the
|
||||
// typing indicator) in a UIStackView proved non-trivial since we're using
|
||||
// UIStackViewAlignmentLastBaseline. Therefore we hide the _contents_ of the
|
||||
// snippet label using an empty string.
|
||||
self.snippetLabel.text = @" ";
|
||||
self.typingIndicatorView.hidden = NO;
|
||||
[self.typingIndicatorView startAnimation];
|
||||
} else {
|
||||
if (self.overrideSnippet) {
|
||||
self.snippetLabel.attributedText = self.overrideSnippet;
|
||||
} else {
|
||||
self.snippetLabel.attributedText = [self attributedSnippetForThread:self.thread isBlocked:self.isBlocked];
|
||||
}
|
||||
self.typingIndicatorView.hidden = YES;
|
||||
[self.typingIndicatorView stopAnimation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)typingIndicatorStateDidChange:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(self.thread);
|
||||
|
||||
if (notification.object && ![notification.object isEqual:self.thread.threadRecord.uniqueId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self updatePreview];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,27 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConversationViewController.h"
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TSThread;
|
||||
|
||||
@interface HomeViewController : OWSViewController
|
||||
|
||||
- (void)presentThread:(TSThread *)thread action:(ConversationViewAction)action animated:(BOOL)isAnimated;
|
||||
|
||||
- (void)presentThread:(TSThread *)thread
|
||||
action:(ConversationViewAction)action
|
||||
focusMessageId:(nullable NSString *)focusMessageId
|
||||
animated:(BOOL)isAnimated;
|
||||
|
||||
// Used by force-touch Springboard icon shortcut
|
||||
- (void)showNewConversationVC;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
File diff suppressed because it is too large
Load diff
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NewContactThreadViewController : OWSViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
File diff suppressed because it is too large
Load diff
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NewGroupViewController : OWSViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,695 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NewGroupViewController.h"
|
||||
#import "AddToGroupViewController.h"
|
||||
#import "AvatarViewHelper.h"
|
||||
#import "OWSNavigationController.h"
|
||||
#import "Session-Swift.h"
|
||||
#import "SignalApp.h"
|
||||
#import <SessionCoreKit/NSDate+OWS.h>
|
||||
#import <SessionCoreKit/Randomness.h>
|
||||
#import <SignalMessaging/BlockListUIUtils.h>
|
||||
#import <SignalMessaging/ContactTableViewCell.h>
|
||||
#import <SignalMessaging/ContactsViewHelper.h>
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/OWSContactsManager.h>
|
||||
#import <SignalMessaging/OWSTableViewController.h>
|
||||
#import <SignalMessaging/SignalKeyingStorage.h>
|
||||
#import <SignalMessaging/UIUtil.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
#import <SessionServiceKit/NSString+SSK.h>
|
||||
#import <SessionServiceKit/OWSMessageSender.h>
|
||||
#import <SessionServiceKit/SignalAccount.h>
|
||||
#import <SessionServiceKit/TSGroupModel.h>
|
||||
#import <SessionServiceKit/TSGroupThread.h>
|
||||
#import <SessionServiceKit/TSOutgoingMessage.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NewGroupViewController () <UIImagePickerControllerDelegate,
|
||||
UITextFieldDelegate,
|
||||
ContactsViewHelperDelegate,
|
||||
AvatarViewHelperDelegate,
|
||||
AddToGroupViewControllerDelegate,
|
||||
OWSTableViewControllerDelegate,
|
||||
UINavigationControllerDelegate,
|
||||
OWSNavigationView>
|
||||
|
||||
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
||||
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
|
||||
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
|
||||
|
||||
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
|
||||
@property (nonatomic, readonly) AvatarImageView *avatarView;
|
||||
@property (nonatomic, readonly) UITextField *groupNameTextField;
|
||||
|
||||
@property (nonatomic, readonly) NSData *groupId;
|
||||
|
||||
@property (nonatomic, nullable) UIImage *groupAvatar;
|
||||
@property (nonatomic) NSMutableSet<NSString *> *memberRecipientIds;
|
||||
@property (nonatomic) NSMutableSet<NSString* > *adminIds;
|
||||
@property (nonatomic) GroupType groupType;
|
||||
|
||||
@property (nonatomic) BOOL hasUnsavedChanges;
|
||||
@property (nonatomic) BOOL hasAppeared;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation NewGroupViewController
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
[self commonInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
[self commonInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commonInit
|
||||
{
|
||||
_groupId = [LKGroupUtilities getEncodedClosedGroupIDAsData:[[Randomness generateRandomBytes:kGroupIdLength] hexadecimalString]];
|
||||
_groupType = closedGroup;
|
||||
|
||||
_messageSender = SSKEnvironment.shared.messageSender;
|
||||
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
|
||||
_avatarViewHelper = [AvatarViewHelper new];
|
||||
_avatarViewHelper.delegate = self;
|
||||
|
||||
self.memberRecipientIds = [NSMutableSet new];
|
||||
self.adminIds = [NSMutableSet new];
|
||||
}
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
self.title = [MessageStrings newGroupDefaultTitle];
|
||||
|
||||
self.view.backgroundColor = Theme.backgroundColor;
|
||||
|
||||
self.navigationItem.rightBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"NEW_GROUP_CREATE_BUTTON",
|
||||
@"The title for the 'create group' button.")
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(createGroup)
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"create")];
|
||||
self.navigationItem.rightBarButtonItem.imageInsets = UIEdgeInsetsMake(0, -10, 0, 10);
|
||||
self.navigationItem.rightBarButtonItem.accessibilityLabel
|
||||
= NSLocalizedString(@"FINISH_GROUP_CREATION_LABEL", @"Accessibility label for finishing new group");
|
||||
|
||||
// First section.
|
||||
|
||||
UIView *firstSection = [self firstSectionHeader];
|
||||
[self.view addSubview:firstSection];
|
||||
[firstSection autoSetDimension:ALDimensionHeight toSize:100.f];
|
||||
[firstSection autoPinWidthToSuperview];
|
||||
[firstSection autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:0.0f];
|
||||
|
||||
_tableViewController = [OWSTableViewController new];
|
||||
_tableViewController.delegate = self;
|
||||
[self.view addSubview:self.tableViewController.view];
|
||||
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
|
||||
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
|
||||
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:firstSection];
|
||||
[self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view avoidNotch:NO];
|
||||
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
self.tableViewController.tableView.estimatedRowHeight = 60;
|
||||
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (UIView *)firstSectionHeader
|
||||
{
|
||||
UIView *firstSectionHeader = [UIView new];
|
||||
firstSectionHeader.userInteractionEnabled = YES;
|
||||
[firstSectionHeader
|
||||
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(headerWasTapped:)]];
|
||||
firstSectionHeader.backgroundColor = [Theme backgroundColor];
|
||||
UIView *threadInfoView = [UIView new];
|
||||
[firstSectionHeader addSubview:threadInfoView];
|
||||
[threadInfoView autoPinWidthToSuperviewWithMargin:16.f];
|
||||
[threadInfoView autoPinHeightToSuperviewWithMargin:16.f];
|
||||
|
||||
AvatarImageView *avatarView = [AvatarImageView new];
|
||||
_avatarView = avatarView;
|
||||
|
||||
[threadInfoView addSubview:avatarView];
|
||||
[avatarView autoVCenterInSuperview];
|
||||
[avatarView autoPinLeadingToSuperviewMargin];
|
||||
[avatarView autoSetDimension:ALDimensionWidth toSize:kLargeAvatarSize];
|
||||
[avatarView autoSetDimension:ALDimensionHeight toSize:kLargeAvatarSize];
|
||||
[self updateAvatarView];
|
||||
|
||||
[avatarView
|
||||
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTouched:)]];
|
||||
avatarView.userInteractionEnabled = YES;
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, avatarView);
|
||||
|
||||
UITextField *groupNameTextField = [OWSTextField new];
|
||||
_groupNameTextField = groupNameTextField;
|
||||
groupNameTextField.textColor = Theme.primaryColor;
|
||||
groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font];
|
||||
groupNameTextField.attributedPlaceholder =
|
||||
[[NSAttributedString alloc] initWithString:NSLocalizedString(@"NEW_GROUP_NAMEGROUP_REQUEST_DEFAULT",
|
||||
@"Placeholder text for group name field")
|
||||
attributes:@{
|
||||
NSForegroundColorAttributeName : Theme.secondaryColor,
|
||||
}];
|
||||
groupNameTextField.delegate = self;
|
||||
[groupNameTextField addTarget:self
|
||||
action:@selector(groupNameDidChange:)
|
||||
forControlEvents:UIControlEventEditingChanged];
|
||||
[threadInfoView addSubview:groupNameTextField];
|
||||
[groupNameTextField autoVCenterInSuperview];
|
||||
[groupNameTextField autoPinTrailingToSuperviewMargin];
|
||||
[groupNameTextField autoPinLeadingToTrailingEdgeOfView:avatarView offset:16.f];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, groupNameTextField);
|
||||
|
||||
return firstSectionHeader;
|
||||
}
|
||||
|
||||
- (void)headerWasTapped:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
[self.groupNameTextField becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)avatarTouched:(UIGestureRecognizer *)sender
|
||||
{
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
[self showChangeAvatarUI];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Table Contents
|
||||
|
||||
- (void)updateTableContents
|
||||
{
|
||||
OWSTableContents *contents = [OWSTableContents new];
|
||||
|
||||
__weak NewGroupViewController *weakSelf = self;
|
||||
ContactsViewHelper *contactsViewHelper = self.contactsViewHelper;
|
||||
|
||||
NSArray<SignalAccount *> *signalAccounts = self.contactsViewHelper.signalAccounts;
|
||||
NSMutableSet *nonContactMemberRecipientIds = [self.memberRecipientIds mutableCopy];
|
||||
for (SignalAccount *signalAccount in signalAccounts) {
|
||||
[nonContactMemberRecipientIds removeObject:signalAccount.recipientId];
|
||||
}
|
||||
|
||||
// Non-contact Members
|
||||
|
||||
if (nonContactMemberRecipientIds.count > 0 || signalAccounts.count < 1) {
|
||||
|
||||
OWSTableSection *nonContactsSection = [OWSTableSection new];
|
||||
nonContactsSection.headerTitle = NSLocalizedString(
|
||||
@"NEW_GROUP_NON_CONTACTS_SECTION_TITLE", @"a title for the non-contacts section of the 'new group' view.");
|
||||
|
||||
[nonContactsSection addItem:[self createAddNonContactItem]];
|
||||
|
||||
for (NSString *recipientId in
|
||||
[nonContactMemberRecipientIds.allObjects sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
|
||||
[nonContactsSection
|
||||
addItem:[OWSTableItem
|
||||
itemWithCustomCellBlock:^{
|
||||
NewGroupViewController *strongSelf = weakSelf;
|
||||
OWSCAssertDebug(strongSelf);
|
||||
|
||||
ContactTableViewCell *cell = [ContactTableViewCell new];
|
||||
BOOL isCurrentMember = [strongSelf.memberRecipientIds containsObject:recipientId];
|
||||
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
|
||||
if (isCurrentMember) {
|
||||
// In the "contacts" section, we label members as such when editing an existing
|
||||
// group.
|
||||
cell.accessoryMessage = NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL",
|
||||
@"An indicator that a user is a member of the new group.");
|
||||
} else if (isBlocked) {
|
||||
cell.accessoryMessage = NSLocalizedString(
|
||||
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
|
||||
}
|
||||
[cell configureWithRecipientId:recipientId];
|
||||
|
||||
NSString *cellName = [NSString stringWithFormat:@"non_signal_contact.%@", recipientId];
|
||||
cell.accessibilityIdentifier
|
||||
= ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewGroupViewController, cellName);
|
||||
|
||||
return cell;
|
||||
}
|
||||
customRowHeight:UITableViewAutomaticDimension
|
||||
actionBlock:^{
|
||||
BOOL isCurrentMember = [weakSelf.memberRecipientIds containsObject:recipientId];
|
||||
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
|
||||
if (isCurrentMember) {
|
||||
[weakSelf removeRecipientId:recipientId];
|
||||
} else if (isBlocked) {
|
||||
[BlockListUIUtils
|
||||
showUnblockPhoneNumberActionSheet:recipientId
|
||||
fromViewController:weakSelf
|
||||
blockingManager:contactsViewHelper.blockingManager
|
||||
contactsManager:contactsViewHelper.contactsManager
|
||||
completionBlock:^(BOOL isStillBlocked) {
|
||||
if (!isStillBlocked) {
|
||||
[weakSelf addRecipientId:recipientId];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
|
||||
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
|
||||
presentAlertIfNecessaryWithRecipientId:recipientId
|
||||
confirmationText:NSLocalizedString(
|
||||
@"SAFETY_NUMBER_CHANGED_CONFIRM_"
|
||||
@"ADD_TO_GROUP_ACTION",
|
||||
@"button title to confirm adding "
|
||||
@"a recipient to a group when "
|
||||
@"their safety "
|
||||
@"number has recently changed")
|
||||
contactsManager:contactsViewHelper.contactsManager
|
||||
completion:^(BOOL didConfirmIdentity) {
|
||||
if (didConfirmIdentity) {
|
||||
[weakSelf addRecipientId:recipientId];
|
||||
}
|
||||
}];
|
||||
if (didShowSNAlert) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
[weakSelf addRecipientId:recipientId];
|
||||
}
|
||||
}]];
|
||||
}
|
||||
[contents addSection:nonContactsSection];
|
||||
}
|
||||
|
||||
// Contacts
|
||||
|
||||
OWSTableSection *signalAccountSection = [OWSTableSection new];
|
||||
signalAccountSection.headerTitle = NSLocalizedString(
|
||||
@"EDIT_GROUP_CONTACTS_SECTION_TITLE", @"a title for the contacts section of the 'new/update group' view.");
|
||||
if (signalAccounts.count > 0) {
|
||||
|
||||
if (nonContactMemberRecipientIds.count < 1) {
|
||||
// If the group contains any non-contacts or has not contacts,
|
||||
// the "add non-contact user" will show up in the previous section
|
||||
// of the table. However, it's more attractive to hide that section
|
||||
// for the common case where people want to create a group from just
|
||||
// their contacts. Therefore, when that section is hidden, we want
|
||||
// to allow people to add non-contacts.
|
||||
[signalAccountSection addItem:[self createAddNonContactItem]];
|
||||
}
|
||||
|
||||
for (SignalAccount *signalAccount in signalAccounts) {
|
||||
[signalAccountSection
|
||||
addItem:[OWSTableItem
|
||||
itemWithCustomCellBlock:^{
|
||||
NewGroupViewController *strongSelf = weakSelf;
|
||||
OWSCAssertDebug(strongSelf);
|
||||
|
||||
ContactTableViewCell *cell = [ContactTableViewCell new];
|
||||
|
||||
NSString *recipientId = signalAccount.recipientId;
|
||||
BOOL isCurrentMember = [strongSelf.memberRecipientIds containsObject:recipientId];
|
||||
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
|
||||
if (isCurrentMember) {
|
||||
// In the "contacts" section, we label members as such when editing an existing
|
||||
// group.
|
||||
cell.accessoryMessage = NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL",
|
||||
@"An indicator that a user is a member of the new group.");
|
||||
} else if (isBlocked) {
|
||||
cell.accessoryMessage = NSLocalizedString(
|
||||
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
|
||||
}
|
||||
|
||||
[cell configureWithRecipientId:signalAccount.recipientId];
|
||||
|
||||
NSString *cellName =
|
||||
[NSString stringWithFormat:@"signal_contact.%@", signalAccount.recipientId];
|
||||
cell.accessibilityIdentifier
|
||||
= ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewGroupViewController, cellName);
|
||||
|
||||
return cell;
|
||||
}
|
||||
customRowHeight:UITableViewAutomaticDimension
|
||||
actionBlock:^{
|
||||
NSString *recipientId = signalAccount.recipientId;
|
||||
BOOL isCurrentMember = [weakSelf.memberRecipientIds containsObject:recipientId];
|
||||
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
|
||||
if (isCurrentMember) {
|
||||
[weakSelf removeRecipientId:recipientId];
|
||||
} else if (isBlocked) {
|
||||
[BlockListUIUtils
|
||||
showUnblockSignalAccountActionSheet:signalAccount
|
||||
fromViewController:weakSelf
|
||||
blockingManager:contactsViewHelper.blockingManager
|
||||
contactsManager:contactsViewHelper.contactsManager
|
||||
completionBlock:^(BOOL isStillBlocked) {
|
||||
if (!isStillBlocked) {
|
||||
[weakSelf addRecipientId:recipientId];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
|
||||
presentAlertIfNecessaryWithRecipientId:signalAccount.recipientId
|
||||
confirmationText:NSLocalizedString(
|
||||
@"SAFETY_NUMBER_CHANGED_CONFIRM_"
|
||||
@"ADD_TO_GROUP_ACTION",
|
||||
@"button title to confirm adding "
|
||||
@"a recipient to a group when "
|
||||
@"their safety "
|
||||
@"number has recently changed")
|
||||
contactsManager:contactsViewHelper.contactsManager
|
||||
completion:^(BOOL didConfirmIdentity) {
|
||||
if (didConfirmIdentity) {
|
||||
[weakSelf addRecipientId:recipientId];
|
||||
}
|
||||
}];
|
||||
if (didShowSNAlert) {
|
||||
return;
|
||||
}
|
||||
|
||||
[weakSelf addRecipientId:recipientId];
|
||||
}
|
||||
}]];
|
||||
}
|
||||
} else {
|
||||
[signalAccountSection
|
||||
addItem:[OWSTableItem
|
||||
softCenterLabelItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_NO_CONTACTS",
|
||||
@"A label that indicates the user has no Signal contacts.")]];
|
||||
}
|
||||
[contents addSection:signalAccountSection];
|
||||
|
||||
self.tableViewController.contents = contents;
|
||||
}
|
||||
|
||||
- (OWSTableItem *)createAddNonContactItem
|
||||
{
|
||||
__weak NewGroupViewController *weakSelf = self;
|
||||
return [OWSTableItem
|
||||
disclosureItemWithText:NSLocalizedString(@"NEW_GROUP_ADD_NON_CONTACT",
|
||||
@"A label for the cell that lets you add a new non-contact member to a group.")
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(NewGroupViewController, @"add_non_contact")
|
||||
customRowHeight:UITableViewAutomaticDimension
|
||||
actionBlock:^{
|
||||
AddToGroupViewController *viewController = [AddToGroupViewController new];
|
||||
viewController.addToGroupDelegate = weakSelf;
|
||||
viewController.hideContacts = YES;
|
||||
[weakSelf.navigationController pushViewController:viewController animated:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeRecipientId:(NSString *)recipientId
|
||||
{
|
||||
OWSAssertDebug(recipientId.length > 0);
|
||||
|
||||
[self.memberRecipientIds removeObject:recipientId];
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)addRecipientId:(NSString *)recipientId
|
||||
{
|
||||
OWSAssertDebug(recipientId.length > 0);
|
||||
|
||||
[self.memberRecipientIds addObject:recipientId];
|
||||
self.hasUnsavedChanges = YES;
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
#pragma mark - Methods
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
if (!self.hasAppeared) {
|
||||
[self.groupNameTextField becomeFirstResponder];
|
||||
self.hasAppeared = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)createGroup
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
TSGroupModel *model = [self makeGroup];
|
||||
|
||||
__block TSGroupThread *thread;
|
||||
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
thread = [TSGroupThread getOrCreateThreadWithGroupModel:model transaction:transaction];
|
||||
} error:nil];
|
||||
OWSAssertDebug(thread);
|
||||
|
||||
[OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread];
|
||||
|
||||
void (^successHandler)(void) = ^{
|
||||
OWSLogError(@"Group creation successful.");
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO];
|
||||
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
});
|
||||
};
|
||||
|
||||
void (^failureHandler)(NSError *error) = ^(NSError *error) {
|
||||
OWSLogError(@"Group creation failed: %@", error);
|
||||
|
||||
// Add an error message to the new group indicating
|
||||
// that group creation didn't succeed.
|
||||
// MJK TODO should be safe to remove senderTimestamp and just save immediately
|
||||
TSErrorMessage *errorMessage = [[TSErrorMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
|
||||
inThread:thread
|
||||
failedMessageType:TSErrorMessageGroupCreationFailed];
|
||||
[errorMessage save];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO];
|
||||
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
});
|
||||
};
|
||||
|
||||
[ModalActivityIndicatorViewController
|
||||
presentFromViewController:self
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
|
||||
groupMetaMessage:TSGroupMetaMessageNew
|
||||
expiresInSeconds:0];
|
||||
|
||||
[message updateWithCustomMessage:NSLocalizedString(@"GROUP_CREATED", nil)];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (model.groupImage) {
|
||||
NSData *data = UIImagePNGRepresentation(model.groupImage);
|
||||
DataSource *_Nullable dataSource =
|
||||
[DataSourceValue dataSourceWithData:data fileExtension:@"png"];
|
||||
// CLEANUP DURABLE - Replace with a durable operation e.g. `GroupCreateJob`, which creates
|
||||
// an error in the thread if group creation fails
|
||||
[self.messageSender sendTemporaryAttachment:dataSource
|
||||
contentType:OWSMimeTypeImagePng
|
||||
inMessage:message
|
||||
success:successHandler
|
||||
failure:failureHandler];
|
||||
} else {
|
||||
// CLEANUP DURABLE - Replace with a durable operation e.g. `GroupCreateJob`, which creates
|
||||
// an error in the thread if group creation fails
|
||||
[self.messageSender sendMessage:message success:successHandler failure:failureHandler];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (TSGroupModel *)makeGroup
|
||||
{
|
||||
NSString *groupName = [self.groupNameTextField.text ows_stripped];
|
||||
NSMutableArray<NSString *> *recipientIds = [self.memberRecipientIds.allObjects mutableCopy];
|
||||
NSMutableArray<NSString *> *adminIds = [self.adminIds.allObjects mutableCopy];
|
||||
//Test: Add Ryan to a new group. Should be deleted!!!!!
|
||||
[recipientIds addObject:@"057fffb55430abb2df5be80fab693ffe4db26a8b76e590c1748a47baef7c483604"];
|
||||
[recipientIds addObject:@"050c159f0d46c40ec6306bb0b8470972af3f67b5cb1e24b6460a6f692275c8b57f"];
|
||||
[recipientIds addObject:[self.contactsViewHelper localNumber]];
|
||||
//Loki - Add the creator as the admin
|
||||
[adminIds addObject:[self.contactsViewHelper localNumber]];
|
||||
TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:groupName
|
||||
memberIds:recipientIds
|
||||
image:self.groupAvatar
|
||||
groupId:self.groupId
|
||||
groupType:self.groupType
|
||||
adminIds:adminIds];
|
||||
return group;
|
||||
}
|
||||
|
||||
#pragma mark - Group Avatar
|
||||
|
||||
- (void)showChangeAvatarUI
|
||||
{
|
||||
[self.avatarViewHelper showChangeAvatarUI];
|
||||
}
|
||||
|
||||
- (void)setGroupAvatar:(nullable UIImage *)groupAvatar
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
_groupAvatar = groupAvatar;
|
||||
|
||||
self.hasUnsavedChanges = YES;
|
||||
|
||||
[self updateAvatarView];
|
||||
}
|
||||
|
||||
- (void)updateAvatarView
|
||||
{
|
||||
UIImage *_Nullable groupAvatar = self.groupAvatar;
|
||||
if (!groupAvatar) {
|
||||
NSString *conversationColorName = [TSGroupThread defaultConversationColorNameForGroupId:self.groupId];
|
||||
groupAvatar = [OWSGroupAvatarBuilder defaultAvatarForGroupId:self.groupId
|
||||
conversationColorName:conversationColorName
|
||||
diameter:kLargeAvatarSize];
|
||||
}
|
||||
self.avatarView.image = groupAvatar;
|
||||
}
|
||||
|
||||
#pragma mark - Event Handling
|
||||
|
||||
- (void)backButtonPressed
|
||||
{
|
||||
[self.groupNameTextField resignFirstResponder];
|
||||
|
||||
if (!self.hasUnsavedChanges) {
|
||||
// If user made no changes, return to conversation settings view.
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
UIAlertController *alert = [UIAlertController
|
||||
alertControllerWithTitle:
|
||||
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
|
||||
@"The alert title if user tries to exit the new group view without saving changes.")
|
||||
message:
|
||||
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
|
||||
@"The alert message if user tries to exit the new group view without saving changes.")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert
|
||||
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DISCARD_BUTTON",
|
||||
@"The label for the 'discard' button in alerts and action sheets.")
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"discard")
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}]];
|
||||
[alert addAction:[OWSAlerts cancelAction]];
|
||||
[self presentAlert:alert];
|
||||
}
|
||||
|
||||
- (void)groupNameDidChange:(id)sender
|
||||
{
|
||||
self.hasUnsavedChanges = YES;
|
||||
}
|
||||
|
||||
#pragma mark - Text Field Delegate
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField
|
||||
{
|
||||
[self.groupNameTextField resignFirstResponder];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - OWSTableViewControllerDelegate
|
||||
|
||||
- (void)tableViewWillBeginDragging
|
||||
{
|
||||
[self.groupNameTextField resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - ContactsViewHelperDelegate
|
||||
|
||||
- (void)contactsViewHelperDidUpdateContacts
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (BOOL)shouldHideLocalNumber
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - AvatarViewHelperDelegate
|
||||
|
||||
- (nullable NSString *)avatarActionSheetTitle
|
||||
{
|
||||
return NSLocalizedString(
|
||||
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");
|
||||
}
|
||||
|
||||
- (void)avatarDidChange:(UIImage *)image
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(image);
|
||||
|
||||
self.groupAvatar = image;
|
||||
}
|
||||
|
||||
- (UIViewController *)fromViewController
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)hasClearAvatarAction
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - AddToGroupViewControllerDelegate
|
||||
|
||||
- (void)recipientIdWasAdded:(NSString *)recipientId
|
||||
{
|
||||
[self addRecipientId:recipientId];
|
||||
}
|
||||
|
||||
- (BOOL)isRecipientGroupMember:(NSString *)recipientId
|
||||
{
|
||||
OWSAssertDebug(recipientId.length > 0);
|
||||
|
||||
return [self.memberRecipientIds containsObject:recipientId];
|
||||
}
|
||||
|
||||
#pragma mark - OWSNavigationView
|
||||
|
||||
- (BOOL)shouldCancelNavigationBack
|
||||
{
|
||||
BOOL result = self.hasUnsavedChanges;
|
||||
if (self.hasUnsavedChanges) {
|
||||
[self backButtonPressed];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalMessaging/OWSViewController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, Enable2FAMode) {
|
||||
OWS2FASettingsMode_Status = 0,
|
||||
OWS2FASettingsMode_SelectPIN,
|
||||
OWS2FASettingsMode_ConfirmPIN,
|
||||
};
|
||||
|
||||
@interface OWS2FASettingsViewController : OWSViewController
|
||||
|
||||
@property (nonatomic) Enable2FAMode mode;
|
||||
|
||||
// When confirming the PIN, this is the PIN that was
|
||||
// initially entered by the user.
|
||||
@property (nonatomic, nullable) NSString *candidatePin;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,455 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS2FASettingsViewController.h"
|
||||
#import "OWSTableViewController.h"
|
||||
#import "Session-Swift.h"
|
||||
#import "SignalMessaging.h"
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalMessaging/SignalMessaging.h>
|
||||
#import <SignalMessaging/UIColor+OWS.h>
|
||||
#import <SignalMessaging/UIFont+OWS.h>
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
#import <SessionServiceKit/NSString+SSK.h>
|
||||
#import <SessionServiceKit/OWS2FAManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWS2FASettingsViewController () <UITextFieldDelegate>
|
||||
|
||||
@property (nonatomic, weak) UIViewController *root2FAViewController;
|
||||
|
||||
@property (nonatomic) UITextField *pinTextfield;
|
||||
@property (nonatomic) OWSTableViewController *tableViewController;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWS2FASettingsViewController
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [Theme backgroundColor];
|
||||
|
||||
self.title = NSLocalizedString(@"ENABLE_2FA_VIEW_TITLE", @"Title for the 'enable two factor auth PIN' views.");
|
||||
|
||||
[self createContents];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(stateDidChange:)
|
||||
name:NSNotificationName_2FAStateDidChange
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)createContents
|
||||
{
|
||||
for (UIView *subview in self.view.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
switch (self.mode) {
|
||||
case OWS2FASettingsMode_Status:
|
||||
[self createStatusContents];
|
||||
break;
|
||||
case OWS2FASettingsMode_SelectPIN:
|
||||
[self createSelectCodeContents];
|
||||
break;
|
||||
case OWS2FASettingsMode_ConfirmPIN:
|
||||
[self createConfirmCodeContents];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
switch (self.mode) {
|
||||
case OWS2FASettingsMode_Status:
|
||||
break;
|
||||
case OWS2FASettingsMode_SelectPIN:
|
||||
case OWS2FASettingsMode_ConfirmPIN:
|
||||
OWSAssertDebug(![OWS2FAManager.sharedManager is2FAEnabled]);
|
||||
break;
|
||||
}
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// If we're using a table, refresh its contents.
|
||||
[self updateTableContents];
|
||||
|
||||
[self updateNavigationItems];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// If we're using a PIN textfield, select it.
|
||||
[self.pinTextfield becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (UILabel *)createLabelWithText:(NSString *)text
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.textColor = [Theme primaryColor];
|
||||
label.text = text;
|
||||
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(14.f, 16.f)];
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
[self.view addSubview:label];
|
||||
return label;
|
||||
}
|
||||
|
||||
- (void)createPinTextfield
|
||||
{
|
||||
self.pinTextfield = [OWSTextField new];
|
||||
self.pinTextfield.textColor = [Theme primaryColor];
|
||||
self.pinTextfield.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(30.f, 36.f)];
|
||||
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
|
||||
self.pinTextfield.keyboardType = UIKeyboardTypeNumberPad;
|
||||
self.pinTextfield.delegate = self;
|
||||
self.pinTextfield.secureTextEntry = YES;
|
||||
self.pinTextfield.textAlignment = NSTextAlignmentCenter;
|
||||
[self.pinTextfield addTarget:self
|
||||
action:@selector(textFieldDidChange:)
|
||||
forControlEvents:UIControlEventEditingChanged];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _pinTextfield);
|
||||
[self.view addSubview:self.pinTextfield];
|
||||
}
|
||||
|
||||
- (void)createTableView
|
||||
{
|
||||
self.tableViewController = [OWSTableViewController new];
|
||||
[self.view addSubview:self.tableViewController.view];
|
||||
}
|
||||
|
||||
- (void)createStatusContents
|
||||
{
|
||||
const CGFloat kVSpacing = 30.f;
|
||||
|
||||
// TODO: Add hero image?
|
||||
// TODO: Tweak background color?
|
||||
|
||||
NSString *instructions = ([OWS2FAManager.sharedManager is2FAEnabled]
|
||||
? NSLocalizedString(@"ENABLE_2FA_VIEW_STATUS_ENABLED_INSTRUCTIONS",
|
||||
@"Indicates that user has 'two factor auth pin' enabled.")
|
||||
: NSLocalizedString(@"ENABLE_2FA_VIEW_STATUS_DISABLED_INSTRUCTIONS",
|
||||
@"Indicates that user has 'two factor auth pin' disabled."));
|
||||
UILabel *instructionsLabel = [self createLabelWithText:instructions];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, instructionsLabel);
|
||||
|
||||
[self createTableView];
|
||||
|
||||
[instructionsLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self.view withOffset:kVSpacing];
|
||||
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
|
||||
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
|
||||
|
||||
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeLeading];
|
||||
[self.tableViewController.view autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing];
|
||||
[self.tableViewController.view autoPinEdge:ALEdgeTop
|
||||
toEdge:ALEdgeBottom
|
||||
ofView:instructionsLabel
|
||||
withOffset:kVSpacing];
|
||||
[self.tableViewController.view autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:self.view withOffset:0.0f];
|
||||
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)createSelectCodeContents
|
||||
{
|
||||
[self createEnterPINContentsWithInstructions:NSLocalizedString(@"ENABLE_2FA_VIEW_SELECT_PIN_INSTRUCTIONS",
|
||||
@"Indicates that user should select a 'two factor auth pin'.")];
|
||||
}
|
||||
|
||||
- (void)createConfirmCodeContents
|
||||
{
|
||||
[self
|
||||
createEnterPINContentsWithInstructions:NSLocalizedString(@"ENABLE_2FA_VIEW_CONFIRM_PIN_INSTRUCTIONS",
|
||||
@"Indicates that user should confirm their 'two factor auth pin'.")];
|
||||
}
|
||||
|
||||
- (CGFloat)hMargin
|
||||
{
|
||||
return 20.f;
|
||||
}
|
||||
|
||||
- (void)createEnterPINContentsWithInstructions:(NSString *)instructionsText
|
||||
{
|
||||
const CGFloat kVSpacing = 30.f;
|
||||
|
||||
UILabel *instructionsLabel = [self createLabelWithText:instructionsText];
|
||||
|
||||
[self createPinTextfield];
|
||||
|
||||
[instructionsLabel autoPinTopToSuperviewMarginWithInset:kVSpacing];
|
||||
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
|
||||
[instructionsLabel autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, instructionsLabel);
|
||||
|
||||
[self.pinTextfield autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:instructionsLabel withOffset:kVSpacing];
|
||||
[self.pinTextfield autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
|
||||
[self.pinTextfield autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
|
||||
|
||||
UIView *underscoreView = [UIView new];
|
||||
underscoreView.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1.f];
|
||||
[self.view addSubview:underscoreView];
|
||||
[underscoreView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.pinTextfield withOffset:3];
|
||||
[underscoreView autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:self.hMargin];
|
||||
[underscoreView autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:self.hMargin];
|
||||
[underscoreView autoSetDimension:ALDimensionHeight toSize:1.f];
|
||||
|
||||
[self updateNavigationItems];
|
||||
}
|
||||
|
||||
- (void)updateTableContents
|
||||
{
|
||||
__weak OWS2FASettingsViewController *weakSelf = self;
|
||||
|
||||
// Only some modes use a table.
|
||||
switch (self.mode) {
|
||||
case OWS2FASettingsMode_Status: {
|
||||
OWSTableContents *contents = [OWSTableContents new];
|
||||
OWSTableSection *section = [OWSTableSection new];
|
||||
if ([OWS2FAManager.sharedManager is2FAEnabled]) {
|
||||
[section
|
||||
addItem:[OWSTableItem disclosureItemWithText:
|
||||
NSLocalizedString(@"ENABLE_2FA_VIEW_DISABLE_2FA",
|
||||
@"Label for the 'enable two-factor auth' item in the settings view")
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"enable_2fa")
|
||||
actionBlock:^{
|
||||
[weakSelf tryToDisable2FA];
|
||||
}]];
|
||||
} else {
|
||||
[section addItem:[OWSTableItem
|
||||
disclosureItemWithText:
|
||||
NSLocalizedString(@"ENABLE_2FA_VIEW_ENABLE_2FA",
|
||||
@"Label for the 'enable two-factor auth' item in the settings view")
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"disable_2fa")
|
||||
actionBlock:^{
|
||||
[weakSelf showEnable2FAWorkUI];
|
||||
}]];
|
||||
}
|
||||
[contents addSection:section];
|
||||
self.tableViewController.contents = contents;
|
||||
break;
|
||||
}
|
||||
case OWS2FASettingsMode_SelectPIN:
|
||||
case OWS2FASettingsMode_ConfirmPIN:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldHaveNextButton
|
||||
{
|
||||
switch (self.mode) {
|
||||
case OWS2FASettingsMode_Status:
|
||||
return NO;
|
||||
case OWS2FASettingsMode_SelectPIN:
|
||||
case OWS2FASettingsMode_ConfirmPIN:
|
||||
return [self hasValidPin];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateNavigationItems
|
||||
{
|
||||
// Note: This affects how the "back" button will look if another
|
||||
// view is pushed on top of this one, not how the "back"
|
||||
// button looks when this view is visible.
|
||||
UIBarButtonItem *backButton =
|
||||
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"BACK_BUTTON", @"button text for back button")
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(backButtonWasPressed)
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"back")];
|
||||
self.navigationItem.backBarButtonItem = backButton;
|
||||
|
||||
if (self.shouldHaveNextButton) {
|
||||
UIBarButtonItem *nextButton = [[UIBarButtonItem alloc]
|
||||
initWithTitle:NSLocalizedString(@"ENABLE_2FA_VIEW_NEXT_BUTTON",
|
||||
@"Label for the 'next' button in the 'enable two factor auth' views.")
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(nextButtonWasPressed)
|
||||
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"next")];
|
||||
self.navigationItem.rightBarButtonItem = nextButton;
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textField:(UITextField *)textField
|
||||
shouldChangeCharactersInRange:(NSRange)range
|
||||
replacementString:(NSString *)insertionText
|
||||
{
|
||||
[ViewControllerUtils ows2FAPINTextField:textField
|
||||
shouldChangeCharactersInRange:range
|
||||
replacementString:insertionText];
|
||||
|
||||
[self updateNavigationItems];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)textFieldDidChange:(id)sender
|
||||
{
|
||||
[self updateNavigationItems];
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)nextButtonWasPressed
|
||||
{
|
||||
switch (self.mode) {
|
||||
case OWS2FASettingsMode_Status:
|
||||
OWSFailDebug(@"status mode should not have a next button.");
|
||||
return;
|
||||
case OWS2FASettingsMode_SelectPIN: {
|
||||
OWSAssertDebug(self.hasValidPin);
|
||||
|
||||
OWS2FASettingsViewController *vc = [OWS2FASettingsViewController new];
|
||||
vc.mode = OWS2FASettingsMode_ConfirmPIN;
|
||||
vc.candidatePin = self.pinTextfield.text;
|
||||
OWSAssertDebug(self.root2FAViewController);
|
||||
vc.root2FAViewController = self.root2FAViewController;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
break;
|
||||
}
|
||||
case OWS2FASettingsMode_ConfirmPIN: {
|
||||
OWSAssertDebug(self.hasValidPin);
|
||||
|
||||
if ([self.pinTextfield.text isEqualToString:self.candidatePin]) {
|
||||
[self tryToEnable2FA];
|
||||
} else {
|
||||
// Clear the PIN so that the user can try again.
|
||||
self.pinTextfield.text = nil;
|
||||
|
||||
[OWSAlerts showErrorAlertWithMessage:
|
||||
NSLocalizedString(@"ENABLE_2FA_VIEW_PIN_DOES_NOT_MATCH",
|
||||
@"Error indicating that the entered 'two-factor auth PINs' do not match.")];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasValidPin
|
||||
{
|
||||
return self.pinTextfield.text.length >= kMin2FAPinLength;
|
||||
}
|
||||
|
||||
- (void)showEnable2FAWorkUI
|
||||
{
|
||||
OWSAssertDebug(![OWS2FAManager.sharedManager is2FAEnabled]);
|
||||
|
||||
OWSLogInfo(@"");
|
||||
|
||||
OWS2FASettingsViewController *vc = [OWS2FASettingsViewController new];
|
||||
vc.mode = OWS2FASettingsMode_SelectPIN;
|
||||
vc.root2FAViewController = self;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
- (void)tryToDisable2FA
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
|
||||
__weak OWS2FASettingsViewController *weakSelf = self;
|
||||
|
||||
[ModalActivityIndicatorViewController
|
||||
presentFromViewController:self
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
[OWS2FAManager.sharedManager disable2FAWithSuccess:^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
// TODO: Should we show an alert?
|
||||
|
||||
[weakSelf updateTableContents];
|
||||
}];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
[weakSelf updateTableContents];
|
||||
|
||||
[OWSAlerts showErrorAlertWithMessage:
|
||||
NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_DISABLE_2FA",
|
||||
@"Error indicating that attempt to disable 'two-factor "
|
||||
@"auth' failed.")];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)tryToEnable2FA
|
||||
{
|
||||
OWSAssertDebug(self.candidatePin.length > 0);
|
||||
|
||||
OWSLogInfo(@"");
|
||||
|
||||
__weak OWS2FASettingsViewController *weakSelf = self;
|
||||
|
||||
[ModalActivityIndicatorViewController
|
||||
presentFromViewController:self
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
[OWS2FAManager.sharedManager requestEnable2FAWithPin:self.candidatePin
|
||||
success:^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
[weakSelf showCompleteUI];
|
||||
}];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
// The client may have fallen out of sync with the service.
|
||||
// Try to get back to a known good state by disabling 2FA
|
||||
// whenever enabling it fails.
|
||||
[OWS2FAManager.sharedManager disable2FAWithSuccess:nil failure:nil];
|
||||
|
||||
[weakSelf updateTableContents];
|
||||
|
||||
[OWSAlerts showErrorAlertWithMessage:
|
||||
NSLocalizedString(@"ENABLE_2FA_VIEW_COULD_NOT_ENABLE_2FA",
|
||||
@"Error indicating that attempt to enable 'two-factor "
|
||||
@"auth' failed.")];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showCompleteUI
|
||||
{
|
||||
OWSAssertDebug([OWS2FAManager.sharedManager is2FAEnabled]);
|
||||
OWSAssertDebug(self.root2FAViewController);
|
||||
|
||||
OWSLogInfo(@"");
|
||||
|
||||
[self.navigationController popToViewController:self.root2FAViewController animated:YES];
|
||||
}
|
||||
|
||||
- (void)backButtonWasPressed
|
||||
{
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)stateDidChange:(NSNotification *)notification
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
|
||||
if (self.mode == OWS2FASettingsMode_Status) {
|
||||
[self createContents];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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("")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,549 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import PromiseKit
|
||||
|
||||
private protocol OnboardingCodeViewTextFieldDelegate {
|
||||
func textFieldDidDeletePrevious()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
// Editing a code should feel seamless, as even though
|
||||
// the UITextField only lets you edit a single digit at
|
||||
// a time. For deletes to work properly, we need to
|
||||
// detect delete events that would affect the _previous_
|
||||
// digit.
|
||||
private class OnboardingCodeViewTextField: UITextField {
|
||||
|
||||
fileprivate var codeDelegate: OnboardingCodeViewTextFieldDelegate?
|
||||
|
||||
override func deleteBackward() {
|
||||
var isDeletePrevious = false
|
||||
if let selectedTextRange = selectedTextRange {
|
||||
let cursorPosition = offset(from: beginningOfDocument, to: selectedTextRange.start)
|
||||
if cursorPosition == 0 {
|
||||
isDeletePrevious = true
|
||||
}
|
||||
}
|
||||
|
||||
super.deleteBackward()
|
||||
|
||||
if isDeletePrevious {
|
||||
codeDelegate?.textFieldDidDeletePrevious()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
protocol OnboardingCodeViewDelegate {
|
||||
func codeViewDidChange()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
// The OnboardingCodeView is a special "verification code"
|
||||
// editor that should feel like editing a single piece
|
||||
// of text (ala UITextField) even though the individual
|
||||
// digits of the code are visually separated.
|
||||
//
|
||||
// We use a separate UILabel for each digit, and move
|
||||
// around a single UITextfield to let the user edit the
|
||||
// last/next digit.
|
||||
private class OnboardingCodeView: UIView {
|
||||
|
||||
var delegate: OnboardingCodeViewDelegate?
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
createSubviews()
|
||||
|
||||
updateViewState()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private let digitCount = 6
|
||||
private var digitLabels = [UILabel]()
|
||||
private var digitStrokes = [UIView]()
|
||||
|
||||
// We use a single text field to edit the "current" digit.
|
||||
// The "current" digit is usually the "last"
|
||||
fileprivate let textfield = OnboardingCodeViewTextField()
|
||||
private var currentDigitIndex = 0
|
||||
private var textfieldConstraints = [NSLayoutConstraint]()
|
||||
|
||||
// The current complete text - the "model" for this view.
|
||||
private var digitText = ""
|
||||
|
||||
var isComplete: Bool {
|
||||
return digitText.count == digitCount
|
||||
}
|
||||
var verificationCode: String {
|
||||
return digitText
|
||||
}
|
||||
|
||||
private func createSubviews() {
|
||||
textfield.textAlignment = .left
|
||||
textfield.delegate = self
|
||||
textfield.keyboardType = .numberPad
|
||||
textfield.textColor = Theme.primaryColor
|
||||
textfield.font = UIFont.ows_dynamicTypeLargeTitle1Clamped
|
||||
textfield.codeDelegate = self
|
||||
|
||||
var digitViews = [UIView]()
|
||||
(0..<digitCount).forEach { (_) in
|
||||
let (digitView, digitLabel, digitStroke) = makeCellView(text: "", hasStroke: true)
|
||||
|
||||
digitLabels.append(digitLabel)
|
||||
digitStrokes.append(digitStroke)
|
||||
digitViews.append(digitView)
|
||||
}
|
||||
|
||||
let (hyphenView, _, _) = makeCellView(text: "-", hasStroke: false)
|
||||
|
||||
digitViews.insert(hyphenView, at: 3)
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: digitViews)
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = 8
|
||||
addSubview(stackView)
|
||||
stackView.autoPinHeightToSuperview()
|
||||
stackView.autoHCenterInSuperview()
|
||||
|
||||
self.addSubview(textfield)
|
||||
}
|
||||
|
||||
private func makeCellView(text: String, hasStroke: Bool) -> (UIView, UILabel, UIView) {
|
||||
let digitView = UIView()
|
||||
|
||||
let digitLabel = UILabel()
|
||||
digitLabel.text = text
|
||||
digitLabel.font = UIFont.ows_dynamicTypeLargeTitle1Clamped
|
||||
digitLabel.textColor = Theme.primaryColor
|
||||
digitLabel.textAlignment = .center
|
||||
digitView.addSubview(digitLabel)
|
||||
digitLabel.autoCenterInSuperview()
|
||||
|
||||
let strokeColor = (hasStroke ? Theme.primaryColor : UIColor.clear)
|
||||
let strokeView = digitView.addBottomStroke(color: strokeColor, strokeWidth: 1)
|
||||
|
||||
let vMargin: CGFloat = 4
|
||||
let cellHeight: CGFloat = digitLabel.font.lineHeight + vMargin * 2
|
||||
let cellWidth: CGFloat = cellHeight * 2 / 3
|
||||
digitView.autoSetDimensions(to: CGSize(width: cellWidth, height: cellHeight))
|
||||
|
||||
return (digitView, digitLabel, strokeView)
|
||||
}
|
||||
|
||||
private func digit(at index: Int) -> String {
|
||||
guard index < digitText.count else {
|
||||
return ""
|
||||
}
|
||||
return digitText.substring(from: index).substring(to: 1)
|
||||
}
|
||||
|
||||
// Ensure that all labels are displaying the correct
|
||||
// digit (if any) and that the UITextField has replaced
|
||||
// the "current" digit.
|
||||
private func updateViewState() {
|
||||
currentDigitIndex = min(digitCount - 1,
|
||||
digitText.count)
|
||||
|
||||
(0..<digitCount).forEach { (index) in
|
||||
let digitLabel = digitLabels[index]
|
||||
digitLabel.text = digit(at: index)
|
||||
digitLabel.isHidden = index == currentDigitIndex
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(textfieldConstraints)
|
||||
textfieldConstraints.removeAll()
|
||||
|
||||
let digitLabelToReplace = digitLabels[currentDigitIndex]
|
||||
textfield.text = digit(at: currentDigitIndex)
|
||||
textfieldConstraints.append(textfield.autoAlignAxis(.horizontal, toSameAxisOf: digitLabelToReplace))
|
||||
textfieldConstraints.append(textfield.autoAlignAxis(.vertical, toSameAxisOf: digitLabelToReplace))
|
||||
|
||||
// Move cursor to end of text.
|
||||
let newPosition = textfield.endOfDocument
|
||||
textfield.selectedTextRange = textfield.textRange(from: newPosition, to: newPosition)
|
||||
}
|
||||
|
||||
public override func becomeFirstResponder() -> Bool {
|
||||
return textfield.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func setHasError(_ hasError: Bool) {
|
||||
let backgroundColor = (hasError ? UIColor.ows_destructiveRed : Theme.primaryColor)
|
||||
for digitStroke in digitStrokes {
|
||||
digitStroke.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func set(verificationCode: String) {
|
||||
digitText = verificationCode
|
||||
|
||||
updateViewState()
|
||||
|
||||
self.delegate?.codeViewDidChange()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension OnboardingCodeView: UITextFieldDelegate {
|
||||
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString newString: String) -> Bool {
|
||||
var oldText = ""
|
||||
if let textFieldText = textField.text {
|
||||
oldText = textFieldText
|
||||
}
|
||||
let left = oldText.substring(to: range.location)
|
||||
let right = oldText.substring(from: range.location + range.length)
|
||||
let unfiltered = left + newString + right
|
||||
let characterSet = CharacterSet(charactersIn: "0123456789")
|
||||
let filtered = unfiltered.components(separatedBy: characterSet.inverted).joined()
|
||||
let filteredAndTrimmed = filtered.substring(to: 1)
|
||||
textField.text = filteredAndTrimmed
|
||||
|
||||
digitText = digitText.substring(to: currentDigitIndex) + filteredAndTrimmed
|
||||
|
||||
updateViewState()
|
||||
|
||||
self.delegate?.codeViewDidChange()
|
||||
|
||||
// Inform our caller that we took care of performing the change.
|
||||
return false
|
||||
}
|
||||
|
||||
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
self.delegate?.codeViewDidChange()
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension OnboardingCodeView: OnboardingCodeViewTextFieldDelegate {
|
||||
public func textFieldDidDeletePrevious() {
|
||||
guard digitText.count > 0 else {
|
||||
return
|
||||
}
|
||||
digitText = digitText.substring(to: currentDigitIndex - 1)
|
||||
|
||||
updateViewState()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class OnboardingVerificationViewController: OnboardingBaseViewController {
|
||||
|
||||
private enum CodeState {
|
||||
case sent
|
||||
case readyForResend
|
||||
case resent
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private var codeState = CodeState.sent
|
||||
|
||||
private var titleLabel: UILabel?
|
||||
private var backLink: UIView?
|
||||
private let onboardingCodeView = OnboardingCodeView()
|
||||
private var codeStateLink: OWSFlatButton?
|
||||
private let errorLabel = UILabel()
|
||||
|
||||
@objc
|
||||
public func hideBackLink() {
|
||||
backLink?.isHidden = true
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
super.loadView()
|
||||
|
||||
view.backgroundColor = Theme.backgroundColor
|
||||
view.layoutMargins = .zero
|
||||
|
||||
let titleLabel = self.createTitleLabel(text: "")
|
||||
self.titleLabel = titleLabel
|
||||
titleLabel.accessibilityIdentifier = "onboarding.verification." + "titleLabel"
|
||||
|
||||
let backLink = self.createLinkButton(title: NSLocalizedString("ONBOARDING_VERIFICATION_BACK_LINK",
|
||||
comment: "Label for the link that lets users change their phone number in the onboarding views."),
|
||||
selector: #selector(backLinkTapped))
|
||||
self.backLink = backLink
|
||||
backLink.accessibilityIdentifier = "onboarding.verification." + "backLink"
|
||||
|
||||
onboardingCodeView.delegate = self
|
||||
|
||||
errorLabel.text = NSLocalizedString("ONBOARDING_VERIFICATION_INVALID_CODE",
|
||||
comment: "Label indicating that the verification code is incorrect in the 'onboarding verification' view.")
|
||||
errorLabel.textColor = .ows_destructiveRed
|
||||
errorLabel.font = UIFont.ows_dynamicTypeBodyClamped.ows_mediumWeight()
|
||||
errorLabel.textAlignment = .center
|
||||
errorLabel.autoSetDimension(.height, toSize: errorLabel.font.lineHeight)
|
||||
errorLabel.accessibilityIdentifier = "onboarding.verification." + "errorLabel"
|
||||
|
||||
// Wrap the error label in a row so that we can show/hide it without affecting view layout.
|
||||
let errorRow = UIView()
|
||||
errorRow.addSubview(errorLabel)
|
||||
errorLabel.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let codeStateLink = self.createLinkButton(title: "",
|
||||
selector: #selector(resendCodeLinkTapped))
|
||||
codeStateLink.enableMultilineLabel()
|
||||
self.codeStateLink = codeStateLink
|
||||
codeStateLink.accessibilityIdentifier = "onboarding.verification." + "codeStateLink"
|
||||
|
||||
let topSpacer = UIView.vStretchingSpacer()
|
||||
let bottomSpacer = UIView.vStretchingSpacer()
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [
|
||||
titleLabel,
|
||||
UIView.spacer(withHeight: 12),
|
||||
backLink,
|
||||
topSpacer,
|
||||
onboardingCodeView,
|
||||
UIView.spacer(withHeight: 12),
|
||||
errorRow,
|
||||
bottomSpacer,
|
||||
codeStateLink
|
||||
])
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .fill
|
||||
stackView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
view.addSubview(stackView)
|
||||
stackView.autoPinWidthToSuperview()
|
||||
stackView.autoPinEdge(.top, to: .top, of: view)
|
||||
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
|
||||
|
||||
// Ensure whitespace is balanced, so inputs are vertically centered.
|
||||
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
|
||||
|
||||
startCodeCountdown()
|
||||
|
||||
updateCodeState()
|
||||
|
||||
setHasInvalidCode(false)
|
||||
}
|
||||
|
||||
// MARK: - Code State
|
||||
|
||||
private let countdownDuration: TimeInterval = 60
|
||||
private var codeCountdownTimer: Timer?
|
||||
private var codeCountdownStart: NSDate?
|
||||
|
||||
deinit {
|
||||
codeCountdownTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func startCodeCountdown() {
|
||||
codeCountdownStart = NSDate()
|
||||
codeCountdownTimer = Timer.weakScheduledTimer(withTimeInterval: 0.25, target: self, selector: #selector(codeCountdownTimerFired), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func codeCountdownTimerFired() {
|
||||
guard let codeCountdownStart = codeCountdownStart else {
|
||||
owsFailDebug("Missing codeCountdownStart.")
|
||||
return
|
||||
}
|
||||
guard let codeCountdownTimer = codeCountdownTimer else {
|
||||
owsFailDebug("Missing codeCountdownTimer.")
|
||||
return
|
||||
}
|
||||
|
||||
let countdownInterval = abs(codeCountdownStart.timeIntervalSinceNow)
|
||||
|
||||
guard countdownInterval < countdownDuration else {
|
||||
// Countdown complete.
|
||||
codeCountdownTimer.invalidate()
|
||||
self.codeCountdownTimer = nil
|
||||
|
||||
if codeState != .sent {
|
||||
owsFailDebug("Unexpected codeState: \(codeState)")
|
||||
}
|
||||
codeState = .readyForResend
|
||||
updateCodeState()
|
||||
return
|
||||
}
|
||||
|
||||
// Update the "code state" UI to reflect the countdown.
|
||||
updateCodeState()
|
||||
}
|
||||
|
||||
private func updateCodeState() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
guard let codeCountdownStart = codeCountdownStart else {
|
||||
owsFailDebug("Missing codeCountdownStart.")
|
||||
return
|
||||
}
|
||||
guard let titleLabel = titleLabel else {
|
||||
owsFailDebug("Missing titleLabel.")
|
||||
return
|
||||
}
|
||||
guard let codeStateLink = codeStateLink else {
|
||||
owsFailDebug("Missing codeStateLink.")
|
||||
return
|
||||
}
|
||||
|
||||
var e164PhoneNumber = ""
|
||||
if let phoneNumber = onboardingController.phoneNumber {
|
||||
e164PhoneNumber = phoneNumber.e164
|
||||
}
|
||||
let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: e164PhoneNumber)
|
||||
|
||||
// Update titleLabel
|
||||
switch codeState {
|
||||
case .sent, .readyForResend:
|
||||
titleLabel.text = String(format: NSLocalizedString("ONBOARDING_VERIFICATION_TITLE_DEFAULT_FORMAT",
|
||||
comment: "Format for the title of the 'onboarding verification' view. Embeds {{the user's phone number}}."),
|
||||
formattedPhoneNumber)
|
||||
case .resent:
|
||||
titleLabel.text = String(format: NSLocalizedString("ONBOARDING_VERIFICATION_TITLE_RESENT_FORMAT",
|
||||
comment: "Format for the title of the 'onboarding verification' view after the verification code has been resent. Embeds {{the user's phone number}}."),
|
||||
formattedPhoneNumber)
|
||||
}
|
||||
|
||||
// Update codeStateLink
|
||||
switch codeState {
|
||||
case .sent:
|
||||
let countdownInterval = abs(codeCountdownStart.timeIntervalSinceNow)
|
||||
let countdownRemaining = max(0, countdownDuration - countdownInterval)
|
||||
let formattedCountdown = OWSFormat.formatDurationSeconds(Int(round(countdownRemaining)))
|
||||
let text = String(format: NSLocalizedString("ONBOARDING_VERIFICATION_CODE_COUNTDOWN_FORMAT",
|
||||
comment: "Format for the label of the 'sent code' label of the 'onboarding verification' view. Embeds {{the time until the code can be resent}}."),
|
||||
formattedCountdown)
|
||||
codeStateLink.setTitle(title: text, font: .ows_dynamicTypeBodyClamped, titleColor: Theme.secondaryColor)
|
||||
case .readyForResend:
|
||||
codeStateLink.setTitle(title: NSLocalizedString("ONBOARDING_VERIFICATION_ORIGINAL_CODE_MISSING_LINK",
|
||||
comment: "Label for link that can be used when the original code did not arrive."),
|
||||
font: .ows_dynamicTypeBodyClamped,
|
||||
titleColor: .ows_materialBlue)
|
||||
case .resent:
|
||||
codeStateLink.setTitle(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESENT_CODE_MISSING_LINK",
|
||||
comment: "Label for link that can be used when the resent code did not arrive."),
|
||||
font: .ows_dynamicTypeBodyClamped,
|
||||
titleColor: .ows_materialBlue)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
_ = onboardingCodeView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
@objc func backLinkTapped() {
|
||||
Logger.info("")
|
||||
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
@objc func resendCodeLinkTapped() {
|
||||
Logger.info("")
|
||||
|
||||
switch codeState {
|
||||
case .sent:
|
||||
// Ignore taps until the countdown expires.
|
||||
break
|
||||
case .readyForResend, .resent:
|
||||
showResendActionSheet()
|
||||
}
|
||||
}
|
||||
|
||||
private func showResendActionSheet() {
|
||||
Logger.info("")
|
||||
|
||||
let actionSheet = UIAlertController(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_ALERT_TITLE",
|
||||
comment: "Title for the 'resend code' alert in the 'onboarding verification' view."),
|
||||
message: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_ALERT_MESSAGE",
|
||||
comment: "Message for the 'resend code' alert in the 'onboarding verification' view."),
|
||||
preferredStyle: .actionSheet)
|
||||
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_BY_SMS_BUTTON",
|
||||
comment: "Label for the 'resend code by SMS' button in the 'onboarding verification' view."),
|
||||
style: .default) { _ in
|
||||
self.onboardingController.tryToRegister(fromViewController: self, smsVerification: true)
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ONBOARDING_VERIFICATION_RESEND_CODE_BY_VOICE_BUTTON",
|
||||
comment: "Label for the 'resend code by voice' button in the 'onboarding verification' view."),
|
||||
style: .default) { _ in
|
||||
self.onboardingController.tryToRegister(fromViewController: self, smsVerification: false)
|
||||
})
|
||||
actionSheet.addAction(OWSAlerts.cancelAction)
|
||||
|
||||
self.presentAlert(actionSheet)
|
||||
}
|
||||
|
||||
private func tryToVerify() {
|
||||
Logger.info("")
|
||||
|
||||
guard onboardingCodeView.isComplete else {
|
||||
self.setHasInvalidCode(false)
|
||||
return
|
||||
}
|
||||
|
||||
setHasInvalidCode(false)
|
||||
|
||||
onboardingController.update(verificationCode: onboardingCodeView.verificationCode)
|
||||
|
||||
// Temporarily hide the "resend link" button during the verification attempt.
|
||||
codeStateLink?.layer.opacity = 0.05
|
||||
|
||||
onboardingController.tryToVerify(fromViewController: self, completion: { (outcome) in
|
||||
self.codeStateLink?.layer.opacity = 1
|
||||
|
||||
if outcome == .invalidVerificationCode {
|
||||
self.setHasInvalidCode(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func setHasInvalidCode(_ value: Bool) {
|
||||
onboardingCodeView.setHasError(value)
|
||||
errorLabel.isHidden = !value
|
||||
}
|
||||
|
||||
@objc
|
||||
public func setVerificationCodeAndTryToVerify(_ verificationCode: String) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let filteredCode = verificationCode.digitsOnly
|
||||
guard filteredCode.count > 0 else {
|
||||
owsFailDebug("Invalid code: \(verificationCode)")
|
||||
return
|
||||
}
|
||||
|
||||
onboardingCodeView.set(verificationCode: filteredCode)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension OnboardingVerificationViewController: OnboardingCodeViewDelegate {
|
||||
public func codeViewDidChange() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
setHasInvalidCode(false)
|
||||
|
||||
tryToVerify()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
// }
|
||||
}
|
|
@ -54,8 +54,6 @@ public enum PushRegistrationError: Error {
|
|||
// MARK: Public interface
|
||||
|
||||
public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> {
|
||||
Logger.info("")
|
||||
|
||||
return firstly {
|
||||
self.registerUserNotificationSettings()
|
||||
}.then { () -> Promise<(pushToken: String, voipToken: String)> in
|
||||
|
@ -127,7 +125,6 @@ public enum PushRegistrationError: Error {
|
|||
// return any requested push tokens.
|
||||
public func registerUserNotificationSettings() -> Promise<Void> {
|
||||
AssertIsOnMainThread()
|
||||
Logger.info("registering user notification settings")
|
||||
return notificationPresenter.registerNotificationSettings()
|
||||
}
|
||||
|
||||
|
@ -158,16 +155,14 @@ public enum PushRegistrationError: Error {
|
|||
|
||||
private func registerForVanillaPushToken() -> Promise<String> {
|
||||
AssertIsOnMainThread()
|
||||
Logger.info("")
|
||||
|
||||
guard self.vanillaTokenPromise == nil else {
|
||||
let promise = vanillaTokenPromise!
|
||||
assert(promise.isPending)
|
||||
Logger.info("alreay pending promise for vanilla push token")
|
||||
return promise.map { $0.hexEncodedString }
|
||||
}
|
||||
|
||||
// No pending vanilla token yet. Create a new promise
|
||||
// No pending vanilla token yet; create a new promise
|
||||
let (promise, resolver) = Promise<Data>.pending()
|
||||
self.vanillaTokenPromise = promise
|
||||
self.vanillaTokenResolver = resolver
|
||||
|
@ -196,11 +191,10 @@ public enum PushRegistrationError: Error {
|
|||
}
|
||||
}.map { (pushTokenData: Data) -> String in
|
||||
if self.isSusceptibleToFailedPushRegistration {
|
||||
// Sentinal in case this bug is fixed.
|
||||
// Sentinal in case this bug is fixed
|
||||
owsFailDebug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.")
|
||||
}
|
||||
|
||||
Logger.info("successfully registered for vanilla push notifications")
|
||||
return pushTokenData.hexEncodedString
|
||||
}.ensure {
|
||||
self.vanillaTokenPromise = nil
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
|
@ -1,110 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RegistrationUtils.h"
|
||||
#import "OWSNavigationController.h"
|
||||
#import "Session-Swift.h"
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/OWSPreferences.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SessionServiceKit/TSAccountManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation RegistrationUtils
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
+ (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
|
||||
return SSKEnvironment.shared.tsAccountManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (void)showReregistrationUIFromViewController:(UIViewController *)fromViewController
|
||||
{
|
||||
UIAlertController *actionSheet =
|
||||
[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
[actionSheet
|
||||
addAction:[UIAlertAction
|
||||
actionWithTitle:NSLocalizedString(@"DEREGISTRATION_REREGISTER_WITH_SAME_PHONE_NUMBER",
|
||||
@"Label for button that lets users re-register using the same phone number.")
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *action) {
|
||||
[RegistrationUtils reregisterWithFromViewController:fromViewController];
|
||||
}]];
|
||||
|
||||
[actionSheet addAction:[OWSAlerts cancelAction]];
|
||||
|
||||
[fromViewController presentAlert:actionSheet];
|
||||
}
|
||||
|
||||
+ (void)reregisterWithFromViewController:(UIViewController *)fromViewController
|
||||
{
|
||||
OWSLogInfo(@"reregisterWithSamePhoneNumber.");
|
||||
|
||||
if (![self.tsAccountManager resetForReregistration]) {
|
||||
OWSFailDebug(@"could not reset for re-registration.");
|
||||
return;
|
||||
}
|
||||
|
||||
[Environment.shared.preferences unsetRecordedAPNSTokens];
|
||||
|
||||
[ModalActivityIndicatorViewController
|
||||
presentFromViewController:fromViewController
|
||||
canCancel:NO
|
||||
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
|
||||
NSString *phoneNumber = self.tsAccountManager.reregisterationPhoneNumber;
|
||||
[self.tsAccountManager registerWithPhoneNumber:phoneNumber
|
||||
captchaToken:nil
|
||||
success:^{
|
||||
OWSLogInfo(@"re-registering: send verification code succeeded.");
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
OnboardingController *onboardingController = [OnboardingController new];
|
||||
OnboardingPhoneNumber *onboardingPhoneNumber =
|
||||
[[OnboardingPhoneNumber alloc] initWithE164:phoneNumber
|
||||
userInput:phoneNumber];
|
||||
[onboardingController updateWithPhoneNumber:onboardingPhoneNumber];
|
||||
OnboardingVerificationViewController *viewController =
|
||||
[[OnboardingVerificationViewController alloc]
|
||||
initWithOnboardingController:onboardingController];
|
||||
[viewController hideBackLink];
|
||||
OWSNavigationController *navigationController =
|
||||
[[OWSNavigationController alloc] initWithRootViewController:viewController];
|
||||
navigationController.navigationBarHidden = YES;
|
||||
|
||||
[UIApplication sharedApplication].delegate.window.rootViewController
|
||||
= navigationController;
|
||||
}];
|
||||
});
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
OWSLogError(@"re-registering: send verification code failed.");
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[modalActivityIndicator dismissWithCompletion:^{
|
||||
if (error.code == 400) {
|
||||
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"REGISTRATION_ERROR", nil)
|
||||
message:NSLocalizedString(
|
||||
@"REGISTRATION_NON_VALID_NUMBER", nil)];
|
||||
} else {
|
||||
[OWSAlerts showAlertWithTitle:error.localizedDescription
|
||||
message:error.localizedRecoverySuggestion];
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
smsVerification:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -17,7 +17,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[];
|
|||
#import <SignalMessaging/ContactCellView.h>
|
||||
#import <SignalMessaging/ContactTableViewCell.h>
|
||||
#import <SignalMessaging/ContactsViewHelper.h>
|
||||
#import <SignalMessaging/CountryCodeViewController.h>
|
||||
#import <SignalMessaging/DebugLogger.h>
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/NSAttributedString+OWS.h>
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSTableViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CountryCodeViewController;
|
||||
|
||||
@protocol CountryCodeViewControllerDelegate <NSObject>
|
||||
|
||||
- (void)countryCodeViewController:(CountryCodeViewController *)vc
|
||||
didSelectCountryCode:(NSString *)countryCode
|
||||
countryName:(NSString *)countryName
|
||||
callingCode:(NSString *)callingCode;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface CountryCodeViewController : OWSTableViewController
|
||||
|
||||
@property (nonatomic, weak) id<CountryCodeViewControllerDelegate> countryCodeDelegate;
|
||||
|
||||
@property (nonatomic) BOOL isPresentedInNavigationController;
|
||||
|
||||
@property (nonatomic) UIInterfaceOrientationMask interfaceOrientationMask;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,176 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CountryCodeViewController.h"
|
||||
#import "OWSSearchBar.h"
|
||||
#import "PhoneNumberUtil.h"
|
||||
#import "Theme.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import "UIFont+OWS.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import <SessionServiceKit/NSString+SSK.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CountryCodeViewController () <OWSTableViewControllerDelegate, UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, readonly) UISearchBar *searchBar;
|
||||
|
||||
@property (nonatomic) NSArray<NSString *> *countryCodes;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation CountryCodeViewController
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
self.shouldUseTheme = NO;
|
||||
self.interfaceOrientationMask = DefaultUIInterfaceOrientationMask();
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
self.title = NSLocalizedString(@"COUNTRYCODE_SELECT_TITLE", @"");
|
||||
|
||||
self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:nil];
|
||||
|
||||
if (!self.isPresentedInNavigationController) {
|
||||
self.navigationItem.leftBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
|
||||
target:self
|
||||
action:@selector(dismissWasPressed:)];
|
||||
}
|
||||
|
||||
[self createViews];
|
||||
}
|
||||
|
||||
- (void)createViews
|
||||
{
|
||||
// Search
|
||||
UISearchBar *searchBar = [OWSSearchBar new];
|
||||
_searchBar = searchBar;
|
||||
searchBar.delegate = self;
|
||||
searchBar.placeholder = NSLocalizedString(@"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT", @"");
|
||||
[searchBar sizeToFit];
|
||||
|
||||
self.tableView.tableHeaderView = searchBar;
|
||||
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
#pragma mark - Table Contents
|
||||
|
||||
- (void)updateTableContents
|
||||
{
|
||||
OWSTableContents *contents = [OWSTableContents new];
|
||||
|
||||
__weak CountryCodeViewController *weakSelf = self;
|
||||
OWSTableSection *section = [OWSTableSection new];
|
||||
|
||||
for (NSString *countryCode in self.countryCodes) {
|
||||
OWSAssertDebug(countryCode.length > 0);
|
||||
OWSAssertDebug([PhoneNumberUtil countryNameFromCountryCode:countryCode].length > 0);
|
||||
OWSAssertDebug([PhoneNumberUtil callingCodeFromCountryCode:countryCode].length > 0);
|
||||
OWSAssertDebug(![[PhoneNumberUtil callingCodeFromCountryCode:countryCode] isEqualToString:@"+0"]);
|
||||
|
||||
[section addItem:[OWSTableItem
|
||||
itemWithCustomCellBlock:^{
|
||||
UITableViewCell *cell = [OWSTableItem newCell];
|
||||
[OWSTableItem configureCell:cell];
|
||||
cell.textLabel.text = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
|
||||
|
||||
UILabel *countryCodeLabel = [UILabel new];
|
||||
countryCodeLabel.text = [PhoneNumberUtil callingCodeFromCountryCode:countryCode];
|
||||
countryCodeLabel.font = [UIFont ows_regularFontWithSize:16.f];
|
||||
countryCodeLabel.textColor = Theme.secondaryColor;
|
||||
[countryCodeLabel sizeToFit];
|
||||
cell.accessoryView = countryCodeLabel;
|
||||
|
||||
return cell;
|
||||
}
|
||||
actionBlock:^{
|
||||
[weakSelf countryCodeWasSelected:countryCode];
|
||||
}]];
|
||||
}
|
||||
|
||||
[contents addSection:section];
|
||||
|
||||
self.contents = contents;
|
||||
}
|
||||
|
||||
- (void)countryCodeWasSelected:(NSString *)countryCode
|
||||
{
|
||||
OWSAssertDebug(countryCode.length > 0);
|
||||
|
||||
NSString *callingCodeSelected = [PhoneNumberUtil callingCodeFromCountryCode:countryCode];
|
||||
NSString *countryNameSelected = [PhoneNumberUtil countryNameFromCountryCode:countryCode];
|
||||
NSString *countryCodeSelected = countryCode;
|
||||
[self.countryCodeDelegate countryCodeViewController:self
|
||||
didSelectCountryCode:countryCodeSelected
|
||||
countryName:countryNameSelected
|
||||
callingCode:callingCodeSelected];
|
||||
[self.searchBar resignFirstResponder];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)dismissWasPressed:(id)sender
|
||||
{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
[self searchTextDidChange];
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
[self searchTextDidChange];
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
[self searchTextDidChange];
|
||||
}
|
||||
|
||||
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
[self searchTextDidChange];
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
|
||||
{
|
||||
[self searchTextDidChange];
|
||||
}
|
||||
|
||||
- (void)searchTextDidChange
|
||||
{
|
||||
NSString *searchText = [self.searchBar.text ows_stripped];
|
||||
|
||||
self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:searchText];
|
||||
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
#pragma mark - OWSTableViewControllerDelegate
|
||||
|
||||
- (void)tableViewWillBeginDragging
|
||||
{
|
||||
[self.searchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return self.interfaceOrientationMask;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -3,7 +3,6 @@
|
|||
//
|
||||
|
||||
#import "SelectRecipientViewController.h"
|
||||
#import "CountryCodeViewController.h"
|
||||
#import "PhoneNumber.h"
|
||||
#import "ViewControllerUtils.h"
|
||||
#import <SignalMessaging/ContactTableViewCell.h>
|
||||
|
@ -26,7 +25,7 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
|
|||
|
||||
#pragma mark -
|
||||
|
||||
@interface SelectRecipientViewController () <CountryCodeViewControllerDelegate,
|
||||
@interface SelectRecipientViewController () </*CountryCodeViewControllerDelegate,*/
|
||||
ContactsViewHelperDelegate,
|
||||
OWSTableViewControllerDelegate,
|
||||
UITextFieldDelegate>
|
||||
|
@ -267,16 +266,7 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
|
|||
|
||||
- (void)showCountryCodeView:(nullable id)sender
|
||||
{
|
||||
CountryCodeViewController *countryCodeController = [CountryCodeViewController new];
|
||||
countryCodeController.countryCodeDelegate = self;
|
||||
countryCodeController.isPresentedInNavigationController = self.isPresentedInNavigationController;
|
||||
if (self.isPresentedInNavigationController) {
|
||||
[self.navigationController pushViewController:countryCodeController animated:YES];
|
||||
} else {
|
||||
OWSNavigationController *navigationController =
|
||||
[[OWSNavigationController alloc] initWithRootViewController:countryCodeController];
|
||||
[self presentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)phoneNumberButtonPressed
|
||||
|
@ -401,20 +391,20 @@ NSString *const kSelectRecipientViewControllerCellIdentifier = @"kSelectRecipien
|
|||
|
||||
#pragma mark - CountryCodeViewControllerDelegate
|
||||
|
||||
- (void)countryCodeViewController:(CountryCodeViewController *)vc
|
||||
didSelectCountryCode:(NSString *)countryCode
|
||||
countryName:(NSString *)countryName
|
||||
callingCode:(NSString *)callingCode
|
||||
{
|
||||
OWSAssertDebug(countryCode.length > 0);
|
||||
OWSAssertDebug(countryName.length > 0);
|
||||
OWSAssertDebug(callingCode.length > 0);
|
||||
|
||||
[self updateCountryWithName:countryName callingCode:callingCode countryCode:countryCode];
|
||||
|
||||
// Trigger the formatting logic with a no-op edit.
|
||||
[self textField:self.phoneNumberTextField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
|
||||
}
|
||||
//- (void)countryCodeViewController:(CountryCodeViewController *)vc
|
||||
// didSelectCountryCode:(NSString *)countryCode
|
||||
// countryName:(NSString *)countryName
|
||||
// callingCode:(NSString *)callingCode
|
||||
//{
|
||||
// OWSAssertDebug(countryCode.length > 0);
|
||||
// OWSAssertDebug(countryName.length > 0);
|
||||
// OWSAssertDebug(callingCode.length > 0);
|
||||
//
|
||||
// [self updateCountryWithName:countryName callingCode:callingCode countryCode:countryCode];
|
||||
//
|
||||
// // Trigger the formatting logic with a no-op edit.
|
||||
// [self textField:self.phoneNumberTextField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
|
||||
//}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
|
|
|
@ -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
|
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS100RemoveTSRecipientsMigration.h"
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS100RemoveTSRecipientsMigrationId = @"100";
|
||||
|
||||
@implementation OWS100RemoveTSRecipientsMigration
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS100RemoveTSRecipientsMigrationId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
NSUInteger legacyRecipientCount = [transaction numberOfKeysInCollection:@"TSRecipient"];
|
||||
OWSLogWarn(@"Removing %lu objects from TSRecipient collection", (unsigned long)legacyRecipientCount);
|
||||
[transaction removeAllObjectsInCollection:@"TSRecipient"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS101ExistingUsersBlockOnIdentityChange.h"
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS101ExistingUsersBlockOnIdentityChangeMigrationId = @"101";
|
||||
|
||||
/**
|
||||
* This migration is no longer necessary, but deleting a class is complicated in Yap
|
||||
* and involves writing another migration to remove the previous. It seemed like the
|
||||
* simplest/safest thing to do would be to just leave it.
|
||||
*/
|
||||
@implementation OWS101ExistingUsersBlockOnIdentityChange
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS101ExistingUsersBlockOnIdentityChangeMigrationId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
OWSFailDebug(@"[OWS101ExistingUsersBlockOnIdentityChange] has been obviated.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDatabaseMigration.h"
|
||||
|
||||
@interface OWS102MoveLoggingPreferenceToUserDefaults : OWSDatabaseMigration
|
||||
|
||||
@end
|
|
@ -1,47 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS102MoveLoggingPreferenceToUserDefaults.h"
|
||||
#import "DebugLogger.h"
|
||||
#import "Environment.h"
|
||||
#import "OWSPreferences.h"
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS102MoveLoggingPreferenceToUserDefaultsMigrationId = @"102";
|
||||
|
||||
@implementation OWS102MoveLoggingPreferenceToUserDefaults
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS102MoveLoggingPreferenceToUserDefaultsMigrationId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
OWSLogWarn(@"[OWS102MoveLoggingPreferenceToUserDefaultsMigrationId] copying existing logging preference to "
|
||||
@"NSUserDefaults");
|
||||
|
||||
NSNumber *existingValue =
|
||||
[transaction objectForKey:OWSPreferencesKeyEnableDebugLog inCollection:OWSPreferencesSignalDatabaseCollection];
|
||||
|
||||
if (existingValue) {
|
||||
OWSLogInfo(@"assigning existing value: %@", existingValue);
|
||||
[OWSPreferences setIsLoggingEnabled:[existingValue boolValue]];
|
||||
|
||||
if (![existingValue boolValue]) {
|
||||
OWSLogInfo(@"Disabling file logger after one-time log settings migration.");
|
||||
// Since we're migrating, we didn't have the appropriate value on startup, and incorrectly started logging.
|
||||
[DebugLogger.sharedLogger disableFileLogging];
|
||||
} else {
|
||||
OWSLogInfo(@"Continuing to log after one-time log settings migration.");
|
||||
}
|
||||
} else {
|
||||
OWSLogInfo(@"not assigning any value, since no previous value was stored.");
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDatabaseMigration.h"
|
||||
|
||||
@interface OWS103EnableVideoCalling : OWSDatabaseMigration
|
||||
|
||||
@end
|
|
@ -1,67 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS103EnableVideoCalling.h"
|
||||
#import <SessionServiceKit/OWSRequestFactory.h>
|
||||
#import <SessionServiceKit/SSKEnvironment.h>
|
||||
#import <SessionServiceKit/TSAccountManager.h>
|
||||
#import <SessionServiceKit/TSNetworkManager.h>
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS103EnableVideoCallingMigrationId = @"103";
|
||||
|
||||
@implementation OWS103EnableVideoCalling
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
|
||||
return SSKEnvironment.shared.tsAccountManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS103EnableVideoCallingMigrationId;
|
||||
}
|
||||
|
||||
// Override parent migration
|
||||
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion
|
||||
{
|
||||
OWSAssertDebug(completion);
|
||||
|
||||
OWSLogWarn(@"running migration...");
|
||||
if ([self.tsAccountManager isRegistered]) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
TSRequest *request = [OWSRequestFactory updateAttributesRequest];
|
||||
[[TSNetworkManager sharedManager] makeRequest:request
|
||||
success:^(NSURLSessionDataTask *task, id responseObject) {
|
||||
OWSLogInfo(@"successfully ran");
|
||||
[self save];
|
||||
|
||||
completion();
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
if (!IsNSErrorNetworkFailure(error)) {
|
||||
OWSProdError([OWSAnalyticsEvents errorEnableVideoCallingRequestFailed]);
|
||||
}
|
||||
OWSLogError(@"failed with error: %@", error);
|
||||
|
||||
completion();
|
||||
}];
|
||||
});
|
||||
} else {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
OWSLogInfo(@"skipping; not registered");
|
||||
[self save];
|
||||
|
||||
completion();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -1,61 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS104CreateRecipientIdentities.h"
|
||||
#import <SessionServiceKit/OWSIdentityManager.h>
|
||||
#import <SessionServiceKit/OWSRecipientIdentity.h>
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS104CreateRecipientIdentitiesMigrationId = @"104";
|
||||
|
||||
/**
|
||||
* New SN behavior requires tracking additional state - not just the identity key data.
|
||||
* So we wrap the key, along with the new meta-data in an OWSRecipientIdentity.
|
||||
*/
|
||||
@implementation OWS104CreateRecipientIdentities
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS104CreateRecipientIdentitiesMigrationId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
NSMutableDictionary<NSString *, NSData *> *identityKeys = [NSMutableDictionary new];
|
||||
|
||||
[transaction
|
||||
enumerateKeysAndObjectsInCollection:OWSPrimaryStorageTrustedKeysCollection
|
||||
usingBlock:^(NSString *_Nonnull recipientId, id _Nonnull object, BOOL *_Nonnull stop) {
|
||||
if (![object isKindOfClass:[NSData class]]) {
|
||||
OWSFailDebug(
|
||||
@"Unexpected object in trusted keys collection key: %@ object: %@",
|
||||
recipientId,
|
||||
[object class]);
|
||||
return;
|
||||
}
|
||||
NSData *identityKey = (NSData *)object;
|
||||
[identityKeys setObject:identityKey forKey:recipientId];
|
||||
}];
|
||||
|
||||
[identityKeys enumerateKeysAndObjectsUsingBlock:^(
|
||||
NSString *_Nonnull recipientId, NSData *_Nonnull identityKey, BOOL *_Nonnull stop) {
|
||||
OWSLogInfo(@"Migrating identity key for recipient: %@", recipientId);
|
||||
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
|
||||
identityKey:identityKey
|
||||
isFirstKnownKey:NO
|
||||
createdAt:[NSDate dateWithTimeIntervalSince1970:0]
|
||||
verificationState:OWSVerificationStateDefault]
|
||||
saveWithTransaction:transaction];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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
|
|
@ -1,47 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS105AttachmentFilePaths.h"
|
||||
#import <SessionServiceKit/TSAttachmentStream.h>
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS105AttachmentFilePathsMigrationId = @"105";
|
||||
|
||||
@implementation OWS105AttachmentFilePaths
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS105AttachmentFilePathsMigrationId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray new];
|
||||
[transaction enumerateKeysAndObjectsInCollection:TSAttachmentStream.collection
|
||||
usingBlock:^(NSString *key, TSAttachment *attachment, BOOL *stop) {
|
||||
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
|
||||
return;
|
||||
}
|
||||
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
|
||||
[attachmentStreams addObject:attachmentStream];
|
||||
}];
|
||||
|
||||
OWSLogInfo(@"Saving %lu attachment streams.", (unsigned long)attachmentStreams.count);
|
||||
|
||||
// Persist the new localRelativeFilePath property of TSAttachmentStream.
|
||||
// For performance, we want to upgrade all existing attachment streams in
|
||||
// a single transaction.
|
||||
for (TSAttachmentStream *attachmentStream in attachmentStreams) {
|
||||
[attachmentStream saveWithTransaction:transaction];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,123 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
import SessionServiceKit
|
||||
|
||||
@objc
|
||||
public class OWS106EnsureProfileComplete: OWSDatabaseMigration {
|
||||
|
||||
private static var sharedCompleteRegistrationFixerJob: CompleteRegistrationFixerJob?
|
||||
|
||||
// increment a similar constant for each migration.
|
||||
@objc
|
||||
class func migrationId() -> String {
|
||||
return "106"
|
||||
}
|
||||
|
||||
// Overriding runUp since we have some specific completion criteria which
|
||||
// is more likely to fail since it involves network requests.
|
||||
override public func runUp(completion:@escaping () -> Void) {
|
||||
let job = CompleteRegistrationFixerJob(completionHandler: { (didSucceed) in
|
||||
|
||||
if (didSucceed) {
|
||||
Logger.info("Completed. Saving.")
|
||||
self.save()
|
||||
} else {
|
||||
Logger.error("Failed.")
|
||||
}
|
||||
|
||||
completion()
|
||||
})
|
||||
|
||||
type(of: self).sharedCompleteRegistrationFixerJob = job
|
||||
|
||||
job.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* A previous client bug made it possible for re-registering users to register their new account
|
||||
* but never upload new pre-keys. The symptom is that there will be accounts with no uploaded
|
||||
* identity key. We detect that here and fix the situation
|
||||
*/
|
||||
private class CompleteRegistrationFixerJob: NSObject {
|
||||
|
||||
// MARK: - Dependencies
|
||||
|
||||
private var tsAccountManager: TSAccountManager {
|
||||
return TSAccountManager.sharedInstance()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
// Duration between retries if update fails.
|
||||
let kRetryInterval: TimeInterval = 5
|
||||
|
||||
let completionHandler: (Bool) -> Void
|
||||
|
||||
init(completionHandler: @escaping (Bool) -> Void) {
|
||||
self.completionHandler = completionHandler
|
||||
}
|
||||
|
||||
func start() {
|
||||
guard tsAccountManager.isRegistered() else {
|
||||
self.completionHandler(true)
|
||||
return
|
||||
}
|
||||
|
||||
self.ensureProfileComplete().done {
|
||||
Logger.info("complete. Canceling timer and saving.")
|
||||
self.completionHandler(true)
|
||||
}.catch { error in
|
||||
let nserror = error as NSError
|
||||
if nserror.domain == TSNetworkManagerErrorDomain {
|
||||
// Don't retry if we had an unrecoverable error.
|
||||
// In particular, 401 (invalid auth) is unrecoverable.
|
||||
let isUnrecoverableError = nserror.code == 401
|
||||
if isUnrecoverableError {
|
||||
Logger.error("failed due to unrecoverable error: \(error). Aborting.")
|
||||
self.completionHandler(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Logger.error("failed with \(error).")
|
||||
self.completionHandler(false)
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
func ensureProfileComplete() -> Promise<Void> {
|
||||
guard let localRecipientId = TSAccountManager.localNumber() else {
|
||||
// local app doesn't think we're registered, so nothing to worry about.
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
return firstly {
|
||||
ProfileFetcherJob().getProfile(recipientId: localRecipientId)
|
||||
}.done { _ in
|
||||
Logger.info("verified recipient profile is in good shape: \(localRecipientId)")
|
||||
}.recover { error -> Promise<Void> in
|
||||
switch error {
|
||||
case SignalServiceProfile.ValidationError.invalidIdentityKey(let description):
|
||||
Logger.warn("detected incomplete profile for \(localRecipientId) error: \(description)")
|
||||
|
||||
let (promise, resolver) = Promise<Void>.pending()
|
||||
// This is the error condition we're looking for. Update prekeys to properly set the identity key, completing registration.
|
||||
TSPreKeyManager.createPreKeys(success: {
|
||||
Logger.info("successfully uploaded pre-keys. Profile should be fixed.")
|
||||
resolver.fulfill(())
|
||||
},
|
||||
failure: { _ in
|
||||
resolver.reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.logTag) Unknown error"))
|
||||
})
|
||||
|
||||
return promise
|
||||
default:
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -1,30 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS107LegacySounds.h"
|
||||
#import "OWSSounds.h"
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS107LegacySoundsMigrationId = @"107";
|
||||
|
||||
@implementation OWS107LegacySounds
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS107LegacySoundsMigrationId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
[OWSSounds setGlobalNotificationSound:OWSSound_SignalClassic transaction:transaction];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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
|
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS108CallLoggingPreference.h"
|
||||
#import "Environment.h"
|
||||
#import "OWSPreferences.h"
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS108CallLoggingPreferenceId = @"108";
|
||||
|
||||
@implementation OWS108CallLoggingPreference
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS108CallLoggingPreferenceId;
|
||||
}
|
||||
|
||||
- (void)runUpWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
[Environment.shared.preferences applyCallLoggingSettingsForLegacyUsersWithTransaction:transaction];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -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
|
|
@ -1,46 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS109OutgoingMessageState.h"
|
||||
#import <SessionServiceKit/OWSPrimaryStorage.h>
|
||||
#import <SessionServiceKit/TSOutgoingMessage.h>
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS109OutgoingMessageStateMigrationId = @"109";
|
||||
|
||||
@implementation OWS109OutgoingMessageState
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS109OutgoingMessageStateMigrationId;
|
||||
}
|
||||
|
||||
// Override parent migration
|
||||
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion
|
||||
{
|
||||
OWSAssertDebug(completion);
|
||||
|
||||
|
||||
OWSDatabaseConnection *dbConnection = (OWSDatabaseConnection *)self.primaryStorage.newDatabaseConnection;
|
||||
|
||||
[self resaveDBCollection:TSOutgoingMessage.collection
|
||||
filter:^(id entity) {
|
||||
return [entity isKindOfClass:[TSOutgoingMessage class]];
|
||||
}
|
||||
dbConnection:dbConnection
|
||||
completion:^{
|
||||
OWSLogInfo(@"Completed migration %@", self.uniqueId);
|
||||
|
||||
[self save];
|
||||
|
||||
completion();
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,118 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class OWS110SortIdMigration: OWSDatabaseMigration {
|
||||
// increment a similar constant for each migration.
|
||||
@objc
|
||||
class func migrationId() -> String {
|
||||
// append char "x" because we want to rerun on some internal devices which
|
||||
// have already run this migration.
|
||||
return "110x"
|
||||
}
|
||||
|
||||
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
Logger.debug("")
|
||||
BenchAsync(title: "Sort Migration") { completeBenchmark in
|
||||
self.doMigration {
|
||||
completeBenchmark()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func doMigration(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
// TODO batch this?
|
||||
try! Storage.writeSync { transaction in
|
||||
|
||||
var archivedThreads: [TSThread] = []
|
||||
|
||||
// get archived threads before migration
|
||||
TSThread.enumerateCollectionObjects(with: transaction) { (object, _) in
|
||||
guard let thread = object as? TSThread else {
|
||||
owsFailDebug("unexpected object: \(type(of: object))")
|
||||
return
|
||||
}
|
||||
|
||||
if thread.isArchivedByLegacyTimestampForSorting {
|
||||
archivedThreads.append(thread)
|
||||
}
|
||||
}
|
||||
|
||||
guard let legacySorting: YapDatabaseAutoViewTransaction = transaction.extension(TSMessageDatabaseViewExtensionName_Legacy) as? YapDatabaseAutoViewTransaction else {
|
||||
owsFailDebug("legacySorting was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let totalCount: UInt = legacySorting.numberOfItemsInAllGroups()
|
||||
var completedCount: UInt = 0
|
||||
|
||||
var allGroups = [String]()
|
||||
legacySorting.enumerateGroups { group, _ in
|
||||
allGroups.append(group)
|
||||
}
|
||||
|
||||
var seenGroups: Set<String> = Set()
|
||||
for group in allGroups {
|
||||
autoreleasepool {
|
||||
// Sanity Check #1
|
||||
// Make sure our enumeration is monotonically increasing.
|
||||
// Note: sortIds increase monotonically WRT timestampForLegacySorting, but only WRT the interaction's thread.
|
||||
//
|
||||
// e.g. When we migrate the **next** thread, we start with that thread's oldest interaction. So it's possible and expected
|
||||
// that thread2's oldest interaction will have a smaller timestampForLegacySorting, but a greater sortId then an interaction
|
||||
// in thread1. That's OK because we only sort messages with respect to the thread they belong in. We don't have any sort of
|
||||
// "global sort" of messages across all threads.
|
||||
var previousTimestampForLegacySorting: UInt64 = 0
|
||||
|
||||
// Sanity Check #2
|
||||
// Ensure we only process a DB View's group (i.e. threadId) once.
|
||||
guard !seenGroups.contains(group) else {
|
||||
owsFail("unexpectedly seeing a repeated group: \(group)")
|
||||
}
|
||||
seenGroups.insert(group)
|
||||
|
||||
var groupKeys = [String]()
|
||||
legacySorting.enumerateKeys(inGroup: group, using: { (_, key, _, _) in
|
||||
groupKeys.append(key)
|
||||
})
|
||||
let groupKeyBatchSize: Int = 1024
|
||||
for batch in groupKeys.chunked(by: groupKeyBatchSize) {
|
||||
autoreleasepool {
|
||||
for uniqueId in batch {
|
||||
guard let interaction = TSInteraction.fetch(uniqueId: uniqueId, transaction: transaction) else {
|
||||
owsFailDebug("Could not load interaction: \(uniqueId)")
|
||||
return
|
||||
}
|
||||
if interaction.timestampForLegacySorting() < previousTimestampForLegacySorting {
|
||||
owsFailDebug("unexpected object ordering previousTimestampForLegacySorting: \(previousTimestampForLegacySorting) interaction.timestampForLegacySorting: \(interaction.timestampForLegacySorting())")
|
||||
}
|
||||
previousTimestampForLegacySorting = interaction.timestampForLegacySorting()
|
||||
|
||||
interaction.saveNextSortId(transaction: transaction)
|
||||
|
||||
completedCount += 1
|
||||
if completedCount % 100 == 0 {
|
||||
// Legit usage of legacy sorting for migration to new sorting
|
||||
Logger.info("thread: \(interaction.uniqueThreadId), timestampForLegacySorting:\(interaction.timestampForLegacySorting()), sortId: \(interaction.sortId) totalCount: \(totalCount), completedcount: \(completedCount)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("re-archiving \(archivedThreads.count) threads which were previously archived")
|
||||
for archivedThread in archivedThreads {
|
||||
archivedThread.archiveThread(with: transaction)
|
||||
}
|
||||
|
||||
self.save(with: transaction)
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SessionServiceKit
|
||||
|
||||
@objc
|
||||
public class OWS114RemoveDynamicInteractions: OWSDatabaseMigration {
|
||||
|
||||
// MARK: - Dependencies
|
||||
|
||||
// MARK: -
|
||||
|
||||
// Increment a similar constant for each migration.
|
||||
@objc
|
||||
class func migrationId() -> String {
|
||||
return "114"
|
||||
}
|
||||
|
||||
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
Logger.debug("")
|
||||
BenchAsync(title: "\(self.logTag)") { (benchCompletion) in
|
||||
self.doMigrationAsync(completion: {
|
||||
benchCompletion()
|
||||
completion()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doMigrationAsync(completion : @escaping OWSDatabaseMigrationCompletion) {
|
||||
DispatchQueue.global().async {
|
||||
try! Storage.writeSync { transaction in
|
||||
guard let dbView = TSDatabaseView.threadSpecialMessagesDatabaseView(transaction) as? YapDatabaseViewTransaction else {
|
||||
owsFailDebug("Couldn't load db view.")
|
||||
return
|
||||
}
|
||||
|
||||
var interactionsToDelete = [TSInteraction]()
|
||||
let groupIds = dbView.allGroups()
|
||||
for groupId in groupIds {
|
||||
dbView.enumerateKeysAndObjects(inGroup: groupId) { (_: String, _: String, object: Any, _: UInt, _: UnsafeMutablePointer<ObjCBool>) in
|
||||
guard let interaction = object as? TSInteraction else {
|
||||
owsFailDebug("Invalid database entity: \(type(of: object)).")
|
||||
return
|
||||
}
|
||||
guard interaction.isDynamicInteraction() else {
|
||||
return
|
||||
}
|
||||
interactionsToDelete.append(interaction)
|
||||
}
|
||||
}
|
||||
|
||||
for interaction in interactionsToDelete {
|
||||
Logger.debug("Cleaning up interaction: \(type(of: interaction)).")
|
||||
interaction.remove(with: transaction)
|
||||
}
|
||||
|
||||
self.save(with: transaction)
|
||||
}
|
||||
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,14 +3,6 @@
|
|||
//
|
||||
|
||||
#import "OWSDatabaseMigrationRunner.h"
|
||||
#import "OWS100RemoveTSRecipientsMigration.h"
|
||||
#import "OWS102MoveLoggingPreferenceToUserDefaults.h"
|
||||
#import "OWS103EnableVideoCalling.h"
|
||||
#import "OWS104CreateRecipientIdentities.h"
|
||||
#import "OWS105AttachmentFilePaths.h"
|
||||
#import "OWS107LegacySounds.h"
|
||||
#import "OWS108CallLoggingPreference.h"
|
||||
#import "OWS109OutgoingMessageState.h"
|
||||
#import "OWSDatabaseMigration.h"
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SessionServiceKit/AppContext.h>
|
||||
|
@ -34,20 +26,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (NSArray<OWSDatabaseMigration *> *)allMigrations
|
||||
{
|
||||
return @[
|
||||
[[OWS100RemoveTSRecipientsMigration alloc] init],
|
||||
[[OWS102MoveLoggingPreferenceToUserDefaults alloc] init],
|
||||
[[OWS103EnableVideoCalling alloc] init],
|
||||
[[OWS104CreateRecipientIdentities alloc] init],
|
||||
[[OWS105AttachmentFilePaths alloc] init],
|
||||
[[OWS106EnsureProfileComplete alloc] init],
|
||||
[[OWS107LegacySounds alloc] init],
|
||||
[[OWS108CallLoggingPreference alloc] init],
|
||||
[[OWS109OutgoingMessageState alloc] init],
|
||||
[OWS110SortIdMigration new],
|
||||
[[OWS111UDAttributesMigration alloc] init],
|
||||
[[OWS112TypingIndicatorsMigration alloc] init],
|
||||
[[OWS113MultiAttachmentMediaMessages alloc] init],
|
||||
[[OWS114RemoveDynamicInteractions alloc] init],
|
||||
[[LK002RemoveFriendRequests alloc] init]
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue