Merge branch 'dev' into onion-requests

This commit is contained in:
nielsandriesse 2020-07-23 10:54:01 +10:00
commit 70eaa5982c
166 changed files with 955 additions and 13146 deletions

View File

@ -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

@ -1 +1 @@
Subproject commit 158e066d79806ef0e466370da5c303dedb90c654
Subproject commit 8f3d6d46718795227074d6545776206cf18c0244

View File

@ -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|

View File

@ -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;

View File

@ -4,9 +4,7 @@
#import "AppDelegate.h"
#import "DebugLogger.h"
#import "HomeViewController.h"
#import "MainAppContext.h"
#import "OWS2FASettingsViewController.h"
#import "OWSBackup.h"
#import "OWSOrphanDataCleaner.h"
#import "OWSScreenLockUI.h"
@ -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

View File

@ -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()

View File

@ -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"

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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())
}
}
}

View File

@ -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)

View File

@ -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 ]
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()
}

View File

@ -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)

View File

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

View File

@ -19,8 +19,6 @@
#import "DebugUIPage.h"
#import "DebugUITableViewController.h"
#import "FingerprintViewController.h"
#import "HomeViewCell.h"
#import "HomeViewController.h"
#import "MediaDetailViewController.h"
#import "NotificationSettingsViewController.h"
#import "OWSAddToContactViewController.h"
@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

@ -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

View File

@ -19,7 +19,6 @@
#import "DebugUITableViewController.h"
#import "FingerprintViewController.h"
#import "NSAttributedString+OWS.h"
#import "NewGroupViewController.h"
#import "OWSAudioPlayer.h"
#import "OWSContactOffersCell.h"
#import "OWSConversationSettingsViewController.h"
@ -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) {

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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)
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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 lexpiration 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 dapparaître dans le sélecteur dapplis.";
"Require Touch ID, Face ID or your device passcode to unlock Sessions screen. You can still receive notifications when Screen Lock is enabled. Use Sessions 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 dune 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 dautorisation";
"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 lappareil";
"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 users 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 dutilisateur";
"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 dont 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 daccueil. 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 navez 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 dautres 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 lappareil";
"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 Sessions 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 dentré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.";

View File

@ -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()
}
}
}

View File

@ -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()
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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; }

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,14 +3,6 @@
//
#import "OWSDatabaseMigrationRunner.h"
#import "OWS100RemoveTSRecipientsMigration.h"
#import "OWS102MoveLoggingPreferenceToUserDefaults.h"
#import "OWS103EnableVideoCalling.h"
#import "OWS104CreateRecipientIdentities.h"
#import "OWS105AttachmentFilePaths.h"
#import "OWS107LegacySounds.h"
#import "OWS108CallLoggingPreference.h"
#import "OWS109OutgoingMessageState.h"
#import "OWSDatabaseMigration.h"
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SessionServiceKit/AppContext.h>
@ -34,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]
];
}

View File

@ -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];

View File

@ -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

View File

@ -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