Merge branch 'dev' into onion-requests
This commit is contained in:
commit
70eaa5982c
30
Podfile.lock
30
Podfile.lock
|
@ -42,13 +42,13 @@ PODS:
|
|||
- PureLayout (3.1.6)
|
||||
- Reachability (3.2)
|
||||
- SAMKeychain (1.5.3)
|
||||
- SessionAxolotlKit (1.0.6):
|
||||
- SessionAxolotlKit (1.0.7):
|
||||
- CocoaLumberjack
|
||||
- SessionCoreKit (~> 1.0.0)
|
||||
- SessionCurve25519Kit (~> 2.1.2)
|
||||
- SessionHKDFKit (~> 0.0.5)
|
||||
- SwiftProtobuf (~> 1.5.0)
|
||||
- SessionAxolotlKit/Tests (1.0.6):
|
||||
- SessionAxolotlKit/Tests (1.0.7):
|
||||
- CocoaLumberjack
|
||||
- SessionCoreKit (~> 1.0.0)
|
||||
- SessionCurve25519Kit (~> 2.1.2)
|
||||
|
@ -72,18 +72,18 @@ PODS:
|
|||
- SessionHKDFKit/Tests (0.0.5):
|
||||
- CocoaLumberjack
|
||||
- SessionCoreKit
|
||||
- SessionMetadataKit (1.0.6):
|
||||
- SessionMetadataKit (1.0.7):
|
||||
- CocoaLumberjack
|
||||
- CryptoSwift (~> 1.3)
|
||||
- SessionAxolotlKit (~> 1.0.6)
|
||||
- SessionAxolotlKit (~> 1.0.7)
|
||||
- SessionCoreKit (~> 1.0.0)
|
||||
- SessionCurve25519Kit (~> 2.1.2)
|
||||
- SessionHKDFKit (~> 0.0.5)
|
||||
- SwiftProtobuf (~> 1.5.0)
|
||||
- SessionMetadataKit/Tests (1.0.6):
|
||||
- SessionMetadataKit/Tests (1.0.7):
|
||||
- CocoaLumberjack
|
||||
- CryptoSwift (~> 1.3)
|
||||
- SessionAxolotlKit (~> 1.0.6)
|
||||
- SessionAxolotlKit (~> 1.0.7)
|
||||
- SessionCoreKit (~> 1.0.0)
|
||||
- SessionCurve25519Kit (~> 2.1.2)
|
||||
- SessionHKDFKit (~> 0.0.5)
|
||||
|
@ -98,10 +98,10 @@ PODS:
|
|||
- PromiseKit (~> 6.0)
|
||||
- Reachability
|
||||
- SAMKeychain
|
||||
- SessionAxolotlKit (~> 1.0.6)
|
||||
- SessionAxolotlKit (~> 1.0.7)
|
||||
- SessionCoreKit (~> 1.0.0)
|
||||
- SessionCurve25519Kit (~> 2.1.3)
|
||||
- SessionMetadataKit (~> 1.0.6)
|
||||
- SessionMetadataKit (~> 1.0.7)
|
||||
- Starscream
|
||||
- SwiftProtobuf (~> 1.5.0)
|
||||
- YapDatabase/SQLCipher
|
||||
|
@ -115,10 +115,10 @@ PODS:
|
|||
- PromiseKit (~> 6.0)
|
||||
- Reachability
|
||||
- SAMKeychain
|
||||
- SessionAxolotlKit (~> 1.0.6)
|
||||
- SessionAxolotlKit (~> 1.0.7)
|
||||
- SessionCoreKit (~> 1.0.0)
|
||||
- SessionCurve25519Kit (~> 2.1.3)
|
||||
- SessionMetadataKit (~> 1.0.6)
|
||||
- SessionMetadataKit (~> 1.0.7)
|
||||
- Starscream
|
||||
- SwiftProtobuf (~> 1.5.0)
|
||||
- YapDatabase/SQLCipher
|
||||
|
@ -277,7 +277,7 @@ CHECKOUT OPTIONS:
|
|||
:commit: b72c2d1e6132501db906de2cffa8ded7803c54f4
|
||||
:git: https://github.com/signalapp/Mantle
|
||||
SessionAxolotlKit:
|
||||
:commit: 663f58f4da7bf4d159e366352c2bb7715049671b
|
||||
:commit: be92fccb6152ee02c8c2658cb3c2e21201f119d1
|
||||
:git: https://github.com/loki-project/session-ios-protocol-kit.git
|
||||
SessionCoreKit:
|
||||
:commit: 0d66c90657b62cb66ecd2767c57408a951650f23
|
||||
|
@ -289,7 +289,7 @@ CHECKOUT OPTIONS:
|
|||
:commit: 0dcf8cf8a7995ef8663146f7063e6c1d7f5a3274
|
||||
:git: https://github.com/nielsandriesse/session-ios-hkdf-kit.git
|
||||
SessionMetadataKit:
|
||||
:commit: 935300f1de6c3e6b77fd6f7ad69b7ce3d7ee9ab5
|
||||
:commit: 43f1de289de7a2fbf629294652dc34a1e9ba76e7
|
||||
:git: https://github.com/loki-project/session-ios-metadata-kit
|
||||
Starscream:
|
||||
:commit: b09ea163c3cb305152c65b299cb024610f52e735
|
||||
|
@ -312,12 +312,12 @@ SPEC CHECKSUMS:
|
|||
PureLayout: bd3c4ec3a3819ad387c99ebb72c6b129c3ed4d2d
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
SessionAxolotlKit: f65b7402b63549597e23bc6c8f92c5e2da182ae4
|
||||
SessionAxolotlKit: f3558573a3cb52ebb921572f2f3b683da5eddad9
|
||||
SessionCoreKit: 778a3f6e3da788b43497734166646025b6392e88
|
||||
SessionCurve25519Kit: 9bb9afe199e4bc23578a4b15932ad2c57bd047b1
|
||||
SessionHKDFKit: b0f4e669411703ab925aba07491c5611564d1419
|
||||
SessionMetadataKit: 1e5dbd59f6229d9238557751bfbc57c0f4b1dd6b
|
||||
SessionServiceKit: 903f4a01384ad4f827e035e693cd87605c223724
|
||||
SessionMetadataKit: 22629bea760b0c124a6a7a4d0bf0195362bc8e92
|
||||
SessionServiceKit: 98198f84944d91f585cacbc557de8fc70bee6d1f
|
||||
SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072
|
||||
SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9
|
||||
Starscream: 8aaf1a7feb805c816d0e7d3190ef23856f6665b9
|
||||
|
|
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit 158e066d79806ef0e466370da5c303dedb90c654
|
||||
Subproject commit 8f3d6d46718795227074d6545776206cf18c0244
|
|
@ -42,7 +42,7 @@ A Swift/Objective-C library for communicating with the Session messaging service
|
|||
s.dependency 'CocoaLumberjack'
|
||||
s.dependency 'CryptoSwift', '~> 1.3'
|
||||
s.dependency 'AFNetworking'
|
||||
s.dependency 'SessionAxolotlKit', '~> 1.0.6'
|
||||
s.dependency 'SessionAxolotlKit', '~> 1.0.7'
|
||||
s.dependency 'Mantle'
|
||||
s.dependency 'YapDatabase/SQLCipher'
|
||||
s.dependency 'Starscream'
|
||||
|
@ -52,7 +52,7 @@ A Swift/Objective-C library for communicating with the Session messaging service
|
|||
s.dependency 'Reachability'
|
||||
s.dependency 'SwiftProtobuf', '~> 1.5.0'
|
||||
s.dependency 'SessionCoreKit', '~> 1.0.0'
|
||||
s.dependency 'SessionMetadataKit', '~> 1.0.6'
|
||||
s.dependency 'SessionMetadataKit', '~> 1.0.7'
|
||||
s.dependency 'PromiseKit', '~> 6.0'
|
||||
|
||||
s.test_spec 'Tests' do |test_spec|
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2400888D239F30A600305217 /* SessionRestorationView.swift */; };
|
||||
241C1192245F8878005CB2F4 /* LK001UpdateFriendRequestStatusStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C1191245F8878005CB2F4 /* LK001UpdateFriendRequestStatusStorage.swift */; };
|
||||
241C6314231F64C000B4198E /* JazzIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C630E231F5AAC00B4198E /* JazzIcon.swift */; };
|
||||
241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */; };
|
||||
241C6316231F64CE00B4198E /* UIColor+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6310231F5C4400B4198E /* UIColor+Helper.swift */; };
|
||||
|
@ -35,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 */; };
|
||||
|
@ -70,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 */; };
|
||||
|
@ -90,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 */; };
|
||||
|
@ -124,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 */; };
|
||||
|
@ -149,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 */; };
|
||||
|
@ -181,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 */; };
|
||||
|
@ -215,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, ); }; };
|
||||
|
@ -241,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 */; };
|
||||
|
@ -268,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, ); }; };
|
||||
|
@ -307,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 */; };
|
||||
|
@ -324,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 */; };
|
||||
|
@ -334,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 */; };
|
||||
|
@ -412,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 */; };
|
||||
|
@ -454,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 */; };
|
||||
|
@ -474,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 */; };
|
||||
|
@ -489,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 */; };
|
||||
|
@ -505,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 */; };
|
||||
|
@ -566,8 +519,6 @@
|
|||
B80C6B572384A56D00FDBC8B /* DeviceLinksVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80C6B562384A56D00FDBC8B /* DeviceLinksVC.swift */; };
|
||||
B80C6B592384C4E700FDBC8B /* DeviceNameModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80C6B582384C4E700FDBC8B /* DeviceNameModal.swift */; };
|
||||
B80C6B5B2384C7F900FDBC8B /* DeviceNameModalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80C6B5A2384C7F900FDBC8B /* DeviceNameModalDelegate.swift */; };
|
||||
B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8162F0222891AD600D46544 /* FriendRequestView.swift */; };
|
||||
B8162F0522892C5F00D46544 /* FriendRequestViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */; };
|
||||
B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */; };
|
||||
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40872399EB0E00A248E7 /* LandingVC.swift */; };
|
||||
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40892399EC0600A248E7 /* FakeChatView.swift */; };
|
||||
|
@ -633,6 +584,7 @@
|
|||
C35E8AA82485C85800ACB629 /* GeoLite2-Country-Locations-English.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */; };
|
||||
C35E8AA92485C85800ACB629 /* GeoLite2-Country-Blocks-IPv4.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */; };
|
||||
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAD2485E51D00ACB629 /* IP2Country.swift */; };
|
||||
C3638C0524C7F0B500AF29BC /* LK002RemoveFriendRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3638C0424C7F0B500AF29BC /* LK002RemoveFriendRequests.swift */; };
|
||||
C36B8707243C50C60049991D /* SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; };
|
||||
C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; };
|
||||
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; };
|
||||
|
@ -746,7 +698,6 @@
|
|||
1C93CF3971B64E8B6C1F9AC1 /* Pods-SignalShareExtension.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.test.xcconfig"; sourceTree = "<group>"; };
|
||||
1CE3CD5C23334683BDD3D78C /* Pods-Signal.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.test.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.test.xcconfig"; sourceTree = "<group>"; };
|
||||
2400888D239F30A600305217 /* SessionRestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRestorationView.swift; sourceTree = "<group>"; };
|
||||
241C1191245F8878005CB2F4 /* LK001UpdateFriendRequestStatusStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LK001UpdateFriendRequestStatusStorage.swift; sourceTree = "<group>"; };
|
||||
241C630E231F5AAC00B4198E /* JazzIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JazzIcon.swift; sourceTree = "<group>"; };
|
||||
241C6310231F5C4400B4198E /* UIColor+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helper.swift"; sourceTree = "<group>"; };
|
||||
241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Rounding.swift"; sourceTree = "<group>"; };
|
||||
|
@ -778,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>"; };
|
||||
|
@ -793,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>"; };
|
||||
|
@ -839,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>"; };
|
||||
|
@ -869,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>"; };
|
||||
|
@ -908,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>"; };
|
||||
|
@ -936,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>"; };
|
||||
|
@ -976,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>"; };
|
||||
|
@ -1010,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>"; };
|
||||
|
@ -1041,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>"; };
|
||||
|
@ -1072,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>"; };
|
||||
|
@ -1135,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>"; };
|
||||
|
@ -1164,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>"; };
|
||||
|
@ -1176,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>"; };
|
||||
|
@ -1248,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; };
|
||||
|
@ -1294,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>"; };
|
||||
|
@ -1315,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>"; };
|
||||
|
@ -1326,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>"; };
|
||||
|
@ -1335,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>"; };
|
||||
|
@ -1352,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>"; };
|
||||
|
@ -1443,8 +1340,6 @@
|
|||
B80C6B562384A56D00FDBC8B /* DeviceLinksVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinksVC.swift; sourceTree = "<group>"; };
|
||||
B80C6B582384C4E700FDBC8B /* DeviceNameModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceNameModal.swift; sourceTree = "<group>"; };
|
||||
B80C6B5A2384C7F900FDBC8B /* DeviceNameModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceNameModalDelegate.swift; sourceTree = "<group>"; };
|
||||
B8162F0222891AD600D46544 /* FriendRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestView.swift; sourceTree = "<group>"; };
|
||||
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestViewDelegate.swift; sourceTree = "<group>"; };
|
||||
B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiRSSFeedPoller.swift; sourceTree = "<group>"; };
|
||||
B82B40872399EB0E00A248E7 /* LandingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingVC.swift; sourceTree = "<group>"; };
|
||||
B82B40892399EC0600A248E7 /* FakeChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeChatView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1514,6 +1409,7 @@
|
|||
C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Locations-English.csv"; sourceTree = "<group>"; };
|
||||
C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Blocks-IPv4.csv"; sourceTree = "<group>"; };
|
||||
C35E8AAD2485E51D00ACB629 /* IP2Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IP2Country.swift; sourceTree = "<group>"; };
|
||||
C3638C0424C7F0B500AF29BC /* LK002RemoveFriendRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LK002RemoveFriendRequests.swift; sourceTree = "<group>"; };
|
||||
C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = "<group>"; };
|
||||
C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = "<group>"; };
|
||||
C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionSheet.swift; sourceTree = "<group>"; };
|
||||
|
@ -1636,14 +1532,6 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
241C1190245F8765005CB2F4 /* Migrations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
241C1191245F8878005CB2F4 /* LK001UpdateFriendRequestStatusStorage.swift */,
|
||||
);
|
||||
path = Migrations;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
34074F54203D0722004596AE /* Sounds */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1713,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>";
|
||||
|
@ -1747,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 */,
|
||||
|
@ -1794,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 = (
|
||||
|
@ -1935,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 */,
|
||||
|
@ -2098,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 */,
|
||||
|
@ -2111,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 */,
|
||||
|
@ -2361,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 */,
|
||||
|
@ -2481,7 +2310,6 @@
|
|||
458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */,
|
||||
458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */,
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
|
||||
4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2617,8 +2445,6 @@
|
|||
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */,
|
||||
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */,
|
||||
450DF2041E0D74AC003D14BE /* Platform.swift */,
|
||||
34E5DC8020D8050D00C08145 /* RegistrationUtils.h */,
|
||||
34E5DC8120D8050D00C08145 /* RegistrationUtils.m */,
|
||||
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
|
||||
FCFA64B11A24F29E0007FB87 /* UI Categories */,
|
||||
);
|
||||
|
@ -2800,7 +2626,7 @@
|
|||
B846365922B7417900AF1514 /* Loki */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
241C1190245F8765005CB2F4 /* Migrations */,
|
||||
C3638C0324C7F09F00AF29BC /* Migrations */,
|
||||
B8C9689223FA1B05005F64E0 /* Redesign */,
|
||||
B8544E3623D520F600299F14 /* Jazz Icon */,
|
||||
);
|
||||
|
@ -2880,8 +2706,6 @@
|
|||
B8BB82AA238F669C00BA5194 /* ConversationCell.swift */,
|
||||
B82B4093239DF15900A248E7 /* ConversationTitleView.swift */,
|
||||
B82B40892399EC0600A248E7 /* FakeChatView.swift */,
|
||||
B8162F0222891AD600D46544 /* FriendRequestView.swift */,
|
||||
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */,
|
||||
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
|
||||
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
|
||||
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */,
|
||||
|
@ -2967,6 +2791,14 @@
|
|||
path = CSV;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C3638C0324C7F09F00AF29BC /* Migrations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C3638C0424C7F0B500AF29BC /* LK002RemoveFriendRequests.swift */,
|
||||
);
|
||||
path = Migrations;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D221A07E169C9E5E00537ABF = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3095,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 */,
|
||||
|
@ -3114,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 */,
|
||||
|
@ -3123,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 */,
|
||||
|
@ -3131,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 */,
|
||||
|
@ -3156,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 */,
|
||||
);
|
||||
|
@ -3227,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 */,
|
||||
|
@ -3636,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;
|
||||
|
@ -3862,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 */,
|
||||
|
@ -3910,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 */,
|
||||
|
@ -3923,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 */,
|
||||
|
@ -3954,12 +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 */,
|
||||
|
@ -3967,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 */,
|
||||
|
@ -3992,10 +3785,8 @@
|
|||
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 */,
|
||||
241C1192245F8878005CB2F4 /* LK001UpdateFriendRequestStatusStorage.swift in Sources */,
|
||||
340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */,
|
||||
34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */,
|
||||
4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */,
|
||||
|
@ -4031,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 */,
|
||||
|
@ -4050,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 */,
|
||||
|
@ -4067,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 */,
|
||||
|
@ -4078,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 */,
|
||||
|
@ -4089,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 */,
|
||||
|
@ -4112,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 */,
|
||||
|
@ -4122,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 */,
|
||||
|
@ -4149,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 */,
|
||||
|
@ -4164,12 +3942,9 @@
|
|||
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
|
||||
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
|
||||
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */,
|
||||
B8162F0522892C5F00D46544 /* FriendRequestViewDelegate.swift in Sources */,
|
||||
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
|
||||
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */,
|
||||
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
|
||||
B8CCF639239721E20091D419 /* TabBar.swift in Sources */,
|
||||
B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */,
|
||||
458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */,
|
||||
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */,
|
||||
2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */,
|
||||
|
@ -4187,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 */,
|
||||
|
@ -4203,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 */,
|
||||
|
@ -4219,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 */,
|
||||
|
@ -4262,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 */,
|
||||
|
@ -4281,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 */,
|
||||
|
@ -4443,7 +4212,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -4457,7 +4226,7 @@
|
|||
INFOPLIST_FILE = SignalShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.share-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -4505,7 +4274,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -4524,7 +4293,7 @@
|
|||
INFOPLIST_FILE = SignalShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.share-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -4559,7 +4328,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
|
@ -4578,7 +4347,7 @@
|
|||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.utilities";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
|
@ -4629,7 +4398,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
|
@ -4653,7 +4422,7 @@
|
|||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.utilities";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
|
@ -4691,7 +4460,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -4703,7 +4472,7 @@
|
|||
INFOPLIST_FILE = LokiPushNotificationService/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.push-notification-service";
|
||||
|
@ -4754,7 +4523,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -4771,7 +4540,7 @@
|
|||
INFOPLIST_FILE = LokiPushNotificationService/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.push-notification-service";
|
||||
|
@ -4955,7 +4724,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -4990,7 +4759,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
|
@ -5023,7 +4792,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
CURRENT_PROJECT_VERSION = 91;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -5058,7 +4827,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 1.2.6;
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
PRODUCT_NAME = Session;
|
||||
|
|
|
@ -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"
|
||||
|
@ -162,30 +160,17 @@ static NSTimeInterval launchStartedAt;
|
|||
return AppEnvironment.shared.legacyNotificationActionHandler;
|
||||
}
|
||||
|
||||
- (LKFriendRequestExpirationJob *)lokiFriendRequestExpirationJob
|
||||
{
|
||||
return SSKEnvironment.shared.lokiFriendRequestExpirationJob;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (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");
|
||||
|
@ -193,11 +178,8 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application
|
||||
{
|
||||
OWSLogInfo(@"applicationWillTerminate");
|
||||
|
||||
[DDLog flushLog];
|
||||
|
||||
// Loki: Stop pollers
|
||||
[self stopPoller];
|
||||
[self stopClosedGroupPoller];
|
||||
[self stopOpenGroupPollers];
|
||||
|
@ -205,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();
|
||||
|
@ -223,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.
|
||||
|
@ -238,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];
|
||||
|
@ -262,7 +231,7 @@ static NSTimeInterval launchStartedAt;
|
|||
|
||||
[AppSetup
|
||||
setupEnvironmentWithAppSpecificSingletonBlock:^{
|
||||
// Create AppEnvironment.
|
||||
// Create AppEnvironment
|
||||
[AppEnvironment.shared setup];
|
||||
[SignalApp.sharedApp setup];
|
||||
}
|
||||
|
@ -333,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];
|
||||
|
@ -359,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.
|
||||
|
@ -460,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);
|
||||
|
@ -590,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();
|
||||
|
||||
|
@ -648,7 +455,6 @@ static NSTimeInterval launchStartedAt;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogWarn(@"applicationDidBecomeActive");
|
||||
if (CurrentAppContext().isRunningTests) {
|
||||
return;
|
||||
}
|
||||
|
@ -667,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];
|
||||
// }
|
||||
// ========
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -695,8 +486,6 @@ static NSTimeInterval launchStartedAt;
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
OWSLogWarn(@"handleActivation");
|
||||
|
||||
// Always check prekeys after app launches, and sometimes check on app activation.
|
||||
[TSPreKeyManager checkPreKeysIfNecessary];
|
||||
|
||||
|
@ -713,9 +502,6 @@ static NSTimeInterval launchStartedAt;
|
|||
// Clean up any messages that expired since last launch immediately
|
||||
// and continue cleaning in the background.
|
||||
[self.disappearingMessagesJob startIfNecessary];
|
||||
|
||||
// Loki: Start friend request expiration job
|
||||
[self.lokiFriendRequestExpirationJob startIfNecessary];
|
||||
|
||||
[self enableBackgroundRefreshIfNecessary];
|
||||
|
||||
|
@ -752,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];
|
||||
|
@ -785,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
|
||||
|
@ -812,8 +580,6 @@ static NSTimeInterval launchStartedAt;
|
|||
return;
|
||||
}
|
||||
|
||||
OWSLogWarn(@"applicationWillResignActive");
|
||||
|
||||
[self clearAllNotificationsAndRestoreBadgeCount];
|
||||
|
||||
[DDLog flushLog];
|
||||
|
@ -867,7 +633,6 @@ static NSTimeInterval launchStartedAt;
|
|||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Orientation
|
||||
|
||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application
|
||||
|
@ -876,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();
|
||||
|
||||
|
@ -1030,8 +706,6 @@ static NSTimeInterval launchStartedAt;
|
|||
withResponseInfo:(NSDictionary *)responseInfo
|
||||
completionHandler:(void (^)())completionHandler
|
||||
{
|
||||
OWSLogInfo(@"Handling action with identifier: %@", identifier);
|
||||
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (self.didAppLaunchFail) {
|
||||
|
@ -1063,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];
|
||||
|
@ -1121,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;
|
||||
}
|
||||
|
@ -1129,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];
|
||||
|
||||
|
@ -1142,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 =
|
||||
|
@ -1182,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
|
||||
|
||||
|
@ -1240,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];
|
||||
|
@ -1259,16 +917,9 @@ static NSTimeInterval launchStartedAt;
|
|||
// For non-legacy users, read receipts are on by default.
|
||||
[self.readReceiptManager setAreReadReceiptsEnabled:YES];
|
||||
|
||||
// Loki: Start friend request expiration job
|
||||
[self.lokiFriendRequestExpirationJob startIfNecessary];
|
||||
|
||||
// Loki: Start pollers
|
||||
[self startPollerIfNeeded];
|
||||
[self startClosedGroupPollerIfNeeded];
|
||||
[self startOpenGroupPollersIfNeeded];
|
||||
|
||||
// Loki: Get device links
|
||||
[[LKFileServerAPI getDeviceLinksAssociatedWithHexEncodedPublicKey:self.tsAccountManager.localNumber] retainUntilComplete]; // TODO: Is this even needed?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1281,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]) {
|
||||
|
@ -1300,7 +944,7 @@ static NSTimeInterval launchStartedAt;
|
|||
rootViewController = [HomeVC new];
|
||||
}
|
||||
} else {
|
||||
rootViewController = [[OnboardingController new] initialViewController];
|
||||
rootViewController = [LandingVC new];
|
||||
navigationBarHidden = NO;
|
||||
}
|
||||
OWSAssertDebug(rootViewController);
|
||||
|
@ -1309,8 +953,6 @@ static NSTimeInterval launchStartedAt;
|
|||
navigationController.navigationBarHidden = navigationBarHidden;
|
||||
self.window.rootViewController = navigationController;
|
||||
|
||||
[AppUpdateNag.sharedInstance showAppUpgradeNagIfNecessary];
|
||||
|
||||
[UIViewController attemptRotationToDeviceOrientation];
|
||||
}
|
||||
|
||||
|
@ -1322,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];
|
||||
}
|
||||
}
|
||||
|
@ -1338,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;
|
||||
|
@ -1363,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];
|
||||
}];
|
||||
|
@ -1377,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()
|
||||
|
|
|
@ -5,11 +5,7 @@ final class ConversationCell : UITableViewCell {
|
|||
static let reuseIdentifier = "ConversationCell"
|
||||
|
||||
// MARK: Components
|
||||
private lazy var unreadMessagesIndicatorView: UIView = {
|
||||
let result = UIView()
|
||||
result.backgroundColor = Colors.accent
|
||||
return result
|
||||
}()
|
||||
private let accentView = UIView()
|
||||
|
||||
private lazy var profilePictureView = ProfilePictureView()
|
||||
|
||||
|
@ -67,9 +63,9 @@ final class ConversationCell : UITableViewCell {
|
|||
let selectedBackgroundView = UIView()
|
||||
selectedBackgroundView.backgroundColor = Colors.cellSelected
|
||||
self.selectedBackgroundView = selectedBackgroundView
|
||||
// Set up the unread messages indicator view
|
||||
unreadMessagesIndicatorView.set(.width, to: Values.accentLineThickness)
|
||||
unreadMessagesIndicatorView.set(.height, to: cellHeight)
|
||||
// Set up the accent view
|
||||
accentView.set(.width, to: Values.accentLineThickness)
|
||||
accentView.set(.height, to: cellHeight)
|
||||
// Set up the profile picture view
|
||||
let profilePictureViewSize = Values.mediumProfilePictureSize
|
||||
profilePictureView.set(.width, to: profilePictureViewSize)
|
||||
|
@ -93,14 +89,14 @@ final class ConversationCell : UITableViewCell {
|
|||
labelContainerView.addSubview(topLabelStackView)
|
||||
labelContainerView.addSubview(bottomLabelStackView)
|
||||
// Set up the main stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicatorView, profilePictureView, labelContainerView ])
|
||||
let stackView = UIStackView(arrangedSubviews: [ accentView, profilePictureView, labelContainerView ])
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = Values.mediumSpacing
|
||||
contentView.addSubview(stackView)
|
||||
// Set up the constraints
|
||||
unreadMessagesIndicatorView.pin(.top, to: .top, of: contentView)
|
||||
unreadMessagesIndicatorView.pin(.bottom, to: .bottom, of: contentView)
|
||||
accentView.pin(.top, to: .top, of: contentView)
|
||||
accentView.pin(.bottom, to: .bottom, of: contentView)
|
||||
// The three lines below are part of a workaround for a weird layout bug
|
||||
topLabelStackView.set(.width, to: UIScreen.main.bounds.width - Values.accentLineThickness - Values.mediumSpacing - profilePictureViewSize - Values.mediumSpacing - Values.mediumSpacing)
|
||||
topLabelStackView.set(.height, to: 20)
|
||||
|
@ -136,7 +132,19 @@ final class ConversationCell : UITableViewCell {
|
|||
private func update() {
|
||||
AssertIsOnMainThread()
|
||||
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this
|
||||
unreadMessagesIndicatorView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12
|
||||
let isBlocked: Bool
|
||||
if let thread = threadViewModel.threadRecord as? TSContactThread {
|
||||
isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactIdentifier())
|
||||
} else {
|
||||
isBlocked = false
|
||||
}
|
||||
if isBlocked {
|
||||
accentView.backgroundColor = Colors.destructive
|
||||
accentView.alpha = 1
|
||||
} else {
|
||||
accentView.backgroundColor = Colors.accent
|
||||
accentView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12
|
||||
}
|
||||
profilePictureView.openGroupProfilePicture = nil
|
||||
if threadViewModel.isGroupThread {
|
||||
if threadViewModel.name == "Loki Public Chat"
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
|
||||
@objc(LKFriendRequestView)
|
||||
final class FriendRequestView : UIView {
|
||||
private let message: TSMessage
|
||||
@objc weak var delegate: FriendRequestViewDelegate?
|
||||
|
||||
private var kind: Kind {
|
||||
let isIncoming = message.interactionType() == .incomingMessage
|
||||
return isIncoming ? .incoming : .outgoing
|
||||
}
|
||||
|
||||
// MARK: Kind
|
||||
enum Kind : String { case incoming, outgoing }
|
||||
|
||||
// MARK: Components
|
||||
private lazy var spacer1: UIView = {
|
||||
let result = UIView()
|
||||
result.set(.height, to: 12)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var spacer2: UIView = {
|
||||
let result = UIView()
|
||||
result.set(.height, to: Values.mediumSpacing)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var label: UILabel = {
|
||||
let result = UILabel()
|
||||
result.textColor = Colors.text
|
||||
result.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
result.numberOfLines = 0
|
||||
result.textAlignment = .center
|
||||
result.lineBreakMode = .byWordWrapping
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var buttonStackView: UIStackView = {
|
||||
let result = UIStackView()
|
||||
result.axis = .horizontal
|
||||
result.spacing = Values.mediumSpacing
|
||||
result.distribution = .fillEqually
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Lifecycle
|
||||
@objc init(message: TSMessage) {
|
||||
self.message = message
|
||||
super.init(frame: CGRect.zero)
|
||||
initialize()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("Using FriendRequestView.init(coder:) isn't allowed. Use FriendRequestView.init(message:) instead.") }
|
||||
override init(frame: CGRect) { fatalError("Using FriendRequestView.init(frame:) isn't allowed. Use FriendRequestView.init(message:) instead.") }
|
||||
|
||||
private func initialize() {
|
||||
// Set up UI
|
||||
let mainStackView = UIStackView()
|
||||
mainStackView.axis = .vertical
|
||||
mainStackView.distribution = .fill
|
||||
mainStackView.layoutMargins = UIEdgeInsets(top: 0, left: Values.largeSpacing, bottom: 0, right: Values.largeSpacing)
|
||||
mainStackView.isLayoutMarginsRelativeArrangement = true
|
||||
mainStackView.addArrangedSubview(spacer1)
|
||||
mainStackView.addArrangedSubview(label)
|
||||
switch kind {
|
||||
case .incoming:
|
||||
mainStackView.addArrangedSubview(spacer2)
|
||||
mainStackView.addArrangedSubview(buttonStackView)
|
||||
let declineButton = UIButton()
|
||||
declineButton.set(.height, to: Values.mediumButtonHeight)
|
||||
declineButton.layer.cornerRadius = Values.modalButtonCornerRadius
|
||||
declineButton.backgroundColor = Colors.buttonBackground
|
||||
declineButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
declineButton.setTitleColor(Colors.text, for: UIControl.State.normal)
|
||||
declineButton.setTitle(NSLocalizedString("Decline", comment: ""), for: UIControl.State.normal)
|
||||
declineButton.addTarget(self, action: #selector(decline), for: UIControl.Event.touchUpInside)
|
||||
buttonStackView.addArrangedSubview(declineButton)
|
||||
let acceptButton = UIButton()
|
||||
acceptButton.set(.height, to: Values.mediumButtonHeight)
|
||||
acceptButton.layer.cornerRadius = Values.modalButtonCornerRadius
|
||||
acceptButton.backgroundColor = Colors.accent
|
||||
acceptButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
acceptButton.setTitleColor(Colors.text, for: UIControl.State.normal)
|
||||
acceptButton.setTitle(NSLocalizedString("Accept", comment: ""), for: UIControl.State.normal)
|
||||
acceptButton.addTarget(self, action: #selector(accept), for: UIControl.Event.touchUpInside)
|
||||
buttonStackView.addArrangedSubview(acceptButton)
|
||||
case .outgoing: break
|
||||
}
|
||||
addSubview(mainStackView)
|
||||
mainStackView.pin(to: self)
|
||||
updateUI()
|
||||
// Observe friend request status changes
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleFriendRequestStatusChangedNotification), name: .userFriendRequestStatusChanged, object: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
@objc private func handleFriendRequestStatusChangedNotification(_ notification: Notification) {
|
||||
let messageID = notification.object as! String
|
||||
// It's possible for the message to be deleted at this point
|
||||
guard messageID == message.uniqueId && TSMessage.fetch(uniqueId: messageID) != nil else { return }
|
||||
message.reload()
|
||||
updateUI()
|
||||
}
|
||||
|
||||
@objc public func updateUI() {
|
||||
let thread = message.thread
|
||||
let friendRequestStatus = FriendRequestProtocol.getFriendRequestUIStatus(for: thread)
|
||||
guard let contactID = thread.contactIdentifier() else { return }
|
||||
let displayName = UserDisplayNameUtilities.getPrivateChatDisplayName(for: contactID) ?? contactID
|
||||
let format: String?
|
||||
switch kind {
|
||||
case .incoming:
|
||||
buttonStackView.isHidden = (friendRequestStatus != .received)
|
||||
spacer2.isHidden = buttonStackView.isHidden
|
||||
switch friendRequestStatus {
|
||||
case .none: format = NSLocalizedString("You've declined %@'s session request", comment: "")
|
||||
case .friends: format = nil
|
||||
case .received: format = NSLocalizedString("%@ sent you a session request", comment: "")
|
||||
case .sent: return // Should never occur
|
||||
case .expired: format = NSLocalizedString("%@'s session request has expired", comment: "")
|
||||
}
|
||||
case .outgoing:
|
||||
switch friendRequestStatus {
|
||||
case .none: format = nil // The message failed to send
|
||||
case .friends: format = nil
|
||||
case .received: return // Should never occur
|
||||
case .sent: format = NSLocalizedString("You've sent %@ a session request", comment: "")
|
||||
case .expired: format = NSLocalizedString("Your session request to %@ has expired", comment: "")
|
||||
}
|
||||
}
|
||||
if let format = format {
|
||||
label.text = String(format: format, displayName)
|
||||
}
|
||||
label.isHidden = (format == nil)
|
||||
spacer1.isHidden = label.isHidden
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func accept() {
|
||||
guard let message = message as? TSIncomingMessage else { preconditionFailure() }
|
||||
delegate?.acceptFriendRequest(message)
|
||||
}
|
||||
|
||||
@objc private func decline() {
|
||||
guard let message = message as? TSIncomingMessage else { preconditionFailure() }
|
||||
delegate?.declineFriendRequest(message)
|
||||
}
|
||||
|
||||
// MARK: Measuring
|
||||
@objc static func calculateHeight(message: TSMessage, conversationStyle: ConversationStyle) -> CGFloat {
|
||||
let width = conversationStyle.contentWidth - 2 * Values.largeSpacing
|
||||
let dummyFriendRequestView = FriendRequestView(message: message)
|
||||
let hasTopSpacer = !dummyFriendRequestView.spacer1.isHidden
|
||||
let topSpacing: CGFloat = hasTopSpacer ? 12 : 0
|
||||
let hasLabel = !dummyFriendRequestView.label.isHidden
|
||||
let labelHeight = hasLabel ? dummyFriendRequestView.label.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)).height : 0
|
||||
let hasButtonStackView = dummyFriendRequestView.buttonStackView.superview != nil && !dummyFriendRequestView.buttonStackView.isHidden
|
||||
let buttonHeight = hasButtonStackView ? Values.mediumButtonHeight + Values.mediumSpacing : 0 // Values.mediumSpacing is the height of the spacer
|
||||
let totalHeight = topSpacing + labelHeight + buttonHeight
|
||||
return totalHeight.rounded(.up)
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
@objc(LKFriendRequestViewDelegate)
|
||||
protocol FriendRequestViewDelegate {
|
||||
/// Implementations of this method should update the thread's friend request status
|
||||
/// and send a friend request accepted message.
|
||||
@objc func acceptFriendRequest(_ friendRequest: TSIncomingMessage)
|
||||
/// Implementations of this method should update the thread's friend request status
|
||||
/// and remove the prekeys associated with the contact.
|
||||
@objc func declineFriendRequest(_ friendRequest: TSIncomingMessage)
|
||||
}
|
|
@ -83,7 +83,7 @@ final class MentionCandidateSelectionView : UIView, UITableViewDataSource, UITab
|
|||
private extension MentionCandidateSelectionView {
|
||||
|
||||
final class Cell : UITableViewCell {
|
||||
var mentionCandidate = Mention(hexEncodedPublicKey: "", displayName: "") { didSet { update() } }
|
||||
var mentionCandidate = Mention(publicKey: "", displayName: "") { didSet { update() } }
|
||||
var publicChatServer: String?
|
||||
var publicChatChannel: UInt64?
|
||||
|
||||
|
@ -161,10 +161,10 @@ private extension MentionCandidateSelectionView {
|
|||
// MARK: Updating
|
||||
private func update() {
|
||||
displayNameLabel.text = mentionCandidate.displayName
|
||||
profilePictureView.hexEncodedPublicKey = mentionCandidate.hexEncodedPublicKey
|
||||
profilePictureView.hexEncodedPublicKey = mentionCandidate.publicKey
|
||||
profilePictureView.update()
|
||||
if let server = publicChatServer, let channel = publicChatChannel {
|
||||
let isUserModerator = LokiPublicChatAPI.isUserModerator(mentionCandidate.hexEncodedPublicKey, for: channel, on: server)
|
||||
let isUserModerator = PublicChatAPI.isUserModerator(mentionCandidate.publicKey, for: channel, on: server)
|
||||
moderatorIconImageView.isHidden = !isUserModerator
|
||||
} else {
|
||||
moderatorIconImageView.isHidden = true
|
||||
|
|
|
@ -10,7 +10,7 @@ public final class MentionUtilities : NSObject {
|
|||
|
||||
@objc public static func highlightMentions(in string: String, isOutgoingMessage: Bool, threadID: String, attributes: [NSAttributedString.Key:Any]) -> NSAttributedString {
|
||||
let userHexEncodedPublicKey = getUserHexEncodedPublicKey()
|
||||
var publicChat: LokiPublicChat?
|
||||
var publicChat: PublicChat?
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
publicChat = LokiDatabaseUtilities.getPublicChat(for: threadID, in: transaction)
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
|
|||
qrCodeImageViewContainer.isHidden = true
|
||||
titleLabel.text = NSLocalizedString("Linking Request Received", comment: "")
|
||||
subtitleLabel.text = NSLocalizedString("Please check that the words below match those shown on your other device", comment: "")
|
||||
let hexEncodedPublicKey = deviceLink.slave.hexEncodedPublicKey.removing05PrefixIfNeeded()
|
||||
let hexEncodedPublicKey = deviceLink.slave.publicKey.removing05PrefixIfNeeded()
|
||||
mnemonicLabel.text = Mnemonic.hash(hexEncodedString: hexEncodedPublicKey)
|
||||
mnemonicLabel.isHidden = false
|
||||
authorizeButton.isHidden = false
|
||||
|
@ -180,14 +180,13 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
|
|||
DeviceLinkingSession.current!.markLinkingRequestAsProcessed()
|
||||
DeviceLinkingSession.current!.stopListeningForLinkingRequests()
|
||||
let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink)
|
||||
let master = DeviceLink.Device(hexEncodedPublicKey: deviceLink.master.hexEncodedPublicKey, signature: linkingAuthorizationMessage.masterSignature)
|
||||
let master = DeviceLink.Device(publicKey: deviceLink.master.publicKey, signature: linkingAuthorizationMessage.masterSignature)
|
||||
let signedDeviceLink = DeviceLink(between: master, and: deviceLink.slave)
|
||||
FileServerAPI.addDeviceLink(signedDeviceLink).done(on: DispatchQueue.main) { [weak self] in
|
||||
SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: {
|
||||
let storage = OWSPrimaryStorage.shared()
|
||||
let slaveHexEncodedPublicKey = deviceLink.slave.hexEncodedPublicKey
|
||||
let slavePublicKey = deviceLink.slave.publicKey
|
||||
try! Storage.writeSync { transaction in
|
||||
let thread = TSContactThread.getOrCreateThread(withContactId: slaveHexEncodedPublicKey, transaction: transaction)
|
||||
let thread = TSContactThread.getOrCreateThread(withContactId: slavePublicKey, transaction: transaction)
|
||||
thread.save(with: transaction)
|
||||
}
|
||||
let _ = SSKEnvironment.shared.syncManager.syncAllGroups().ensure {
|
||||
|
@ -196,9 +195,6 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
|
|||
let _ = SSKEnvironment.shared.syncManager.syncAllContacts()
|
||||
}
|
||||
let _ = SSKEnvironment.shared.syncManager.syncAllOpenGroups()
|
||||
try! Storage.writeSync { transaction in
|
||||
storage.setFriendRequestStatus(.friends, for: slaveHexEncodedPublicKey, transaction: transaction)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self?.dismiss(animated: true, completion: nil)
|
||||
self?.delegate?.handleDeviceLinkAuthorized(signedDeviceLink)
|
||||
|
@ -252,7 +248,7 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
|
|||
delegate?.handleDeviceLinkingModalDismissed() // Only relevant in slave mode
|
||||
if let deviceLink = deviceLink {
|
||||
try! Storage.writeSync { transaction in
|
||||
OWSPrimaryStorage.shared().removePreKeyBundle(forContact: deviceLink.slave.hexEncodedPublicKey, transaction: transaction)
|
||||
OWSPrimaryStorage.shared().removePreKeyBundle(forContact: deviceLink.slave.publicKey, transaction: transaction)
|
||||
}
|
||||
}
|
||||
dismiss(animated: true, completion: nil)
|
||||
|
|
|
@ -78,7 +78,7 @@ final class DeviceLinksVC : BaseVC, UITableViewDataSource, UITableViewDelegate,
|
|||
var deviceLinks: [DeviceLink] = []
|
||||
storage.dbReadConnection.read { transaction in
|
||||
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction).sorted { lhs, rhs in
|
||||
return lhs.other.hexEncodedPublicKey > rhs.other.hexEncodedPublicKey
|
||||
return lhs.other.publicKey > rhs.other.publicKey
|
||||
}
|
||||
}
|
||||
self.deviceLinks = deviceLinks
|
||||
|
@ -141,7 +141,7 @@ final class DeviceLinksVC : BaseVC, UITableViewDataSource, UITableViewDelegate,
|
|||
|
||||
private func removeDeviceLink(_ deviceLink: DeviceLink) {
|
||||
FileServerAPI.removeDeviceLink(deviceLink).done { [weak self] in
|
||||
let linkedDevicePublicKey = deviceLink.other.hexEncodedPublicKey
|
||||
let linkedDevicePublicKey = deviceLink.other.publicKey
|
||||
guard let thread = TSContactThread.fetch(uniqueId: TSContactThread.threadId(fromContactId: linkedDevicePublicKey)) else { return }
|
||||
let unlinkDeviceMessage = UnlinkDeviceMessage(thread: thread)
|
||||
SSKEnvironment.shared.messageSender.send(unlinkDeviceMessage, success: {
|
||||
|
@ -226,7 +226,7 @@ private extension DeviceLinksVC {
|
|||
// MARK: Updating
|
||||
private func update() {
|
||||
titleLabel.text = device.displayName
|
||||
subtitleLabel.text = Mnemonic.hash(hexEncodedString: device.hexEncodedPublicKey.removing05PrefixIfNeeded())
|
||||
subtitleLabel.text = Mnemonic.hash(hexEncodedString: device.publicKey.removing05PrefixIfNeeded())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ final class DeviceNameModal : Modal {
|
|||
@objc private func changeName() {
|
||||
let name = nameTextField.text!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
if !name.isEmpty {
|
||||
UserDefaults.standard[.slaveDeviceName(device.hexEncodedPublicKey)] = name
|
||||
UserDefaults.standard[.slaveDeviceName(device.publicKey)] = name
|
||||
delegate?.handleDeviceNameChanged(to: name, for: device)
|
||||
} else {
|
||||
let alert = UIAlertController(title: NSLocalizedString("Error", comment: ""), message: NSLocalizedString("Please pick a name", comment: ""), preferredStyle: .alert)
|
||||
|
|
|
@ -145,6 +145,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
|||
notificationCenter.addObserver(self, selector: #selector(handleProfileDidChangeNotification(_:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleSeedViewedNotification(_:)), name: .seedViewed, object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleBlockedContactsUpdatedNotification(_:)), name: .blockedContactsUpdated, object: nil)
|
||||
// Set up public chats and RSS feeds if needed
|
||||
if OWSIdentityManager.shared().identityKeyPair() != nil {
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
|
@ -161,7 +162,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
|||
let storage = OWSPrimaryStorage.shared()
|
||||
storage.dbReadConnection.read { transaction in
|
||||
TSContactThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||
guard let thread = object as? TSContactThread, thread.shouldThreadBeVisible && thread.isContactFriend else { return }
|
||||
guard let thread = object as? TSContactThread, thread.shouldThreadBeVisible else { return }
|
||||
let publicKey = thread.contactIdentifier()
|
||||
guard UserDisplayNameUtilities.getPrivateChatDisplayName(for: publicKey) != nil,
|
||||
storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) == nil else { return }
|
||||
|
@ -279,6 +280,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
|||
tableViewTopConstraint = tableView.pin(.top, to: .top, of: view, withInset: Values.smallSpacing)
|
||||
seedReminderView.removeFromSuperview()
|
||||
}
|
||||
|
||||
@objc private func handleBlockedContactsUpdatedNotification(_ notification: Notification) {
|
||||
self.tableView.reloadData() // TODO: Just reload the affected cell
|
||||
}
|
||||
|
||||
private func updateNavigationBarButtons() {
|
||||
let profilePictureSize = Values.verySmallProfilePictureSize
|
||||
|
@ -358,25 +363,16 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
|||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let threadID = self.thread(at: indexPath.row)?.uniqueId else { return false }
|
||||
var publicChat: LokiPublicChat?
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
publicChat = LokiDatabaseUtilities.getPublicChat(for: threadID, in: transaction)
|
||||
}
|
||||
if let publicChat = publicChat {
|
||||
return publicChat.isDeletable
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
||||
guard let thread = self.thread(at: indexPath.row) else { return [] }
|
||||
var publicChat: LokiPublicChat?
|
||||
var publicChat: PublicChat?
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
publicChat = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction)
|
||||
}
|
||||
let delete = UITableViewRowAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { [weak self] action, indexPath in
|
||||
let delete = UITableViewRowAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { [weak self] _, _ in
|
||||
let alert = UIAlertController(title: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE", comment: ""), message: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE", comment: ""), preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in
|
||||
try! Storage.writeSync { transaction in
|
||||
|
@ -386,9 +382,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
|||
messageIDs.insert(interaction.uniqueId!)
|
||||
}
|
||||
OWSPrimaryStorage.shared().updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, in: transaction)
|
||||
transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection)
|
||||
transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection)
|
||||
let _ = LokiPublicChatAPI.leave(publicChat.channel, on: publicChat.server)
|
||||
transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: PublicChatAPI.lastMessageServerIDCollection)
|
||||
transaction.removeObject(forKey: "\(publicChat.server).\(publicChat.channel)", inCollection: PublicChatAPI.lastDeletionServerIDCollection)
|
||||
let _ = PublicChatAPI.leave(publicChat.channel, on: publicChat.server)
|
||||
}
|
||||
thread.removeAllThreadInteractions(with: transaction)
|
||||
thread.remove(with: transaction)
|
||||
|
@ -400,8 +396,24 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
|||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
delete.backgroundColor = Colors.destructive
|
||||
if let publicChat = publicChat {
|
||||
return publicChat.isDeletable ? [ delete ] : []
|
||||
if thread is TSContactThread {
|
||||
var publicKey: String!
|
||||
Storage.read { transaction in
|
||||
publicKey = OWSPrimaryStorage.shared().getMasterHexEncodedPublicKey(for: thread.contactIdentifier()!, in: transaction) ?? thread.contactIdentifier()!
|
||||
}
|
||||
let blockingManager = SSKEnvironment.shared.blockingManager
|
||||
let isBlocked = blockingManager.isRecipientIdBlocked(publicKey)
|
||||
let block = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_BLOCK_BUTTON", comment: "")) { _, _ in
|
||||
blockingManager.addBlockedPhoneNumber(publicKey)
|
||||
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
|
||||
}
|
||||
block.backgroundColor = Colors.unimportant
|
||||
let unblock = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_UNBLOCK_BUTTON", comment: "")) { _, _ in
|
||||
blockingManager.removeBlockedPhoneNumber(publicKey)
|
||||
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
|
||||
}
|
||||
unblock.backgroundColor = Colors.unimportant
|
||||
return [ delete, (isBlocked ? unblock : block) ]
|
||||
} else {
|
||||
return [ delete ]
|
||||
}
|
||||
|
|
|
@ -139,14 +139,14 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
|
|||
let profilePictureURL = profileManager.profilePictureURL()
|
||||
let profileKey = profileManager.localProfileKey().keyData
|
||||
try! Storage.writeSync { transaction in
|
||||
transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection)
|
||||
transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection)
|
||||
transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: PublicChatAPI.lastMessageServerIDCollection)
|
||||
transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: PublicChatAPI.lastDeletionServerIDCollection)
|
||||
}
|
||||
LokiPublicChatManager.shared.addChat(server: urlAsString, channel: channelID)
|
||||
PublicChatManager.shared.addChat(server: urlAsString, channel: channelID)
|
||||
.done(on: .main) { [weak self] _ in
|
||||
let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: urlAsString)
|
||||
let _ = LokiPublicChatAPI.setProfilePictureURL(to: profilePictureURL, using: profileKey, on: urlAsString)
|
||||
let _ = LokiPublicChatAPI.join(channelID, on: urlAsString)
|
||||
let _ = PublicChatAPI.setDisplayName(to: displayName, on: urlAsString)
|
||||
let _ = PublicChatAPI.setProfilePictureURL(to: profilePictureURL, using: profileKey, on: urlAsString)
|
||||
let _ = PublicChatAPI.join(channelID, on: urlAsString)
|
||||
let syncManager = SSKEnvironment.shared.syncManager
|
||||
let _ = syncManager.syncAllOpenGroups()
|
||||
self?.presentingViewController!.dismiss(animated: true, completion: nil)
|
||||
|
|
|
@ -151,7 +151,7 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate
|
|||
}
|
||||
|
||||
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
|
||||
UserDefaults.standard[.masterHexEncodedPublicKey] = deviceLink.master.hexEncodedPublicKey
|
||||
UserDefaults.standard[.masterHexEncodedPublicKey] = deviceLink.master.publicKey
|
||||
fakeChatViewContentOffset = fakeChatView.contentOffset
|
||||
DispatchQueue.main.async {
|
||||
self.fakeChatView.contentOffset = self.fakeChatViewContentOffset
|
||||
|
|
|
@ -8,7 +8,7 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
|
|||
let storage = OWSPrimaryStorage.shared()
|
||||
storage.dbReadConnection.read { transaction in
|
||||
TSContactThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||
guard let thread = object as? TSContactThread, thread.shouldThreadBeVisible && thread.isContactFriend else { return }
|
||||
guard let thread = object as? TSContactThread, thread.shouldThreadBeVisible else { return }
|
||||
let publicKey = thread.contactIdentifier()
|
||||
guard UserDisplayNameUtilities.getPrivateChatDisplayName(for: publicKey) != nil else { return }
|
||||
// We shouldn't be able to add slave devices to groups
|
||||
|
|
|
@ -62,10 +62,10 @@ final class OpenGroupSuggestionSheet : Sheet {
|
|||
let url = "https://chat.getsession.org"
|
||||
let displayName = OWSProfileManager.shared().localProfileName()
|
||||
// TODO: Profile picture & profile key
|
||||
let _ = LokiPublicChatManager.shared.addChat(server: url, channel: channelID).done(on: .main) { _ in
|
||||
let _ = LokiPublicChatAPI.getMessages(for: channelID, on: url)
|
||||
let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: url)
|
||||
let _ = LokiPublicChatAPI.join(channelID, on: url)
|
||||
let _ = PublicChatManager.shared.addChat(server: url, channel: channelID).done(on: .main) { _ in
|
||||
let _ = PublicChatAPI.getMessages(for: channelID, on: url)
|
||||
let _ = PublicChatAPI.setDisplayName(to: displayName, on: url)
|
||||
let _ = PublicChatAPI.join(channelID, on: url)
|
||||
}
|
||||
close()
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ final class PNModeVC : BaseVC, OptionViewDelegate {
|
|||
let topSpacer = UIView.vStretchingSpacer()
|
||||
let bottomSpacer = UIView.vStretchingSpacer()
|
||||
let registerButtonBottomOffsetSpacer = UIView()
|
||||
registerButtonBottomOffsetSpacer.set(.height, to: Values.onboardingButtonBottomOffset)
|
||||
registerButtonBottomOffsetSpacer.set(.height, to: isIPhone5OrSmaller ? CGFloat(16) : Values.onboardingButtonBottomOffset)
|
||||
// Set up register button
|
||||
let registerButton = Button(style: .prominentFilled, size: .large)
|
||||
registerButton.setTitle(NSLocalizedString("Continue", comment: ""), for: UIControl.State.normal)
|
||||
|
|
|
@ -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"
|
||||
|
@ -94,7 +92,6 @@
|
|||
#import <SessionServiceKit/LKDeviceLinkMessage.h>
|
||||
#import <SessionServiceKit/OWSError.h>
|
||||
#import <SessionServiceKit/OWSFileSystem.h>
|
||||
#import <SessionServiceKit/LKFriendRequestMessage.h>
|
||||
#import <SessionServiceKit/OWSIdentityManager.h>
|
||||
#import <SessionServiceKit/OWSMediaGalleryFinder.h>
|
||||
#import <SessionServiceKit/OWSMessageManager.h>
|
||||
|
@ -130,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
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
#import "ConversationViewCell.h"
|
||||
|
||||
@class OWSMessageBubbleView;
|
||||
@protocol LKFriendRequestViewDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSMessageCell : ConversationViewCell
|
||||
|
||||
@property (nonatomic, readonly) OWSMessageBubbleView *messageBubbleView;
|
||||
@property (nonatomic, nullable, weak) id<LKFriendRequestViewDelegate> friendRequestViewDelegate;
|
||||
|
||||
+ (NSString *)cellReuseIdentifier;
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic) NSLayoutConstraint *messageBubbleViewBottomConstraint;
|
||||
@property (nonatomic) LKProfilePictureView *avatarView;
|
||||
@property (nonatomic) UIImageView *moderatorIconImageView;
|
||||
@property (nonatomic, nullable) LKFriendRequestView *friendRequestView;
|
||||
@property (nonatomic, nullable) UIImageView *sendFailureBadgeView;
|
||||
|
||||
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
||||
|
@ -212,24 +211,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
]];
|
||||
}
|
||||
}
|
||||
|
||||
// Loki: Attach the friend request view if needed
|
||||
if ([self shouldShowFriendRequestUIForMessage:self.message]) {
|
||||
self.friendRequestView = [[LKFriendRequestView alloc] initWithMessage:self.message];
|
||||
self.friendRequestView.delegate = self.friendRequestViewDelegate;
|
||||
[self.contentView addSubview:self.friendRequestView];
|
||||
[self.messageBubbleViewBottomConstraint setActive:NO];
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
[self.friendRequestView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:self.conversationStyle.gutterLeading],
|
||||
[self.friendRequestView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:self.conversationStyle.gutterTrailing],
|
||||
[self.friendRequestView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.messageBubbleView],
|
||||
[self.friendRequestView autoPinEdgeToSuperviewEdge:ALEdgeBottom]
|
||||
]];
|
||||
} else {
|
||||
[self.friendRequestView removeFromSuperview];
|
||||
self.friendRequestView = nil;
|
||||
[self.messageBubbleViewBottomConstraint setActive:YES];
|
||||
}
|
||||
|
||||
if ([self updateAvatarView]) {
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
|
@ -384,11 +365,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
if (self.shouldHaveSendFailureBadge) {
|
||||
cellSize.width += self.sendFailureBadgeSize + self.sendFailureBadgeSpacing;
|
||||
}
|
||||
|
||||
// Loki: Include the friend request view if needed
|
||||
if ([self shouldShowFriendRequestUIForMessage:self.message]) {
|
||||
cellSize.height += [LKFriendRequestView calculateHeightWithMessage:self.message conversationStyle:self.conversationStyle];
|
||||
}
|
||||
|
||||
cellSize = CGSizeCeil(cellSize);
|
||||
|
||||
|
@ -408,9 +384,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[self.messageBubbleView unloadContent];
|
||||
|
||||
[self.headerView removeFromSuperview];
|
||||
|
||||
[self.friendRequestView removeFromSuperview];
|
||||
self.friendRequestView = nil;
|
||||
|
||||
[self.avatarView removeFromSuperview];
|
||||
|
||||
|
@ -469,10 +442,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)sender
|
||||
{
|
||||
OWSAssertDebug(self.delegate);
|
||||
|
||||
if ([self shouldShowFriendRequestUIForMessage:self.message]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender.state != UIGestureRecognizerStateBegan) {
|
||||
return;
|
||||
|
@ -532,13 +501,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return location.y <= headerBottom.y;
|
||||
}
|
||||
|
||||
#pragma mark - Convenience
|
||||
|
||||
- (BOOL)shouldShowFriendRequestUIForMessage:(TSMessage *)message
|
||||
{
|
||||
return message.isFriendRequest;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -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"
|
||||
|
@ -124,7 +123,6 @@ typedef enum : NSUInteger {
|
|||
ConversationViewCellDelegate,
|
||||
ConversationInputTextViewDelegate,
|
||||
ConversationSearchControllerDelegate,
|
||||
LKFriendRequestViewDelegate,
|
||||
LongTextViewDelegate,
|
||||
MessageActionsDelegate,
|
||||
MessageDetailViewDelegate,
|
||||
|
@ -420,10 +418,6 @@ typedef enum : NSUInteger {
|
|||
selector:@selector(keyboardDidChangeFrame:)
|
||||
name:UIKeyboardDidChangeFrameNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleUserFriendRequestStatusChangedNotification:)
|
||||
name:NSNotification.userFriendRequestStatusChanged
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleThreadSessionRestoreDevicesChangedNotifiaction:)
|
||||
name:NSNotification.threadSessionRestoreDevicesChanged
|
||||
|
@ -533,28 +527,6 @@ typedef enum : NSUInteger {
|
|||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
- (void)handleUserFriendRequestStatusChangedNotification:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
// Friend request status doesn't apply to group threads
|
||||
if (self.thread.isGroupThread) { return; }
|
||||
NSString *hexEncodedPublicKey = (NSString *)notification.object;
|
||||
// Check if we should update the UI
|
||||
__block NSSet<NSString *> *linkedDevices;
|
||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
linkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:self.thread.contactIdentifier in:transaction];
|
||||
}];
|
||||
if (![linkedDevices containsObject:hexEncodedPublicKey]) { return; }
|
||||
// Update the UI
|
||||
[self updateInputBar];
|
||||
[self.collectionView.collectionViewLayout invalidateLayout];
|
||||
for (id<ConversationViewItem> item in self.viewItems) {
|
||||
[item clearCachedLayoutState];
|
||||
}
|
||||
[self.conversationViewModel reloadViewItems];
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
- (void)handleThreadSessionRestoreDevicesChangedNotifiaction:(NSNotification *)notification
|
||||
{
|
||||
// Check thread
|
||||
|
@ -791,7 +763,6 @@ typedef enum : NSUInteger {
|
|||
self.inputToolbar.inputToolbarDelegate = self;
|
||||
self.inputToolbar.inputTextViewDelegate = self;
|
||||
SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(self, _inputToolbar);
|
||||
[self updateInputBar];
|
||||
|
||||
self.loadMoreHeader = [UILabel new];
|
||||
self.loadMoreHeader.text = NSLocalizedString(@"CONVERSATION_VIEW_LOADING_MORE_MESSAGES", @"Indicates that the app is loading more messages in this conversation.");
|
||||
|
@ -1128,13 +1099,16 @@ typedef enum : NSUInteger {
|
|||
NSString *blockStateMessage = nil;
|
||||
if ([self isBlockedConversation]) {
|
||||
if (self.isGroupConversation) {
|
||||
/*
|
||||
blockStateMessage = NSLocalizedString(
|
||||
@"MESSAGES_VIEW_GROUP_BLOCKED", @"Indicates that this group conversation has been blocked.");
|
||||
*/
|
||||
} else {
|
||||
blockStateMessage = NSLocalizedString(
|
||||
@"MESSAGES_VIEW_CONTACT_BLOCKED", @"Indicates that this 1:1 conversation has been blocked.");
|
||||
}
|
||||
} else if (self.isGroupConversation) {
|
||||
/*
|
||||
int blockedGroupMemberCount = [self blockedGroupMemberCount];
|
||||
if (blockedGroupMemberCount == 1) {
|
||||
blockStateMessage = NSLocalizedString(@"MESSAGES_VIEW_GROUP_1_MEMBER_BLOCKED",
|
||||
|
@ -1146,11 +1120,12 @@ typedef enum : NSUInteger {
|
|||
@"{{the number of blocked users in this group}}."),
|
||||
[OWSFormat formatInt:blockedGroupMemberCount]];
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (blockStateMessage) {
|
||||
[self createBannerWithTitle:blockStateMessage
|
||||
bannerColor:[UIColor ows_destructiveRedColor]
|
||||
bannerColor:LKColors.destructive
|
||||
tapSelector:@selector(blockBannerViewWasTapped:)];
|
||||
return;
|
||||
}
|
||||
|
@ -1337,19 +1312,15 @@ typedef enum : NSUInteger {
|
|||
{
|
||||
self.userHasScrolled = NO;
|
||||
|
||||
// To avoid "noisy" animations (hiding the keyboard before showing
|
||||
// the action sheet, re-showing it after), hide the keyboard before
|
||||
// showing the "unblock" action sheet.
|
||||
//
|
||||
// Unblocking is a rare interaction, so it's okay to leave the keyboard
|
||||
// hidden.
|
||||
[self dismissKeyBoard];
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
|
||||
[BlockListUIUtils showUnblockThreadActionSheet:self.thread
|
||||
fromViewController:self
|
||||
blockingManager:self.blockingManager
|
||||
contactsManager:self.contactsManager
|
||||
completionBlock:completionBlock];
|
||||
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isBlockedConversation
|
||||
|
@ -1676,17 +1647,6 @@ typedef enum : NSUInteger {
|
|||
self.navigationItem.rightBarButtonItems = [barButtons copy];
|
||||
}
|
||||
|
||||
#pragma mark - Updating
|
||||
|
||||
- (void)updateInputBar {
|
||||
BOOL shouldInputBarBeEnabled = [LKFriendRequestProtocol shouldInputBarBeEnabledForThread:self.thread];
|
||||
[self.inputToolbar setUserInteractionEnabled:shouldInputBarBeEnabled];
|
||||
NSString *placeholderText = shouldInputBarBeEnabled ? NSLocalizedString(@"Message", "") : NSLocalizedString(@"Pending session request", "");
|
||||
[self.inputToolbar setPlaceholderText:placeholderText];
|
||||
BOOL shouldAttachmentButtonBeEnabled = [LKFriendRequestProtocol shouldAttachmentButtonBeEnabledForThread:self.thread];
|
||||
[self.inputToolbar setAttachmentButtonHidden:!shouldAttachmentButtonBeEnabled];
|
||||
}
|
||||
|
||||
#pragma mark - Identity
|
||||
|
||||
/**
|
||||
|
@ -2895,15 +2855,6 @@ typedef enum : NSUInteger {
|
|||
AudioServicesPlaySystemSound(soundId);
|
||||
}
|
||||
[self.typingIndicators didSendOutgoingMessageInThread:self.thread];
|
||||
|
||||
// Loki: Lock the input bar early
|
||||
if ([self.thread isKindOfClass:TSContactThread.class] && [message isKindOfClass:LKFriendRequestMessage.class]) {
|
||||
NSString *recipientID = self.thread.contactIdentifier;
|
||||
OWSAssertIsOnMainThread();
|
||||
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[LKFriendRequestProtocol setFriendRequestStatusToSendingIfNeededForHexEncodedPublicKey:recipientID transaction:transaction];
|
||||
} error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark UIDocumentMenuDelegate
|
||||
|
@ -3893,7 +3844,7 @@ typedef enum : NSUInteger {
|
|||
NSString *result = self.inputToolbar.messageText;
|
||||
for (LKMention *mention in self.mentions) {
|
||||
NSRange range = [result rangeOfString:[NSString stringWithFormat:@"@%@", mention.displayName]];
|
||||
result = [result stringByReplacingCharactersInRange:range withString:[[NSString alloc] initWithFormat:@"@%@", mention.hexEncodedPublicKey]];
|
||||
result = [result stringByReplacingCharactersInRange:range withString:[[NSString alloc] initWithFormat:@"@%@", mention.publicKey]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -3985,6 +3936,12 @@ typedef enum : NSUInteger {
|
|||
if (didAddToProfileWhitelist) {
|
||||
[self.conversationViewModel ensureDynamicInteractionsAndUpdateIfNecessary:YES];
|
||||
}
|
||||
|
||||
if ([self.thread isKindOfClass:TSContactThread.class]) {
|
||||
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[LKSessionManagementProtocol sendSessionRequestIfNeededToPublicKey:self.thread.contactIdentifier transaction:transaction];
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4497,24 +4454,6 @@ typedef enum : NSUInteger {
|
|||
animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - FriendRequestViewDelegate
|
||||
|
||||
- (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest
|
||||
{
|
||||
if (self.thread.isGroupThread || self.thread.contactIdentifier == nil) { return; }
|
||||
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[LKFriendRequestProtocol acceptFriendRequestFromHexEncodedPublicKey:self.thread.contactIdentifier using:transaction];
|
||||
} error:nil];
|
||||
}
|
||||
|
||||
- (void)declineFriendRequest:(TSIncomingMessage *)friendRequest
|
||||
{
|
||||
if (self.thread.isGroupThread || self.thread.contactIdentifier == nil) { return; }
|
||||
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[LKFriendRequestProtocol declineFriendRequestFromHexEncodedPublicKey:self.thread.contactIdentifier using:transaction];
|
||||
} error:nil];
|
||||
}
|
||||
|
||||
#pragma mark - ConversationViewLayoutDelegate
|
||||
|
||||
- (NSArray<id<ConversationViewLayoutItem>> *)layoutItems
|
||||
|
@ -4618,6 +4557,12 @@ typedef enum : NSUInteger {
|
|||
if (didAddToProfileWhitelist) {
|
||||
[self.conversationViewModel ensureDynamicInteractionsAndUpdateIfNecessary:YES];
|
||||
}
|
||||
|
||||
if ([self.thread isKindOfClass:TSContactThread.class]) {
|
||||
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[LKSessionManagementProtocol sendSessionRequestIfNeededToPublicKey:self.thread.contactIdentifier transaction:transaction];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)voiceMemoGestureDidStart
|
||||
|
@ -4802,7 +4747,6 @@ typedef enum : NSUInteger {
|
|||
if ([cell isKindOfClass:[OWSMessageCell class]]) {
|
||||
OWSMessageCell *messageCell = (OWSMessageCell *)cell;
|
||||
messageCell.messageBubbleView.delegate = self;
|
||||
messageCell.friendRequestViewDelegate = self;
|
||||
}
|
||||
cell.conversationStyle = self.conversationStyle;
|
||||
|
||||
|
@ -5107,7 +5051,6 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
|
||||
[self dismissMenuActionsIfNecessary];
|
||||
[self updateInputBar];
|
||||
|
||||
if (self.isGroupConversation) {
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
|
|
|
@ -413,12 +413,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
&& previousLayoutItem.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
||||
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.interaction;
|
||||
TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousLayoutItem.interaction;
|
||||
if ([incomingMessage.authorId isEqualToString:previousIncomingMessage.authorId] && !previousIncomingMessage.isFriendRequest) {
|
||||
if ([incomingMessage.authorId isEqualToString:previousIncomingMessage.authorId]) {
|
||||
return 2.f;
|
||||
}
|
||||
} else if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage
|
||||
&& previousLayoutItem.interaction.interactionType == OWSInteractionType_OutgoingMessage
|
||||
&& !((TSOutgoingMessage *)previousLayoutItem.interaction).hasFriendRequestStatusMessage) {
|
||||
&& previousLayoutItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
return 2.f;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,592 +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];
|
||||
BOOL hasCheckmark = self.thread.threadRecord.isContactFriend;
|
||||
if (hasCheckmark) {
|
||||
NSMutableAttributedString *checkmark = [[NSMutableAttributedString alloc] initWithString:@"✓"];
|
||||
[checkmark beginEditing];
|
||||
[checkmark addAttribute:NSForegroundColorAttributeName value:UIColor.lokiGreen range:NSMakeRange(0, 1)];
|
||||
[checkmark endEditing];
|
||||
NSMutableAttributedString *mutableName = [name mutableCopy];
|
||||
[mutableName appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]];
|
||||
[mutableName appendAttributedString:checkmark];
|
||||
name = [mutableName copy];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -679,7 +679,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
case let incomingMessage as TSIncomingMessage:
|
||||
let hexEncodedPublicKey = incomingMessage.authorId
|
||||
if incomingMessage.thread.isGroupThread() {
|
||||
var publicChat: LokiPublicChat?
|
||||
var publicChat: PublicChat?
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
publicChat = LokiDatabaseUtilities.getPublicChat(for: incomingMessage.thread.uniqueId!, in: transaction)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
}
|
|
@ -810,34 +810,19 @@ const CGFloat kIconViewLength = 24;
|
|||
}
|
||||
// Block Conversation section.
|
||||
|
||||
/**
|
||||
* Loki: Original code
|
||||
* ========
|
||||
if (!isNoteToSelf) {
|
||||
OWSTableSection *section = [OWSTableSection new];
|
||||
if (self.thread.isGroupThread) {
|
||||
section.footerTitle = NSLocalizedString(
|
||||
@"BLOCK_GROUP_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking a group.");
|
||||
} else {
|
||||
section.footerTitle = NSLocalizedString(
|
||||
@"BLOCK_USER_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking another user.");
|
||||
}
|
||||
if (!isNoteToSelf && [self.thread isKindOfClass:TSContactThread.class]) {
|
||||
mainSection.footerTitle = NSLocalizedString(
|
||||
@"BLOCK_USER_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking another user.");
|
||||
|
||||
[section addItem:[OWSTableItem
|
||||
[mainSection addItem:[OWSTableItem
|
||||
itemWithCustomCellBlock:^{
|
||||
OWSConversationSettingsViewController *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return [UITableViewCell new];
|
||||
}
|
||||
|
||||
NSString *cellTitle;
|
||||
if (strongSelf.thread.isGroupThread) {
|
||||
cellTitle = NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_GROUP",
|
||||
@"table cell label in conversation settings");
|
||||
} else {
|
||||
cellTitle = NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_USER",
|
||||
@"table cell label in conversation settings");
|
||||
}
|
||||
NSString *cellTitle = NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_USER",
|
||||
@"table cell label in conversation settings");
|
||||
UITableViewCell *cell = [strongSelf
|
||||
disclosureCellWithName:cellTitle
|
||||
iconName:@"table_ic_block"
|
||||
|
@ -857,10 +842,7 @@ const CGFloat kIconViewLength = 24;
|
|||
return cell;
|
||||
}
|
||||
actionBlock:nil]];
|
||||
[contents addSection:section];
|
||||
}
|
||||
* ========
|
||||
*/
|
||||
|
||||
self.contents = contents;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -1020,6 +1020,15 @@
|
|||
/* No comment provided by engineer. */
|
||||
"GROUP_MEMBER_LEFT" = "%@ a quitté le groupe.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"GROUP_MEMBER_REMOVED" = " %@ a été retiré du groupe. ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"GROUP_MEMBERS_REMOVED" = " %@ ont été retirés du groupe. ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"YOU_WERE_REMOVED" = " Vous avez été retiré du groupe. ";
|
||||
|
||||
/* Button label to add information to an unknown contact */
|
||||
"GROUP_MEMBERS_ADD_CONTACT_INFO" = "Ajouter un contact";
|
||||
|
||||
|
@ -2055,7 +2064,7 @@
|
|||
"SETTINGS_ADVANCED_TITLE" = "Avancés";
|
||||
|
||||
/* Format string for the default 'Note' sound. Embeds the system {{sound name}}. */
|
||||
"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (default)";
|
||||
"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT" = "%@ (par défaut)";
|
||||
|
||||
/* Label for the backup view in app settings. */
|
||||
"SETTINGS_BACKUP" = "Sauvegarde";
|
||||
|
@ -2166,7 +2175,7 @@
|
|||
"SETTINGS_LINK_PREVIEWS" = "Envoyer des aperçus de liens.";
|
||||
|
||||
/* Footer for setting for enabling & disabling link previews. */
|
||||
"SETTINGS_LINK_PREVIEWS_FOOTER" = "Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links.";
|
||||
"SETTINGS_LINK_PREVIEWS_FOOTER" = "Les aperçus sont pris en charge pour les liens Imgur, Instagram, Pinterest, Reddit et YouTube.";
|
||||
|
||||
/* Header for setting for enabling & disabling link previews. */
|
||||
"SETTINGS_LINK_PREVIEWS_HEADER" = "Aperçus de liens";
|
||||
|
@ -2542,3 +2551,209 @@
|
|||
|
||||
/* Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context. */
|
||||
"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "Vous avez défini l’expiration des messages éphémères à %@.";
|
||||
|
||||
|
||||
|
||||
// MARK: - Loki Messenger:
|
||||
|
||||
"Link Device" = "Relier un appareil";
|
||||
"Prevent Session previews from appearing in the app switcher." = "Empêcher les aperçus de Session d’apparaître dans le sélecteur d’applis.";
|
||||
"Require Touch ID, Face ID or your device passcode to unlock Session’s screen. You can still receive notifications when Screen Lock is enabled. Use Session’s notification settings to customise the information displayed in notifications." = "Requiert Touch ID, Face ID ou le code d'accès de votre appareil pour débloquer Session. Vous continuerez de recevoir des notifications lorsque le verrouillage de l'écran est activé. Utilisez les paramètres de notification de Session pour personnaliser les informations affichées dans les notifications.";
|
||||
"Cancel" = "Annuler";
|
||||
"You haven't linked any devices yet" = "Vous n'avez encore relié aucun appareil";
|
||||
"Link a Device (Beta)" = "Relier un appareil";
|
||||
"Waiting for Device" = "En attente d’une demande de liaison";
|
||||
"Linking Request Received" = "Demande de liaison reçue";
|
||||
"%@ sent you a session request" = "%@ vous a envoyé une demande de session";
|
||||
"You've accepted %@'s session request" = "Vous avez accepté la demande de Session de %@";
|
||||
"You've declined %@'s session request" = "Vous avez refusé la demande de Session de %@";
|
||||
"%@'s session request has expired" = "La demande de Session de %@ a expiré";
|
||||
"You've sent %@ a session request" = "Vous avez envoyé une demande de session à %@";
|
||||
"%@ accepted your session request" = "%@ a accepté votre demande de Session";
|
||||
"Your session request to %@ has expired" = "Votre demande de Session à %@ a expiré";
|
||||
"Pending session request" = "Demande de session en attente";
|
||||
"Accept" = "Accepter";
|
||||
"Decline" = "Refuser";
|
||||
"Secure session reset in progress" = "Réinitialisation sécurisée de la session en cours";
|
||||
"Please check that the words below match those shown on your other device" = "Veuillez vérifier que les mots ci-dessous correspondent à ceux affichés sur votre autre appareil";
|
||||
"Waiting for Authorization" = "En attente d’autorisation";
|
||||
"Authorize" = "Autoriser";
|
||||
"Device Link Authorized" = "Liaison de l'appareil autorisée";
|
||||
"Your device has been linked successfully" = "Votre appareil a été connecté avec succès";
|
||||
"Your device was unlinked successfully" = "Votre appareil a été déconnecté avec succès";
|
||||
"Couldn't Link Device" = "Impossible de relier l'appareil.";
|
||||
"Couldn't Unlink Device" = "Impossible de déconnecter l'appareil";
|
||||
"Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters" = "Veuillez choisir un nom d'utilisateur composé uniquement de caractères a-z, A-Z, 0-9 et _";
|
||||
"Please pick a shorter display name" = "Veuillez choisir un nom d'utilisateur plus court";
|
||||
"Please pick a display name" = "Veuillez choisir un nom d'utilisateur";
|
||||
"Multi Device Limit Reached" = "Limite d'appareils atteinte";
|
||||
"It's currently not allowed to link more than one device." = "Il n'est actuellement pas possible de relier plus d'un appareil.";
|
||||
"Enter a Name" = "Saisissez un nom";
|
||||
"Change Name" = "Changer de nom";
|
||||
"Change Device Name" = "Modifier le nom de l’appareil";
|
||||
"Enter the new display name for your device below" = "Saisissez ci-dessous le nouveau nom pour votre appareil";
|
||||
"Unlink" = "Déconnecter l'appareil";
|
||||
|
||||
|
||||
|
||||
// MARK: - Session
|
||||
|
||||
"Messages" = "Messages";
|
||||
"Note to Self" = "Note à mon intention";
|
||||
"New Group" = "Nouveau groupe";
|
||||
"Delete" = "Supprimer";
|
||||
"Search" = "Recherche";
|
||||
"New Session" = "Nouvelle Session";
|
||||
"Enter a Session ID" = "Saisir un Session ID";
|
||||
"Users can share their Session ID from their account settings, or by sharing their QR code." = "Les utilisateurs peuvent partager leur Session ID depuis les paramètres du compte ou en utilisant le code QR.";
|
||||
"Scan a user’s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings." = "Scannez le code QR d'un utilisateur pour démarrer une session. Les codes QR peuvent se trouver en appuyant sur l'icône du code QR dans les paramètres du compte.";
|
||||
"Your Session ID" = "Votre Session ID";
|
||||
"Copy" = "Copier";
|
||||
"Copied" = "Copié";
|
||||
"Share" = "Partager";
|
||||
"Next" = "Suivant";
|
||||
"Session needs camera access to scan QR codes" = "Session a besoin d'accéder à l'appareil photo pour scanner les codes QR";
|
||||
"Enable Camera Access" = "Autoriser l'accès";
|
||||
"Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Session's in-app settings and tapping \"Show QR Code\"." = "Scannez le code QR de la personne avec laquelle vous souhaitez établir une conversation sécurisée. Le code QR si situe dans les paramètres de l'application en cliquant sur \"Afficher le code QR\".";
|
||||
"Enter Session ID" = "Saisir un Session ID";
|
||||
"Open Group URL" = "URL du groupe public";
|
||||
"Scan QR Code" = "Scanner un Code QR";
|
||||
"Scan the QR code of the open group you'd like to join" = "Scannez le code QR du groupe public que vous souhaitez rejoindre";
|
||||
"Join Open Group" = "Joindre un groupe public";
|
||||
"Enter an open group URL" = "Saisissez une URL de groupe public";
|
||||
"Invalid URL" = "URL non valide";
|
||||
"Please check the URL you entered and try again" = "Vérifiez l'URL saisie, puis réessayez";
|
||||
"Couldn't Join" = "Impossible de rejoindre le groupe";
|
||||
"Settings" = "Paramètres";
|
||||
"Privacy" = "Confidentialité";
|
||||
"Notifications" = "Notifications";
|
||||
"Devices" = "Appareils reliés";
|
||||
"Recovery Phrase" = "Phrase de récupération";
|
||||
"Clear All Data" = "Effacer toutes les données";
|
||||
"This will permanently delete your messages, sessions, and contacts." = "Cela supprimera définitivement vos messages, vos sessions et vos contacts.";
|
||||
"Delete" = "Supprimer";
|
||||
"This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device." = "Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil.";
|
||||
"The information shown in notifications when your phone is locked." = "Les informations affichées dans les notifications quand votre appareil est verrouillé.";
|
||||
"Notifications" = "Notifications";
|
||||
"Back" = "Retour";
|
||||
"View My QR Code" = "Afficher mon code QR";
|
||||
"Scan someone's QR code to start a conversation with them" = "Scannez le code QR d'un autre utilisateur pour démarrer une session";
|
||||
"QR Code" = "Code QR";
|
||||
"Scan Me" = "Scannez moi";
|
||||
"This is your QR code. Other users can scan it to start a session with you." = "Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous.";
|
||||
"Privacy" = "Confidentialité";
|
||||
"Unlock Session's screen using Touch ID, Face ID, or your iOS device passcode. You can still receive message notifications while Screen Lock is enabled. Session's notification settings allow you to customize the information that is displayed." = "Débloquez Session à l'aide de Touch ID, Face ID, ou votre mot de passe iOS. Vous Continuerez de recevoir des notifications lorsque l'écran est verrouillé. Les paramètres de notifications de Session vous permettent de personnaliser les informations affichées.";
|
||||
"Sound" = "Son";
|
||||
"Content" = "Contenu";
|
||||
"Update Profile Picture" = "Mettre à jour la photo de profil";
|
||||
"Couldn't Update Profile Picture" = "La photo de profil n'a pas pu être mise à jour";
|
||||
"Clear" = "Effacer";
|
||||
"Enter a display name" = "Saisissez un nom d’utilisateur";
|
||||
"Your Session begins here..." = "Votre Session débute ici...";
|
||||
"What's Session?" = "Qu'est-ce que Session?";
|
||||
"It's a decentralized, encrypted messaging app." = "C'est une application de messagerie décentralisée et cryptée.";
|
||||
"So it doesn't collect my personal information or my conversation metadata? How does it work?" = "Elle ne recueille donc pas mes informations personnelles ou mes métadonnées de conversations? Comment ça marche?";
|
||||
"Using a combination of advanced anonymous routing and end-to-end encryption technologies." = "En utilisant une combinaison de technologies avancées de routage anonyme et de chiffrement de bout en bout.";
|
||||
"Friends don't let friends use compromised messengers. You're welcome." = "Les vrais amis ne laissent pas leurs amis utiliser des outils de messagerie compromis. De rien.";
|
||||
"Create Session ID" = "Créer un Session ID";
|
||||
"Continue your Session" = "Continuer votre Session";
|
||||
"Say hello to your Session ID" = "Dites bonjour à votre Session ID";
|
||||
"Continue" = "Continuer";
|
||||
"Copy Session ID" = "Copier le Session ID";
|
||||
"Pick your display name" = "Choisissez votre nom d'utilisateur";
|
||||
"Enter a display name" = "Saisissez un nom d'utilisateur";
|
||||
"Restore your account" = "Restaurez votre compte";
|
||||
"Enter your recovery phrase" = "Saisissez votre phrase de récupération";
|
||||
"Message" = "Message";
|
||||
"You" = "Vous";
|
||||
"Encrypting message" = "Chiffrement du message";
|
||||
"Tracing a path" = "Traçage du chemin";
|
||||
"Sending message" = "Envoi du message";
|
||||
"Message sent securely" = "Message sécurisé envoyé";
|
||||
"Message failed to send" = "Échec de l'envoi du message";
|
||||
"Secure your account by saving your recovery phrase" = "Sécurisez votre compte en sauvegardant votre phrase de récupération";
|
||||
"Continue" = "Continuer";
|
||||
"Your Recovery Phrase" = "Votre phrase de récupération";
|
||||
"Meet your recovery phrase" = "Voici votre phrase de récupération";
|
||||
"Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don’t give it to anyone." = "Votre phrase de récupération est la clé principale de votre Session ID - vous pouvez l'utiliser pour restaurer votre Session ID si vous perdez l'accès à votre appareil. Conservez la dans un endroit sûr et ne la donnez à personne.";
|
||||
"Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID." = "Appuyez et maintenez les mots masqués pour révéler votre phrase de récupération, puis stockez-la en toute sécurité pour sécuriser votre Session ID.";
|
||||
"Hold to reveal" = "Appuyer pour révéler";
|
||||
"Make sure to store your recovery phrase in a safe place" = "Assurez-vous de conserver votre phrase de récupération dans un endroit sûr";
|
||||
"Link to an existing account" = "Relier à un compte existant";
|
||||
"Enter your public key" = "Saisissez votre clé publique";
|
||||
"Link to your existing account by going into your in-app settings and clicking \"Devices\"." = "Associez à votre compte existant en accédant aux paramètres de l'application et en cliquant sur \"Appareil reliés\".";
|
||||
"Download Session on your other device and tap \"Link to an existing account\" at the bottom of the landing screen. If you have an existing account on your other device already you will have to delete that account first." = "Téléchargez Session sur votre autre appareil, puis cliquez sur \"Relier à un compte existant\" en bas de l'écran de d’accueil. Si vous possédez déjà un compte sur votre autre appareil, vous devez d'abord le supprimer.";
|
||||
"Group Settings" = "Paramètres du groupe";
|
||||
"Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design." = "Votre Session ID est l'identifiant unique que les gens utilisent pour vous contacter dans Session. Sans lien avec votre identité réelle, votre Session ID est complètement anonyme et privé.";
|
||||
"Enter the recovery phrase that was given to you when you signed up to restore your account." = "Pour restaurer votre compte, veuillez entrer la phrase de récupération qui vous a été fournie lors de la création de votre compte.";
|
||||
"Enter Session ID" = "Saisir un Session ID";
|
||||
"Link your device" = "Reliez votre appareil";
|
||||
"Enter your Session ID to start the linking process." = "Saisissez votre Session ID pour démarrer le processus de liaison.";
|
||||
"Enter your Session ID" = "Saisissez votre Session ID";
|
||||
"Recent Chats" = "Conversations récentes";
|
||||
"Other Chats" = "Autres conversations";
|
||||
"See and share when messages are being typed (applies to all sessions)." = "Permet de voir et de partager quand les messages sont saisis (s'applique à toutes les sessions).";
|
||||
"Disable Preview in App Switcher" = "Désactiver l'aperçu dans le sélecteur d'app";
|
||||
"Are you sure? This cannot be undone." = "Êtes-vous sûr? Cette action est irréversible.";
|
||||
"When enabled, messages between you and %@ will disappear after they have been seen." = "Lorsque activé, les messages entre vous et %@ disparaîtront après avoir été vus.";
|
||||
"This will be your name when you use Session. It can be your real name, an alias, or anything else you like." = "Ce sera votre nom lorsque vous utiliserez Session. Il peut s'agir de votre vrai nom, d'un pseudo ou de ce que vous voulez.";
|
||||
"Session Out of Sync" = "La session est désynchronisée";
|
||||
"Would you like to restore your session? This can help resolve issues. Your messages will be preserved." = "Voulez-vous restaurer votre session? Cela peut aider à résoudre les problèmes. Vos messages seront conservés.";
|
||||
"Would you like to restore your session with %@? This can help resolve issues. Your messages will be preserved." = "Voulez-vous restaurer votre session avec %@? Cela peut aider à résoudre les problèmes. Vos messages seront conservés.";
|
||||
"Restore" = "Restaurer";
|
||||
"Dismiss" = "Fermer";
|
||||
"New Closed Group" = "Nouveau groupe privé";
|
||||
"Group Members" = "Membres du groupe";
|
||||
"You don't have any contacts yet" = "Vous n’avez pas encore de contacts.";
|
||||
"Start a Session" = "Démarrer une session";
|
||||
"Enter a group name" = "Saisissez un nom de groupe";
|
||||
"Please enter a group name" = "Veuillez saisir un nom de groupe";
|
||||
"Please enter a shorter group name" = "Veuillez saisir un nom de groupe plus court";
|
||||
"Please pick at least 2 group members" = "Veuillez sélectionner au moins 2 membres";
|
||||
"Enable Link Previews?" = "Activer les aperçus de liens?";
|
||||
"You will not have full metadata protection when sending or receiving link previews." = "Vous ne disposez pas d'une protection totale des métadonnées lorsque vous envoyer ou recevez des aperçus de liens.";
|
||||
"Open groups can be joined by anyone and do not provide full privacy protection" = "Les groupes publics peuvent être rejoints par n'importe qui et ne fournissent pas une confidentialité totale";
|
||||
"Search GIFs?" = "Rechercher des GIFs?";
|
||||
"You will not have full metadata protection when sending GIFs." = "Vous ne disposez pas d'une protection totale des métadonnées lorsque vous envoyer des GIFs.";
|
||||
"The ability to add members to a closed group is coming soon." = "La possibilité d'ajouter des membres à un groupe privé arrive bientôt.";
|
||||
"A closed group cannot have more than 10 members" = "Un groupe privé ne peut pas avoir plus de 10 membres";
|
||||
"A closed group cannot have more than 50 members" = "Un groupe privé ne peut pas avoir plus de 50 membres";
|
||||
"Closed groups support up to 10 members" = "Les groupes privés prennent en charge jusqu'à 10 membres";
|
||||
"Closed groups support up to 50 members" = "Les groupes privés prennent en charge jusqu'à 50 membres";
|
||||
"No messages yet" = "Aucun messages";
|
||||
"Would you like to join the Session Public Chat?" = " Voulez-vous rejoindre le chat public de Session?";
|
||||
"Join Public Chat" = "Rejoindre le chat public";
|
||||
"No, thank you" = "Non merci";
|
||||
"Report" = "Rapport";
|
||||
"Please Pick an Option" = "Veuillez choisir une option";
|
||||
"There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose." = "Session peut gérer les notifications push de deux manières. Assurez-vous de lire attentivement les détails avant de faire votre choix.";
|
||||
"Apple Push Notification Service" = "Service Apple Push Notification";
|
||||
"Session will use the Apple Push Notification service to receive push notifications. You'll be notified of new messages reliably and immediately. Using APNs means that your IP address and device token will be exposed to Apple. If you use push notifications for other apps, this will already be the case. Your IP address and device token will also be exposed to Loki, but your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private." = "Session utilisera le service Apple Push Notification (APNs) pour la réception des notifications. Vous serez notifié immédiatement des nouveaux messages. L'utilisation d'APNs signifie que votre adresse IP et votre jeton d'appareil seront exposés à Apple. Cela est déjà le cas si vous utilisez des notifications push dans d’autres applications. Ceux-ci seront également exposés à Loki. Vos messages seront toujours routés anonymement et chiffrés de bout en bout, ainsi leur contenu restera totalement confidentiel.";
|
||||
"Background Polling" = "Consultation d'arrière-plan";
|
||||
"Session will occasionally check for new messages in the background. This guarantees full metadata protection, but message notifications may be significantly delayed." = "Session vérifiera de temps en temps les nouveaux messages en arrière-plan. Cette option garantit une confidentialité totale, mais les notifications de messages peuvent être retardées.";
|
||||
"Use APNs" = "Utiliser APNs";
|
||||
"Recommended" = "Recommandé";
|
||||
"Notification Strategy" = "Stratégie de notification";
|
||||
"Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose." = "Session propose désormais deux façons de gérer les notifications push. Assurez-vous de lire attentivement les descriptions avant de choisir.";
|
||||
"Push Notifications" = "Notifications Push";
|
||||
"Confirm" = "Confirmer";
|
||||
"Skip" = "Ignorer";
|
||||
"Link Previews" = "Aperçus de liens";
|
||||
"Invalid Session ID" = "Session ID non valide";
|
||||
"Please make sure the Session ID you entered is correct and try again." = "Veuillez vérifier que le Session ID saisi est correct, puis réessayez.";
|
||||
"Device Linking Failed" = "Échec de liaison de l’appareil";
|
||||
"Please check your internet connection and try again" = "Veuillez vérifiez votre connexion à Internet, puis réessayez";
|
||||
"Authorizing Device Link" = "Autorisation de la liaison de l'appareil";
|
||||
"Please wait while the device link is created. This can take up to a minute." = "Veuillez patienter pendant la création de la liaison. Cela peut prendre jusqu'à une minute.";
|
||||
"Path" = "Chemin";
|
||||
"Session hides your IP by bouncing your messages through several Service Nodes in Session’s decentralized network. These are the countries your connection is currently being bounced through:" = "Session occulte votre adresse IP en envoyant vos messages via plusieurs nœuds de service dans le réseau décentralisé de Session. Voici les pays par le biais desquels votre connexion est actuellement envoyée :";
|
||||
"Entry Node" = "Noeud d’entrée";
|
||||
"Service Node" = "Noeud de service";
|
||||
"You" = "Vous";
|
||||
"Destination" = "Destination";
|
||||
"Learn More" = "En savoir plus";
|
||||
"Please ask the open group operator to add you to the group." = "Veuillez demander à l'opérateur du groupe public de vous ajouter au groupe.";
|
||||
"Unauthorized" = "Non autorisé";
|
||||
"Closed group created" = "Groupe privé créé";
|
||||
"Couldn't Create Group" = "Impossible de créer le groupe";
|
||||
"Please check your internet connection and try again." = "Veuillez vérifiez votre connexion à Internet, puis réessayez.";
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import Foundation
|
||||
import SessionServiceKit
|
||||
|
||||
@objc
|
||||
public class LK001UpdateFriendRequestStatusStorage : OWSDatabaseMigration {
|
||||
|
||||
// MARK: -
|
||||
|
||||
// Increment a similar constant for each migration.
|
||||
// 100-114 are reserved for Signal migrations
|
||||
@objc
|
||||
class func migrationId() -> String {
|
||||
return "001"
|
||||
}
|
||||
|
||||
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
self.doMigrationAsync(completion: completion)
|
||||
}
|
||||
|
||||
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
DispatchQueue.global().async {
|
||||
try! Storage.writeSync { transaction in
|
||||
var threads: [TSContactThread] = []
|
||||
TSContactThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||
guard let thread = object as? TSContactThread else { return }
|
||||
threads.append(thread)
|
||||
}
|
||||
threads.forEach { thread in
|
||||
guard let friendRequestStatus = LKFriendRequestStatus(rawValue: thread.friendRequestStatus) else { return }
|
||||
OWSPrimaryStorage.shared().setFriendRequestStatus(friendRequestStatus, for: thread.contactIdentifier(), transaction: transaction)
|
||||
}
|
||||
self.save(with: transaction)
|
||||
}
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
@objc
|
||||
public class LK002RemoveFriendRequests : OWSDatabaseMigration {
|
||||
|
||||
@objc
|
||||
class func migrationId() -> String {
|
||||
return "002"
|
||||
}
|
||||
|
||||
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
self.doMigrationAsync(completion: completion)
|
||||
}
|
||||
|
||||
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
DispatchQueue.global().async {
|
||||
try! Storage.writeSync { transaction in
|
||||
var interactionIDsToRemove: [String] = []
|
||||
transaction.enumerateRows(inCollection: TSInteraction.collection()) { key, object, _, _ in
|
||||
if !(object is TSInteraction) {
|
||||
interactionIDsToRemove.append(key)
|
||||
}
|
||||
}
|
||||
interactionIDsToRemove.forEach { transaction.removeObject(forKey: $0, inCollection: TSInteraction.collection()) }
|
||||
self.save(with: transaction)
|
||||
}
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
TSThread *thread = [[transaction extension:TSThreadDatabaseViewExtensionName]
|
||||
objectAtIndexPath:[NSIndexPath indexPathForItem:(NSInteger)item inSection:(NSInteger)section]
|
||||
withMappings:self.threadMappings];
|
||||
if (!thread.shouldThreadBeVisible || !thread.isContactFriend) { continue; }
|
||||
if (!thread.shouldThreadBeVisible) { continue; }
|
||||
if ([thread isKindOfClass:TSContactThread.class]) {
|
||||
NSString *publicKey = thread.contactIdentifier;
|
||||
if ([LKUserDisplayNameUtilities getPrivateChatDisplayNameFor:publicKey] == nil) { continue; }
|
||||
|
|
|
@ -125,10 +125,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
syncManager:syncManager
|
||||
typingIndicators:typingIndicators
|
||||
attachmentDownloads:attachmentDownloads]];
|
||||
|
||||
// Loki
|
||||
LKFriendRequestExpirationJob *lokiFriendRequestExpirationJob = [[LKFriendRequestExpirationJob alloc] initWithPrimaryStorage:primaryStorage];
|
||||
SSKEnvironment.shared.lokiFriendRequestExpirationJob = lokiFriendRequestExpirationJob;
|
||||
|
||||
appSpecificSingletonBlock();
|
||||
|
||||
|
|
|
@ -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,21 +26,7 @@ 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],
|
||||
[[LK001UpdateFriendRequestStatusStorage alloc] init]
|
||||
[[LK002RemoveFriendRequests alloc] init]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -934,8 +934,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
TSGroupThread *groupThread = (TSGroupThread *)thread;
|
||||
NSData *groupId = groupThread.groupModel.groupId;
|
||||
return [self isGroupIdInProfileWhitelist:groupId];
|
||||
} else if ([LKFriendRequestProtocol isFriendsWithAnyLinkedDeviceOfHexEncodedPublicKey:thread.contactIdentifier]) {
|
||||
return true;
|
||||
} else {
|
||||
NSString *recipientId = thread.contactIdentifier;
|
||||
return [self isUserInProfileWhitelist:recipientId];
|
||||
|
|
|
@ -291,7 +291,7 @@ typedef void (^BlockAlertCompletionBlock)(UIAlertAction *action);
|
|||
contactsManager:(OWSContactsManager *)contactsManager
|
||||
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
|
||||
{
|
||||
NSString *displayName = [contactsManager displayNameForPhoneIdentifier:phoneNumber];
|
||||
NSString *displayName = [LKUserDisplayNameUtilities getPrivateChatDisplayNameFor:phoneNumber] ?: phoneNumber;
|
||||
[self showUnblockPhoneNumbersActionSheet:@[ phoneNumber ]
|
||||
displayName:displayName
|
||||
fromViewController:fromViewController
|
||||
|
|
|
@ -181,13 +181,8 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
|
|||
|
||||
BOOL isVoiceMessage = (attachments.count == 1 && attachments.lastObject.isVoiceMessage);
|
||||
|
||||
// Loki: If we're not friends then always set the message to a friend request message.
|
||||
// If we're friends then the assumption is that we have the other user's pre key bundle.
|
||||
NSString *messageClassAsString = (thread.isContactFriend || thread.isGroupThread) ? @"TSOutgoingMessage" : @"LKFriendRequestMessage";
|
||||
Class messageClass = NSClassFromString(messageClassAsString);
|
||||
|
||||
TSOutgoingMessage *message =
|
||||
[[messageClass alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
|
||||
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
|
||||
inThread:thread
|
||||
messageBody:truncatedText
|
||||
attachmentIds:[NSMutableArray new]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue