Merge branch 'dev' of https://github.com/oxen-io/session-ios into preformance-improvement

pull/541/head
Ryan Zhao 2 years ago
commit 3c9cfa9d2e

@ -2,97 +2,68 @@ platform :ios, '12.0'
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
inhibit_all_warnings!
target 'Session' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'NVActivityIndicatorView', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true
pod 'Reachability', :inhibit_warnings => true
pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true
pod 'ZXingObjC', :inhibit_warnings => true
end
target 'SessionShareExtension' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
end
target 'SessionNotificationServiceExtension' do
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
end
target 'SignalUtilitiesKit' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'GRKOpenSSLFramework', :inhibit_warnings => true
pod 'HKDFKit', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'NVActivityIndicatorView', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true
pod 'Reachability', :inhibit_warnings => true
pod 'SAMKeychain', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true
end
target 'SessionUIKit' do
end
target 'SessionMessagingKit' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'HKDFKit', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true
pod 'Reachability', :inhibit_warnings => true
pod 'SAMKeychain', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true
pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
end
target 'SessionSnodeKit' do
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
# Dependencies to be included in the app and all extensions/frameworks
abstract_target 'GlobalDependencies' do
pod 'PromiseKit'
pod 'CryptoSwift'
pod 'Sodium', '~> 0.9.1'
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/oxen-io/session-ios-yap-database.git', branch: 'signal-release'
target 'Session' do
pod 'AFNetworking'
pod 'Reachability'
pod 'PureLayout', '~> 3.1.8'
pod 'NVActivityIndicatorView'
pod 'YYImage', git: 'https://github.com/signalapp/YYImage'
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master'
pod 'ZXingObjC'
end
# Dependencies to be included only in all extensions/frameworks
abstract_target 'FrameworkAndExtensionDependencies' do
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git'
pod 'SignalCoreKit', git: 'https://github.com/oxen-io/session-ios-core-kit', branch: 'session-version'
target 'SessionNotificationServiceExtension'
target 'SessionSnodeKit'
# Dependencies that are shared across a number of extensions/frameworks but not all
abstract_target 'ExtendedDependencies' do
pod 'AFNetworking'
pod 'PureLayout', '~> 3.1.8'
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master'
target 'SessionShareExtension' do
pod 'NVActivityIndicatorView'
end
target 'SignalUtilitiesKit' do
pod 'NVActivityIndicatorView'
pod 'Reachability'
pod 'SAMKeychain'
pod 'SwiftProtobuf', '~> 1.5.0'
pod 'YYImage', git: 'https://github.com/signalapp/YYImage'
end
target 'SessionMessagingKit' do
pod 'Reachability'
pod 'SAMKeychain'
pod 'SwiftProtobuf', '~> 1.5.0'
end
target 'SessionUtilitiesKit' do
pod 'SAMKeychain'
end
end
end
end
target 'SessionUtilitiesKit' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true
pod 'SAMKeychain', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
end
# No dependencies for this
target 'SessionUIKit'
# Actions to perform post-install
post_install do |installer|
enable_whole_module_optimization_for_crypto_swift(installer)
set_minimum_deployment_target(installer)

@ -14,41 +14,40 @@ PODS:
- AFNetworking/Serialization (4.0.1)
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- CocoaLumberjack (3.6.2):
- CocoaLumberjack/Core (= 3.6.2)
- CocoaLumberjack/Core (3.6.2)
- CryptoSwift (1.3.2)
- CocoaLumberjack (3.7.4):
- CocoaLumberjack/Core (= 3.7.4)
- CocoaLumberjack/Core (3.7.4)
- CryptoSwift (1.4.2)
- Curve25519Kit (2.1.0):
- CocoaLumberjack
- SignalCoreKit
- GRKOpenSSLFramework (1.0.2.20)
- HKDFKit (0.0.3)
- Mantle (2.1.0):
- Mantle/extobjc (= 2.1.0)
- Mantle/extobjc (2.1.0)
- NVActivityIndicatorView (5.0.1):
- NVActivityIndicatorView/Base (= 5.0.1)
- NVActivityIndicatorView/Base (5.0.1)
- PromiseKit (6.13.1):
- PromiseKit/CorePromise (= 6.13.1)
- PromiseKit/Foundation (= 6.13.1)
- PromiseKit/UIKit (= 6.13.1)
- PromiseKit/CorePromise (6.13.1)
- PromiseKit/Foundation (6.13.1):
- NVActivityIndicatorView (5.1.1):
- NVActivityIndicatorView/Base (= 5.1.1)
- NVActivityIndicatorView/Base (5.1.1)
- OpenSSL-Universal (1.1.1300)
- PromiseKit (6.15.3):
- PromiseKit/CorePromise (= 6.15.3)
- PromiseKit/Foundation (= 6.15.3)
- PromiseKit/UIKit (= 6.15.3)
- PromiseKit/CorePromise (6.15.3)
- PromiseKit/Foundation (6.15.3):
- PromiseKit/CorePromise
- PromiseKit/UIKit (6.13.1):
- PromiseKit/UIKit (6.15.3):
- PromiseKit/CorePromise
- PureLayout (3.1.8)
- PureLayout (3.1.9)
- Reachability (3.2)
- SAMKeychain (1.5.3)
- SignalCoreKit (1.0.0):
- CocoaLumberjack
- GRKOpenSSLFramework
- Sodium (0.8.0)
- SQLCipher (4.4.0):
- SQLCipher/standard (= 4.4.0)
- SQLCipher/common (4.4.0)
- SQLCipher/standard (4.4.0):
- OpenSSL-Universal
- Sodium (0.9.1)
- SQLCipher (4.5.0):
- SQLCipher/standard (= 4.5.0)
- SQLCipher/common (4.5.0)
- SQLCipher/standard (4.5.0):
- SQLCipher/common
- SwiftProtobuf (1.5.0)
- YapDatabase/SQLCipher (3.1.1):
@ -124,18 +123,16 @@ DEPENDENCIES:
- AFNetworking
- CryptoSwift
- Curve25519Kit (from `https://github.com/signalapp/Curve25519Kit.git`)
- GRKOpenSSLFramework
- HKDFKit
- Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`)
- NVActivityIndicatorView
- PromiseKit
- PureLayout (~> 3.1.8)
- Reachability
- SAMKeychain
- SignalCoreKit (from `https://github.com/signalapp/SignalCoreKit.git`)
- Sodium (~> 0.8.0)
- SignalCoreKit (from `https://github.com/oxen-io/session-ios-core-kit`, branch `session-version`)
- Sodium (~> 0.9.1)
- SwiftProtobuf (~> 1.5.0)
- YapDatabase/SQLCipher (from `https://github.com/loki-project/session-ios-yap-database.git`, branch `signal-release`)
- YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`)
- YYImage (from `https://github.com/signalapp/YYImage`)
- ZXingObjC
@ -144,9 +141,8 @@ SPEC REPOS:
- AFNetworking
- CocoaLumberjack
- CryptoSwift
- GRKOpenSSLFramework
- HKDFKit
- NVActivityIndicatorView
- OpenSSL-Universal
- PromiseKit
- PureLayout
- Reachability
@ -163,10 +159,11 @@ EXTERNAL SOURCES:
:branch: signal-master
:git: https://github.com/signalapp/Mantle
SignalCoreKit:
:git: https://github.com/signalapp/SignalCoreKit.git
:branch: session-version
:git: https://github.com/oxen-io/session-ios-core-kit
YapDatabase:
:branch: signal-release
:git: https://github.com/loki-project/session-ios-yap-database.git
:git: https://github.com/oxen-io/session-ios-yap-database.git
YYImage:
:git: https://github.com/signalapp/YYImage
@ -175,39 +172,38 @@ CHECKOUT OPTIONS:
:commit: 4fc1c10e98fff2534b5379a9bb587430fdb8e577
:git: https://github.com/signalapp/Curve25519Kit.git
Mantle:
:commit: b72c2d1e6132501db906de2cffa8ded7803c54f4
:commit: e7e46253bb01ce39525d90aa69ed9e85e758bfc4
:git: https://github.com/signalapp/Mantle
SignalCoreKit:
:commit: 21c092e94b307690957b50f2305e5e65d28fa89e
:git: https://github.com/signalapp/SignalCoreKit.git
:commit: 4590c2737a2b5dc0ef4ace9f9019b581caccc1de
:git: https://github.com/oxen-io/session-ios-core-kit
YapDatabase:
:commit: 5806f6b6e0b34124ee09283a9eca9ce7e6eaf14e
:git: https://github.com/loki-project/session-ios-yap-database.git
:commit: d84069e25e12a16ab4422e5258127a04b70489ad
:git: https://github.com/oxen-io/session-ios-yap-database.git
YYImage:
:commit: d91910e6f313a255febbf69795198e74259bd51c
:commit: 62a4cede20bcf31da73d18163408e46a92f171c6
:git: https://github.com/signalapp/YYImage
SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
CocoaLumberjack: bd155f2dd06c0e0b03f876f7a3ee55693122ec94
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
CocoaLumberjack: 543c79c114dadc3b1aba95641d8738b06b05b646
CryptoSwift: a532e74ed010f8c95f611d00b8bbae42e9fe7c17
Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6
GRKOpenSSLFramework: dc635b0a9d4cd8af2a9ff80a61e779e21b69dfd8
HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a
Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
NVActivityIndicatorView: 738e843cb8924e9e4fc3e559d0728031624bf860
PromiseKit: 28fda91c973cc377875d8c0ea4f973013c05b6db
PureLayout: a4afb3d79dd958564ce33d22c89f407280d8e6a8
NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667
OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2
PromiseKit: 3b2b6995e51a954c46dbc550ce3da44fbfb563c5
PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
SignalCoreKit: 4562b2bbd9830077439ca003f952a798457d4ea5
Sodium: 63c0ca312a932e6da481689537d4b35568841bdc
SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072
SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d
Sodium: 23d11554ecd556196d313cf6130d406dfe7ac6da
SQLCipher: 98dc22f27c0b1790d39e710d440f22a466ebdb59
SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 50e6a35c838ba28d2ee02bc6018fdd297c04e55f
PODFILE CHECKSUM: 7f961dc4934dd213f5a3277af57d54caef7a4442
COCOAPODS: 1.10.1
COCOAPODS: 1.11.2

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
10AC6C7D50A0C865C5E4779B /* Pods_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71CFEDD2D3C54277731012DF /* Pods_SessionUIKit.framework */; };
19329AE141BF804420343B92 /* Pods_GlobalDependencies_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B883F8C5D6D90C5077B2FD14 /* Pods_GlobalDependencies_Session.framework */; };
29343CA386C1F4E9DAEFB602 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E19F30497676B0FA3553CCE6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */; };
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */; };
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */; };
@ -21,10 +23,8 @@
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; };
34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */; };
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */; };
346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.m */; };
34641E1F2088DA6D00E2EDE5 /* SAEScreenLockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */; };
34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; };
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
3478504C1FD7496D007B8332 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; };
@ -63,7 +63,6 @@
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; };
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; };
3681EBBAC430992520DBD9AC /* Pods_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 200605FD180CB8B89F566B41 /* Pods_SessionShareExtension.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 */; };
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2041E0D74AC003D14BE /* Platform.swift */; };
@ -130,9 +129,7 @@
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */; };
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; };
4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC613352227A00400E21A3A /* ConversationSearch.swift */; };
5DF9AB212C6DB1E8BE70EFF6 /* Pods_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB523C549815DE935E98151E /* Pods_SessionMessagingKit.framework */; };
70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; };
75A5E31037A6F0E5677F3B5C /* Pods_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62ED73E38E0EC8506A9131AD /* Pods_SessionNotificationServiceExtension.framework */; };
768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; };
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; };
@ -140,18 +137,20 @@
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; };
7BA7F4BB279F9F5800B3A466 /* EmptySearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7F4BA279F9F5800B3A466 /* EmptySearchResultCell.swift */; };
7BA7F4BD27A216B600B3A466 /* Storage+RecentSearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7F4BC27A216B600B3A466 /* Storage+RecentSearchResults.swift */; };
7BA9057E27911C5800998B3C /* GlobalSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA9057D27911C5800998B3C /* GlobalSearchViewController.swift */; };
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; };
7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; };
9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */; };
8DDB50527360BA38AE415C6F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5060C3B36A848B71CCE4685 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */; };
9B0A583E9B89FEF0916B793A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278EF43EB1E6A0B83C9234F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; };
A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; };
A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; };
A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; };
A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; };
A5509ECA1A69AB8B00ABA4BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A5509EC91A69AB8B00ABA4BC /* Main.storyboard */; };
B3E0C9C6F1633B1ABCE5AD0B /* Pods_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D547348A367C8A14D37FC0 /* Pods_SignalUtilitiesKit.framework */; };
B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; };
B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; };
B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
@ -285,6 +284,7 @@
B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8FF8EA525C11FEF004D1F22 /* IPv4.swift */; };
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
C193959302ABEA1B4B1CDAFC /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 038A3BABD5BA0CE41D8C17F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; };
C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; };
C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5BC2554B00D00555489 /* ReadReceipt.swift */; };
C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5D22554B05A00555489 /* TypingIndicator.swift */; };
@ -771,10 +771,17 @@
D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A0E7169DFFC500537ABF /* AVFoundation.framework */; };
D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D24B5BD4169F568C00681372 /* AudioToolbox.framework */; };
D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; };
D48CEFD2222D323FEFEFC6CC /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8962372EEC51D3F56FE3A68A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; };
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */; };
FAD6392E0205566D11AA9E48 /* Pods_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB3724C70247A916D43271FE /* Pods_Session.framework */; };
F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; };
FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; };
FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; };
FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */; };
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8D278CE29800F16121 /* String+Localization.swift */; };
FD705A90278CEBBC00F16121 /* ShareAppExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */; };
FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; };
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */; };
FD705A98278E9F4D00F16121 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -923,14 +930,16 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
038A3BABD5BA0CE41D8C17F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0D3D13FEE4FF6A2E2ED85322 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
174BD0AE74771D02DAC2B7A9 /* Pods-SessionProtocolKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionProtocolKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionProtocolKit/Pods-SessionProtocolKit.app store release.xcconfig"; sourceTree = "<group>"; };
18D19142FD6E60FD0A5D89F7 /* Pods-LokiPushNotificationService.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LokiPushNotificationService.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-LokiPushNotificationService/Pods-LokiPushNotificationService.app store release.xcconfig"; sourceTree = "<group>"; };
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>"; };
200605FD180CB8B89F566B41 /* Pods_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionProtocolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
264033E641846B67E0CB21B0 /* Pods-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; };
278EF43EB1E6A0B83C9234F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3303495F6651CE2F3CC9693B /* Pods-SessionUtilities.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilities.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilities/Pods-SessionUtilities.app store release.xcconfig"; sourceTree = "<group>"; };
340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; };
340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsViewController.m; sourceTree = "<group>"; };
@ -956,16 +965,12 @@
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = "<group>"; };
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupRestoreViewController.swift; sourceTree = "<group>"; };
34480B341FD0929200BC14EF /* ShareAppExtensionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareAppExtensionContext.h; sourceTree = "<group>"; };
34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareAppExtensionContext.m; sourceTree = "<group>"; };
34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = "<group>"; };
34480B381FD092E300BC14EF /* SessionShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SessionShareExtension-Prefix.pch"; sourceTree = "<group>"; };
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>"; };
346129971FD1E4D900532771 /* SignalApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalApp.m; sourceTree = "<group>"; };
346129981FD1E4DA00532771 /* SignalApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalApp.h; sourceTree = "<group>"; };
34641E1D2088DA6C00E2EDE5 /* SAEScreenLockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAEScreenLockViewController.h; sourceTree = "<group>"; };
34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAEScreenLockViewController.m; sourceTree = "<group>"; };
34661FB720C1C0D60056EDD6 /* message_sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = message_sent.aiff; path = Session/Meta/AudioFiles/message_sent.aiff; sourceTree = SOURCE_ROOT; };
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
3488F9352191CC4000E524CC /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
@ -1014,8 +1019,11 @@
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProgressView.swift; sourceTree = "<group>"; };
34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = "<group>"; };
34F308A11ECB469700BB7697 /* OWSBezierPathView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBezierPathView.m; sourceTree = "<group>"; };
35D136C9A36A8DB7FD5DBB39 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.app store release.xcconfig"; sourceTree = "<group>"; };
36098A00B2C7DB91D85A4AE3 /* Pods-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Session.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Session/Pods-Session.debug.xcconfig"; sourceTree = "<group>"; };
37A3185C08AE9AE72A9E0922 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
40E4C5D2ABFC1940BEB88BB9 /* Pods-Session.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Session.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-Session/Pods-Session.app store release.xcconfig"; sourceTree = "<group>"; };
432A8B2E3178790E44BE1FE9 /* Pods-GlobalDependencies-Session.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.app store release.xcconfig"; sourceTree = "<group>"; };
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>"; };
@ -1089,11 +1097,12 @@
4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureViewController.swift; sourceTree = "<group>"; };
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateNag.swift; sourceTree = "<group>"; };
4CC613352227A00400E21A3A /* ConversationSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearch.swift; sourceTree = "<group>"; };
53D547348A367C8A14D37FC0 /* Pods_SignalUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5A3F440C6CC32A23AD67A2FD /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
5B7FDA4BA2DDFF4612600FB8 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
5F3070F3395081DD0EB4F933 /* Pods-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalUtilitiesKit/Pods-SignalUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
62ED73E38E0EC8506A9131AD /* Pods_SessionNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
6A26D6558DE69AF455E571C1 /* Pods-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.debug.xcconfig"; sourceTree = "<group>"; };
6A993C279619F78EA9A8FA1D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.debug.xcconfig"; sourceTree = "<group>"; };
6AD66810558DCCC8D6FD14C6 /* Pods-SessionPushNotificationExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionPushNotificationExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionPushNotificationExtension/Pods-SessionPushNotificationExtension.app store release.xcconfig"; sourceTree = "<group>"; };
70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
71CFEDD2D3C54277731012DF /* Pods_SessionUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1102,22 +1111,31 @@
76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
7ABE4694B110C1BBCB0E46A2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = "<group>"; };
7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = "<group>"; };
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
7B7CB18A270591630079FF93 /* ShareLogsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareLogsModal.swift; sourceTree = "<group>"; };
7BA6F47DAD18D44D75B7110F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; sourceTree = "<group>"; };
7BA7F4BA279F9F5800B3A466 /* EmptySearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySearchResultCell.swift; sourceTree = "<group>"; };
7BA7F4BC27A216B600B3A466 /* Storage+RecentSearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+RecentSearchResults.swift"; sourceTree = "<group>"; };
7BA9057D27911C5800998B3C /* GlobalSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchViewController.swift; sourceTree = "<group>"; };
7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = "<group>"; };
7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = "<group>"; };
7D2176E949A052F37AD7C34A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; sourceTree = "<group>"; };
7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
7FE200DAC207C27151388BC4 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
871E24CC4E053248B2C01E96 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
8962372EEC51D3F56FE3A68A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = "<group>"; };
8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = "<group>"; };
9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
948239851C08032C842937CC /* Pods-SignalMessaging.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.test.xcconfig"; sourceTree = "<group>"; };
9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9AE1058A3BB2148A279432B2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; sourceTree = "<group>"; };
9B3329176C10E9640865E65B /* Pods-GlobalDependencies-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.debug.xcconfig"; sourceTree = "<group>"; };
9B533A9FA46206D3D99C9ADA /* Pods-SignalMessaging.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.debug.xcconfig"; sourceTree = "<group>"; };
9C0469AC557930C01552CC83 /* Pods-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalUtilitiesKit/Pods-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
@ -1126,9 +1144,14 @@
A1C32D4F17A06537000A904E /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; };
A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
A5509EC91A69AB8B00ABA4BC /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
A5C037C0D2746ABEE2684E70 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
A6344D429FFAC3B44E6A06FA /* Pods-SessionSnodeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionSnodeKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionSnodeKit/Pods-SessionSnodeKit.debug.xcconfig"; sourceTree = "<group>"; };
A9F14F620D87A5BA98DDB608 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
AD2AB1207E8888E4262D781B /* Pods-SignalTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.debug.xcconfig"; sourceTree = "<group>"; };
ADF724B347C8815D97258101 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionProtocolKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionProtocolKit/Pods-SessionProtocolKit.debug.xcconfig"; sourceTree = "<group>"; };
B0085B6ECC397FF1A45C78BF /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = "<group>"; };
B07145B99769E0ABAA990514 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = "<group>"; };
B27A64C349BBE85670300948 /* Pods-SessionShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.app store release.xcconfig"; sourceTree = "<group>"; };
B60EDE031A05A01700D73516 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
B646D10E1AA5461A004133BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1143,6 +1166,7 @@
B69CD25019773E79005CE69A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
B6B226961BE4B7D200860F4D /* ContactsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ContactsUI.framework; path = System/Library/Frameworks/ContactsUI.framework; sourceTree = SDKROOT; };
B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; };
B7BFCBEA5C557836D76275D5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; sourceTree = "<group>"; };
B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = "<group>"; };
B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = "<group>"; };
B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = "<group>"; };
@ -1196,6 +1220,7 @@
B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = "<group>"; };
B87EF17026367CF800124B3C /* FileServerAPIV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerAPIV2.swift; sourceTree = "<group>"; };
B87EF18026377A1D00124B3C /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; };
B883F8C5D6D90C5077B2FD14 /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B8856D5F256F129B001CE70E /* OWSAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAlerts.swift; sourceTree = "<group>"; };
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = "<group>"; };
B886B4A62398B23E00211ABE /* QRCodeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeVC.swift; sourceTree = "<group>"; };
@ -1751,9 +1776,9 @@
C3F0A5EB255C970D007BE2A3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Shared.swift"; sourceTree = "<group>"; };
C3F0A607255C98A6007BE2A3 /* Storage+SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SnodeAPI.swift"; sourceTree = "<group>"; };
C5060C3B36A848B71CCE4685 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C88965DE4F4EC4FC919BEC4E /* Pods-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.debug.xcconfig"; sourceTree = "<group>"; };
C98441E849C3CA7FE8220D33 /* Pods-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionNotificationServiceExtension/Pods-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = "<group>"; };
CB3724C70247A916D43271FE /* Pods_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
D221A089169C9E5E00537ABF /* Session.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Session.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1768,7 +1793,9 @@
D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
DE2DD605305BC6EFAD731723 /* Pods-Signal.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.debug.xcconfig"; sourceTree = "<group>"; };
DF728B4B438716EAF95CEC18 /* Pods-Signal.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.app store release.xcconfig"; sourceTree = "<group>"; };
E19F30497676B0FA3553CCE6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E631A7167783FA9D1FFBC453 /* Pods-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionNotificationServiceExtension/Pods-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
E7E2FBF1546840C91B7E4879 /* Pods-SessionUtilities.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilities.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilities/Pods-SessionUtilities.debug.xcconfig"; sourceTree = "<group>"; };
E85DB184824BA9DC302EC8B3 /* Pods-SignalTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.app store release.xcconfig"; sourceTree = "<group>"; };
@ -1777,9 +1804,16 @@
F121FB43E2A1C1CF7F2AFC23 /* Pods-SessionPushNotificationExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionPushNotificationExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionPushNotificationExtension/Pods-SessionPushNotificationExtension.debug.xcconfig"; sourceTree = "<group>"; };
F62ECF7B8AF4F8089AA705B3 /* Pods-LokiPushNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LokiPushNotificationService.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LokiPushNotificationService/Pods-LokiPushNotificationService.debug.xcconfig"; sourceTree = "<group>"; };
F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
FB523C549815DE935E98151E /* Pods_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = "<group>"; };
FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppExtensionContext.swift; sourceTree = "<group>"; };
FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = "<group>"; };
FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -1793,7 +1827,7 @@
C3D90A5C25773A25002C9DF5 /* SessionUtilitiesKit.framework in Frameworks */,
C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */,
B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */,
3681EBBAC430992520DBD9AC /* Pods_SessionShareExtension.framework in Frameworks */,
C193959302ABEA1B4B1CDAFC /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1805,7 +1839,7 @@
B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */,
B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */,
C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */,
75A5E31037A6F0E5677F3B5C /* Pods_SessionNotificationServiceExtension.framework in Frameworks */,
F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1825,7 +1859,7 @@
C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */,
C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */,
C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */,
B3E0C9C6F1633B1ABCE5AD0B /* Pods_SignalUtilitiesKit.framework in Frameworks */,
D48CEFD2222D323FEFEFC6CC /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1833,8 +1867,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */,
C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */,
29343CA386C1F4E9DAEFB602 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1842,7 +1876,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */,
8DDB50527360BA38AE415C6F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1850,8 +1884,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5DF9AB212C6DB1E8BE70EFF6 /* Pods_SessionMessagingKit.framework in Frameworks */,
C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */,
9B0A583E9B89FEF0916B793A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1890,7 +1924,7 @@
D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */,
D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */,
D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */,
FAD6392E0205566D11AA9E48 /* Pods_Session.framework in Frameworks */,
19329AE141BF804420343B92 /* Pods_GlobalDependencies_Session.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1971,10 +2005,8 @@
children = (
C31C21A4255BCA4800EC2D66 /* Meta */,
4535186C1FC635DD00210559 /* MainInterface.storyboard */,
34641E1D2088DA6C00E2EDE5 /* SAEScreenLockViewController.h */,
34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */,
34480B341FD0929200BC14EF /* ShareAppExtensionContext.h */,
34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */,
FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */,
FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */,
C3ADC66026426688005F1414 /* ShareVC.swift */,
B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */,
B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */,
@ -2021,6 +2053,16 @@
path = Utilities;
sourceTree = "<group>";
};
7BA7F4B9279F9F3700B3A466 /* GlobalSearch */ = {
isa = PBXGroup;
children = (
7BA9057D27911C5800998B3C /* GlobalSearchViewController.swift */,
7BA7F4BA279F9F5800B3A466 /* EmptySearchResultCell.swift */,
7BA7F4BC27A216B600B3A466 /* Storage+RecentSearchResults.swift */,
);
path = GlobalSearch;
sourceTree = "<group>";
};
7BC01A3C241F40AB00BC7C55 /* SessionNotificationServiceExtension */ = {
isa = PBXGroup;
children = (
@ -2070,6 +2112,28 @@
B27A64C349BBE85670300948 /* Pods-SessionShareExtension.app store release.xcconfig */,
E631A7167783FA9D1FFBC453 /* Pods-SessionNotificationServiceExtension.debug.xcconfig */,
C98441E849C3CA7FE8220D33 /* Pods-SessionNotificationServiceExtension.app store release.xcconfig */,
6A993C279619F78EA9A8FA1D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.debug.xcconfig */,
7FE200DAC207C27151388BC4 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionMessagingKit.app store release.xcconfig */,
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */,
B07145B99769E0ABAA990514 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */,
A9F14F620D87A5BA98DDB608 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.debug.xcconfig */,
35D136C9A36A8DB7FD5DBB39 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionShareExtension.app store release.xcconfig */,
7BA6F47DAD18D44D75B7110F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */,
7D2176E949A052F37AD7C34A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */,
5A3F440C6CC32A23AD67A2FD /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.debug.xcconfig */,
B0085B6ECC397FF1A45C78BF /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionUtilitiesKit.app store release.xcconfig */,
A5C037C0D2746ABEE2684E70 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.debug.xcconfig */,
ADF724B347C8815D97258101 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SignalUtilitiesKit.app store release.xcconfig */,
9B3329176C10E9640865E65B /* Pods-GlobalDependencies-Session.debug.xcconfig */,
432A8B2E3178790E44BE1FE9 /* Pods-GlobalDependencies-Session.app store release.xcconfig */,
9AE1058A3BB2148A279432B2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */,
0D3D13FEE4FF6A2E2ED85322 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */,
871E24CC4E053248B2C01E96 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */,
B7BFCBEA5C557836D76275D5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */,
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */,
37A3185C08AE9AE72A9E0922 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */,
5B7FDA4BA2DDFF4612600FB8 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */,
7ABE4694B110C1BBCB0E46A2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@ -2282,8 +2346,11 @@
C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */,
C33FDB3F255A580C00E217F9 /* String+SSK.swift */,
C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */,
FD705A8D278CE29800F16121 /* String+Localization.swift */,
C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */,
C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */,
FD705A91278D051200F16121 /* ReusableView.swift */,
FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */,
C38EF23D255B6D66007E1867 /* UIView+OWS.h */,
C38EF23E255B6D66007E1867 /* UIView+OWS.m */,
C38EF2EF255B6DBB007E1867 /* Weak.swift */,
@ -2757,6 +2824,7 @@
children = (
B8BB82A4238F627000BA5194 /* HomeVC.swift */,
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */,
7BA7F4B9279F9F3700B3A466 /* GlobalSearch */,
);
path = Home;
sourceTree = "<group>";
@ -3316,6 +3384,7 @@
C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */,
C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */,
C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */,
FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */,
C38EF30A255B6DBE007E1867 /* UIUtil.h */,
C38EF300255B6DBD007E1867 /* UIUtil.m */,
C38EF239255B6D66007E1867 /* UIFont+OWS.h */,
@ -3500,15 +3569,15 @@
D221A091169C9E5E00537ABF /* CoreGraphics.framework */,
748A5CAEDD7C919FC64C6807 /* Pods_SignalTests.framework */,
264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */,
9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */,
FB523C549815DE935E98151E /* Pods_SessionMessagingKit.framework */,
2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */,
9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */,
71CFEDD2D3C54277731012DF /* Pods_SessionUIKit.framework */,
53D547348A367C8A14D37FC0 /* Pods_SignalUtilitiesKit.framework */,
CB3724C70247A916D43271FE /* Pods_Session.framework */,
200605FD180CB8B89F566B41 /* Pods_SessionShareExtension.framework */,
62ED73E38E0EC8506A9131AD /* Pods_SessionNotificationServiceExtension.framework */,
E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */,
E19F30497676B0FA3553CCE6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */,
B883F8C5D6D90C5077B2FD14 /* Pods_GlobalDependencies_Session.framework */,
278EF43EB1E6A0B83C9234F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */,
038A3BABD5BA0CE41D8C17F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */,
C5060C3B36A848B71CCE4685 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */,
8962372EEC51D3F56FE3A68A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -4148,7 +4217,7 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Session-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-Session-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4166,7 +4235,7 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SessionShareExtension-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4188,7 +4257,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SessionNotificationServiceExtension-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4201,15 +4270,15 @@
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Session/Pods-Session-frameworks-${CONFIGURATION}-input-files.xcfilelist",
"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Session/Pods-Session-frameworks-${CONFIGURATION}-output-files.xcfilelist",
"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Session/Pods-Session-frameworks.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
5BEA71AEF5E31390FEFA2E99 /* [CP] Check Pods Manifest.lock */ = {
@ -4227,7 +4296,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SessionMessagingKit-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4249,7 +4318,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SignalUtilitiesKit-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4271,7 +4340,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SessionUtilitiesKit-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4293,7 +4362,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SessionSnodeKit-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -4330,9 +4399,9 @@
buildActionMask = 2147483647;
files = (
B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */,
34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */,
C3ADC66126426688005F1414 /* ShareVC.swift in Sources */,
34641E1F2088DA6D00E2EDE5 /* SAEScreenLockViewController.m in Sources */,
FD705A90278CEBBC00F16121 /* ShareAppExtensionContext.swift in Sources */,
FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */,
B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -4440,6 +4509,7 @@
C38EF248255B6D67007E1867 /* UIViewController+OWS.m in Sources */,
C38EF272255B6D7A007E1867 /* OWSResaveCollectionDBMigration.m in Sources */,
C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */,
FD705A98278E9F4D00F16121 /* UIColor+Extensions.swift in Sources */,
C38EF276255B6D7A007E1867 /* OWSDatabaseMigration.m in Sources */,
C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */,
C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */,
@ -4549,6 +4619,7 @@
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */,
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */,
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */,
C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */,
C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */,
C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */,
@ -4561,6 +4632,7 @@
C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */,
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */,
C352A3A62557B60D00338F3E /* TSRequest.m in Sources */,
FD705A92278D051200F16121 /* ReusableView.swift in Sources */,
B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */,
B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */,
C3471ED42555386B00297E91 /* AESGCM.swift in Sources */,
@ -4584,6 +4656,7 @@
C300A60D2554B31900555489 /* Logging.swift in Sources */,
B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */,
C3D9E35525675EE10040E4F3 /* MIMETypeUtil.m in Sources */,
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */,
C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */,
C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */,
);
@ -4811,6 +4884,7 @@
B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */,
C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */,
B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */,
7BA9057E27911C5800998B3C /* GlobalSearchViewController.swift in Sources */,
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
@ -4868,6 +4942,7 @@
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */,
C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */,
7BA7F4BB279F9F5800B3A466 /* EmptySearchResultCell.swift in Sources */,
C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */,
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */,
C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */,
@ -4895,6 +4970,7 @@
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */,
B90418E6183E9DD40038554A /* DateUtil.m in Sources */,
C33100092558FF6D00070591 /* UserCell.swift in Sources */,
7BA7F4BD27A216B600B3A466 /* Storage+RecentSearchResults.swift in Sources */,
B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */,
C374EEE225DA26740073A857 /* LinkPreviewModal.swift in Sources */,
3496956F21A301A100DCFE74 /* OWSBackupLazyRestore.swift in Sources */,
@ -5030,7 +5106,7 @@
/* Begin XCBuildConfiguration section */
453518731FC635DD00210559 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */;
baseConfigurationReference = 871E24CC4E053248B2C01E96 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -5049,7 +5125,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 316;
CURRENT_PROJECT_VERSION = 319;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5074,7 +5150,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.11.20;
MARKETING_VERSION = 1.11.21;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -5089,7 +5165,7 @@
};
453518751FC635DD00210559 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B27A64C349BBE85670300948 /* Pods-SessionShareExtension.app store release.xcconfig */;
baseConfigurationReference = B7BFCBEA5C557836D76275D5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -5122,7 +5198,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 316;
CURRENT_PROJECT_VERSION = 319;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5152,7 +5228,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.11.20;
MARKETING_VERSION = 1.11.21;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -5169,7 +5245,7 @@
};
7BC01A43241F40AB00BC7C55 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E631A7167783FA9D1FFBC453 /* Pods-SessionNotificationServiceExtension.debug.xcconfig */;
baseConfigurationReference = FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -5188,7 +5264,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 316;
CURRENT_PROJECT_VERSION = 319;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5211,7 +5287,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.11.20;
MARKETING_VERSION = 1.11.21;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -5226,7 +5302,7 @@
};
7BC01A44241F40AB00BC7C55 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C98441E849C3CA7FE8220D33 /* Pods-SessionNotificationServiceExtension.app store release.xcconfig */;
baseConfigurationReference = B07145B99769E0ABAA990514 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -5262,7 +5338,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 316;
CURRENT_PROJECT_VERSION = 319;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO;
@ -5290,7 +5366,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.11.20;
MARKETING_VERSION = 1.11.21;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -5445,7 +5521,7 @@
};
C33FD9B4255A548A00E217F9 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5F3070F3395081DD0EB4F933 /* Pods-SignalUtilitiesKit.debug.xcconfig */;
baseConfigurationReference = 5B7FDA4BA2DDFF4612600FB8 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
@ -5511,7 +5587,7 @@
};
C33FD9B5255A548A00E217F9 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9C0469AC557930C01552CC83 /* Pods-SignalUtilitiesKit.app store release.xcconfig */;
baseConfigurationReference = 7ABE4694B110C1BBCB0E46A2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
@ -5600,7 +5676,7 @@
};
C3C2A5A8255385C100C340D1 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A6344D429FFAC3B44E6A06FA /* Pods-SessionSnodeKit.debug.xcconfig */;
baseConfigurationReference = 7BA6F47DAD18D44D75B7110F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
@ -5658,7 +5734,7 @@
};
C3C2A5A9255385C100C340D1 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C022DD8E076866C6241610BF /* Pods-SessionSnodeKit.app store release.xcconfig */;
baseConfigurationReference = 7D2176E949A052F37AD7C34A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
@ -5739,7 +5815,7 @@
};
C3C2A682255388CC00C340D1 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 264033E641846B67E0CB21B0 /* Pods-SessionUtilitiesKit.debug.xcconfig */;
baseConfigurationReference = FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
@ -5806,7 +5882,7 @@
};
C3C2A683255388CC00C340D1 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */;
baseConfigurationReference = 37A3185C08AE9AE72A9E0922 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
@ -5896,7 +5972,7 @@
};
C3C2A6FA25539DE700C340D1 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6A26D6558DE69AF455E571C1 /* Pods-SessionMessagingKit.debug.xcconfig */;
baseConfigurationReference = 9AE1058A3BB2148A279432B2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_NONNULL = YES;
@ -5954,7 +6030,7 @@
};
C3C2A6FB25539DE700C340D1 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */;
baseConfigurationReference = 0D3D13FEE4FF6A2E2ED85322 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
@ -6063,7 +6139,7 @@
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/ThirdParty/Carthage/Build/iOS";
FRAMEWORK_SEARCH_PATHS = "";
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@ -6138,7 +6214,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/ThirdParty/Carthage/Build/iOS";
FRAMEWORK_SEARCH_PATHS = "";
GCC_NO_COMMON_BLOCKS = YES;
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
@ -6184,7 +6260,7 @@
};
D221A0BD169C9E5F00537ABF /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 36098A00B2C7DB91D85A4AE3 /* Pods-Session.debug.xcconfig */;
baseConfigurationReference = 9B3329176C10E9640865E65B /* Pods-GlobalDependencies-Session.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -6198,7 +6274,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 316;
CURRENT_PROJECT_VERSION = 319;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6237,7 +6313,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 1.11.20;
MARKETING_VERSION = 1.11.21;
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -6257,7 +6333,7 @@
};
D221A0BE169C9E5F00537ABF /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 40E4C5D2ABFC1940BEB88BB9 /* Pods-Session.app store release.xcconfig */;
baseConfigurationReference = 432A8B2E3178790E44BE1FE9 /* Pods-GlobalDependencies-Session.app store release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -6269,7 +6345,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 316;
CURRENT_PROJECT_VERSION = 319;
DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6308,7 +6384,7 @@
"$(SRCROOT)",
);
LLVM_LTO = NO;
MARKETING_VERSION = 1.11.20;
MARKETING_VERSION = 1.11.21;
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session;

@ -306,7 +306,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
// MARK: Convenience
private func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
}

@ -151,7 +151,7 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
@objc private func createClosedGroup() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
guard let name = nameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), name.count > 0 else {
@ -184,7 +184,7 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
let title = "Couldn't Create Group"
let message = "Please check your internet connection and try again."
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.presentAlert(alert)
}
}

@ -1,6 +1,9 @@
import UIKit
import CoreServices
import Photos
import PhotosUI
import SessionUtilitiesKit
import SignalUtilitiesKit
extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuActionDelegate, ScrollToBottomButtonDelegate,
SendMediaNavDelegate, UIDocumentPickerDelegate, AttachmentApprovalViewControllerDelegate, GifPickerViewControllerDelegate,
@ -79,11 +82,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
}
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], messageText: String?) {
sendAttachments(attachments, with: messageText ?? "")
sendAttachments(attachments, with: messageText ?? "") { [weak self] in
self?.dismiss(animated: true, completion: nil)
}
scrollToBottom(isAnimated: false)
resetMentions()
self.snInputView.text = ""
dismiss(animated: true) { }
}
func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController) {
@ -198,7 +203,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
if !attachment.hasError {
self?.showAttachmentApprovalDialog(for: [ attachment ])
} else {
self?.showErrorAlert(for: attachment)
self?.showErrorAlert(for: attachment, onDismiss: nil)
}
}
}.retainUntilComplete()
@ -248,11 +253,11 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
})
}
func sendAttachments(_ attachments: [SignalAttachment], with text: String) {
func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) {
guard !showBlockedModalIfNeeded() else { return }
for attachment in attachments {
if attachment.hasError {
return showErrorAlert(for: attachment)
return showErrorAlert(for: attachment, onDismiss: onComplete)
}
}
let thread = self.thread
@ -272,6 +277,9 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
self?.scrollToBottom(isAnimated: false)
})
self?.handleMessageSent()
// Attachment successfully sent - dismiss the screen
onComplete?()
})
}
@ -463,7 +471,18 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
let thread = self.thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true {
confirmDownload()
} else {
}
else if (
viewItem.attachmentStream?.isText == true ||
viewItem.attachmentStream?.isMicrosoftDoc == true ||
viewItem.attachmentStream?.contentType == OWSMimeTypeApplicationPdf
), let filePathString: String = viewItem.attachmentStream?.originalFilePath {
let fileUrl: URL = URL(fileURLWithPath: filePathString)
let interactionController: UIDocumentInteractionController = UIDocumentInteractionController(url: fileUrl)
interactionController.delegate = self
interactionController.presentPreview(animated: true)
}
else {
// Open the document if possible
guard let url = viewItem.attachmentStream?.originalMediaURL else { return }
let shareVC = UIActivityViewController(activityItems: [ url ], applicationActivities: nil)
@ -849,7 +868,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
dataSource.sourceFilename = fileName
let attachment = SignalAttachment.voiceMessageAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4Audio as String)
guard !attachment.hasError else {
return showErrorAlert(for: attachment)
return showErrorAlert(for: attachment, onDismiss: nil)
}
// Send attachment
sendAttachments([ attachment ], with: "")
@ -974,10 +993,22 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
}
}
// MARK: Convenience
func showErrorAlert(for attachment: SignalAttachment) {
// MARK: - Convenience
func showErrorAlert(for attachment: SignalAttachment, onDismiss: (() -> ())?) {
let title = NSLocalizedString("ATTACHMENT_ERROR_ALERT_TITLE", comment: "")
let message = attachment.localizedErrorDescription ?? SignalAttachment.missingDataErrorMessage
OWSAlerts.showAlert(title: title, message: message)
OWSAlerts.showAlert(title: title, message: message, buttonTitle: nil) { _ in
onDismiss?()
}
}
}
// MARK: - UIDocumentInteractionControllerDelegate
extension ConversationVC: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
}

@ -7,7 +7,8 @@
final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate {
let isUnsendRequestsEnabled = true // Set to true once unsend requests are done on all platforms
let thread: TSThread
let focusedMessageID: String? // This isn't actually used ATM
let focusedMessageID: String? // This is used for global search
var focusedMessageIndexPath: IndexPath?
var unreadViewItems: [ConversationViewItem] = []
var scrollButtonConstraint: NSLayoutConstraint?
// Search
@ -112,7 +113,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
let result = UIView()
result.backgroundColor = Colors.text.withAlphaComponent(Values.veryLowOpacity)
let size = ConversationVC.unreadCountViewSize
result.set(.width, to: size)
result.set(.width, greaterThanOrEqualTo: size)
result.set(.height, to: size)
result.layer.masksToBounds = true
result.layer.cornerRadius = size / 2
@ -192,7 +193,10 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
// Unread count view
view.addSubview(unreadCountView)
unreadCountView.addSubview(unreadCountLabel)
unreadCountLabel.pin(to: unreadCountView)
unreadCountLabel.pin(.top, to: .top, of: unreadCountView)
unreadCountLabel.pin(.bottom, to: .bottom, of: unreadCountView)
unreadCountView.pin(.leading, to: .leading, of: unreadCountLabel, withInset: -4)
unreadCountView.pin(.trailing, to: .trailing, of: unreadCountLabel, withInset: 4)
unreadCountView.centerYAnchor.constraint(equalTo: scrollButton.topAnchor).isActive = true
unreadCountView.center(.horizontal, in: scrollButton)
updateUnreadCountView()
@ -236,13 +240,17 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
// unreadIndicatorIndex is calculated during loading of the viewItems, so it's
// supposed to be accurate.
DispatchQueue.main.async {
let firstUnreadMessageIndex = self.viewModel.viewState.unreadIndicatorIndex?.intValue
?? (self.viewItems.count - self.unreadViewItems.count)
if unreadCount > 0, let viewItem = self.viewItems[ifValid: firstUnreadMessageIndex], let interactionID = viewItem.interaction.uniqueId {
self.scrollToInteraction(with: interactionID, position: .top, isAnimated: false)
self.unreadCountView.alpha = self.scrollButton.alpha
if let focusedMessageID = self.focusedMessageID {
self.scrollToInteraction(with: focusedMessageID, isAnimated: false, highlighted: true)
} else {
self.scrollToBottom(isAnimated: false)
let firstUnreadMessageIndex = self.viewModel.viewState.unreadIndicatorIndex?.intValue
?? (self.viewItems.count - self.unreadViewItems.count)
if unreadCount > 0, let viewItem = self.viewItems[ifValid: firstUnreadMessageIndex], let interactionID = viewItem.interaction.uniqueId {
self.scrollToInteraction(with: interactionID, position: .top, isAnimated: false)
self.unreadCountView.alpha = self.scrollButton.alpha
} else {
self.scrollToBottom(isAnimated: false)
}
}
self.scrollButton.alpha = self.getScrollButtonOpacity()
}
@ -251,6 +259,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
highlightFocusedMessageIfNeeded()
didFinishInitialLayout = true
markAllAsRead()
}
@ -313,6 +322,13 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
}
}
private func highlightFocusedMessageIfNeeded() {
if let indexPath = focusedMessageIndexPath, let cell = messagesTableView.cellForRow(at: indexPath) as? VisibleMessageCell {
cell.highlight()
focusedMessageIndexPath = nil
}
}
@objc func handleKeyboardWillChangeFrameNotification(_ notification: Notification) {
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
if (newHeight > 0 && baselineKeyboardHeight == 0) {
@ -460,6 +476,10 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
func scrollToBottom(isAnimated: Bool) {
guard !isUserScrolling else { return }
if let interactionID = viewItems.last?.interaction.uniqueId {
self.scrollToInteraction(with: interactionID, position: .top, isAnimated: isAnimated)
return
}
// Ensure the view is fully up to date before we try to scroll to the bottom, since
// we use the table view's bounds to determine where the bottom is.
view.layoutIfNeeded()
@ -490,8 +510,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
unreadViewItems.remove(at: index)
}
let unreadCount = unreadViewItems.count
unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+"
let fontSize = (unreadCount < 100) ? Values.verySmallFontSize : 8
unreadCountLabel.text = unreadCount < 10000 ? "\(unreadCount)" : "9999+"
let fontSize = (unreadCount < 10000) ? Values.verySmallFontSize : 8
unreadCountLabel.font = .boldSystemFont(ofSize: fontSize)
unreadCountView.isHidden = (unreadCount == 0)
}
@ -538,6 +558,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
func showSearchUI() {
isShowingSearchUI = true
// Search bar
// FIXME: This code is duplicated with SearchBar
let searchBar = searchController.uiSearchController.searchBar
searchBar.searchBarStyle = .minimal
searchBar.barStyle = .black
@ -616,8 +637,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
scrollToInteraction(with: interactionID)
}
func scrollToInteraction(with interactionID: String, position: UITableView.ScrollPosition = .middle, isAnimated: Bool = true) {
func scrollToInteraction(with interactionID: String, position: UITableView.ScrollPosition = .middle, isAnimated: Bool = true, highlighted: Bool = false) {
guard let indexPath = viewModel.ensureLoadWindowContainsInteractionId(interactionID) else { return }
messagesTableView.scrollToRow(at: indexPath, at: position, animated: isAnimated)
if highlighted {
focusedMessageIndexPath = indexPath
}
}
}

@ -567,13 +567,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
TSAttachment *_Nullable linkPreviewAttachment =
[TSAttachment fetchObjectWithUniqueID:message.linkPreview.imageAttachmentId transaction:transaction];
if (!linkPreviewAttachment) {
OWSFailDebug(@"Could not load link preview image attachment.");
OWSLogDebug(@"Could not load link preview image attachment.");
} else if (!linkPreviewAttachment.isImage) {
OWSFailDebug(@"Link preview attachment isn't an image.");
OWSLogDebug(@"Link preview attachment isn't an image.");
} else if ([linkPreviewAttachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)linkPreviewAttachment;
if (!attachmentStream.isValidImage) {
OWSFailDebug(@"Link preview image attachment isn't valid.");
OWSLogDebug(@"Link preview image attachment isn't valid.");
} else {
self.linkPreviewAttachment = linkPreviewAttachment;
}

@ -56,7 +56,7 @@ final class MediaTextOverlayView : UIView {
self.readMoreButton = readMoreButton
readMoreButton.setTitle("Read More", for: UIControl.State.normal)
readMoreButton.titleLabel!.font = .boldSystemFont(ofSize: Values.smallFontSize)
readMoreButton.setTitleColor(.white, for: UIControl.State.normal)
readMoreButton.setTitleColor(self.textColor, for: UIControl.State.normal)
readMoreButton.addTarget(self, action: #selector(readMore), for: UIControl.Event.touchUpInside)
addSubview(readMoreButton)
readMoreButton.pin(.left, to: .left, of: self, withInset: inset)

@ -65,7 +65,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
lazy var bubbleView: UIView = {
let result = UIView()
result.layer.cornerRadius = VisibleMessageCell.smallCornerRadius
result.layer.cornerRadius = VisibleMessageCell.largeCornerRadius
return result
}()
@ -131,6 +131,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
private var bodyLabelTextColor: UIColor {
switch (direction, AppModeManager.shared.currentAppMode) {
case (.outgoing, .dark), (.incoming, .light): return .black
case (.outgoing, .light): return Colors.grey
default: return .white
}
}
@ -207,8 +208,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
// MARK: Updating
override func update() {
guard let viewItem = viewItem, let message = viewItem.interaction as? TSMessage else { return }
let thread = message.thread
let isGroupThread = thread.isGroupThread()
let isGroupThread = viewItem.isGroupThread
// Profile picture view
profilePictureViewLeftConstraint.constant = isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0
profilePictureViewWidthConstraint.constant = isGroupThread ? VisibleMessageCell.profilePictureSize : 0
@ -217,8 +217,8 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
if let senderSessionID = senderSessionID {
profilePictureView.update(for: senderSessionID)
}
if let thread = thread as? TSGroupThread, thread.isOpenGroup, let senderSessionID = senderSessionID {
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) {
if let senderSessionID = senderSessionID, message.isOpenGroupMessage {
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: message.uniqueThreadId) {
let isUserModerator = OpenGroupAPIV2.isUserModerator(senderSessionID, for: openGroupV2.room, on: openGroupV2.server)
moderatorIconImageView.isHidden = !isUserModerator || profilePictureView.isHidden
} else {
@ -431,10 +431,12 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
}
private func updateBubbleViewCorners() {
let maskPath = UIBezierPath(roundedRect: bubbleView.bounds, byRoundingCorners: getCornersToRound(),
let cornersToRound = getCornersToRound()
let maskPath = UIBezierPath(roundedRect: bubbleView.bounds, byRoundingCorners: cornersToRound,
cornerRadii: CGSize(width: VisibleMessageCell.largeCornerRadius, height: VisibleMessageCell.largeCornerRadius))
bubbleViewMaskLayer.path = maskPath.cgPath
bubbleView.layer.mask = bubbleViewMaskLayer
bubbleView.layer.cornerRadius = VisibleMessageCell.largeCornerRadius
bubbleView.layer.maskedCorners = getCornerMask(from: cornersToRound)
}
override func prepareForReuse() {
@ -470,6 +472,18 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
return abs(v.x) > abs(v.y) // It has to be more horizontal than vertical
} else {
return true
}
}
func highlight() {
let shawdowColour = isLightMode ? UIColor.black.cgColor : Colors.accent.cgColor
let opacity : Float = isLightMode ? 0.5 : 1
bubbleView.setShadow(radius: 10, opacity: opacity, offset: .zero, color: shawdowColour)
DispatchQueue.main.async {
UIView.animate(withDuration: 1.6) {
self.bubbleView.setShadow(radius: 0, opacity: 0, offset: .zero, color: UIColor.clear.cgColor)
}
}
}
@ -571,6 +585,19 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
return result
}
private func getCornerMask(from rectCorner: UIRectCorner) -> CACornerMask {
var cornerMask = CACornerMask()
if rectCorner.contains(.allCorners) {
cornerMask = [ .layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
} else {
if rectCorner.contains(.topRight) { cornerMask.insert(.layerMaxXMinYCorner) }
if rectCorner.contains(.topLeft) { cornerMask.insert(.layerMinXMinYCorner) }
if rectCorner.contains(.bottomRight) { cornerMask.insert(.layerMaxXMaxYCorner) }
if rectCorner.contains(.bottomLeft) { cornerMask.insert(.layerMinXMaxYCorner) }
}
return cornerMask
}
private static func getFontSize(for viewItem: ConversationViewItem) -> CGFloat {
let baselineFontSize = Values.mediumFontSize
switch viewItem.displayableBodyText?.jumbomojiCount {
@ -612,8 +639,15 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
let maxAspectRatio = 1 / minAspectRatio
aspectRatio = aspectRatio.clamp(minAspectRatio, maxAspectRatio)
let maxSize = CGSize(width: maxMessageWidth, height: maxMessageWidth)
var width = with(maxSize.height * aspectRatio) { $0 > maxSize.width ? maxSize.width : $0 }
var height = (width > maxSize.width) ? (maxSize.width / aspectRatio) : maxSize.height
var width: CGFloat
var height: CGFloat
if aspectRatio > 1 {
width = maxSize.width
height = width / aspectRatio
} else {
height = maxSize.height
width = height * aspectRatio
}
// Don't blow up small images unnecessarily
let minSize: CGFloat = 150
let shortSourceDimension = min(size.width, size.height)

@ -65,7 +65,7 @@ final class JoinOpenGroupModal : Modal {
@objc private func joinOpenGroup() {
guard let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: url) else {
let alert = UIAlertController(title: "Couldn't Join", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
return presentingViewController!.present(alert, animated: true, completion: nil)
}
presentingViewController!.dismiss(animated: true, completion: nil)
@ -77,7 +77,7 @@ final class JoinOpenGroupModal : Modal {
}
.catch(on: DispatchQueue.main) { error in
let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentingViewController.present(alert, animated: true, completion: nil)
}
}

@ -158,7 +158,7 @@ final class NewDMVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControll
}
let message = messageOrNil ?? "Please check the Session ID or ONS name and try again"
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.presentAlert(alert)
}
}

@ -0,0 +1,59 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import NVActivityIndicatorView
class EmptySearchResultCell: UITableViewCell {
static let reuseIdentifier = "EmptySearchResultCell"
private lazy var messageLabel: UILabel = {
let result = UILabel()
result.textAlignment = .center
result.numberOfLines = 3
result.textColor = Colors.text
result.text = NSLocalizedString("CONVERSATION_SEARCH_NO_RESULTS", comment: "")
return result
}()
private lazy var spinner: NVActivityIndicatorView = {
let result = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
result.set(.width, to: 40)
result.set(.height, to: 40)
return result
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
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()
contentView.addSubview(spinner)
spinner.autoCenterInSuperview()
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
public func configure(isLoading: Bool) {
if isLoading {
// Calling stopAnimating() here is a workaround for
// the spinner won't change its colour as the theme changed.
spinner.stopAnimating()
spinner.startAnimating()
messageLabel.isHidden = true
} else {
spinner.stopAnimating()
messageLabel.isHidden = false
}
}
}

@ -0,0 +1,380 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
@objc
class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSource {
let isRecentSearchResultsEnabled = false
@objc public var searchText = "" {
didSet {
AssertIsOnMainThread()
// Use a slight delay to debounce updates.
refreshSearchResults()
}
}
var recentSearchResults: [String] = Array(Storage.shared.getRecentSearchResults().reversed())
var searchResultSet: HomeScreenSearchResultSet = HomeScreenSearchResultSet.empty
private var lastSearchText: String?
var searcher: FullTextSearcher {
return FullTextSearcher.shared
}
var isLoading = false
enum SearchSection: Int {
case noResults
case contacts
case messages
case recent
}
// MARK: UI Components
internal lazy var searchBar: SearchBar = {
let result = SearchBar()
result.tintColor = Colors.text
result.delegate = self
result.showsCancelButton = true
return result
}()
internal lazy var tableView: UITableView = {
let result = UITableView(frame: .zero, style: .grouped)
result.rowHeight = UITableView.automaticDimension
result.estimatedRowHeight = 60
result.separatorStyle = .none
result.keyboardDismissMode = .onDrag
result.register(EmptySearchResultCell.self, forCellReuseIdentifier: EmptySearchResultCell.reuseIdentifier)
result.register(ConversationCell.self, forCellReuseIdentifier: ConversationCell.reuseIdentifier)
result.showsVerticalScrollIndicator = false
return result
}()
// MARK: Dependencies
var dbReadConnection: YapDatabaseConnection {
return OWSPrimaryStorage.shared().dbReadConnection
}
// MARK: View Lifecycle
public override func viewDidLoad() {
super.viewDidLoad()
setUpGradientBackground()
tableView.dataSource = self
tableView.delegate = self
view.addSubview(tableView)
tableView.pin(.leading, to: .leading, of: view)
tableView.pin(.top, to: .top, of: view, withInset: Values.smallSpacing)
tableView.pin(.trailing, to: .trailing, of: view)
tableView.pin(.bottom, to: .bottom, of: view)
navigationItem.hidesBackButton = true
setupNavigationBar()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
searchBar.becomeFirstResponder()
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
searchBar.resignFirstResponder()
}
private func setupNavigationBar() {
// This is a workaround for a UI issue that the navigation bar can be a bit higher if
// the search bar is put directly to be the titleView. And this can cause the tableView
// in home screen doing a weird scrolling when going back to home screen.
let searchBarContainer = UIView()
searchBarContainer.layoutMargins = UIEdgeInsets.zero
searchBar.sizeToFit()
searchBar.layoutMargins = UIEdgeInsets.zero
searchBarContainer.set(.height, to: 44)
searchBarContainer.set(.width, to: UIScreen.main.bounds.width - 32)
searchBarContainer.addSubview(searchBar)
searchBar.autoPinEdgesToSuperviewMargins()
navigationItem.titleView = searchBarContainer
}
private func reloadTableData() {
tableView.reloadData()
}
// MARK: Update Search Results
var refreshTimer: Timer?
private func refreshSearchResults() {
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 self = self else {
return
}
self.updateSearchResults(searchText: self.searchText)
self.refreshTimer = nil
}
}
private func updateSearchResults(searchText rawSearchText: String) {
let searchText = rawSearchText.stripped
guard searchText.count > 0 else {
searchResultSet = HomeScreenSearchResultSet.noteToSelfOnly
lastSearchText = nil
reloadTableData()
return
}
guard lastSearchText != searchText else { return }
lastSearchText = searchText
var searchResults: HomeScreenSearchResultSet?
self.dbReadConnection.asyncRead({[weak self] transaction in
guard let self = self else { return }
self.isLoading = true
// The max search result count is set according to the keyword length. This is just a workaround for performance issue.
// The longer and more accurate the keyword is, the less search results should there be.
searchResults = self.searcher.searchForHomeScreen(searchText: searchText, maxSearchResults: min(searchText.count * 50, 500), transaction: transaction)
}, completionBlock: { [weak self] in
AssertIsOnMainThread()
guard let self = self, let results = searchResults, self.lastSearchText == searchText else { return }
self.searchResultSet = results
self.isLoading = false
self.reloadTableData()
})
}
// MARK: Interaction
@objc func clearRecentSearchResults() {
recentSearchResults = []
tableView.reloadSections([ SearchSection.recent.rawValue ], with: .top)
Storage.shared.clearRecentSearchResults()
}
}
// MARK: - UISearchBarDelegate
extension GlobalSearchViewController: UISearchBarDelegate {
public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.updateSearchText()
}
public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
self.updateSearchText()
}
public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.updateSearchText()
}
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
}
func updateSearchText() {
guard let searchText = searchBar.text?.ows_stripped() else { return }
self.searchText = searchText
}
}
// MARK: - UITableViewDelegate & UITableViewDataSource
extension GlobalSearchViewController {
// MARK: UITableViewDelegate
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
guard let searchSection = SearchSection(rawValue: indexPath.section) else { return }
switch searchSection {
case .noResults:
SNLog("shouldn't be able to tap 'no results' section")
case .contacts:
let sectionResults = searchResultSet.conversations
guard let searchResult = sectionResults[safe: indexPath.row], let threadId = searchResult.thread.threadRecord.uniqueId, let thread = TSThread.fetch(uniqueId: threadId) else { return }
show(thread, highlightedMessageID: nil, animated: true)
case .messages:
let sectionResults = searchResultSet.messages
guard let searchResult = sectionResults[safe: indexPath.row], let threadId = searchResult.thread.threadRecord.uniqueId, let thread = TSThread.fetch(uniqueId: threadId) else { return }
show(thread, highlightedMessageID: searchResult.messageId, animated: true)
case .recent:
guard let threadId = recentSearchResults[safe: indexPath.row], let thread = TSThread.fetch(uniqueId: threadId) else { return }
show(thread, highlightedMessageID: nil, animated: true, isFromRecent: true)
}
}
private func show(_ thread: TSThread, highlightedMessageID: String?, animated: Bool, isFromRecent: Bool = false) {
if let threadId = thread.uniqueId {
recentSearchResults = Array(Storage.shared.addSearchResults(threadID: threadId).reversed())
}
DispatchMainThreadSafe {
if let presentedVC = self.presentedViewController {
presentedVC.dismiss(animated: false, completion: nil)
}
let conversationVC = ConversationVC(thread: thread, focusedMessageID: highlightedMessageID)
var viewControllers = self.navigationController?.viewControllers
if isFromRecent, let index = viewControllers?.firstIndex(of: self) { viewControllers?.remove(at: index) }
viewControllers?.append(conversationVC)
self.navigationController?.setViewControllers(viewControllers!, animated: true)
}
}
// MARK: UITableViewDataSource
public func numberOfSections(in tableView: UITableView) -> Int {
return 4
}
public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
UIView()
}
public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
.leastNonzeroMagnitude
}
public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard nil != self.tableView(tableView, titleForHeaderInSection: section) else {
return .leastNonzeroMagnitude
}
return UITableView.automaticDimension
}
public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let searchSection = SearchSection(rawValue: section) else { return nil }
guard let title = self.tableView(tableView, titleForHeaderInSection: section) else {
return UIView()
}
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
let container = UIView()
container.backgroundColor = Colors.cellBackground
container.layoutMargins = UIEdgeInsets(top: Values.smallSpacing, left: Values.mediumSpacing, bottom: Values.smallSpacing, right: Values.mediumSpacing)
container.addSubview(titleLabel)
titleLabel.autoPinEdgesToSuperviewMargins()
if searchSection == .recent {
let clearButton = UIButton()
clearButton.setTitle("Clear", for: .normal)
clearButton.setTitleColor(Colors.text, for: UIControl.State.normal)
clearButton.titleLabel!.font = .boldSystemFont(ofSize: Values.smallFontSize)
clearButton.addTarget(self, action: #selector(clearRecentSearchResults), for: .touchUpInside)
container.addSubview(clearButton)
clearButton.autoPinTrailingToSuperviewMargin()
clearButton.autoVCenterInSuperview()
}
return container
}
public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let searchSection = SearchSection(rawValue: section) else { return nil }
switch searchSection {
case .noResults:
return nil
case .contacts:
if searchResultSet.conversations.count > 0 {
return NSLocalizedString("SEARCH_SECTION_CONTACTS", comment: "")
} else {
return nil
}
case .messages:
if searchResultSet.messages.count > 0 {
return NSLocalizedString("SEARCH_SECTION_MESSAGES", comment: "")
} else {
return nil
}
case .recent:
if recentSearchResults.count > 0 && searchText.isEmpty && isRecentSearchResultsEnabled {
return NSLocalizedString("SEARCH_SECTION_RECENT", comment: "")
} else {
return nil
}
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let searchSection = SearchSection(rawValue: section) else { return 0 }
switch searchSection {
case .noResults:
return (searchText.count > 0 && searchResultSet.isEmpty) ? 1 : 0
case .contacts:
return searchResultSet.conversations.count
case .messages:
return searchResultSet.messages.count
case .recent:
return searchText.isEmpty && isRecentSearchResultsEnabled ? recentSearchResults.count : 0
}
}
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
public 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, indexPath.row == 0 else { return UITableViewCell() }
cell.configure(isLoading: isLoading)
return cell
case .contacts:
let sectionResults = searchResultSet.conversations
let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell
cell.isShowingGlobalSearchResult = true
let searchResult = sectionResults[safe: indexPath.row]
cell.threadViewModel = searchResult?.thread
cell.configure(messageDate: searchResult?.messageDate, snippet: searchResult?.snippet, searchText: searchResultSet.searchText)
return cell
case .messages:
let sectionResults = searchResultSet.messages
let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell
cell.isShowingGlobalSearchResult = true
let searchResult = sectionResults[safe: indexPath.row]
cell.threadViewModel = searchResult?.thread
var message: TSMessage? = nil
if let messageId = searchResult?.messageId { message = TSMessage.fetch(uniqueId: messageId) }
cell.configure(messageDate: searchResult?.messageDate, snippet: searchResult?.snippet, searchText: searchResultSet.searchText, message: message)
return cell
case .recent:
let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell
cell.isShowingGlobalSearchResult = true
dbReadConnection.read { transaction in
guard let threadId = self.recentSearchResults[safe: indexPath.row], let thread = TSThread.fetch(uniqueId: threadId, transaction: transaction) else { return }
cell.threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
}
cell.configureForRecent()
return cell
}
}
}

@ -0,0 +1,32 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
extension Storage{
private static let recentSearchResultDatabaseCollection = "RecentSearchResultDatabaseCollection"
private static let recentSearchResultKey = "RecentSearchResult"
public func getRecentSearchResults() -> [String] {
var result: [String]?
Storage.read { transaction in
result = transaction.object(forKey: Storage.recentSearchResultKey, inCollection: Storage.recentSearchResultDatabaseCollection) as? [String]
}
return result ?? []
}
public func clearRecentSearchResults() {
Storage.write { transaction in
transaction.removeObject(forKey: Storage.recentSearchResultKey, inCollection: Storage.recentSearchResultDatabaseCollection)
}
}
public func addSearchResults(threadID: String) -> [String] {
var recentSearchResults = getRecentSearchResults()
if recentSearchResults.count > 20 { recentSearchResults.remove(at: 0) } // Limit the size of the collection to 20
if let index = recentSearchResults.firstIndex(of: threadID) { recentSearchResults.remove(at: index) }
recentSearchResults.append(threadID)
Storage.write { transaction in
transaction.setObject(recentSearchResults, forKey: Storage.recentSearchResultKey, inCollection: Storage.recentSearchResultDatabaseCollection)
}
return recentSearchResults
}
}

@ -2,7 +2,6 @@
// See https://github.com/yapstudios/YapDatabase/wiki/LongLivedReadTransactions and
// https://github.com/yapstudios/YapDatabase/wiki/YapDatabaseModifiedNotification for
// more information on database handling.
final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate {
private var threads: YapDatabaseViewMappings!
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
@ -89,7 +88,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
setUpNavBarStyle()
}
updateNavBarButtons()
setNavBarTitle(NSLocalizedString("vc_home_title", comment: ""))
setUpNavBarSessionHeading()
// Recovery phrase reminder
let hasViewedSeed = UserDefaults.standard[.hasViewedSeed]
if !hasViewedSeed {
@ -266,6 +265,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
}
private func updateNavBarButtons() {
// Profile picture view
let profilePictureSize = Values.verySmallProfilePictureSize
let profilePictureView = ProfilePictureView()
profilePictureView.accessibilityLabel = "Settings button"
@ -276,32 +276,27 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
profilePictureView.set(.height, to: profilePictureSize)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
// Path status indicator
let pathStatusView = PathStatusView()
pathStatusView.accessibilityLabel = "Current onion routing path indicator"
pathStatusView.set(.width, to: PathStatusView.size)
pathStatusView.set(.height, to: PathStatusView.size)
// Container view
let profilePictureViewContainer = UIView()
profilePictureViewContainer.accessibilityLabel = "Settings button"
profilePictureViewContainer.addSubview(profilePictureView)
profilePictureView.pin(.leading, to: .leading, of: profilePictureViewContainer, withInset: 4)
profilePictureView.pin(.top, to: .top, of: profilePictureViewContainer)
profilePictureView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
profilePictureView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
profilePictureView.autoPinEdgesToSuperviewEdges()
profilePictureViewContainer.addSubview(pathStatusView)
pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
// Left bar button item
let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
leftBarButtonItem.accessibilityLabel = "Settings button"
leftBarButtonItem.isAccessibilityElement = true
navigationItem.leftBarButtonItem = leftBarButtonItem
let pathStatusViewContainer = UIView()
pathStatusViewContainer.accessibilityLabel = "Current onion routing path button"
let pathStatusViewContainerSize = Values.verySmallProfilePictureSize // Match the profile picture view
pathStatusViewContainer.set(.width, to: pathStatusViewContainerSize)
pathStatusViewContainer.set(.height, to: pathStatusViewContainerSize)
let pathStatusView = PathStatusView()
pathStatusView.accessibilityLabel = "Current onion routing path button"
pathStatusView.set(.width, to: PathStatusView.size)
pathStatusView.set(.height, to: PathStatusView.size)
pathStatusViewContainer.addSubview(pathStatusView)
pathStatusView.center(.horizontal, in: pathStatusViewContainer)
pathStatusView.center(.vertical, in: pathStatusViewContainer)
pathStatusViewContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showPath)))
let rightBarButtonItem = UIBarButtonItem(customView: pathStatusViewContainer)
rightBarButtonItem.accessibilityLabel = "Current onion routing path button"
// Right bar button item - search button
let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearchUI))
rightBarButtonItem.accessibilityLabel = "Search button"
rightBarButtonItem.isAccessibilityElement = true
navigationItem.rightBarButtonItem = rightBarButtonItem
}
@ -418,10 +413,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
present(navigationController, animated: true, completion: nil)
}
@objc private func showPath() {
let pathVC = PathVC()
let navigationController = OWSNavigationController(rootViewController: pathVC)
present(navigationController, animated: true, completion: nil)
@objc private func showSearchUI() {
if let presentedVC = self.presentedViewController {
presentedVC.dismiss(animated: false, completion: nil)
}
let searchController = GlobalSearchViewController()
self.navigationController?.setViewControllers([ self, searchController ], animated: true)
}
@objc func joinOpenGroup() {

@ -194,7 +194,7 @@ static NSTimeInterval launchStartedAt;
mainWindow.rootViewController = [LoadingViewController new];
[mainWindow makeKeyAndVisible];
LKAppMode appMode = [self getCurrentAppMode];
LKAppMode appMode = [LKAppModeManager getAppModeOrSystemDefault];
[self adaptAppMode:appMode];
if (@available(iOS 11, *)) {
@ -245,7 +245,7 @@ static NSTimeInterval launchStartedAt;
[self ensureRootViewController];
LKAppMode appMode = [self getCurrentAppMode];
LKAppMode appMode = [LKAppModeManager getAppModeOrSystemDefault];
[self adaptAppMode:appMode];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
@ -734,12 +734,6 @@ static NSTimeInterval launchStartedAt;
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.appModeChanged object:nil];
}
- (LKAppMode)getCurrentAppMode
{
LKAppMode appMode = [self getAppModeOrSystemDefault];
return appMode;
}
- (void)setCurrentAppMode:(LKAppMode)appMode
{
[NSUserDefaults.standardUserDefaults setInteger:appMode forKey:@"appMode"];
@ -749,7 +743,7 @@ static NSTimeInterval launchStartedAt;
- (void)setAppModeToSystemDefault
{
[NSUserDefaults.standardUserDefaults removeObjectForKey:@"appMode"];
LKAppMode appMode = [self getCurrentAppMode];
LKAppMode appMode = [LKAppModeManager getAppModeOrSystemDefault];
[self adaptAppMode:appMode];
}

@ -40,20 +40,4 @@ extension AppDelegate {
@objc func stopClosedGroupPoller() {
ClosedGroupPoller.shared.stop()
}
@objc func getAppModeOrSystemDefault() -> AppMode {
let userDefaults = UserDefaults.standard
guard userDefaults.dictionaryRepresentation().keys.contains("appMode") else {
if #available(iOS 13.0, *) {
return UITraitCollection.current.userInterfaceStyle == .dark ? .dark : .light
} else {
return .light
}
}
let mode = userDefaults.integer(forKey: "appMode")
return AppMode(rawValue: mode) ?? .light
}
}

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "heading.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -160,6 +160,10 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
return [UIApplication sharedApplication].applicationState == UIApplicationStateActive;
}
- (BOOL)isShareExtension {
return NO;
}
- (BOOL)isRTL
{
static BOOL isRTL = NO;

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Anhang";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Fehler beim Senden des Anhangs";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Bild kann nicht konvertiert werden.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Video kann nicht verarbeitet werden.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Bild kann nicht geparst werden.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Metadaten können nicht aus dem Bild entfernt werden.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Bildgröße kann nicht geändert werden.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Anhang ist zu groß.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Anhang besitzt ungültigen Inhalt.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Anhang besitzt ein ungültiges Dateiformat.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Anhang ist leer.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Auswählen des Dokuments gescheitert.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Anzeigen";
/* No comment provided by engineer. */
"OK" = "Okay";
"BUTTON_OK" = "Okay";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ hat verschwindende Nachrichten deaktiviert.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Mit Session teilen";
"vc_share_loading_message" = "Anlagen werden vorbereitet...";
"vc_share_sending_message" = "Wird gesendet ...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Gruppeneinladung öffnen";
"vc_conversation_settings_invite_button_title" = "Mitglieder hinzufügen";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Attachment";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Error Sending Attachment";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Show";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Share to Session";
"vc_share_loading_message" = "Preparing attachments...";
"vc_share_sending_message" = "Sending...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Open group invitation";
"vc_conversation_settings_invite_button_title" = "Add Members";
"vc_settings_faq_button_title" = "FAQ";
@ -579,3 +601,9 @@
"light_mode_theme" = "Light";
"PIN_BUTTON_TEXT" = "Pin";
"UNPIN_BUTTON_TEXT" = "Unpin";
"meida_saved" = "Media saved by %@.";
"screenshot_taken" = "%@ took a screenshot.";
"SEARCH_SECTION_CONTACTS" = "Contacts and Groups";
"SEARCH_SECTION_MESSAGES" = "Messages";
"SEARCH_SECTION_RECENT" = "Recent";
"RECENT_SEARCH_LAST_MESSAGE_DATETIME" = "last message: %@";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Adjunto";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Fallo al enviar archivo adjunto";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Bild kann nicht konvertiert werden.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Video kann nicht verarbeitet werden.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Bild kann nicht geparst werden.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Metadaten können nicht aus dem Bild entfernt werden.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Bildgröße kann nicht geändert werden.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Anhang ist zu groß.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Anhang besitzt ungültigen Inhalt.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Anhang besitzt ein ungültiges Dateiformat.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Anhang ist leer.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Fallo al seleccionar documento.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Ver";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha desactivado la desaparición de mensajes.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Compartir en Session";
"vc_share_loading_message" = "Preparando archivos adjuntos...";
"vc_share_sending_message" = "Enviando...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Abrir invitación de grupo";
"vc_conversation_settings_invite_button_title" = "Añadir Miembros";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "ضميمه";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "خطا در ارسال فایل ضمیمه";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "خطا در انتخاب سند.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "نمایش";
/* No comment provided by engineer. */
"OK" = "باشه";
"BUTTON_OK" = "باشه";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ پیام‌های محوشونده را غیرفعال کرده است.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "اشتراک گذاری با Session";
"vc_share_loading_message" = "آماده سازی پیوست‌ها...";
"vc_share_sending_message" = "در حال ارسال...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Open group invitation";
"vc_conversation_settings_invite_button_title" = "Add Members";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Liite";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Virhe liitteen lähettämisessä";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Kuvaa ei voitu muuntaa.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Videon prosessointi ei onnistu.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Kuvaa ei voitu jäsentää.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Metatietojen poistaminen kuvasta ei onnistunut.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Kuvan kokoa ei voitu muuttaa.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Liitetiedosto on liian suuri.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Liitetiedoston sisältö on virheellinen.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Liitetiedoston muoto on virheellinen.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Liitetiedosto on tyhjä.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Dokumentin valinta epäonnistui.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Näytä";
/* No comment provided by engineer. */
"OK" = "Ok";
"BUTTON_OK" = "Ok";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ poisti katoavat viestit käytöstä.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Jaa Sessioniin";
"vc_share_loading_message" = "Valmistellaan liitteitä...";
"vc_share_sending_message" = "Lähetetään...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Avaa ryhmäkutsu";
"vc_conversation_settings_invite_button_title" = "Lisää jäseniä";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Fichier joint";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Erreur denvoi du fichier joint";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Impossible de convertir limage.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Impossible de traiter la vidéo.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Impossible danalyser limage.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Impossible de supprimer les métadonnées de limage.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Impossible de redimensionner limage.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Le fichier joint est trop volumineux.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Le fichier joint comporte du contenu non valide.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Le fichier joint présente un format de fichier invalide.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Le fichier joint est vide.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Échec de sélection du document.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Afficher";
/* No comment provided by engineer. */
"OK" = "Valider";
"BUTTON_OK" = "Valider";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ a désactivé les messages éphémères.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Partager en Session";
"vc_share_loading_message" = "Préparation des pièces jointes ...";
"vc_share_sending_message" = "Envoi...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Invitation à un groupe ouvert";
"vc_conversation_settings_invite_button_title" = "Ajouter des membres";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "अटैचमेंट";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "अनुलग्नक भेजने में त्रुटि";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "दस्तावेज़ चुनने में विफल.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "दिखाएं";
/* No comment provided by engineer. */
"OK" = "ठीक है";
"BUTTON_OK" = "ठीक है";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ने गायब संदेश अक्षम कर दिए हैं।";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "सत्र में साझा करें";
"vc_share_loading_message" = "अटैचमेंट तैयार किए जा रहे हैं...";
"vc_share_sending_message" = "भेजा जा रहा है...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "ग्रुप आमंत्रण खोलें";
"vc_conversation_settings_invite_button_title" = "सदस्य जोड़ें";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Privitak";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Pogreška kod slanja privitka";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Neuspješna pretvorba slike";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Neuspješna obrada videozapisa.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Neuspješna analiza slike.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Neuspješno uklanjanje metapodataka iz slike.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Neuspješna promjena veličine slike.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Privitak je prevelik.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Privitak sadrži nevažeći sadržaj.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Privitak ima nevažeći format datoteke.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Privitak je prazan.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Odabir dokumenta neuspješan.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Prikaži";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ je onemogućio nestajuće poruke.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Podijeli sa Session-om";
"vc_share_loading_message" = "Priprema privitaka...";
"vc_share_sending_message" = "Slanje...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Otvori pozivnicu za grupu";
"vc_conversation_settings_invite_button_title" = "Dodaj članove";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Lampiran";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Gagal Mengirim Lampiran";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Tidak dapat mengonversi gambar.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Video tidak dapat diproses.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Tidak dapat menguraikan gambar.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Tidak dapat menghapus metadata dari gambar.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Tidak dapat mengubah ukuran gambar.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Lampiran terlalu besar.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Lampiran memuat konten yang tidak valid.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Lampiran memiliki format berkas yang tidak valid.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Lampiran kosong.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Gagal memilih dokumen.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Tampilkan";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@menonaktifkan pesan tersembunyi";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Share to Session";
"vc_share_loading_message" = "Preparing attachments...";
"vc_share_sending_message" = "Sending...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Open group invitation";
"vc_conversation_settings_invite_button_title" = "Add Members";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Allegato";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Errore invio allegato";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Nepavyko kovertuoti paveikslo.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Nepavyko apdoroti vaizdo įrašo.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Nepavyko išnagrinėti paveikslo.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Nepavyko pašalinti metaduomenų iš paveikslo.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Nepavyko pakeisti paveikslo dydžio.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Priedas yra per didelis.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Priede yra neteisingas turinys.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Priedas yra neteisingo failo formato.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Priedas yra tuščias.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Scelta del documento fallita.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Mostra";
/* No comment provided by engineer. */
"OK" = "OK,";
"BUTTON_OK" = "OK,";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ha disabilitato la scomparsa dei messaggi.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Condividi con Session";
"vc_share_loading_message" = "Preparazione allegati...";
"vc_share_sending_message" = "Invio...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Apri invito di gruppo";
"vc_conversation_settings_invite_button_title" = "Aggiungi membri";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "添付ファイル";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "添付ファイルの送信でエラー";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "画像を変換できません。";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "動画を処理できません。";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "画像をパースできません。";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "画像からメタデータを消去できませんでした。";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "画像のサイズを変更できません。";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "添付ファイルが大きすぎます。";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "添付ファイルが無効です。";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "添付ファイルのフォーマットが不正です。";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "添付ファイルの中身が空です。";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "ドキュメントの選択に失敗しました";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "表示内容";
/* No comment provided by engineer. */
"OK" = "確定";
"BUTTON_OK" = "確定";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@がメッセージの消失をオフにしました";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Sessionと共有";
"vc_share_loading_message" = "添付ファイルを準備しています...";
"vc_share_sending_message" = "送信中…";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "公開グループからの招待";
"vc_conversation_settings_invite_button_title" = "メンバーを追加する";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Bijlage";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Versturen bijlage mislukt";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Kan afbeelding niet naar JPEG converteren.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Kan video niet verwerken.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Kan afbeelding niet verwerken.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Kan metagegevens niet uit afbeelding verwijderen.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Afbeelding verkleinen mislukt.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Bijlage is te groot.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Bijlage bevat inhoud die niet wordt ondersteund of niet correct is geformatteerd.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Bijlage is een bestandstype welke niet wordt ondersteund.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Bijlage is leeg.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Document kiezen mislukt.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Tonen";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ heeft zelf-wissende berichten uitgeschakeld.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Delen naar de Session";
"vc_share_loading_message" = "Bijlagen voorbereiden...";
"vc_share_sending_message" = "Aan het verzenden...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Open groepsuitnodiging";
"vc_conversation_settings_invite_button_title" = "Voeg deelnemers toe";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Załącznik";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Wystąpił błąd podczas wysyłania załącznika";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Nie można przekonwertować obrazu.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Nie można przetworzyć wideo.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Nie można przetworzyć załączonej grafiki.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Nie można usunąć metadanych z obrazu.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Nie można zmienić rozmiaru obrazu.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Załącznik jest za duży.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Załącznik zawiera nieprawidłową zawartość.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Załącznik ma nieprawidłowy format pliku.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Załącznik jest pusty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Nie udało się wybrać dokumentu.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Pokaż";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ wyłączył(a) znikające wiadomości.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Udostępnij w Session";
"vc_share_loading_message" = "Przygotowywanie załączników...";
"vc_share_sending_message" = "Wysyłanie...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Otwórz zaproszenie do grupy";
"vc_conversation_settings_invite_button_title" = "Dodaj użytkowników";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Anexo";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Erro ao Enviar Anexo";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Impossível converter imagem.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Não foi possível processar o vídeo.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Não foi possível analisar a imagem.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Não foi possível suprimir os metadados da imagem.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Impossível redimensionar imagem.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Anexo excede o tamanho possível.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Anexo inclui conteúdo inválido.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Anexo tem um formato de arquivo inválido.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Anexo vazio.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Falha ao selecionar o documento.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Exibir";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ desabilitou mensagens efêmeras.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Compartilhar no Session";
"vc_share_loading_message" = "Preparando anexos...";
"vc_share_sending_message" = "Enviando...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Convite para grupo aberto";
"vc_conversation_settings_invite_button_title" = "Adicionar Membros";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Вложение";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Ошибка отправки вложения";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Не удалось выбрать документ.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Показывать";
/* No comment provided by engineer. */
"OK" = "Ок";
"BUTTON_OK" = "Ок";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ отключил(а) исчезающие сообщения.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Поделиться в Session";
"vc_share_loading_message" = "Подготовка вложений...";
"vc_share_sending_message" = "Отправка...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Открыть приглашение в группу";
"vc_conversation_settings_invite_button_title" = "Добавить участников";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Attachment";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Error Sending Attachment";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Show";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Share to Session";
"vc_share_loading_message" = "Preparing attachments...";
"vc_share_sending_message" = "Sending...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Open group invitation";
"vc_conversation_settings_invite_button_title" = "Add Members";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Príloha";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Chyba pri posielaní prílohy";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Obrázok sa nepodarilo skonvertovať.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Video sa nepodarilo spracovať.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Obrázok sa nepodarilo spracovať.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Z obrázku sa nepodarilo odstrániť metadáta.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Nepodarilo sa zmeniť veľkosť obrázka.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Príloha je priliš veľká.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Príloha obsahuje neplatný obsah.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Príloha má neplatný formát súboru.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Príloha je prázdna.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Nepodarilo sa vybrať dokument.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Zobraziť";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Share to Session";
"vc_share_loading_message" = "Pripravujú sa prílohy...";
"vc_share_sending_message" = "Odosiela sa...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Otvoriť skupinovú pozvánku";
"vc_conversation_settings_invite_button_title" = "Pridať členov";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Bilaga";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Fel vid sändning av bilaga";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Det går inte att konvertera bilden.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Det går inte att bearbeta videon.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Det går inte att tolka bilden.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Det går inte att ta bort metadata från bilden.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Det går inte att ändra storlek på bilden.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Bilagan är för stor.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Bilagan innehåller ogiltigt innehåll.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Bilagan har ett ogiltigt filformat.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Bilagan är tom.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Det gick inte att välja dokument.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Visa";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ inaktiverade försvinnande meddelanden.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Dela i Session";
"vc_share_loading_message" = "Förbereder bilagor...";
"vc_share_sending_message" = "Skickar...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Öppen gruppinbjudan";
"vc_conversation_settings_invite_button_title" = "Lägg till medlemmar";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "ไฟล์แนบ";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "ส่งไฟล์แนบโดนผิดพลาด";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "เลือกไฟล์ไม่ได้สำเร็จ";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "แสดง";
/* No comment provided by engineer. */
"OK" = "ตกลง";
"BUTTON_OK" = "ตกลง";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ ได้ปิดใช้งานข้อความที่ลบตัวเองแล้ว";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "เชิญมาใช้ Session";
"vc_share_loading_message" = "รวบรวมสิ่งแนบ...";
"vc_share_sending_message" = "กำลังส่ง...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "การเชิญเข้าร่วมกลุ่ม";
"vc_conversation_settings_invite_button_title" = "เพิ่มสมาชิก";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "Attachment";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "Error Sending Attachment";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "Failed to choose document.";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "Show";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ disabled disappearing messages.";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "Share to Session";
"vc_share_loading_message" = "Preparing attachments...";
"vc_share_sending_message" = "Sending...";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "Open group invitation";
"vc_conversation_settings_invite_button_title" = "Add Members";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "附件";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "寄送附件時發生錯誤";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "Unable to convert image.";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "Unable to process video.";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "Unable to parse image.";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "Unable to remove metadata from image.";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "Unable to resize image.";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "Attachment is too large.";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "Attachment includes invalid content.";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "Attachment has an invalid file format.";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty.";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "選取檔案時發生錯誤";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "顯示";
/* No comment provided by engineer. */
"OK" = "OK";
"BUTTON_OK" = "OK";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ 取消了閱後即焚模式";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "分享至 Session";
"vc_share_loading_message" = "準備附件中⋯";
"vc_share_sending_message" = "傳送中⋯";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "打開群組邀請";
"vc_conversation_settings_invite_button_title" = "新增成員";
"vc_settings_faq_button_title" = "FAQ";

@ -26,6 +26,24 @@
"ATTACHMENT_DEFAULT_FILENAME" = "附件";
/* The title of the 'attachment error' alert. */
"ATTACHMENT_ERROR_ALERT_TITLE" = "附件发送错误";
/* Attachment error message for image attachments which could not be converted to JPEG */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG" = "无法转换图片。";
/* Attachment error message for video attachments which could not be converted to MP4 */
"ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_MP4" = "无法处理该视频。";
/* Attachment error message for image attachments which cannot be parsed */
"ATTACHMENT_ERROR_COULD_NOT_PARSE_IMAGE" = "无法解析图片。";
/* Attachment error message for image attachments in which metadata could not be removed */
"ATTACHMENT_ERROR_COULD_NOT_REMOVE_METADATA" = "无法删除图片的元数据。";
/* Attachment error message for image attachments which could not be resized */
"ATTACHMENT_ERROR_COULD_NOT_RESIZE_IMAGE" = "无法调整图像大小。";
/* Attachment error message for attachments whose data exceed file size limits */
"ATTACHMENT_ERROR_FILE_SIZE_TOO_LARGE" = "附件过大。";
/* Attachment error message for attachments with invalid data */
"ATTACHMENT_ERROR_INVALID_DATA" = "附件中存在无效内容。";
/* Attachment error message for attachments with an invalid file format */
"ATTACHMENT_ERROR_INVALID_FILE_FORMAT" = "附件的文件格式无效。";
/* Attachment error message for attachments without any data */
"ATTACHMENT_ERROR_MISSING_DATA" = "附件为空。";
/* Alert title when picking a document fails for an unknown reason */
"ATTACHMENT_PICKER_DOCUMENTS_FAILED_ALERT_TITLE" = "文件选取失败。";
/* Alert body when picking a document fails because user picked a directory/bundle */
@ -269,7 +287,7 @@
/* No comment provided by engineer. */
"NOTIFICATIONS_SHOW" = "显示";
/* No comment provided by engineer. */
"OK" = "好";
"BUTTON_OK" = "好";
/* Info Message when {{other user}} disables or doesn't support disappearing messages */
"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ 取消了阅后即焚。";
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
@ -543,6 +561,10 @@
"vc_share_title" = "分享到 Session";
"vc_share_loading_message" = "正在准备附件......";
"vc_share_sending_message" = "正在发送…";
"vc_share_link_previews_unsecure" = "Preview not loaded for unsecure link";
"vc_share_link_previews_error" = "Unable to load preview";
"vc_share_link_previews_disabled_title" = "Link Previews Disabled";
"vc_share_link_previews_disabled_explanation" = "Enabling link previews will show previews for URLs you share. This can be useful, but Session will need to contact linked websites to generate previews.\n\nYou can enable link previews in Session's settings.";
"view_open_group_invitation_description" = "打开群组邀请";
"vc_conversation_settings_invite_button_title" = "添加成员";
"vc_settings_faq_button_title" = "FAQ";

@ -124,7 +124,7 @@ final class DisplayNameVC : BaseVC {
@objc private func register() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
let displayName = displayNameTextField.text!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

@ -124,7 +124,7 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
func continueWithSeed(_ seed: Data) {
if (seed.count != 16) {
let alert = UIAlertController(title: NSLocalizedString("invalid_recovery_phrase", comment: ""), message: NSLocalizedString("Please check the Recovery Phrase and try again.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { _ in
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: { _ in
self.scanQRCodeWrapperVC.startCapture()
}))
presentAlert(alert)
@ -273,7 +273,7 @@ private final class RecoveryPhraseVC : UIViewController {
@objc private func handleContinueButtonTapped() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
let mnemonic = mnemonicTextView.text!.lowercased()

@ -90,7 +90,7 @@ final class PNModeVC : BaseVC, OptionViewDelegate {
guard selectedOptionView != nil else {
let title = NSLocalizedString("vc_pn_mode_no_option_picked_modal_title", comment: "")
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
return present(alert, animated: true, completion: nil)
}
UserDefaults.standard[.isUsingFullAPNs] = (selectedOptionView == apnsOptionView)

@ -157,7 +157,7 @@ final class RestoreVC : BaseVC {
@objc private func restore() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
let mnemonic = mnemonicTextView.text!.lowercased()

@ -143,7 +143,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
Storage.shared.write { transaction in
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController!.dismiss(animated: true, completion: nil)
self?.presentingViewController?.dismiss(animated: true, completion: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
@ -161,7 +161,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
// MARK: Convenience
private func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
}

@ -40,10 +40,6 @@ final class PathVC : BaseVC {
private func setUpNavBar() {
setUpNavBarStyle()
setNavBarTitle(NSLocalizedString("vc_path_title", comment: ""))
// Set up close button
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
closeButton.tintColor = Colors.text
navigationItem.leftBarButtonItem = closeButton
}
private func setUpViewHierarchy() {
@ -167,10 +163,6 @@ final class PathVC : BaseVC {
}
// MARK: Interaction
@objc private func close() {
dismiss(animated: true, completion: nil)
}
@objc private func learnMore() {
let urlAsString = "https://getsession.org/faq/#onion-routing"
let url = URL(string: urlAsString)!

@ -146,13 +146,13 @@ final class NukeDataModal : Modal {
message = String(format: NSLocalizedString("dialog_clear_all_data_deletion_failed_2", comment: ""), String(potentiallyMaliciousSnodes.count), potentiallyMaliciousSnodes.joined(separator: ", "))
}
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.presentAlert(alert)
}
}.catch(on: DispatchQueue.main) { error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.presentAlert(alert)
}
}

@ -123,7 +123,7 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String) {
if !ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
let alert = UIAlertController(title: NSLocalizedString("invalid_session_id", comment: ""), message: NSLocalizedString("Please check the Session ID and try again.", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
} else {
let thread = TSContactThread.getOrCreateThread(contactSessionID: hexEncodedPublicKey)

@ -244,7 +244,21 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
button.set(.height, to: SettingsVC.buttonHeight)
return button
}
let pathButton = getSettingButton(withTitle: NSLocalizedString("vc_path_title", comment: ""), color: Colors.text, action: #selector(showPath))
let pathStatusView = PathStatusView()
pathStatusView.set(.width, to: PathStatusView.size)
pathStatusView.set(.height, to: PathStatusView.size)
pathButton.addSubview(pathStatusView)
pathStatusView.pin(.leading, to: .trailing, of: pathButton.titleLabel!, withInset: Values.smallSpacing)
pathStatusView.autoVCenterInSuperview()
pathButton.titleEdgeInsets = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: Values.smallSpacing)
return [
getSeparator(),
pathButton,
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_privacy_button_title", comment: ""), color: Colors.text, action: #selector(showPrivacySettings)),
getSeparator(),
@ -377,7 +391,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
let title = isMaxFileSizeExceeded ? "Maximum File Size Exceeded" : "Couldn't Update Profile"
let message = isMaxFileSizeExceeded ? "Please select a smaller photo and try again" : "Please check your internet connection and try again"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
}
@ -443,7 +457,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
@objc private func handleSaveDisplayNameButtonTapped() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
let displayName = displayNameTextField.text!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
@ -480,6 +494,11 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
navigationController!.present(shareVC, animated: true, completion: nil)
}
@objc private func showPath() {
let pathVC = PathVC()
navigationController!.pushViewController(pathVC, animated: true)
}
@objc private func showPrivacySettings() {
let privacySettingsVC = PrivacySettingsTableViewController()
navigationController!.pushViewController(privacySettingsVC, animated: true)

@ -75,6 +75,16 @@ class BaseVC : UIViewController {
crossfadeLabel.pin(to: container)
navigationItem.titleView = container
}
internal func setUpNavBarSessionHeading() {
let headingImageView = UIImageView()
headingImageView.tintColor = Colors.sessionHeading
headingImageView.image = UIImage(named: "SessionHeading")?.withRenderingMode(.alwaysTemplate)
headingImageView.contentMode = .scaleAspectFit
headingImageView.set(.width, to: 150)
headingImageView.set(.height, to: Values.mediumFontSize)
navigationItem.titleView = headingImageView
}
internal func setUpNavBarSessionIcon() {
let logoImageView = UIImageView()

@ -2,7 +2,12 @@ import UIKit
import SessionUIKit
final class ConversationCell : UITableViewCell {
var threadViewModel: ThreadViewModel! { didSet { update() } }
var isShowingGlobalSearchResult = false
var threadViewModel: ThreadViewModel! {
didSet {
isShowingGlobalSearchResult ? updateForSearchResult() : update()
}
}
static let reuseIdentifier = "ConversationCell"
@ -23,7 +28,7 @@ final class ConversationCell : UITableViewCell {
let result = UIView()
result.backgroundColor = Colors.text.withAlphaComponent(Values.veryLowOpacity)
let size = ConversationCell.unreadCountViewSize
result.set(.width, to: size)
result.set(.width, greaterThanOrEqualTo: size)
result.set(.height, to: size)
result.layer.masksToBounds = true
result.layer.cornerRadius = size / 2
@ -96,6 +101,22 @@ final class ConversationCell : UITableViewCell {
return result
}()
private lazy var topLabelStackView: UIStackView = {
let result = UIStackView()
result.axis = .horizontal
result.alignment = .center
result.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
return result
}()
private lazy var bottomLabelStackView: UIStackView = {
let result = UIStackView()
result.axis = .horizontal
result.alignment = .center
result.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
return result
}()
// MARK: Settings
private static let unreadCountViewSize: CGFloat = 20
private static let statusIndicatorSize: CGFloat = 14
@ -129,27 +150,28 @@ final class ConversationCell : UITableViewCell {
profilePictureView.size = profilePictureViewSize
// Unread count view
unreadCountView.addSubview(unreadCountLabel)
unreadCountLabel.pin(to: unreadCountView)
unreadCountLabel.pin([ VerticalEdge.top, VerticalEdge.bottom ], to: unreadCountView)
unreadCountView.pin(.leading, to: .leading, of: unreadCountLabel, withInset: -4)
unreadCountView.pin(.trailing, to: .trailing, of: unreadCountLabel, withInset: 4)
// Has mention view
hasMentionView.addSubview(hasMentionLabel)
hasMentionLabel.pin(to: hasMentionView)
// Label stack view
let topLabelSpacer = UIView.hStretchingSpacer()
let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, isPinnedIcon, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ])
topLabelStackView.axis = .horizontal
topLabelStackView.alignment = .center
topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
[ displayNameLabel, isPinnedIcon, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ].forEach{ view in
topLabelStackView.addArrangedSubview(view)
}
let snippetLabelContainer = UIView()
snippetLabelContainer.addSubview(snippetLabel)
snippetLabelContainer.addSubview(typingIndicatorView)
let bottomLabelSpacer = UIView.hStretchingSpacer()
let bottomLabelStackView = UIStackView(arrangedSubviews: [ snippetLabelContainer, bottomLabelSpacer, statusIndicatorView ])
bottomLabelStackView.axis = .horizontal
bottomLabelStackView.alignment = .center
bottomLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
let labelContainerView = UIView()
labelContainerView.addSubview(topLabelStackView)
labelContainerView.addSubview(bottomLabelStackView)
[ snippetLabelContainer, bottomLabelSpacer, statusIndicatorView ].forEach{ view in
bottomLabelStackView.addArrangedSubview(view)
}
let labelContainerView = UIStackView(arrangedSubviews: [ topLabelStackView, bottomLabelStackView ])
labelContainerView.axis = .vertical
labelContainerView.alignment = .leading
labelContainerView.spacing = 6
// Main stack view
let stackView = UIStackView(arrangedSubviews: [ accentLineView, profilePictureView, labelContainerView ])
stackView.axis = .horizontal
@ -172,16 +194,6 @@ final class ConversationCell : UITableViewCell {
snippetLabel.pin(to: snippetLabelContainer)
typingIndicatorView.pin(.leading, to: .leading, of: snippetLabelContainer)
typingIndicatorView.centerYAnchor.constraint(equalTo: snippetLabel.centerYAnchor).isActive = true
// HACK: Not using a stack view for this is part of a workaround for a weird layout bug
topLabelStackView.pin(.leading, to: .leading, of: labelContainerView)
topLabelStackView.pin(.top, to: .top, of: labelContainerView, withInset: 12)
topLabelStackView.pin(.trailing, to: .trailing, of: labelContainerView)
bottomLabelStackView.pin(.leading, to: .leading, of: labelContainerView)
bottomLabelStackView.pin(.top, to: .bottom, of: topLabelStackView, withInset: 6)
labelContainerView.pin(.bottom, to: .bottom, of: bottomLabelStackView, withInset: 12)
// HACK: The two lines below are part of a workaround for a weird layout bug
labelContainerView.set(.width, to: UIScreen.main.bounds.width - Values.accentLineThickness - Values.mediumSpacing - profilePictureViewSize - Values.mediumSpacing - Values.mediumSpacing)
labelContainerView.set(.height, to: cellHeight)
stackView.pin(.leading, to: .leading, of: contentView)
stackView.pin(.top, to: .top, of: contentView)
// HACK: The two lines below are part of a workaround for a weird layout bug
@ -189,6 +201,79 @@ final class ConversationCell : UITableViewCell {
stackView.set(.height, to: cellHeight)
}
// MARK: Updating for search results
private func updateForSearchResult() {
AssertIsOnMainThread()
guard let thread = threadViewModel?.threadRecord else { return }
profilePictureView.update(for: thread)
isPinnedIcon.isHidden = true
unreadCountView.isHidden = true
hasMentionView.isHidden = true
}
public func configureForRecent() {
displayNameLabel.attributedText = NSMutableAttributedString(string: getDisplayName(), attributes: [.foregroundColor:Colors.text])
bottomLabelStackView.isHidden = false
let snippet = String(format: NSLocalizedString("RECENT_SEARCH_LAST_MESSAGE_DATETIME", comment: ""), DateUtil.formatDate(forDisplay: threadViewModel.lastMessageDate))
snippetLabel.attributedText = NSMutableAttributedString(string: snippet, attributes: [.foregroundColor:Colors.text.withAlphaComponent(Values.lowOpacity)])
timestampLabel.isHidden = true
}
public func configure(messageDate: Date?, snippet: String?, searchText: String, message: TSMessage? = nil) {
let normalizedSearchText = searchText.lowercased()
if let messageDate = messageDate, let snippet = snippet {
// Message
displayNameLabel.attributedText = NSMutableAttributedString(string: getDisplayName(), attributes: [.foregroundColor:Colors.text])
timestampLabel.isHidden = false
timestampLabel.text = DateUtil.formatDate(forDisplay: messageDate)
bottomLabelStackView.isHidden = false
var rawSnippet = snippet
if let message = message, let name = getMessageAuthorName(message: message) {
rawSnippet = "\(name): \(snippet)"
}
snippetLabel.attributedText = getHighlightedSnippet(snippet: rawSnippet, searchText: normalizedSearchText, fontSize: Values.smallFontSize)
} else {
// Contact
if threadViewModel.isGroupThread, let thread = threadViewModel.threadRecord as? TSGroupThread {
displayNameLabel.attributedText = getHighlightedSnippet(snippet: getDisplayName(), searchText: normalizedSearchText, fontSize: Values.mediumFontSize)
bottomLabelStackView.isHidden = false
let context: Contact.Context = thread.isOpenGroup ? .openGroup : .regular
var rawSnippet: String = ""
thread.groupModel.groupMemberIds.forEach{ id in
if let displayName = Storage.shared.getContact(with: id)?.displayName(for: context) {
if !rawSnippet.isEmpty {
rawSnippet += ", \(displayName)"
}
if displayName.lowercased().contains(normalizedSearchText) {
rawSnippet = displayName
}
}
}
snippetLabel.attributedText = getHighlightedSnippet(snippet: rawSnippet, searchText: normalizedSearchText, fontSize: Values.smallFontSize)
} else {
displayNameLabel.attributedText = getHighlightedSnippet(snippet: getDisplayNameForSearch(threadViewModel.contactSessionID!), searchText: normalizedSearchText, fontSize: Values.mediumFontSize)
bottomLabelStackView.isHidden = true
}
timestampLabel.isHidden = true
}
}
private func getHighlightedSnippet(snippet: String, searchText: String, fontSize: CGFloat) -> NSMutableAttributedString {
guard snippet != NSLocalizedString("NOTE_TO_SELF", comment: "") else {
return NSMutableAttributedString(string: snippet, attributes: [.foregroundColor:Colors.text])
}
let result = NSMutableAttributedString(string: snippet, attributes: [.foregroundColor:Colors.text.withAlphaComponent(Values.lowOpacity)])
let normalizedSnippet = snippet.lowercased() as NSString
guard normalizedSnippet.contains(searchText) else { return result }
let range = normalizedSnippet.range(of: searchText)
result.addAttribute(.foregroundColor, value: Colors.text, range: range)
result.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: range)
return result
}
// MARK: Updating
private func update() {
AssertIsOnMainThread()
@ -210,8 +295,8 @@ final class ConversationCell : UITableViewCell {
isPinnedIcon.isHidden = !threadViewModel.isPinned
unreadCountView.isHidden = !threadViewModel.hasUnreadMessages
let unreadCount = threadViewModel.unreadCount
unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+"
let fontSize = (unreadCount < 100) ? Values.verySmallFontSize : 8
unreadCountLabel.text = unreadCount < 10000 ? "\(unreadCount)" : "9999+"
let fontSize = (unreadCount < 10000) ? Values.verySmallFontSize : 8
unreadCountLabel.font = .boldSystemFont(ofSize: fontSize)
hasMentionView.isHidden = !(threadViewModel.hasUnreadMentions && thread.isGroupThread())
profilePictureView.update(for: thread)
@ -246,6 +331,27 @@ final class ConversationCell : UITableViewCell {
}
}
private func getMessageAuthorName(message: TSMessage) -> String? {
guard threadViewModel.isGroupThread else { return nil }
if let incomingMessage = message as? TSIncomingMessage {
return Storage.shared.getContact(with: incomingMessage.authorId)?.displayName(for: .regular) ?? "Anonymous"
}
return nil
}
private func getDisplayNameForSearch(_ sessionID: String) -> String {
if threadViewModel.threadRecord.isNoteToSelf() {
return NSLocalizedString("NOTE_TO_SELF", comment: "")
} else {
var result = sessionID
if let contact = Storage.shared.getContact(with: sessionID), let name = contact.name {
result = name
if let nickname = contact.nickname { result += "(\(nickname))"}
}
return result
}
}
private func getDisplayName() -> String {
if threadViewModel.isGroupThread {
if threadViewModel.name.isEmpty {
@ -275,9 +381,12 @@ final class ConversationCell : UITableViewCell {
result.append(imageString)
result.append(NSAttributedString(string: " ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ]))
}
let font = threadViewModel.hasUnreadMessages ? UIFont.boldSystemFont(ofSize: Values.smallFontSize) : UIFont.systemFont(ofSize: Values.smallFontSize)
if threadViewModel.isGroupThread, let message = threadViewModel.lastMessageForInbox as? TSMessage, let name = getMessageAuthorName(message: message) {
result.append(NSAttributedString(string: "\(name): ", attributes: [ .font : font, .foregroundColor : Colors.text ]))
}
if let rawSnippet = threadViewModel.lastMessageText {
let snippet = MentionUtilities.highlightMentions(in: rawSnippet, threadID: threadViewModel.threadRecord.uniqueId!)
let font = threadViewModel.hasUnreadMessages ? UIFont.boldSystemFont(ofSize: Values.smallFontSize) : UIFont.systemFont(ofSize: Values.smallFontSize)
result.append(NSAttributedString(string: snippet, attributes: [ .font : font, .foregroundColor : Colors.text ]))
}
return result

@ -155,31 +155,10 @@ NS_ASSUME_NONNULL_BEGIN
if (_read && readTimestamp >= self.expireStartedAt) {
return;
}
BOOL isTrusted = YES;
TSThread* thread = [self threadWithTransaction:transaction];
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread* contactThread = (TSContactThread*)thread;
isTrusted = [[LKStorage shared] getContactWithSessionID:[contactThread contactSessionID] using:transaction].isTrusted;
}
BOOL areAllAttachmentsDownloaded = YES;
if (isTrusted) {
for (NSString *attachmentId in self.attachmentIds) {
TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
// If the attachment download failed, we can mark this message as read.
// Otherwise, this message will never be marked as read.
if ([attachment isKindOfClass:[TSAttachmentPointer class]]
&& ((TSAttachmentPointer *)attachment).state == TSAttachmentPointerStateFailed) {
continue;
}
areAllAttachmentsDownloaded = areAllAttachmentsDownloaded && attachment.isDownloaded;
if (!areAllAttachmentsDownloaded) break;
}
}
if (!areAllAttachmentsDownloaded) {
return;
}
// We just ignore all attachments download state here and mark all messages as read
// This is a workaround for a situation that some large attachments won't be downloaded
// and just stuck in a downloading state. In that case, the corresponding message won't
// be able to be marked as read.
_read = YES;
[self saveWithTransaction:transaction];

@ -114,6 +114,9 @@ public class SignalAttachment: NSObject {
@objc
public var captionText: String?
@objc
public var linkPreviewDraft: OWSLinkPreviewDraft?
@objc
public var data: Data {
@ -292,6 +295,15 @@ public class SignalAttachment: NSObject {
return nil
}
}
@objc
public func text() -> String? {
guard let text = String(data: dataSource.data(), encoding: .utf8) else {
return nil
}
return text
}
// Returns the MIME type for this attachment or nil if no MIME type
// can be identified.
@ -450,7 +462,12 @@ public class SignalAttachment: NSObject {
@objc
public var isText: Bool {
return UTTypeConformsTo(dataUTI as CFString, kUTTypeText) || isOversizeText
return (
isConvertibleToTextMessage && (
UTTypeConformsTo(dataUTI as CFString, kUTTypeText) ||
isOversizeText
)
)
}
@objc

@ -84,6 +84,8 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (nonatomic, readonly) BOOL isAudio;
@property (nonatomic, readonly) BOOL isVoiceMessage;
@property (nonatomic, readonly) BOOL isVisualMedia;
@property (nonatomic, readonly) BOOL isText;
@property (nonatomic, readonly) BOOL isMicrosoftDoc;
@property (nonatomic, readonly) BOOL isOversizeText;
+ (NSString *)emojiForMimeType:(NSString *)contentType;

@ -3,6 +3,14 @@
#import "TSAttachmentPointer.h"
#import <SignalCoreKit/NSString+OWS.h>
#if TARGET_OS_IPHONE
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <CoreServices/CoreServices.h>
#endif
NS_ASSUME_NONNULL_BEGIN
NSUInteger const TSAttachmentSchemaVersion = 4;
@ -229,6 +237,14 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
return [MIMETypeUtil isVisualMedia:self.contentType];
}
- (BOOL)isText {
return [MIMETypeUtil isText:self.contentType];
}
- (BOOL)isMicrosoftDoc {
return [MIMETypeUtil isMicrosoftDoc:self.contentType];
}
- (BOOL)isOversizeText
{
return [self.contentType isEqualToString:OWSMimeTypeOversizeTextMessage];

@ -19,10 +19,10 @@ final class DataExtractionNotificationInfoMessage : TSInfoMessage {
let sessionID = thread.contactSessionID()
let displayName = Storage.shared.getContact(with: sessionID)?.displayName(for: .regular) ?? sessionID
switch messageType {
case .screenshotNotification: return "\(displayName) took a screenshot."
case .screenshotNotification: return String(format: NSLocalizedString("screenshot_taken", comment: ""), displayName)
case .mediaSavedNotification:
// TODO: Use referencedAttachmentTimestamp to tell the user * which * media was saved
return "Media saved by \(displayName)."
return String(format: NSLocalizedString("meida_saved", comment: ""), displayName)
default: preconditionFailure()
}
}

@ -385,10 +385,16 @@ public class OWSLinkPreview: MTLModel {
var urlMatches: [URLMatchResult] = []
let matches = detector.matches(in: body, options: [], range: NSRange(location: 0, length: body.count))
for match in matches {
guard let matchURL = match.url else {
continue
}
let urlString = matchURL.absoluteString
guard let matchURL = match.url else { continue }
// If the URL entered didn't have a scheme it will default to 'http', we want to catch this and
// set the scheme to 'https' instead as we don't load previews for 'http' so this will result
// in more previews actually getting loaded without forcing the user to enter 'https://' before
// every URL they enter
let urlString: String = (matchURL.absoluteString == "http://\(body)" ?
"https://\(body)" :
matchURL.absoluteString
)
if isValidLinkUrl(urlString) {
let matchResult = URLMatchResult(urlString: urlString, matchRange: match.range)
urlMatches.append(matchResult)

@ -62,7 +62,15 @@ public final class MentionsManager : NSObject {
if let groupThread = thread as? TSGroupThread, groupThread.groupModel.groupType == .closedGroup {
result = result.union(groupThread.groupModel.groupMemberIds).subtracting([ getUserHexEncodedPublicKey() ])
} else {
guard userPublicKeyCache[threadID] == nil else { return }
let hasOnlyCurrentUser: Bool = (
userPublicKeyCache[threadID]?.count == 1 &&
userPublicKeyCache[threadID]?.first == getUserHexEncodedPublicKey()
)
guard userPublicKeyCache[threadID] == nil || ((thread as? TSGroupThread)?.groupModel.groupType == .openGroup && hasOnlyCurrentUser) else {
return
}
let interactions = transaction.ext(TSMessageDatabaseViewExtensionName) as! YapDatabaseViewTransaction
interactions.enumerateKeysAndObjects(inGroup: threadID) { _, _, object, index, _ in
guard let message = object as? TSIncomingMessage, index < userIDScanLimit else { return }

@ -44,7 +44,7 @@ public final class ClosedGroupPoller : NSObject {
// Might be a race condition that the setUpPolling finishes too soon,
// and the timer is not created, if we mark the group as is polling
// after setUpPolling. So the poller may not work, thus misses messages.
isPolling[groupPublicKey] = true
internalQueue.sync{ isPolling[groupPublicKey] = true }
setUpPolling(for: groupPublicKey)
}
@ -55,7 +55,7 @@ public final class ClosedGroupPoller : NSObject {
}
public func stopPolling(for groupPublicKey: String) {
isPolling[groupPublicKey] = false
internalQueue.sync{ isPolling[groupPublicKey] = false }
timers[groupPublicKey]?.invalidate()
}

@ -69,6 +69,13 @@ NSString *const TSContactThreadPrefix = @"c";
return [contact displayNameFor:SNContactContextRegular] ?: sessionID;
}
- (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID using:transaction];
return [contact displayNameFor:SNContactContextRegular] ?: sessionID;
}
+ (NSString *)threadIDFromContactSessionID:(NSString *)contactSessionID {
return [TSContactThreadPrefix stringByAppendingString:contactSessionID];
}

@ -192,6 +192,11 @@ NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_Notific
return self.groupModel.groupName ?: self.class.defaultGroupName;
}
- (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self name];
}
+ (NSString *)defaultGroupName
{
return @"Group";

@ -38,6 +38,8 @@ BOOL IsNoteToSelfEnabled(void);
*/
- (NSString *)name;
- (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction;
/**
* @returns recipientId for each recipient in the thread
*/

@ -148,6 +148,11 @@ BOOL IsNoteToSelfEnabled(void)
return nil;
}
- (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return nil;
}
- (NSArray<NSString *> *)recipientIdentifiers
{
return @[];

@ -85,18 +85,19 @@ public class FullTextSearchFinder: NSObject {
return query
}
public func enumerateObjects(searchText: String, transaction: YapDatabaseReadTransaction, block: @escaping (Any, String) -> Void) {
public func enumerateObjects(searchText: String, maxSearchResults: Int? = nil, transaction: YapDatabaseReadTransaction, block: @escaping (Any, String) -> Void) {
guard let ext: YapDatabaseFullTextSearchTransaction = ext(transaction: transaction) else {
return
}
let query = FullTextSearchFinder.query(searchText: searchText)
let maxSearchResults = 500
let maxSearchResults = maxSearchResults ?? 500
var searchResultCount = 0
let snippetOptions = YapDatabaseFullTextSearchSnippetOptions()
snippetOptions.startMatchText = ""
snippetOptions.endMatchText = ""
snippetOptions.numberOfTokens = 5
ext.enumerateKeysAndObjects(matching: query, with: snippetOptions) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer<ObjCBool>) in
guard searchResultCount < maxSearchResults else {
stop.pointee = true
@ -177,8 +178,12 @@ public class FullTextSearchFinder: NSObject {
}
private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String, transaction: YapDatabaseReadTransaction) in
let displayName = Storage.shared.getContact(with: recipientId)?.displayName(for: Contact.Context.regular) ?? recipientId
return "\(recipientId) \(displayName)"
var result = "\(recipientId)"
if let contact = Storage.shared.getContact(with: recipientId) {
if let name = contact.name { result += " \(name)" }
if let nickname = contact.nickname { result += " \(nickname)" }
}
return result
}
private static let messageIndexer: SearchIndexer<TSMessage> = SearchIndexer { (message: TSMessage, transaction: YapDatabaseReadTransaction) in
@ -241,6 +246,6 @@ public class FullTextSearchFinder: NSObject {
options: nil,
handler: handler,
ftsVersion: YapDatabaseFullTextSearchFTS5Version,
versionTag: "1")
versionTag: "2")
}
}

@ -43,6 +43,7 @@ typedef NS_ENUM(NSUInteger, OWSAudioBehavior) {
@property (nonatomic) BOOL isLooping;
@property (nonatomic) BOOL isPlaying;
@property (nonatomic) float playbackRate;
@property (nonatomic) NSTimeInterval duration;
- (instancetype)initWithMediaUrl:(NSURL *)mediaUrl audioBehavior:(OWSAudioBehavior)audioBehavior;
- (instancetype)initWithMediaUrl:(NSURL *)mediaUrl audioBehavior:(OWSAudioBehavior)audioBehavior delegate:(id<OWSAudioPlayerDelegate>)delegate;

@ -168,6 +168,11 @@ NS_ASSUME_NONNULL_BEGIN
return self.audioPlayer.rate;
}
- (NSTimeInterval)duration
{
return [self.audioPlayer duration];
}
- (void)setPlaybackRate:(float)rate
{
[self.audioPlayer setRate:rate];

@ -29,7 +29,7 @@ enum ProofOfWork {
value = newValue
}
// Encode as base 64
let base64EncodedNonce = nonce.bigEndianBytes.toBase64()!
let base64EncodedNonce = nonce.bigEndianBytes.toBase64()
// Return
return (timestamp, base64EncodedNonce)
}

@ -9,6 +9,7 @@ final class NotificationServiceExtensionContext : NSObject, AppContext {
let appLaunchTime = Date()
let isMainApp = false
let isMainAppAndActive = false
var isShareExtension: Bool = false
var openSystemSettingsAction: UIAlertAction?
var wasWokenUpByPushNotification = true

@ -6,8 +6,6 @@
#import <UIKit/UIKit.h>
// Separate iOS Frameworks from other imports.
#import "SAEScreenLockViewController.h"
#import "ShareAppExtensionContext.h"
#import <SignalCoreKit/NSObject+OWS.h>
#import <SignalCoreKit/OWSAsserts.h>
#import <SignalCoreKit/OWSLogs.h>

@ -1,18 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSViewController.h>
#import <SignalUtilitiesKit/ScreenLockViewController.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ShareViewDelegate;
@interface SAEScreenLockViewController : ScreenLockViewController
- (instancetype)initWithShareViewDelegate:(id<ShareViewDelegate>)shareViewDelegate;
@end
NS_ASSUME_NONNULL_END

@ -1,206 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "SAEScreenLockViewController.h"
#import "UIColor+OWS.h"
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/AppContext.h>
NS_ASSUME_NONNULL_BEGIN
@interface SAEScreenLockViewController () <ScreenLockViewDelegate>
@property (nonatomic, readonly, weak) id<ShareViewDelegate> shareViewDelegate;
@property (nonatomic) BOOL hasShownAuthUIOnce;
@property (nonatomic) BOOL isShowingAuthUI;
@end
#pragma mark -
@implementation SAEScreenLockViewController
- (instancetype)initWithShareViewDelegate:(id<ShareViewDelegate>)shareViewDelegate
{
self = [super init];
if (!self) {
return self;
}
_shareViewDelegate = shareViewDelegate;
self.delegate = self;
return self;
}
- (void)loadView
{
[super loadView];
UIView.appearance.tintColor = LKColors.text;
// Gradient background
self.view.backgroundColor = UIColor.clearColor;
CAGradientLayer *layer = [CAGradientLayer new];
layer.frame = UIScreen.mainScreen.bounds;
UIColor *gradientStartColor = LKAppModeUtilities.isLightMode ? [UIColor colorWithRGBHex:0xFCFCFC] : [UIColor colorWithRGBHex:0x171717];
UIColor *gradientEndColor = LKAppModeUtilities.isLightMode ? [UIColor colorWithRGBHex:0xFFFFFF] : [UIColor colorWithRGBHex:0x121212];
layer.colors = @[ (id)gradientStartColor.CGColor, (id)gradientEndColor.CGColor ];
[self.view.layer insertSublayer:layer atIndex:0];
// Navigation bar background color
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
navigationBar.shadowImage = [UIImage new];
[navigationBar setTranslucent:NO];
navigationBar.barTintColor = LKColors.navigationBarBackground;
// Title
UILabel *titleLabel = [UILabel new];
titleLabel.text = NSLocalizedString(@"vc_share_title", @"");
titleLabel.textColor = LKColors.text;
titleLabel.font = [UIFont boldSystemFontOfSize:LKValues.veryLargeFontSize];
self.navigationItem.titleView = titleLabel;
// Close button
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"X"] style:UIBarButtonItemStylePlain target:self action:@selector(dismissPressed:)];
closeButton.tintColor = LKColors.text;
self.navigationItem.leftBarButtonItem = closeButton;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self ensureUI];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self ensureUI];
// Auto-show the auth UI f
if (!self.hasShownAuthUIOnce) {
self.hasShownAuthUIOnce = YES;
[self tryToPresentAuthUIToUnlockScreenLock];
}
}
- (void)dealloc
{
// Surface memory leaks by logging the deallocation of view controllers.
OWSLogVerbose(@"Dealloc: %@", self.class);
}
- (void)tryToPresentAuthUIToUnlockScreenLock
{
OWSAssertIsOnMainThread();
if (self.isShowingAuthUI) {
// We're already showing the auth UI; abort.
return;
}
OWSLogInfo(@"try to unlock screen lock");
self.isShowingAuthUI = YES;
[OWSScreenLock.sharedManager tryToUnlockScreenLockWithSuccess:^{
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock succeeded.");
self.isShowingAuthUI = NO;
[self.shareViewDelegate shareViewWasUnlocked];
}
failure:^(NSError *error) {
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock failed.");
self.isShowingAuthUI = NO;
[self ensureUI];
[self showScreenLockFailureAlertWithMessage:error.localizedDescription];
}
unexpectedFailure:^(NSError *error) {
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock unexpectedly failed.");
self.isShowingAuthUI = NO;
// Local Authentication isn't working properly.
// This isn't covered by the docs or the forums but in practice
// it appears to be effective to retry again after waiting a bit.
dispatch_async(dispatch_get_main_queue(), ^{
[self ensureUI];
});
}
cancel:^{
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock cancelled.");
self.isShowingAuthUI = NO;
[self ensureUI];
}];
[self ensureUI];
}
- (void)ensureUI
{
[self updateUIWithState:ScreenLockUIStateScreenLock isLogoAtTop:NO animated:NO];
}
- (void)showScreenLockFailureAlertWithMessage:(NSString *)message
{
OWSAssertIsOnMainThread();
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_UNLOCK_FAILED",
@"Title for alert indicating that screen lock could not be unlocked.")
message:message
buttonTitle:nil
buttonAction:^(UIAlertAction *action) {
// After the alert, update the UI.
[self ensureUI];
}
fromViewController:self];
}
- (void)dismissPressed:(id)sender
{
OWSLogDebug(@"tapped dismiss share button");
[self cancelShareExperience];
}
- (void)cancelShareExperience
{
[self.shareViewDelegate shareViewWasCancelled];
}
#pragma mark - ScreenLockViewDelegate
- (void)unlockButtonWasTapped
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlockButtonWasTapped");
[self tryToPresentAuthUIToUnlockScreenLock];
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,206 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import PromiseKit
import SignalCoreKit
import SignalUtilitiesKit
import SessionUIKit
import SessionUtilitiesKit
final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockViewDelegate {
private var hasShownAuthUIOnce: Bool = false
private var isShowingAuthUI: Bool = false
private weak var shareViewDelegate: ShareViewDelegate?
// MARK: - Initialization
init(shareViewDelegate: ShareViewDelegate) {
super.init(nibName: nil, bundle: nil)
self.shareViewDelegate = shareViewDelegate
self.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
OWSLogger.verbose("Dealloc: \(type(of: self))")
}
// MARK: - UI
private lazy var gradientBackground: CAGradientLayer = {
let layer: CAGradientLayer = CAGradientLayer()
let gradientStartColor: UIColor = (LKAppModeUtilities.isLightMode ?
UIColor(rgbHex: 0xFCFCFC) :
UIColor(rgbHex: 0x171717)
)
let gradientEndColor: UIColor = (LKAppModeUtilities.isLightMode ?
UIColor(rgbHex: 0xFFFFFF) :
UIColor(rgbHex: 0x121212)
)
layer.colors = [gradientStartColor.cgColor, gradientEndColor.cgColor]
return layer
}()
private lazy var titleLabel: UILabel = {
let titleLabel: UILabel = UILabel()
titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = "vc_share_title".localized()
titleLabel.textColor = Colors.text
return titleLabel
}()
private lazy var closeButton: UIBarButtonItem = {
let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(dismissPressed))
closeButton.tintColor = Colors.text
return closeButton
}()
// MARK: - Lifecycle
override func loadView() {
super.loadView()
UIView.appearance().tintColor = Colors.text
self.view.backgroundColor = UIColor.clear
self.view.layer.insertSublayer(gradientBackground, at: 0)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.tintColor = Colors.navigationBarBackground
self.navigationItem.titleView = titleLabel
self.navigationItem.leftBarButtonItem = closeButton
setupLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.ensureUI()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.ensureUI()
// Auto-show the auth UI f
if !hasShownAuthUIOnce {
hasShownAuthUIOnce = true
self.tryToPresentAuthUIToUnlockScreenLock()
}
}
// MARK: - Layout
private func setupLayout() {
gradientBackground.frame = UIScreen.main.bounds
}
// MARK: - Functions
private func tryToPresentAuthUIToUnlockScreenLock() {
AssertIsOnMainThread()
// If we're already showing the auth UI; abort.
if self.isShowingAuthUI { return }
OWSLogger.info("try to unlock screen lock")
isShowingAuthUI = true
OWSScreenLock.shared.tryToUnlockScreenLock(
success: { [weak self] in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock succeeded.")
self?.isShowingAuthUI = false
self?.shareViewDelegate?.shareViewWasUnlocked()
},
failure: { [weak self] error in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock failed.")
self?.isShowingAuthUI = false
self?.ensureUI()
self?.showScreenLockFailureAlert(message: error.localizedDescription)
},
unexpectedFailure: { [weak self] error in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock unexpectedly failed.")
self?.isShowingAuthUI = false
// Local Authentication isn't working properly.
// This isn't covered by the docs or the forums but in practice
// it appears to be effective to retry again after waiting a bit.
DispatchQueue.main.async {
self?.ensureUI()
}
},
cancel: { [weak self] in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock cancelled.")
self?.isShowingAuthUI = false
self?.ensureUI()
}
)
self.ensureUI()
}
private func ensureUI() {
self.updateUI(with: .screenLock, isLogoAtTop: false, animated: false)
}
private func showScreenLockFailureAlert(message: String) {
AssertIsOnMainThread()
OWSAlerts.showAlert(
// Title for alert indicating that screen lock could not be unlocked.
title: "SCREEN_LOCK_UNLOCK_FAILED".localized(),
message: message,
buttonTitle: nil,
buttonAction: { [weak self] action in
// After the alert, update the UI
self?.ensureUI()
},
fromViewController: self
)
}
// MARK: - Transitions
@objc private func dismissPressed() {
OWSLogger.debug("unlock screen lock cancelled.")
self.cancelShareExperience()
}
private func cancelShareExperience() {
self.shareViewDelegate?.shareViewWasCancelled()
}
// MARK: - ScreenLockViewDelegate
func unlockButtonWasTapped() {
AssertIsOnMainThread()
OWSLogger.info("unlockButtonWasTapped")
self.tryToPresentAuthUIToUnlockScreenLock()
}
}

@ -1,18 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <SessionUtilitiesKit/AppContext.h>
NS_ASSUME_NONNULL_BEGIN
// This is _NOT_ a singleton and will be instantiated each time that the SAE is used.
@interface ShareAppExtensionContext : NSObject <AppContext>
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
@end
NS_ASSUME_NONNULL_END

@ -1,240 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "ShareAppExtensionContext.h"
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SessionMessagingKit/OWSStorage.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/TSConstants.h>
#import <SessionUtilitiesKit/SessionUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@interface ShareAppExtensionContext ()
@property (nonatomic) UIViewController *rootViewController;
@property (atomic) UIApplicationState reportedApplicationState;
@end
#pragma mark -
@implementation ShareAppExtensionContext
@synthesize mainWindow = _mainWindow;
@synthesize appLaunchTime = _appLaunchTime;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super init];
if (!self) {
return self;
}
OWSAssertDebug(rootViewController);
_rootViewController = rootViewController;
self.reportedApplicationState = UIApplicationStateActive;
_appLaunchTime = [NSDate new];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostDidBecomeActive:)
name:NSExtensionHostDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostWillResignActive:)
name:NSExtensionHostWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostDidEnterBackground:)
name:NSExtensionHostDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostWillEnterForeground:)
name:NSExtensionHostWillEnterForegroundNotification
object:nil];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Notifications
- (void)extensionHostDidBecomeActive:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
self.reportedApplicationState = UIApplicationStateActive;
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidBecomeActiveNotification object:nil];
}
- (void)extensionHostWillResignActive:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
self.reportedApplicationState = UIApplicationStateInactive;
OWSLogInfo(@"");
[DDLog flushLog];
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillResignActiveNotification object:nil];
}
- (void)extensionHostDidEnterBackground:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
[DDLog flushLog];
self.reportedApplicationState = UIApplicationStateBackground;
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidEnterBackgroundNotification object:nil];
}
- (void)extensionHostWillEnterForeground:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
self.reportedApplicationState = UIApplicationStateInactive;
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillEnterForegroundNotification object:nil];
}
#pragma mark -
- (BOOL)isMainApp
{
return NO;
}
- (BOOL)isMainAppAndActive
{
return NO;
}
- (BOOL)isRTL
{
static BOOL isRTL = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Borrowed from PureLayout's AppExtension compatible RTL support.
// App Extensions may not access -[UIApplication sharedApplication]; fall back to checking the bundle's
// preferred localization character direction
isRTL = [NSLocale characterDirectionForLanguage:[[NSBundle mainBundle] preferredLocalizations][0]]
== NSLocaleLanguageDirectionRightToLeft;
});
return isRTL;
}
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated
{
OWSLogInfo(@"Ignoring request to show/hide status bar since we're in an app extension");
}
- (CGFloat)statusBarHeight
{
return 20;
}
- (BOOL)isInBackground
{
return self.reportedApplicationState == UIApplicationStateBackground;
}
- (BOOL)isAppForegroundAndActive
{
return self.reportedApplicationState == UIApplicationStateActive;
}
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:
(BackgroundTaskExpirationHandler)expirationHandler
{
return UIBackgroundTaskInvalid;
}
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier
{
OWSAssertDebug(backgroundTaskIdentifier == UIBackgroundTaskInvalid);
}
- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray<id> *)blockingObjects
{
OWSLogDebug(@"Ignoring request to block sleep.");
}
- (void)setMainAppBadgeNumber:(NSInteger)value
{
OWSFailDebug(@"");
}
- (nullable UIViewController *)frontmostViewController
{
OWSAssertDebug(self.rootViewController);
return [self.rootViewController findFrontmostViewController:YES];
}
- (nullable UIAlertAction *)openSystemSettingsAction
{
return nil;
}
- (BOOL)isRunningTests
{
// We don't need to distinguish this in the SAE.
return NO;
}
- (void)setNetworkActivityIndicatorVisible:(BOOL)value
{
OWSFailDebug(@"");
}
- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block
{
OWSFailDebug(@"cannot run main app active blocks in share extension.");
}
- (id<SSKKeychainStorage>)keychainStorage
{
return [SSKDefaultKeychainStorage shared];
}
- (NSString *)appDocumentDirectoryPath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentDirectoryURL =
[[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
return [documentDirectoryURL path];
}
- (NSString *)appSharedDataDirectoryPath
{
NSURL *groupContainerDirectoryURL =
[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:SignalApplicationGroup];
return [groupContainerDirectoryURL path];
}
- (NSUserDefaults *)appUserDefaults
{
return [[NSUserDefaults alloc] initWithSuiteName:SignalApplicationGroup];
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,205 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SignalUtilitiesKit
import SessionUtilitiesKit
import SessionMessagingKit
/// This is _NOT_ a singleton and will be instantiated each time that the SAE is used.
final class ShareAppExtensionContext: NSObject, AppContext {
var rootViewController: UIViewController
var reportedApplicationState: UIApplication.State
let appLaunchTime = Date()
let isMainApp = false
let isMainAppAndActive = false
var isShareExtension: Bool = true
var mainWindow: UIWindow?
var wasWokenUpByPushNotification: Bool = false
private static var _isRTL: Bool = {
// Borrowed from PureLayout's AppExtension compatible RTL support.
// App Extensions may not access -[UIApplication sharedApplication]; fall back
// to checking the bundle's preferred localization character direction
return (
Locale.characterDirection(
forLanguage: (Bundle.main.preferredLocalizations.first ?? "")
) == Locale.LanguageDirection.rightToLeft
)
}()
var isRTL: Bool { return ShareAppExtensionContext._isRTL }
var isRunningTests: Bool { return false } // We don't need to distinguish this in the SAE
var statusBarHeight: CGFloat { return 20 }
var openSystemSettingsAction: UIAlertAction?
// MARK: - Initialization
init(rootViewController: UIViewController) {
self.rootViewController = rootViewController
self.reportedApplicationState = .active
super.init()
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostDidBecomeActive(notification:)),
name: .NSExtensionHostDidBecomeActive,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostWillResignActive(notification:)),
name: .NSExtensionHostWillResignActive,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostDidEnterBackground(notification:)),
name: .NSExtensionHostDidEnterBackground,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostWillEnterForeground(notification:)),
name: .NSExtensionHostWillEnterForeground,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Notifications
@objc private func extensionHostDidBecomeActive(notification: NSNotification) {
AssertIsOnMainThread()
OWSLogger.info("")
self.reportedApplicationState = .active
NotificationCenter.default.post(
name: .OWSApplicationDidBecomeActive,
object: nil
)
}
@objc private func extensionHostWillResignActive(notification: NSNotification) {
AssertIsOnMainThread()
self.reportedApplicationState = .inactive
OWSLogger.info("")
DDLog.flushLog()
NotificationCenter.default.post(
name: .OWSApplicationWillResignActive,
object: nil
)
}
@objc private func extensionHostDidEnterBackground(notification: NSNotification) {
AssertIsOnMainThread()
OWSLogger.info("")
DDLog.flushLog()
self.reportedApplicationState = .background
NotificationCenter.default.post(
name: .OWSApplicationDidEnterBackground,
object: nil
)
}
@objc private func extensionHostWillEnterForeground(notification: NSNotification) {
AssertIsOnMainThread()
OWSLogger.info("")
self.reportedApplicationState = .inactive
NotificationCenter.default.post(
name: .OWSApplicationWillEnterForeground,
object: nil
)
}
// MARK: - AppContext Functions
func isAppForegroundAndActive() -> Bool {
return (reportedApplicationState == .active)
}
func isInBackground() -> Bool {
return (reportedApplicationState == .background)
}
func frontmostViewController() -> UIViewController? {
return rootViewController.findFrontmostViewController(true)
}
func keychainStorage() -> SSKKeychainStorage {
return SSKDefaultKeychainStorage.shared
}
func appDocumentDirectoryPath() -> String {
let targetPath: String? = FileManager.default
.urls(
for: .documentDirectory,
in: .userDomainMask
)
.last?
.path
owsAssertDebug(targetPath != nil)
return (targetPath ?? "")
}
func appSharedDataDirectoryPath() -> String {
let targetPath: String? = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: SignalApplicationGroup)?
.path
owsAssertDebug(targetPath != nil)
return (targetPath ?? "")
}
func appUserDefaults() -> UserDefaults {
let targetUserDefaults: UserDefaults? = UserDefaults(suiteName: SignalApplicationGroup)
owsAssertDebug(targetUserDefaults != nil)
return (targetUserDefaults ?? UserDefaults.standard)
}
func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) {
OWSLogger.info("Ignoring request to show/hide status bar since we're in an app extension")
}
func beginBackgroundTask(expirationHandler: @escaping BackgroundTaskExpirationHandler) -> UIBackgroundTaskIdentifier {
return .invalid
}
func endBackgroundTask(_ backgroundTaskIdentifier: UIBackgroundTaskIdentifier) {
owsAssertDebug(backgroundTaskIdentifier == .invalid)
}
func ensureSleepBlocking(_ shouldBeBlocking: Bool, blockingObjects: [Any]) {
OWSLogger.debug("Ignoring request to block sleep.")
}
func setMainAppBadgeNumber(_ value: Int) {
owsFailDebug("")
}
func setNetworkActivityIndicatorVisible(_ value: Bool) {
owsFailDebug("")
}
func runNowOr(whenMainAppIsActive block: @escaping AppActiveBlock) {
owsFailDebug("cannot run main app active blocks in share extension.")
}
}

@ -1,12 +1,14 @@
import CoreServices
import PromiseKit
import SignalUtilitiesKit
import SessionUIKit
final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerDelegate {
private var areVersionMigrationsComplete = false
public static var attachmentPrepPromise: Promise<[SignalAttachment]>?
// MARK: Error
// MARK: - Error
enum ShareViewControllerError: Error {
case assertionError(description: String)
case unsupportedMedia
@ -14,7 +16,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
case obsoleteShare
}
// MARK: Lifecycle
// MARK: - Lifecycle
override func loadView() {
super.loadView()
@ -39,28 +42,35 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
return
}
AppSetup.setupEnvironment(appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
}, migrationCompletion: { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
AppSetup.setupEnvironment(
appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
},
migrationCompletion: { [weak self] in
AssertIsOnMainThread()
self?.versionMigrationsDidComplete()
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
strongSelf.versionMigrationsDidComplete()
})
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
self?.versionMigrationsDidComplete()
}
)
// We don't need to use "screen protection" in the SAE.
NotificationCenter.default.addObserver(self,
selector: #selector(storageIsReady),
name: .StorageIsReady,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidEnterBackground),
name: .OWSApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(storageIsReady),
name: .StorageIsReady,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidEnterBackground),
name: .OWSApplicationDidEnterBackground,
object: nil
)
}
@objc
@ -88,12 +98,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
AssertIsOnMainThread()
// App isn't ready until storage is ready AND all version migrations are complete.
guard areVersionMigrationsComplete else {
return
}
guard OWSStorage.isStorageReady() else {
return
}
guard areVersionMigrationsComplete else { return }
guard OWSStorage.isStorageReady() else { return }
guard !AppReadiness.isAppReady() else {
// Only mark the app as ready once.
return
@ -108,9 +114,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
AppReadiness.setAppIsReady()
// We don't need to use messageFetcherJob in the SAE.
// We don't need to use SyncPushTokensJob in the SAE.
// We don't need to use DeviceSleepManager in the SAE.
AppVersion.sharedInstance().saeLaunchDidComplete()
@ -119,9 +123,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// We don't need to use OWSMessageReceiver in the SAE.
// We don't need to use OWSBatchMessageProcessor in the SAE.
// We don't need to use OWSOrphanDataCleaner in the SAE.
// We don't need to fetch the local profile in the SAE
OWSReadReceiptManager.shared().prepareCachedValues()
@ -129,10 +131,10 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
override func viewDidLoad() {
super.viewDidLoad()
AppReadiness.runNowOrWhenAppDidBecomeReady { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
strongSelf.showLockScreenOrMainContent()
self?.showLockScreenOrMainContent()
}
}
@ -143,11 +145,9 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
Logger.info("")
if OWSScreenLock.shared.isScreenLockEnabled() {
self.dismiss(animated: false) { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
strongSelf.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
}
@ -161,13 +161,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
ExitShareExtension()
}
// MARK: App Mode
public func getCurrentAppMode() -> AppMode {
guard let window = self.view.window else { return .light }
let userInterfaceStyle = window.traitCollection.userInterfaceStyle
let isLightMode = (userInterfaceStyle == .light || userInterfaceStyle == .unspecified)
return isLightMode ? .light : .dark
}
// MARK: - App Mode
public func setCurrentAppMode(to appMode: AppMode) {
return // Not applicable to share extensions
@ -181,7 +175,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
private func showLockScreenOrMainContent() {
if OWSScreenLock.shared.isScreenLockEnabled() {
showLockScreen()
} else {
}
else {
showMainContent()
}
}
@ -192,16 +187,23 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
}
private func showMainContent() {
let threadPickerVC = ThreadPickerVC()
let threadPickerVC: ThreadPickerVC = ThreadPickerVC()
threadPickerVC.shareVC = self
setViewControllers([ threadPickerVC ], animated: false)
let promise = buildAttachments()
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false, message: NSLocalizedString("vc_share_loading_message", comment: "")) { activityIndicator in
promise.done { _ in
activityIndicator.dismiss { }
}.catch { _ in
activityIndicator.dismiss { }
}
ModalActivityIndicatorViewController.present(
fromViewController: self,
canCancel: false,
message: "vc_share_loading_message".localized()) { activityIndicator in
promise
.done { _ in
activityIndicator.dismiss { }
}
.catch { _ in
activityIndicator.dismiss { }
}
}
ShareVC.attachmentPrepPromise = promise
}
@ -220,7 +222,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
func shareViewFailed(error: Error) {
let alert = UIAlertController(title: "Session", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { _ in
alert.addAction(UIAlertAction(title: "BUTTON_OK".localized(), style: .default, handler: { _ in
self.extensionContext!.cancelRequest(withError: error)
}))
present(alert, animated: true, completion: nil)
@ -236,22 +238,29 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
guard let firstUtiType = itemProvider.registeredTypeIdentifiers.first else {
return false
}
return firstUtiType == utiType
return (firstUtiType == utiType)
}
private class func isVisualMediaItem(itemProvider: NSItemProvider) -> Bool {
return (itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) ||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String))
return (
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) ||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String)
)
}
private class func isUrlItem(itemProvider: NSItemProvider) -> Bool {
return itemMatchesSpecificUtiType(itemProvider: itemProvider,
utiType: kUTTypeURL as String)
return itemMatchesSpecificUtiType(
itemProvider: itemProvider,
utiType: kUTTypeURL as String
)
}
private class func isContactItem(itemProvider: NSItemProvider) -> Bool {
return itemMatchesSpecificUtiType(itemProvider: itemProvider,
utiType: kUTTypeContact as String)
return itemMatchesSpecificUtiType(
itemProvider: itemProvider,
utiType: kUTTypeContact as String
)
}
private class func utiType(itemProvider: NSItemProvider) -> String? {
@ -259,7 +268,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
if isUrlItem(itemProvider: itemProvider) {
return kUTTypeURL as String
} else if isContactItem(itemProvider: itemProvider) {
}
else if isContactItem(itemProvider: itemProvider) {
return kUTTypeContact as String
}
@ -278,43 +288,43 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// and send them as normal text messages if possible.
let urlString = url.absoluteString
return DataSourceValue.dataSource(withOversizeText: urlString)
} else if UTTypeConformsTo(utiType as CFString, kUTTypeText) {
}
else if UTTypeConformsTo(utiType as CFString, kUTTypeText) {
// Share text as oversize text messages.
//
// NOTE: SharingThreadPickerViewController will try to unpack them
// and send them as normal text messages if possible.
return DataSourcePath.dataSource(with: url,
shouldDeleteOnDeallocation: false)
} else {
guard let dataSource = DataSourcePath.dataSource(with: url,
shouldDeleteOnDeallocation: false) else {
return nil
}
if let customFileName = customFileName {
dataSource.sourceFilename = customFileName
} else {
// Ignore the filename for URLs.
dataSource.sourceFilename = url.lastPathComponent
}
return dataSource
return DataSourcePath.dataSource(
with: url,
shouldDeleteOnDeallocation: false
)
}
guard let dataSource = DataSourcePath.dataSource(with: url, shouldDeleteOnDeallocation: false) else {
return nil
}
// Fallback to the last part of the URL
dataSource.sourceFilename = (customFileName ?? url.lastPathComponent)
return dataSource
}
private class func preferredItemProviders(inputItem: NSExtensionItem) -> [NSItemProvider]? {
guard let attachments = inputItem.attachments else {
return nil
}
guard let attachments = inputItem.attachments else { return nil }
var visualMediaItemProviders = [NSItemProvider]()
var hasNonVisualMedia = false
for attachment in attachments {
if isVisualMediaItem(itemProvider: attachment) {
visualMediaItemProviders.append(attachment)
} else {
}
else {
hasNonVisualMedia = true
}
}
// Only allow multiple-attachment sends if all attachments
// are visual media.
if visualMediaItemProviders.count > 0 && !hasNonVisualMedia {
@ -334,6 +344,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
guard let itemProvider = attachment as? NSItemProvider else {
return false
}
return isUrlItem(itemProvider: itemProvider)
}) {
return [preferredAttachment]
@ -342,9 +353,11 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// else return whatever is available
if let itemProvider = inputItem.attachments?.first {
return [itemProvider]
} else {
}
else {
owsFailDebug("Missing attachment.")
}
return []
}
@ -359,6 +372,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
Logger.error("invalid inputItem \(inputItemRaw)")
continue
}
if let itemProviders = ShareVC.preferredItemProviders(inputItem: inputItem) {
return Promise.value(itemProviders)
}
@ -366,6 +380,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
let error = ShareViewControllerError.assertionError(description: "no input item")
return Promise(error: error)
}
// MARK: - LoadedItem
private
struct LoadedItem {

@ -4,71 +4,102 @@ import SessionUIKit
final class SimplifiedConversationCell : UITableViewCell {
var threadViewModel: ThreadViewModel! { didSet { update() } }
static let reuseIdentifier = "SimplifiedConversationCell"
// MARK: - Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
// MARK: - UI
private lazy var stackView: UIStackView = {
let stackView: UIStackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
return stackView
}()
// MARK: UI Components
private lazy var accentLineView: UIView = {
let result = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.backgroundColor = Colors.destructive
return result
}()
private lazy var profilePictureView = ProfilePictureView()
private lazy var profilePictureView: ProfilePictureView = {
let view: ProfilePictureView = ProfilePictureView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var displayNameLabel: UILabel = {
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.textColor = Colors.text
result.lineBreakMode = .byTruncatingTail
return result
}()
// MARK: Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
// MARK: - Initialization
private func setUpViewHierarchy() {
// Background color
backgroundColor = Colors.cellBackground
// Highlight color
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = Colors.cellSelected
self.selectedBackgroundView = selectedBackgroundView
// Accent line view
addSubview(stackView)
stackView.addArrangedSubview(accentLineView)
stackView.addArrangedSubview(profilePictureView)
stackView.addArrangedSubview(displayNameLabel)
stackView.addArrangedSubview(UIView.hSpacer(0))
setupLayout()
}
// MARK: - Layout
private func setupLayout() {
accentLineView.set(.width, to: Values.accentLineThickness)
accentLineView.set(.height, to: 68)
// Profile picture view
let profilePictureViewSize = Values.mediumProfilePictureSize
profilePictureView.set(.width, to: profilePictureViewSize)
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Main stack view
let stackView = UIStackView(arrangedSubviews: [ accentLineView, profilePictureView, displayNameLabel, UIView.hSpacer(0) ])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
addSubview(stackView)
stackView.pin(to: self)
}
// MARK: Updating
// MARK: - Content
private func update() {
AssertIsOnMainThread()
guard let thread = threadViewModel?.threadRecord else { return }
let isBlocked: Bool
if let thread = thread as? TSContactThread {
isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID())
} else {
isBlocked = false
}
accentLineView.alpha = isBlocked ? 1 : 0
accentLineView.alpha = (isBlocked ? 1 : 0)
profilePictureView.update(for: thread)
displayNameLabel.text = getDisplayName()
}
@ -76,17 +107,25 @@ final class SimplifiedConversationCell : UITableViewCell {
private func getDisplayName() -> String {
if threadViewModel.isGroupThread {
if threadViewModel.name.isEmpty {
// TODO: Localization
return "Unknown Group"
} else {
return threadViewModel.name
}
} else {
if threadViewModel.threadRecord.isNoteToSelf() {
return NSLocalizedString("NOTE_TO_SELF", comment: "")
} else {
let hexEncodedPublicKey = threadViewModel.contactSessionID!
return Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey
}
return threadViewModel.name
}
if threadViewModel.threadRecord.isNoteToSelf() {
return "NOTE_TO_SELF".localized()
}
guard let hexEncodedPublicKey: String = threadViewModel.contactSessionID else {
// TODO: Localization
return "Unknown"
}
return (
Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ??
hexEncodedPublicKey
)
}
}

@ -1,8 +1,12 @@
import UIKit
import SignalUtilitiesKit
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate {
final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate {
private var threads: YapDatabaseViewMappings!
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
private var threadViewModelCache: [String: ThreadViewModel] = [:] // Thread ID to ThreadViewModel
private var selectedThread: TSThread?
var shareVC: ShareVC?
@ -15,32 +19,50 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
result.objectCacheLimit = 500
return result
}()
// MARK: - UI
private lazy var titleLabel: UILabel = {
let titleLabel: UILabel = UILabel()
titleLabel.text = "vc_share_title".localized()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
return titleLabel
}()
private lazy var tableView: UITableView = {
let result = UITableView()
result.backgroundColor = .clear
result.separatorStyle = .none
result.register(SimplifiedConversationCell.self, forCellReuseIdentifier: SimplifiedConversationCell.reuseIdentifier)
result.showsVerticalScrollIndicator = false
return result
let tableView: UITableView = UITableView()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.register(view: SimplifiedConversationCell.self)
tableView.showsVerticalScrollIndicator = false
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
private lazy var fadeView: UIView = {
let result = UIView()
let view = UIView()
let gradient = Gradients.homeVCFade
result.setGradient(gradient)
result.isUserInteractionEnabled = false
return result
view.setGradient(gradient)
view.isUserInteractionEnabled = false
return view
}()
// MARK: Lifecycle
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
// Gradient
view.backgroundColor = .clear
let gradient = Gradients.defaultBackground
view.setGradient(gradient)
view.setGradient(Gradients.defaultBackground)
// Threads
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
threads = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName) // The extension should be registered at this point
@ -48,23 +70,16 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
dbConnection.read { transaction in
self.threads.update(with: transaction) // Perform the initial update
}
// Title
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("vc_share_title", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel
// Table view
tableView.dataSource = self
tableView.delegate = self
view.addSubview(tableView)
tableView.pin(to: view)
view.addSubview(fadeView)
fadeView.pin(.leading, to: .leading, of: view)
let topInset = 0.15 * view.height()
fadeView.pin(.top, to: .top, of: view, withInset: topInset)
fadeView.pin(.trailing, to: .trailing, of: view)
fadeView.pin(.bottom, to: .bottom, of: view)
setupLayout()
// Reload
reload()
}
@ -80,18 +95,37 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
view.setGradient(Gradients.defaultBackground)
fadeView.setGradient(Gradients.homeVCFade)
}
// MARK: Layout
private func setupLayout() {
let topInset = 0.15 * view.height()
tableView.pin(to: view)
fadeView.pin(.leading, to: .leading, of: view)
fadeView.pin(.top, to: .top, of: view, withInset: topInset)
fadeView.pin(.trailing, to: .trailing, of: view)
fadeView.pin(.bottom, to: .bottom, of: view)
}
// MARK: Table View Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Int(threadCount)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: SimplifiedConversationCell.reuseIdentifier) as! SimplifiedConversationCell
let cell: SimplifiedConversationCell = tableView.dequeue(type: SimplifiedConversationCell.self, for: indexPath)
cell.threadViewModel = threadViewModel(at: indexPath.row)
return cell
}
// MARK: Updating
// MARK: - Updating
private func reload() {
AssertIsOnMainThread()
dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
@ -102,46 +136,89 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
tableView.reloadData()
}
// MARK: Interaction
// MARK: - Interaction
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let thread = self.thread(at: indexPath.row), let attachments = ShareVC.attachmentPrepPromise?.value else { return }
self.selectedThread = thread
tableView.deselectRow(at: indexPath, animated: true)
guard let thread = self.thread(at: indexPath.row), let attachments = ShareVC.attachmentPrepPromise?.value else {
return
}
self.selectedThread = thread
let approvalVC = AttachmentApprovalViewController.wrappedInNavController(attachments: attachments, approvalDelegate: self)
navigationController!.present(approvalVC, animated: true, completion: nil)
}
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], messageText: String?) {
// Sharing a URL or plain text will populate the 'messageText' field so in those
// cases we should ignore the attachments
let isSharingUrl: Bool = (attachments.count == 1 && attachments[0].isUrl)
let isSharingText: Bool = (attachments.count == 1 && attachments[0].isText)
let finalAttachments: [SignalAttachment] = (isSharingUrl || isSharingText ? [] : attachments)
let message = VisibleMessage()
message.sentTimestamp = NSDate.millisecondTimestamp()
message.text = messageText
message.text = (isSharingUrl && (messageText?.isEmpty == true || attachments[0].linkPreviewDraft == nil) ?
(
(messageText?.isEmpty == true ?
attachments[0].text() :
"\(attachments[0].text() ?? "")\n\n\(messageText ?? "")"
)
) :
messageText
)
let tsMessage = TSOutgoingMessage.from(message, associatedWith: selectedThread!)
Storage.write { transaction in
tsMessage.save(with: transaction)
}
shareVC!.dismiss(animated: true, completion: nil)
ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: NSLocalizedString("vc_share_sending_message", comment: "")) { activityIndicator in
MessageSender.sendNonDurably(message, with: attachments, in: self.selectedThread!).done { [weak self] _ in
guard let self = self else { return }
activityIndicator.dismiss { }
self.shareVC!.shareViewWasCompleted()
}.catch { [weak self] error in
guard let self = self else { return }
activityIndicator.dismiss { }
self.shareVC!.shareViewFailed(error: error)
Storage.write(
with: { transaction in
if isSharingUrl {
message.linkPreview = VisibleMessage.LinkPreview.from(
attachments[0].linkPreviewDraft,
using: transaction
)
}
else {
tsMessage.save(with: transaction)
}
},
completion: {
if isSharingUrl {
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
Storage.write { transaction in
tsMessage.save(with: transaction)
}
}
}
)
shareVC!.dismiss(animated: true, completion: nil)
ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
MessageSender.sendNonDurably(message, with: finalAttachments, in: self.selectedThread!)
.done { [weak self] _ in
activityIndicator.dismiss { }
self?.shareVC?.shareViewWasCompleted()
}
.catch { [weak self] error in
activityIndicator.dismiss { }
self?.shareVC?.shareViewFailed(error: error)
}
}
}
func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController) {
// Do nothing
dismiss(animated: true, completion: nil)
}
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didChangeMessageText newMessageText: String?) {
// Do nothing
}
// MARK: Convenience
// MARK: - Convenience
private func thread(at index: Int) -> TSThread? {
var thread: TSThread? = nil
dbConnection.read { transaction in
@ -153,9 +230,11 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
private func threadViewModel(at index: Int) -> ThreadViewModel? {
guard let thread = thread(at: index) else { return nil }
if let cachedThreadViewModel = threadViewModelCache[thread.uniqueId!] {
return cachedThreadViewModel
} else {
}
else {
var threadViewModel: ThreadViewModel? = nil
dbConnection.read { transaction in
threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)

@ -304,9 +304,10 @@ public final class SnodeAPI : NSObject {
let onsName = onsName.lowercased()
// Hash the ONS name using BLAKE2b
let nameAsData = [UInt8](onsName.data(using: String.Encoding.utf8)!)
guard let nameHash = sodium.genericHash.hash(message: nameAsData),
let base64EncodedNameHash = nameHash.toBase64() else { return Promise(error: Error.hashingFailed) }
guard let nameHash = sodium.genericHash.hash(message: nameAsData) else { return Promise(error: Error.hashingFailed) }
// Ask 3 different snodes for the Session ID associated with the given name hash
let base64EncodedNameHash = nameHash.toBase64()
let parameters: [String:Any] = [
"endpoint" : "ons_resolve",
"params" : [
@ -473,7 +474,7 @@ public final class SnodeAPI : NSObject {
"pubkey" : userX25519PublicKey,
"pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(),
"messages": serverHashes,
"signature": signature.toBase64()!
"signature": signature.toBase64()
]
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters).map2{ rawResponse -> [String:Bool] in
@ -520,7 +521,7 @@ public final class SnodeAPI : NSObject {
"pubkey" : userX25519PublicKey,
"pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(),
"timestamp" : timestamp,
"signature" : signature.toBase64()!
"signature" : signature.toBase64()
]
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
invoke(.clearAllData, on: snode, parameters: parameters).map2 { rawResponse -> [String:Bool] in

@ -29,16 +29,8 @@ public final class SearchBar : UISearchBar {
searchTextField.backgroundColor = Colors.searchBarBackground // The search bar background color
searchTextField.textColor = Colors.text
searchTextField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: ""), attributes: [ .foregroundColor : Colors.searchBarPlaceholder ])
searchTextField.keyboardAppearance = .dark
setPositionAdjustment(UIOffset(horizontal: 4, vertical: 0), for: UISearchBar.Icon.search)
searchTextPositionAdjustment = UIOffset(horizontal: 2, vertical: 0)
setPositionAdjustment(UIOffset(horizontal: -4, vertical: 0), for: UISearchBar.Icon.clear)
searchTextField.removeConstraints(searchTextField.constraints)
searchTextField.pin(.leading, to: .leading, of: searchTextField.superview!, withInset: Values.mediumSpacing + 3)
searchTextField.pin(.top, to: .top, of: searchTextField.superview!, withInset: 10)
searchTextField.superview!.pin(.trailing, to: .trailing, of: searchTextField, withInset: Values.mediumSpacing + 3)
searchTextField.superview!.pin(.bottom, to: .bottom, of: searchTextField, withInset: 10)
searchTextField.set(.height, to: Values.searchBarHeight)
searchTextField.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing)
}
}

@ -1,11 +1,11 @@
import Foundation
import UIKit
@objc(LKAppModeManager)
public final class AppModeManager : NSObject {
private let delegate: AppModeManagerDelegate
public var currentAppMode: AppMode {
return delegate.getCurrentAppMode()
return AppModeManager.getAppModeOrSystemDefault()
}
public static var shared: AppModeManager!
@ -29,19 +29,33 @@ public final class AppModeManager : NSObject {
public func setAppModeToSystemDefault() {
delegate.setAppModeToSystemDefault()
}
@objc public static func getAppModeOrSystemDefault() -> AppMode {
let userDefaults = UserDefaults.standard
guard userDefaults.dictionaryRepresentation().keys.contains("appMode") else {
if #available(iOS 13.0, *) {
return UITraitCollection.current.userInterfaceStyle == .dark ? .dark : .light
}
return .light
}
let mode = userDefaults.integer(forKey: "appMode")
return AppMode(rawValue: mode) ?? .light
}
}
@objc(LKAppModeManagerDelegate)
public protocol AppModeManagerDelegate {
func getCurrentAppMode() -> AppMode
@objc(setCurrentAppMode:)
func setCurrentAppMode(to appMode: AppMode)
func setAppModeToSystemDefault()
}
@objc(LKAppMode)
public enum AppMode : Int {
public enum AppMode: Int {
case light, dark
}

@ -13,6 +13,7 @@ import UIKit
@objc(LKColors)
public final class Colors : NSObject {
@objc public static var grey: UIColor { UIColor(named: "session_grey")! }
@objc public static var accent: UIColor { UIColor(named: "session_accent")! }
@objc public static var text: UIColor { UIColor(named: "session_text")! }
@objc public static var destructive: UIColor { UIColor(named: "session_destructive")! }
@ -42,4 +43,5 @@ public final class Colors : NSObject {
@objc public static var pnOptionBorder: UIColor { UIColor(named: "session_pn_option_border")! }
@objc public static var pathsBuilding: UIColor { UIColor(named: "session_paths_building")! }
@objc public static var pinIcon: UIColor { UIColor(named: "session_pin_icon")! }
@objc public static var sessionHeading: UIColor { UIColor(named: "session_heading")! }
}

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFC",
"green" : "0xFC",
"red" : "0xFC"
"blue" : "252",
"green" : "252",
"red" : "252"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x1B",
"green" : "0x1B",
"red" : "0x1B"
"blue" : "22",
"green" : "22",
"red" : "22"
}
},
"idiom" : "universal"

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF0",
"green" : "0xF0",
"red" : "0xF0"
"blue" : "247",
"green" : "247",
"red" : "247"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x40",
"green" : "0x40",
"red" : "0x40"
"blue" : "28",
"green" : "28",
"red" : "28"
}
},
"idiom" : "universal"

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x30",
"green" : "0x2F",
"red" : "0x31"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x00",
"red" : "0x00"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x82",
"green" : "0xF7",
"red" : "0x00"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFC",
"green" : "0xFC",
"red" : "0xFC"
"blue" : "252",
"green" : "252",
"red" : "252"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x16",
"green" : "0x16",
"red" : "0x16"
"blue" : "22",
"green" : "22",
"red" : "22"
}
},
"idiom" : "universal"

@ -95,4 +95,17 @@ public extension UIView {
constraint.isActive = true
return constraint
}
@discardableResult
func set(_ dimension: Dimension, greaterThanOrEqualTo size: CGFloat) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch dimension {
case .width: return widthAnchor.constraint(greaterThanOrEqualToConstant: size)
case .height: return heightAnchor.constraint(greaterThanOrEqualToConstant: size)
}
}()
constraint.isActive = true
return constraint
}
}

@ -35,6 +35,7 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
@property (nonatomic, readonly) BOOL isMainApp;
@property (nonatomic, readonly) BOOL isMainAppAndActive;
@property (nonatomic, readonly) BOOL isShareExtension;
/// Whether the app was woken up by a silent push notification. This is important for determining whether attachments should be downloaded or not.
@property (nonatomic) BOOL wasWokenUpByPushNotification;

@ -0,0 +1,16 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public protocol ReusableView: AnyObject {
static var defaultReuseIdentifier: String { get }
}
public extension ReusableView where Self: UIView {
static var defaultReuseIdentifier: String {
return String(describing: self.self)
}
}
extension UITableViewCell: ReusableView {}
extension UITableViewHeaderFooterView: ReusableView {}

@ -0,0 +1,13 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import SignalCoreKit
public extension String {
func localized() -> String {
// If the localized string matches the key provided then the localisation failed
let localizedString = NSLocalizedString(self, comment: "")
owsAssertDebug(localizedString != self, "Key \"\(self)\" is not set in Localizable.strings")
return localizedString
}
}

@ -0,0 +1,23 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public extension UITableView {
func register<View>(view: View.Type) where View: UITableViewCell {
register(view.self, forCellReuseIdentifier: view.defaultReuseIdentifier)
}
func registerHeaderFooterView<View>(view: View.Type) where View: UITableViewHeaderFooterView {
register(view.self, forHeaderFooterViewReuseIdentifier: view.defaultReuseIdentifier)
}
func dequeue<T>(type: T.Type, for indexPath: IndexPath) -> T where T: UITableViewCell {
let reuseIdentifier = T.defaultReuseIdentifier
return dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! T
}
func dequeueHeaderFooterView<T>(type: T.Type) -> T where T: UITableViewHeaderFooterView {
let reuseIdentifier = T.defaultReuseIdentifier
return dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! T
}
}

@ -4,6 +4,7 @@ NS_ASSUME_NONNULL_BEGIN
extern NSString *const OWSMimeTypeApplicationOctetStream;
extern NSString *const OWSMimeTypeApplicationZip;
extern NSString *const OWSMimeTypeApplicationPdf;
extern NSString *const OWSMimeTypeImagePng;
extern NSString *const OWSMimeTypeImageJpeg;
extern NSString *const OWSMimeTypeImageGif;
@ -40,6 +41,8 @@ extern NSString *const kSyncMessageFileExtension;
+ (BOOL)isImage:(NSString *)contentType;
+ (BOOL)isVideo:(NSString *)contentType;
+ (BOOL)isAudio:(NSString *)contentType;
+ (BOOL)isText:(NSString *)contentType;
+ (BOOL)isMicrosoftDoc:(NSString *)contentType;
+ (BOOL)isVisualMedia:(NSString *)contentType;
// filename is optional and should not be trusted.

@ -22,6 +22,7 @@ NSString *const OWSMimeTypeImageBmp2 = @"image/x-windows-bmp";
NSString *const OWSMimeTypeOversizeTextMessage = @"text/x-signal-plain";
NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype";
NSString *const OWSMimeTypeApplicationZip = @"application/zip";
NSString *const OWSMimeTypeApplicationPdf = @"application/pdf";
NSString *const kOversizeTextAttachmentUTI = @"org.whispersystems.oversize-text-attachment";
NSString *const kOversizeTextAttachmentFileExtension = @"txt";
@ -278,6 +279,53 @@ NSString *const kSyncMessageFileExtension = @"bin";
return [MIMETypeUtil isSupportedAudioMIMEType:contentType];
}
+ (BOOL)isText:(NSString *)contentType {
return [
@[
@"text/plain",
@"text/csv",
@"text/tab-separated-values"
]
containsObject:contentType
];
}
+ (BOOL)isMicrosoftDoc:(NSString *)contentType {
return [
@[
// Word files
@"application/msword",
@"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
@"application/vnd.ms-word.document.macroEnabled.12",
@"application/vnd.ms-word.template.macroEnabled.12",
// Excel files
@"application/vnd.ms-excel",
@"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
@"application/vnd.ms-excel.sheet.macroEnabled.12",
@"application/vnd.ms-excel.template.macroEnabled.12",
@"application/vnd.ms-excel.addin.macroEnabled.12",
@"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
// Powerpoint files
@"application/vnd.ms-powerpoint",
@"application/vnd.openxmlformats-officedocument.presentationml.presentation",
@"application/vnd.openxmlformats-officedocument.presentationml.template",
@"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
@"application/vnd.ms-powerpoint.addin.macroEnabled.12",
@"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
@"application/vnd.ms-powerpoint.template.macroEnabled.12",
@"application/vnd.ms-powerpoint.slideshow.macroEnabled.12"
]
containsObject:contentType
];
}
+ (BOOL)isVisualMedia:(NSString *)contentType
{
if ([self isImage:contentType]) {

@ -594,7 +594,7 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
removeAssetRequestFromQueue(assetRequest: assetRequest)
return
}
guard CurrentAppContext().isMainAppAndActive else {
guard CurrentAppContext().isMainAppAndActive || CurrentAppContext().isShareExtension else {
// If app is not active, fail the asset request.
assetRequest.state = .failed
assetRequestDidFail(assetRequest: assetRequest)

@ -191,6 +191,7 @@ extension AttachmentApprovalInputAccessoryView: AttachmentCaptionToolbarDelegate
return
}
// TODO: Look at refactoring this behaviour to consolidate attachment mutations
currentAttachmentItem.attachment.captionText = attachmentCaptionToolbar.textView.text
delegate?.attachmentApprovalInputUpdateMediaRail()

@ -10,17 +10,25 @@ import SessionUIKit
import CoreServices
@objc
public protocol AttachmentApprovalViewControllerDelegate: class {
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController,
didApproveAttachments attachments: [SignalAttachment], messageText: String?)
public protocol AttachmentApprovalViewControllerDelegate: AnyObject {
func attachmentApproval(
_ attachmentApproval: AttachmentApprovalViewController,
didApproveAttachments attachments: [SignalAttachment],
messageText: String?
)
func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController)
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController,
didChangeMessageText newMessageText: String?)
func attachmentApproval(
_ attachmentApproval: AttachmentApprovalViewController,
didChangeMessageText newMessageText: String?
)
@objc
optional func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didRemoveAttachment attachment: SignalAttachment)
optional func attachmentApproval(
_ attachmentApproval: AttachmentApprovalViewController,
didRemoveAttachment attachment: SignalAttachment
)
@objc
optional func attachmentApprovalDidTapAddMore(_ attachmentApproval: AttachmentApprovalViewController)
@ -38,19 +46,72 @@ public enum AttachmentApprovalViewControllerMode: UInt {
@objc
public class AttachmentApprovalViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
@objc public enum Mode: UInt {
case modal
case sharedNavigation
}
// MARK: - Properties
private let mode: AttachmentApprovalViewControllerMode
private let mode: Mode
private let isAddMoreVisible: Bool
public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate?
public var isEditingCaptions = false {
didSet {
updateContents()
didSet { updateContents() }
}
let attachmentItemCollection: AttachmentItemCollection
var attachmentItems: [SignalAttachmentItem] {
return attachmentItemCollection.attachmentItems
}
var attachments: [SignalAttachment] {
return attachmentItems.map { (attachmentItem) in
autoreleasepool {
return self.processedAttachment(forAttachmentItem: attachmentItem)
}
}
}
public var pageViewControllers: [AttachmentPrepViewController]? {
return viewControllers?.compactMap { $0 as? AttachmentPrepViewController }
}
public var currentPageViewController: AttachmentPrepViewController? {
return pageViewControllers?.first
}
var currentItem: SignalAttachmentItem? {
get { return currentPageViewController?.attachmentItem }
set { setCurrentItem(newValue, direction: .forward, animated: false) }
}
private var cachedPages: [SignalAttachmentItem: AttachmentPrepViewController] = [:]
public var shouldHideControls: Bool {
guard let pageViewController: AttachmentPrepViewController = pageViewControllers?.first else {
return false
}
return pageViewController.shouldHideControls
}
override public var inputAccessoryView: UIView? {
bottomToolView.layoutIfNeeded()
return bottomToolView
}
override public var canBecomeFirstResponder: Bool {
return !shouldHideControls
}
public var messageText: String? {
get { return bottomToolView.attachmentTextToolbar.messageText }
set { bottomToolView.attachmentTextToolbar.messageText = newValue }
}
// MARK: - Initializers
@ -59,29 +120,34 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
notImplemented()
}
let kSpacingBetweenItems: CGFloat = 20
@objc
required public init(mode: AttachmentApprovalViewControllerMode,
attachments: [SignalAttachment]) {
required public init(
mode: Mode,
attachments: [SignalAttachment]
) {
assert(attachments.count > 0)
self.mode = mode
let attachmentItems = attachments.map { SignalAttachmentItem(attachment: $0 )}
self.isAddMoreVisible = mode == .sharedNavigation
self.isAddMoreVisible = (mode == .sharedNavigation)
self.attachmentItemCollection = AttachmentItemCollection(attachmentItems: attachmentItems, isAddMoreVisible: isAddMoreVisible)
let options: [UIPageViewController.OptionsKey: Any] = [.interPageSpacing: kSpacingBetweenItems]
super.init(transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: options)
super.init(
transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: [
.interPageSpacing: kSpacingBetweenItems
]
)
self.dataSource = self
self.delegate = self
NotificationCenter.default.addObserver(self,
selector: #selector(didBecomeActive),
name: NSNotification.Name.OWSApplicationDidBecomeActive,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(didBecomeActive),
name: .OWSApplicationDidBecomeActive,
object: nil
)
}
deinit {
@ -89,62 +155,78 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
}
@objc
public class func wrappedInNavController(attachments: [SignalAttachment], approvalDelegate: AttachmentApprovalViewControllerDelegate) -> OWSNavigationController {
public class func wrappedInNavController(
attachments: [SignalAttachment],
approvalDelegate: AttachmentApprovalViewControllerDelegate
) -> OWSNavigationController {
let vc = AttachmentApprovalViewController(mode: .modal, attachments: attachments)
vc.approvalDelegate = approvalDelegate
let navController = OWSNavigationController(rootViewController: vc)
navController.ows_prefersStatusBarHidden = true
return navController
}
// MARK: - Notifications
@objc func didBecomeActive() {
AssertIsOnMainThread()
updateContents()
}
// MARK: - Subviews
var galleryRailView: GalleryRailView {
return bottomToolView.galleryRailView
}
var attachmentTextToolbar: AttachmentTextToolbar {
return bottomToolView.attachmentTextToolbar
}
lazy var bottomToolView: AttachmentApprovalInputAccessoryView = {
// MARK: - UI
private let kSpacingBetweenItems: CGFloat = 20
public override var prefersStatusBarHidden: Bool { return true }
private lazy var bottomToolView: AttachmentApprovalInputAccessoryView = {
let bottomToolView = AttachmentApprovalInputAccessoryView()
bottomToolView.delegate = self
bottomToolView.attachmentTextToolbar.attachmentTextToolbarDelegate = self
bottomToolView.galleryRailView.delegate = self
return bottomToolView
}()
lazy var touchInterceptorView = UIView()
private var galleryRailView: GalleryRailView { return bottomToolView.galleryRailView }
// MARK: - View Lifecycle
private lazy var touchInterceptorView: UIView = {
let view: UIView = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isHidden = true
let tapGesture = UITapGestureRecognizer(
target: self,
action: #selector(didTapTouchInterceptorView(gesture:))
)
view.addGestureRecognizer(tapGesture)
return view
}()
public override var prefersStatusBarHidden: Bool {
return true
}
private lazy var pagerScrollView: UIScrollView? = {
// This is kind of a hack. Since we don't have first class access to the superview's `scrollView`
// we traverse the view hierarchy until we find it.
let pagerScrollView = view.subviews.first { $0 is UIScrollView } as? UIScrollView
assert(pagerScrollView != nil)
return pagerScrollView
}()
// MARK: - Lifecycle
override public func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Colors.navigationBarBackground
// avoid an unpleasant "bounce" which doesn't make sense in the context of a single item.
pagerScrollView?.isScrollEnabled = attachmentItems.count > 1
// Bottom Toolbar
galleryRailView.delegate = self
attachmentTextToolbar.attachmentTextToolbarDelegate = self
// Navigation
let backgroundImage: UIImage = UIImage(color: Colors.navigationBarBackground)
self.navigationItem.title = nil
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.barTintColor = Colors.navigationBarBackground
(self.navigationController?.navigationBar as? OWSNavigationBar)?.respectsTheme = true
self.navigationController?.navigationBar.backgroundColor = Colors.navigationBarBackground
self.navigationController?.navigationBar.setBackgroundImage(backgroundImage, for: .default)
// Avoid an unpleasant "bounce" which doesn't make sense in the context of a single item.
pagerScrollView?.isScrollEnabled = (attachmentItems.count > 1)
guard let firstItem = attachmentItems.first else {
owsFailDebug("firstItem was unexpectedly nil")
@ -152,36 +234,27 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
}
self.setCurrentItem(firstItem, direction: .forward, animated: false)
view.addSubview(touchInterceptorView)
// layout immediately to avoid animating the layout process during the transition
self.currentPageViewController.view.layoutIfNeeded()
UIView.performWithoutAnimation {
self.currentPageViewController?.view.layoutIfNeeded()
}
// If the first item is just text, or is a URL and LinkPreviews are disabled
// then just fill the 'message' box with it
if firstItem.attachment.isText || (firstItem.attachment.isUrl && OWSLinkPreview.previewURL(forRawBodyText: firstItem.attachment.text()) == nil) {
bottomToolView.attachmentTextToolbar.messageText = firstItem.attachment.text()
}
view.addSubview(touchInterceptorView)
touchInterceptorView.autoPinEdgesToSuperviewEdges()
touchInterceptorView.isHidden = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:)))
touchInterceptorView.addGestureRecognizer(tapGesture)
setupLayout()
}
override public func viewWillAppear(_ animated: Bool) {
Logger.debug("")
super.viewWillAppear(animated)
guard let navigationBar = navigationController?.navigationBar as? OWSNavigationBar else {
owsFailDebug("navigationBar was nil or unexpected class")
return
}
// Loki: Set navigation bar background color
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground
navigationBar.respectsTheme = true
navigationBar.backgroundColor = Colors.navigationBarBackground
let backgroundImage = UIImage(color: Colors.navigationBarBackground)
navigationBar.setBackgroundImage(backgroundImage, for: .default)
updateContents()
}
@ -197,7 +270,23 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
Logger.debug("")
super.viewWillDisappear(animated)
}
// MARK: - Layout
private func setupLayout() {
touchInterceptorView.autoPinEdgesToSuperviewEdges()
}
// MARK: - Notifications
@objc func didBecomeActive() {
AssertIsOnMainThread()
updateContents()
}
// MARK: - Contents
private func updateContents() {
updateNavigationBar()
updateInputAccessory()
@ -207,40 +296,26 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
// MARK: - Input Accessory
override public var inputAccessoryView: UIView? {
bottomToolView.layoutIfNeeded()
return bottomToolView
}
override public var canBecomeFirstResponder: Bool {
return !shouldHideControls
}
public func updateInputAccessory() {
var currentPageViewController: AttachmentPrepViewController?
if pageViewControllers.count == 1 {
currentPageViewController = pageViewControllers.first
if pageViewControllers?.count == 1 {
currentPageViewController = pageViewControllers?.first
}
let currentAttachmentItem: SignalAttachmentItem? = currentPageViewController?.attachmentItem
let hasPresentedView = self.presentedViewController != nil
let hasPresentedView = (self.presentedViewController != nil)
let isToolbarFirstResponder = bottomToolView.hasFirstResponder
if !shouldHideControls, !isFirstResponder, !hasPresentedView, !isToolbarFirstResponder {
becomeFirstResponder()
}
bottomToolView.update(isEditingCaptions: isEditingCaptions,
currentAttachmentItem: currentAttachmentItem,
shouldHideControls: shouldHideControls)
}
public var messageText: String? {
get {
return attachmentTextToolbar.messageText
}
set {
attachmentTextToolbar.messageText = newValue
}
bottomToolView.update(
isEditingCaptions: isEditingCaptions,
currentAttachmentItem: currentAttachmentItem,
shouldHideControls: shouldHideControls
)
}
// MARK: - Navigation Bar
@ -254,10 +329,18 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
guard !isEditingCaptions else {
// Hide all navigation bar items while the caption view is open.
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_TITLE", comment: "Title for 'caption' mode of the attachment approval view."), style: .plain, target: nil, action: nil)
let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full",
selector: #selector(didTapCaptionDone(sender:)))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(
//"Title for 'caption' mode of the attachment approval view."
title: "ATTACHMENT_APPROVAL_CAPTION_TITLE".localized(),
style: .plain,
target: nil,
action: nil
)
let doneButton = navigationBarButton(
imageName: "image_editor_checkmark_full",
selector: #selector(didTapCaptionDone(sender:))
)
let navigationBarItems = [doneButton]
updateNavigationBar(navigationBarItems: navigationBarItems)
return
@ -265,29 +348,23 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
var navigationBarItems = [UIView]()
if let viewControllers = viewControllers,
viewControllers.count == 1,
let firstViewController = viewControllers.first as? AttachmentPrepViewController {
if viewControllers?.count == 1, let firstViewController: AttachmentPrepViewController = viewControllers?.first as? AttachmentPrepViewController {
navigationBarItems = firstViewController.navigationBarItems()
// Show the caption UI if there's more than one attachment
// OR if the attachment already has a caption.
let attachmentCount = attachmentItemCollection.count
var shouldShowCaptionUI = attachmentCount > 0
if let captionText = firstViewController.attachmentItem.captionText, captionText.count > 0 {
shouldShowCaptionUI = true
}
if shouldShowCaptionUI {
let captionButton = navigationBarButton(imageName: "image_editor_caption",
selector: #selector(didTapCaption(sender:)))
if attachmentItemCollection.count > 0, (firstViewController.attachmentItem.captionText?.count ?? 0) > 0 {
let captionButton = navigationBarButton(
imageName: "image_editor_caption",
selector: #selector(didTapCaption(sender:))
)
navigationBarItems.append(captionButton)
}
}
updateNavigationBar(navigationBarItems: navigationBarItems)
let hasCancel = (mode != .sharedNavigation)
if hasCancel {
if mode != .sharedNavigation {
// Mimic a UIBarButtonItem of type .cancel, but with a shadow.
let cancelButton = OWSButton(title: CommonStrings.cancelButton) { [weak self] in
self?.cancelPressed()
@ -300,10 +377,11 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
}
cancelButton.sizeToFit()
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: cancelButton)
} else {
}
else {
// Mimic a conventional back button, but with a shadow.
let isRTL = CurrentAppContext().isRTL
let imageName = isRTL ? "NavBarBackRTL" : "NavBarBack"
let imageName = (isRTL ? "NavBarBackRTL" : "NavBarBack")
let backButton = OWSButton(imageName: imageName, tintColor: Colors.text) { [weak self] in
self?.navigationController?.popViewController(animated: true)
}
@ -328,40 +406,44 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
// Default back button is 1.5 pixel lower than our extracted image.
let kTopInsetPadding: CGFloat = 1.5
backButton.imageEdgeInsets = UIEdgeInsets(top: kTopInsetPadding, left: kExtraLeftPadding, bottom: 0, right: 0)
backButton.imageEdgeInsets = UIEdgeInsets(
top: kTopInsetPadding,
left: kExtraLeftPadding,
bottom: 0,
right: 0
)
var backImageSize = CGSize.zero
if let backImage = UIImage(named: imageName) {
backImageSize = backImage.size
} else {
}
else {
owsFailDebug("Missing backImage.")
}
backButton.frame = CGRect(origin: .zero, size: CGSize(width: backImageSize.width + kExtraRightPadding,
height: backImageSize.height + kExtraHeightPadding))
backButton.frame = CGRect(
origin: .zero,
size: CGSize(
width: backImageSize.width + kExtraRightPadding,
height: backImageSize.height + kExtraHeightPadding
)
)
// Note: using a custom leftBarButtonItem breaks the interactive pop gesture.
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton)
}
}
// MARK: - Control Visibility
public var shouldHideControls: Bool {
guard let pageViewController = pageViewControllers.first else {
return false
}
return pageViewController.shouldHideControls
}
// MARK: - View Helpers
func remove(attachmentItem: SignalAttachmentItem) {
if attachmentItem == currentItem {
if let nextItem = attachmentItemCollection.itemAfter(item: attachmentItem) {
setCurrentItem(nextItem, direction: .forward, animated: true)
} else if let prevItem = attachmentItemCollection.itemBefore(item: attachmentItem) {
}
else if let prevItem = attachmentItemCollection.itemBefore(item: attachmentItem) {
setCurrentItem(prevItem, direction: .reverse, animated: true)
} else {
}
else {
owsFailDebug("removing last item shouldn't be possible because rail should not be visible")
return
}
@ -372,30 +454,27 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return
}
UIView.animate(withDuration: 0.2,
animations: {
// shrink stack view item until it disappears
cell.isHidden = true
// simultaneously fade out
cell.alpha = 0
},
completion: { _ in
self.attachmentItemCollection.remove(item: attachmentItem)
self.approvalDelegate?.attachmentApproval?(self, didRemoveAttachment: attachmentItem.attachment)
self.updateMediaRail()
})
UIView.animate(
withDuration: 0.2,
animations: {
// shrink stack view item until it disappears
cell.isHidden = true
// simultaneously fade out
cell.alpha = 0
},
completion: { [weak self] _ in
self?.attachmentItemCollection.remove(item: attachmentItem)
if let strongSelf: AttachmentApprovalViewController = self {
self?.approvalDelegate?.attachmentApproval?(strongSelf, didRemoveAttachment: attachmentItem.attachment)
}
self?.updateMediaRail()
}
)
}
lazy var pagerScrollView: UIScrollView? = {
// This is kind of a hack. Since we don't have first class access to the superview's `scrollView`
// we traverse the view hierarchy until we find it.
let pagerScrollView = view.subviews.first { $0 is UIScrollView } as? UIScrollView
assert(pagerScrollView != nil)
return pagerScrollView
}()
// MARK: - UIPageViewControllerDelegate
public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
@ -440,10 +519,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
}
let currentItem = currentViewController.attachmentItem
guard let previousItem = attachmentItem(before: currentItem) else {
return nil
}
guard let previousItem = attachmentItem(before: currentItem) else { return nil }
guard let previousPage: AttachmentPrepViewController = buildPage(item: previousItem) else {
return nil
}
@ -460,10 +536,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
}
let currentItem = currentViewController.attachmentItem
guard let nextItem = attachmentItem(after: currentItem) else {
return nil
}
guard let nextItem = attachmentItem(after: currentItem) else { return nil }
guard let nextPage: AttachmentPrepViewController = buildPage(item: nextItem) else {
return nil
}
@ -471,38 +544,19 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return nextPage
}
public var currentPageViewController: AttachmentPrepViewController {
return pageViewControllers.first!
}
public var pageViewControllers: [AttachmentPrepViewController] {
return super.viewControllers!.map { $0 as! AttachmentPrepViewController }
}
@objc
public override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
super.setViewControllers(viewControllers,
direction: direction,
animated: animated) { [weak self] (finished) in
if let completion = completion {
completion(finished)
}
self?.updateContents()
super.setViewControllers(
viewControllers,
direction: direction,
animated: animated
) { [weak self] finished in
completion?(finished)
self?.updateContents()
}
}
var currentItem: SignalAttachmentItem! {
get {
return currentPageViewController.attachmentItem
}
set {
setCurrentItem(newValue, direction: .forward, animated: false)
}
}
private var cachedPages: [SignalAttachmentItem: AttachmentPrepViewController] = [:]
private func buildPage(item: SignalAttachmentItem) -> AttachmentPrepViewController? {
if let cachedPage = cachedPages[item] {
Logger.debug("cache hit.")
return cachedPage
@ -516,8 +570,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return viewController
}
private func setCurrentItem(_ item: SignalAttachmentItem, direction: UIPageViewController.NavigationDirection, animated isAnimated: Bool) {
guard let page = self.buildPage(item: item) else {
private func setCurrentItem(_ item: SignalAttachmentItem?, direction: UIPageViewController.NavigationDirection, animated isAnimated: Bool) {
guard let item: SignalAttachmentItem = item, let page = self.buildPage(item: item) else {
owsFailDebug("unexpectedly unable to build new page")
return
}
@ -536,42 +590,34 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
let cellViewBuilder: (GalleryRailItem) -> GalleryRailCellView = { [weak self] railItem in
switch railItem {
case is AddMoreRailItem:
return GalleryRailCellView()
case is SignalAttachmentItem:
let cell = ApprovalRailCellView()
cell.approvalRailCellDelegate = self
return cell
default:
owsFailDebug("unexpted rail item type: \(railItem)")
return GalleryRailCellView()
case is AddMoreRailItem:
return GalleryRailCellView()
case is SignalAttachmentItem:
let cell = ApprovalRailCellView()
cell.approvalRailCellDelegate = self
return cell
default:
owsFailDebug("unexpted rail item type: \(railItem)")
return GalleryRailCellView()
}
}
galleryRailView.configureCellViews(itemProvider: attachmentItemCollection,
focusedItem: currentItem,
cellViewBuilder: cellViewBuilder)
galleryRailView.configureCellViews(
itemProvider: attachmentItemCollection,
focusedItem: currentItem,
cellViewBuilder: cellViewBuilder
)
if isAddMoreVisible {
galleryRailView.isHidden = false
} else if attachmentItemCollection.attachmentItems.count > 1 {
}
else if attachmentItemCollection.attachmentItems.count > 1 {
galleryRailView.isHidden = false
} else {
galleryRailView.isHidden = true
}
}
let attachmentItemCollection: AttachmentItemCollection
var attachmentItems: [SignalAttachmentItem] {
return attachmentItemCollection.attachmentItems
}
var attachments: [SignalAttachment] {
return attachmentItems.map { (attachmentItem) in
autoreleasepool {
return self.processedAttachment(forAttachmentItem: attachmentItem)
}
else {
galleryRailView.isHidden = true
}
}
@ -596,18 +642,24 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return attachmentItem.attachment
}
var dataUTI = kUTTypeImage as String
guard let dstData: Data = {
let isLossy: Bool = attachmentItem.attachment.mimeType.caseInsensitiveCompare(OWSMimeTypeImageJpeg) == .orderedSame
let maybeDstData: Data? = {
let isLossy: Bool = (
attachmentItem.attachment.mimeType.caseInsensitiveCompare(OWSMimeTypeImageJpeg) == .orderedSame
)
if isLossy {
dataUTI = kUTTypeJPEG as String
return dstImage.jpegData(compressionQuality: 0.9)
} else {
}
else {
dataUTI = kUTTypePNG as String
return dstImage.pngData()
}
}() else {
owsFailDebug("Could not export for output.")
return attachmentItem.attachment
}()
guard let dstData: Data = maybeDstData else {
owsFailDebug("Could not export for output.")
return attachmentItem.attachment
}
guard let dataSource = DataSourceValue.dataSource(with: dstData, utiType: dataUTI) else {
owsFailDebug("Could not prepare data source for output.")
@ -693,18 +745,18 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
extension AttachmentApprovalViewController: AttachmentTextToolbarDelegate {
func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar) {
currentPageViewController.setAttachmentViewScale(.compact, animated: true)
currentPageViewController?.setAttachmentViewScale(.compact, animated: true)
}
func attachmentTextToolbarDidEndEditing(_ attachmentTextToolbar: AttachmentTextToolbar) {
currentPageViewController.setAttachmentViewScale(.fullsize, animated: true)
currentPageViewController?.setAttachmentViewScale(.fullsize, animated: true)
}
func attachmentTextToolbarDidTapSend(_ attachmentTextToolbar: AttachmentTextToolbar) {
// Toolbar flickers in and out if there are errors
// and remains visible momentarily after share extension is dismissed.
// It's easiest to just hide it at this point since we're done with it.
currentPageViewController.shouldAllowAttachmentViewResizing = false
currentPageViewController?.shouldAllowAttachmentViewResizing = false
attachmentTextToolbar.isUserInteractionEnabled = false
attachmentTextToolbar.isHidden = true
@ -769,7 +821,7 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate {
return
}
guard let currentIndex = attachmentItems.firstIndex(of: currentItem) else {
guard let currentItem: SignalAttachmentItem = currentItem, let currentIndex = attachmentItems.firstIndex(of: currentItem) else {
owsFailDebug("currentIndex was unexpectedly nil")
return
}
@ -779,7 +831,7 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate {
return
}
let direction: UIPageViewController.NavigationDirection = currentIndex < targetIndex ? .forward : .reverse
let direction: UIPageViewController.NavigationDirection = (currentIndex < targetIndex ? .forward : .reverse)
self.setCurrentItem(targetItem, direction: direction, animated: true)
}
@ -787,12 +839,6 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate {
// MARK: -
enum KeyboardScenario {
case hidden, editingMessage, editingCaption
}
// MARK: -
extension AttachmentApprovalViewController: ApprovalRailCellViewDelegate {
func approvalRailCellView(_ approvalRailCellView: ApprovalRailCellView, didRemoveItem attachmentItem: SignalAttachmentItem) {
remove(attachmentItem: attachmentItem)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save