mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Redesign conversation screen part 1
This commit is contained in:
parent
f2f3025554
commit
90e53e5cef
221 changed files with 3071 additions and 1362 deletions
11
Podfile
11
Podfile
|
@ -74,17 +74,6 @@ target 'SessionMessagingKit' do
|
|||
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
|
||||
end
|
||||
|
||||
target 'SessionProtocolKit' do
|
||||
pod 'CocoaLumberjack', :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 'PromiseKit', :inhibit_warnings => true
|
||||
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true
|
||||
pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true
|
||||
end
|
||||
|
||||
target 'SessionSnodeKit' do
|
||||
pod 'CryptoSwift', :inhibit_warnings => true
|
||||
pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true
|
||||
|
|
|
@ -124,7 +124,6 @@ PODS:
|
|||
|
||||
DEPENDENCIES:
|
||||
- AFNetworking
|
||||
- CocoaLumberjack
|
||||
- CryptoSwift
|
||||
- Curve25519Kit (from `https://github.com/signalapp/Curve25519Kit.git`)
|
||||
- FeedKit
|
||||
|
@ -217,6 +216,6 @@ SPEC CHECKSUMS:
|
|||
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
|
||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
||||
|
||||
PODFILE CHECKSUM: bb4f6cffd6e7c08814b945e1787d01d639036b1e
|
||||
PODFILE CHECKSUM: 2fca3f32c171e1324c9e3809b96a32d4a929d05c
|
||||
|
||||
COCOAPODS: 1.10.0.rc.1
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */; };
|
||||
340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */; };
|
||||
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */; };
|
||||
340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC893204DAC8C007AEB0F /* AboutTableViewController.m */; };
|
||||
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */; };
|
||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC89A204DAC8D007AEB0F /* OWSConversationSettingsViewController.m */; };
|
||||
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34129B8521EF8779005457A8 /* LinkPreviewView.swift */; };
|
||||
|
@ -43,7 +42,7 @@
|
|||
347850551FD749C0007B8332 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
|
||||
347850571FD86544007B8332 /* SAEFailedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850561FD86544007B8332 /* SAEFailedViewController.swift */; };
|
||||
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 348570A620F67574004FF32B /* OWSMessageHeaderView.m */; };
|
||||
3488F9362191CC4000E524CC /* ConversationMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3488F9352191CC4000E524CC /* ConversationMediaView.swift */; };
|
||||
3488F9362191CC4000E524CC /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3488F9352191CC4000E524CC /* MediaView.swift */; };
|
||||
3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496744C2076768700080B5F /* OWSMessageBubbleView.m */; };
|
||||
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496744E2076ACCE00080B5F /* LongTextViewController.swift */; };
|
||||
3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34969559219B605E00DCFE74 /* ImagePickerController.swift */; };
|
||||
|
@ -58,12 +57,11 @@
|
|||
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956A21A301A100DCFE74 /* OWSBackupJob.m */; };
|
||||
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; };
|
||||
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; };
|
||||
34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */; };
|
||||
34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumView.swift */; };
|
||||
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ABC0E321DD20C500ED9469 /* ConversationMessageMapping.swift */; };
|
||||
34AC0A23211C829F00997B47 /* OWSLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 34AC0A21211C829E00997B47 /* OWSLabel.m */; };
|
||||
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B0796B1FCF46B000E248C2 /* MainAppContext.m */; };
|
||||
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */; };
|
||||
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */; };
|
||||
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */; };
|
||||
34B6A90B218BA1D1007C4606 /* typing-animation.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34B6A90A218BA1D0007C4606 /* typing-animation.gif */; };
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; };
|
||||
|
@ -189,7 +187,6 @@
|
|||
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 */; };
|
||||
A33A4BA9D050805FE156E3ED /* Pods_SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */; };
|
||||
A5509ECA1A69AB8B00ABA4BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A5509EC91A69AB8B00ABA4BC /* Main.storyboard */; };
|
||||
AD83FF3F1A73426500B5C81A /* audio_pause_button_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = AD83FF381A73426500B5C81A /* audio_pause_button_blue.png */; };
|
||||
AD83FF401A73426500B5C81A /* audio_pause_button_blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AD83FF391A73426500B5C81A /* audio_pause_button_blue@2x.png */; };
|
||||
|
@ -216,13 +213,22 @@
|
|||
B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
|
||||
B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; };
|
||||
B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; };
|
||||
B8041AA725C90927003C2166 /* TypingIndicatorCellV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCellV2.swift */; };
|
||||
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; };
|
||||
B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D2825C7A4B400488AB4 /* InputView.swift */; };
|
||||
B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3225C7A8C600488AB4 /* InputViewButton.swift */; };
|
||||
B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3C25C7B34D00488AB4 /* InputTextView.swift */; };
|
||||
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40872399EB0E00A248E7 /* LandingVC.swift */; };
|
||||
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40892399EC0600A248E7 /* FakeChatView.swift */; };
|
||||
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408B239A068800A248E7 /* RegisterVC.swift */; };
|
||||
B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */; };
|
||||
B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408F239DD75000A248E7 /* RestoreVC.swift */; };
|
||||
B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B4093239DF15900A248E7 /* ConversationTitleView.swift */; };
|
||||
B835246E25C38ABF0089A44F /* ConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B835246D25C38ABF0089A44F /* ConversationVC.swift */; };
|
||||
B835247925C38D880089A44F /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B835247825C38D880089A44F /* MessageCell.swift */; };
|
||||
B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B835249A25C3AB650089A44F /* VisibleMessageCell.swift */; };
|
||||
B83524A525C3BA4B0089A44F /* InfoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */; };
|
||||
B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */; };
|
||||
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */; };
|
||||
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
|
||||
|
@ -232,6 +238,9 @@
|
|||
B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */; };
|
||||
B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2D1255B6DAF007E1867 /* OWSUserProfile.m */; };
|
||||
B8566C7D256F62030045A0B9 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2D3255B6DAF007E1867 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */; };
|
||||
B8569AD325CBA13D00DBA3DB /* MediaTextOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AD225CBA13D00DBA3DB /* MediaTextOverlayView.swift */; };
|
||||
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AE225CBB19A00DBA3DB /* DocumentView.swift */; };
|
||||
B85A68B12587141A008CC492 /* Storage+Resetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85A68B02587141A008CC492 /* Storage+Resetting.swift */; };
|
||||
B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; };
|
||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
||||
|
@ -266,8 +275,10 @@
|
|||
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; };
|
||||
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; };
|
||||
B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */; };
|
||||
B88A1AC725C90A4700E6D421 /* TypingIndicatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */; };
|
||||
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; };
|
||||
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; };
|
||||
B897621C25D201F7004F83B2 /* ConversationVC+ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ConversationVC+ScrollToBottomButton.swift */; };
|
||||
B8A14D702589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */; };
|
||||
B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */; };
|
||||
B8AE760B25ABFB5A001A84D2 /* GeneralUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B8AE760A25ABFB5A001A84D2 /* GeneralUtilities.m */; };
|
||||
|
@ -284,23 +295,15 @@
|
|||
B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B2C72563685C00551B4D /* CircleView.swift */; };
|
||||
B8C2B332256376F000551B4D /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B331256376F000551B4D /* ThreadUtil.m */; };
|
||||
B8C2B3442563782400551B4D /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = B8C2B33B2563770800551B4D /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B8CA010125A293260091AF73 /* ClosedGroupSenderKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CA010025A293260091AF73 /* ClosedGroupSenderKey.swift */; };
|
||||
B8CA010B25A293530091AF73 /* ClosedGroupRatchet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CA010A25A293530091AF73 /* ClosedGroupRatchet.swift */; };
|
||||
B8CA011525A293800091AF73 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CA011425A293800091AF73 /* Configuration.swift */; };
|
||||
B8CA011F25A2939F0091AF73 /* SharedSenderKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CA011E25A2939F0091AF73 /* SharedSenderKeys.swift */; };
|
||||
B8CA014125A293EE0091AF73 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CA014025A293EE0091AF73 /* Storage.swift */; };
|
||||
B8CADAE925AFADF400AAFA15 /* OpenGroupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */; };
|
||||
B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */; };
|
||||
B8CCF63723961D6D0091D419 /* NewPrivateChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */; };
|
||||
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */; };
|
||||
B8CCF63F23975CFB0091D419 /* JoinOpenGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */; };
|
||||
B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF6422397711F0091D419 /* SettingsVC.swift */; };
|
||||
B8D64FBB25BA78310029CFC0 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; };
|
||||
B8D64FBC25BA78310029CFC0 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; };
|
||||
B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
|
||||
B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
|
||||
B8D64FC725BA78520029CFC0 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; };
|
||||
B8D64FC825BA78520029CFC0 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; };
|
||||
B8D64FC925BA78520029CFC0 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
|
||||
B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; };
|
||||
B8FF8DAE25C0D00F004D1F22 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; };
|
||||
B8FF8DAF25C0D00F004D1F22 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
|
||||
|
@ -323,12 +326,15 @@
|
|||
C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */; };
|
||||
C31D1DE9252172D4005D4DA8 /* ContactUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */; };
|
||||
C31FFE57254A5FFE00F19441 /* KeyPairUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */; };
|
||||
C32A025A25A7FC55000ED5D4 /* ClosedGroupsV2Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C32A025925A7FC55000ED5D4 /* ClosedGroupsV2Migration.swift */; };
|
||||
C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
|
||||
C328250F25CA06020062D0A7 /* VoiceMessageViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328250E25CA06020062D0A7 /* VoiceMessageViewV2.swift */; };
|
||||
C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328251E25CA3A900062D0A7 /* QuoteView.swift */; };
|
||||
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328252F25CA55360062D0A7 /* ContextMenuWindow.swift */; };
|
||||
C328254025CA55880062D0A7 /* ContextMenuVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328253F25CA55880062D0A7 /* ContextMenuVC.swift */; };
|
||||
C328254925CA60E60062D0A7 /* ContextMenuVC+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328254825CA60E60062D0A7 /* ContextMenuVC+Action.swift */; };
|
||||
C328255225CA64470062D0A7 /* ContextMenuVC+ActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */; };
|
||||
C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D4825589FF20043A11F /* NSData+messagePadding.m */; };
|
||||
C32A026C25A801AF000ED5D4 /* NSData+messagePadding.h in Headers */ = {isa = PBXBuildFile; fileRef = C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C32A027D25A80423000ED5D4 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; };
|
||||
C32A027E25A80428000ED5D4 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; };
|
||||
C32A027F25A80432000ED5D4 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; };
|
||||
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */; };
|
||||
C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */; };
|
||||
C32C59C0256DB41F003C73A2 /* TSThread.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAD3255A580300E217F9 /* TSThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -754,11 +760,8 @@
|
|||
C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7702553A41E00C340D1 /* ControlMessage.swift */; };
|
||||
C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7822553AAF200C340D1 /* SNProto.swift */; };
|
||||
C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */; };
|
||||
C3C2A8662553B41A00C340D1 /* SessionProtocolKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A8642553B41A00C340D1 /* SessionProtocolKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C3C2A86A2553B41A00C340D1 /* SessionProtocolKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */; };
|
||||
C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */; };
|
||||
C3C2AC372553CCE600C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
|
||||
C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3AA1255CDADA00F4C6D4 /* english.txt */; };
|
||||
C3CA3AB4255CDAE600F4C6D4 /* japanese.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3AB3255CDAE600F4C6D4 /* japanese.txt */; };
|
||||
C3CA3ABE255CDB0D00F4C6D4 /* portuguese.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3ABD255CDB0D00F4C6D4 /* portuguese.txt */; };
|
||||
|
@ -838,13 +841,6 @@
|
|||
remoteGlobalIDString = C3C2A6EF25539DE700C340D1;
|
||||
remoteInfo = SessionMessagingKit;
|
||||
};
|
||||
B8D64FB525BA78270029CFC0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = C3C2A8612553B41A00C340D1;
|
||||
remoteInfo = SessionProtocolKit;
|
||||
};
|
||||
B8D64FB725BA78270029CFC0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
|
@ -866,13 +862,6 @@
|
|||
remoteGlobalIDString = C3C2A6EF25539DE700C340D1;
|
||||
remoteInfo = SessionMessagingKit;
|
||||
};
|
||||
B8D64FC125BA784A0029CFC0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = C3C2A8612553B41A00C340D1;
|
||||
remoteInfo = SessionProtocolKit;
|
||||
};
|
||||
B8D64FC325BA784A0029CFC0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
|
@ -922,13 +911,6 @@
|
|||
remoteGlobalIDString = C3C2A6EF25539DE700C340D1;
|
||||
remoteInfo = SessionMessagingKit;
|
||||
};
|
||||
C3C2A8672553B41A00C340D1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = C3C2A8612553B41A00C340D1;
|
||||
remoteInfo = SessionProtocolKit;
|
||||
};
|
||||
C3D90A5425773A1A002C9DF5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D221A080169C9E5E00537ABF /* Project object */;
|
||||
|
@ -971,7 +953,6 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
C3C2A86A2553B41A00C340D1 /* SessionProtocolKit.framework in Embed Frameworks */,
|
||||
C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */,
|
||||
C33FD9B3255A548A00E217F9 /* SignalUtilitiesKit.framework in Embed Frameworks */,
|
||||
C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */,
|
||||
|
@ -998,7 +979,6 @@
|
|||
340FC87E204DAC8C007AEB0F /* PrivacySettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivacySettingsTableViewController.m; sourceTree = "<group>"; };
|
||||
340FC87F204DAC8C007AEB0F /* OWSBackupSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupSettingsViewController.h; sourceTree = "<group>"; };
|
||||
340FC883204DAC8C007AEB0F /* OWSSoundSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSSoundSettingsViewController.m; sourceTree = "<group>"; };
|
||||
340FC884204DAC8C007AEB0F /* AboutTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutTableViewController.h; sourceTree = "<group>"; };
|
||||
340FC886204DAC8C007AEB0F /* AddToBlockListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToBlockListViewController.m; sourceTree = "<group>"; };
|
||||
340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = "<group>"; };
|
||||
340FC88A204DAC8C007AEB0F /* NotificationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsViewController.h; sourceTree = "<group>"; };
|
||||
|
@ -1006,7 +986,6 @@
|
|||
340FC88E204DAC8C007AEB0F /* OWSBackupSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupSettingsViewController.m; sourceTree = "<group>"; };
|
||||
340FC88F204DAC8C007AEB0F /* PrivacySettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivacySettingsTableViewController.h; sourceTree = "<group>"; };
|
||||
340FC892204DAC8C007AEB0F /* AddToBlockListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToBlockListViewController.h; sourceTree = "<group>"; };
|
||||
340FC893204DAC8C007AEB0F /* AboutTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutTableViewController.m; sourceTree = "<group>"; };
|
||||
340FC894204DAC8C007AEB0F /* OWSSoundSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSoundSettingsViewController.h; sourceTree = "<group>"; };
|
||||
340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = "<group>"; };
|
||||
340FC899204DAC8D007AEB0F /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = "<group>"; };
|
||||
|
@ -1045,7 +1024,7 @@
|
|||
347850561FD86544007B8332 /* SAEFailedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEFailedViewController.swift; sourceTree = "<group>"; };
|
||||
348570A620F67574004FF32B /* OWSMessageHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageHeaderView.m; sourceTree = "<group>"; };
|
||||
348570A720F67574004FF32B /* OWSMessageHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageHeaderView.h; sourceTree = "<group>"; };
|
||||
3488F9352191CC4000E524CC /* ConversationMediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationMediaView.swift; sourceTree = "<group>"; };
|
||||
3488F9352191CC4000E524CC /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
|
||||
3496744B2076768600080B5F /* OWSMessageBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageBubbleView.h; sourceTree = "<group>"; };
|
||||
3496744C2076768700080B5F /* OWSMessageBubbleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageBubbleView.m; sourceTree = "<group>"; };
|
||||
3496744E2076ACCE00080B5F /* LongTextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LongTextViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1066,7 +1045,7 @@
|
|||
3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportJob.h; sourceTree = "<group>"; };
|
||||
3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = "<group>"; };
|
||||
34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = "<group>"; };
|
||||
34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumCellView.swift; sourceTree = "<group>"; };
|
||||
34A8B3502190A40E00218A25 /* MediaAlbumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumView.swift; sourceTree = "<group>"; };
|
||||
34ABC0E321DD20C500ED9469 /* ConversationMessageMapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationMessageMapping.swift; sourceTree = "<group>"; };
|
||||
34AC0A21211C829E00997B47 /* OWSLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLabel.m; sourceTree = "<group>"; };
|
||||
34AC0A22211C829E00997B47 /* OWSLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLabel.h; sourceTree = "<group>"; };
|
||||
|
@ -1278,13 +1257,22 @@
|
|||
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; };
|
||||
B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = "<group>"; };
|
||||
B8041AA625C90927003C2166 /* TypingIndicatorCellV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCellV2.swift; sourceTree = "<group>"; };
|
||||
B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = "<group>"; };
|
||||
B8269D2825C7A4B400488AB4 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = "<group>"; };
|
||||
B8269D3225C7A8C600488AB4 /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = "<group>"; };
|
||||
B8269D3C25C7B34D00488AB4 /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = "<group>"; };
|
||||
B82B40872399EB0E00A248E7 /* LandingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingVC.swift; sourceTree = "<group>"; };
|
||||
B82B40892399EC0600A248E7 /* FakeChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeChatView.swift; sourceTree = "<group>"; };
|
||||
B82B408B239A068800A248E7 /* RegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterVC.swift; sourceTree = "<group>"; };
|
||||
B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = "<group>"; };
|
||||
B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = "<group>"; };
|
||||
B82B4093239DF15900A248E7 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = "<group>"; };
|
||||
B835246D25C38ABF0089A44F /* ConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationVC.swift; sourceTree = "<group>"; };
|
||||
B835247825C38D880089A44F /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = "<group>"; };
|
||||
B835249A25C3AB650089A44F /* VisibleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleMessageCell.swift; sourceTree = "<group>"; };
|
||||
B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageCell.swift; sourceTree = "<group>"; };
|
||||
B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSheet.swift; sourceTree = "<group>"; };
|
||||
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = "<group>"; };
|
||||
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1297,6 +1285,9 @@
|
|||
B8544E3023D16CA500299F14 /* DeviceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUtilities.swift; sourceTree = "<group>"; };
|
||||
B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilities.swift; sourceTree = "<group>"; };
|
||||
B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OWSLinkPreview+Conversion.swift"; sourceTree = "<group>"; };
|
||||
B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationVC+Interaction.swift"; sourceTree = "<group>"; };
|
||||
B8569AD225CBA13D00DBA3DB /* MediaTextOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTextOverlayView.swift; sourceTree = "<group>"; };
|
||||
B8569AE225CBB19A00DBA3DB /* DocumentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentView.swift; sourceTree = "<group>"; };
|
||||
B85A68B02587141A008CC492 /* Storage+Resetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Resetting.swift"; sourceTree = "<group>"; };
|
||||
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
|
||||
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
|
||||
|
@ -1310,6 +1301,7 @@
|
|||
B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersVC.swift; sourceTree = "<group>"; };
|
||||
B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = "<group>"; };
|
||||
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = "<group>"; };
|
||||
B897621B25D201F7004F83B2 /* ConversationVC+ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationVC+ScrollToBottomButton.swift"; sourceTree = "<group>"; };
|
||||
B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSuccessSheet.swift; sourceTree = "<group>"; };
|
||||
B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Trimming.swift"; sourceTree = "<group>"; };
|
||||
B8AE760925ABFB00001A84D2 /* GeneralUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneralUtilities.h; sourceTree = "<group>"; };
|
||||
|
@ -1336,15 +1328,10 @@
|
|||
B8C2B331256376F000551B4D /* ThreadUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThreadUtil.m; sourceTree = "<group>"; };
|
||||
B8C2B33B2563770800551B4D /* ThreadUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ThreadUtil.h; sourceTree = "<group>"; };
|
||||
B8C9689023FA1401005F64E0 /* AppMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMode.swift; sourceTree = "<group>"; };
|
||||
B8CA010025A293260091AF73 /* ClosedGroupSenderKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroupSenderKey.swift; sourceTree = "<group>"; };
|
||||
B8CA010A25A293530091AF73 /* ClosedGroupRatchet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroupRatchet.swift; sourceTree = "<group>"; };
|
||||
B8CA011425A293800091AF73 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||
B8CA011E25A2939F0091AF73 /* SharedSenderKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedSenderKeys.swift; sourceTree = "<group>"; };
|
||||
B8CA014025A293EE0091AF73 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||
B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Regular.ttf"; sourceTree = "<group>"; };
|
||||
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPrivateChatVC.swift; sourceTree = "<group>"; };
|
||||
B8CCF638239721E20091D419 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
|
||||
B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinPublicChatVC.swift; sourceTree = "<group>"; };
|
||||
B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupVC.swift; sourceTree = "<group>"; };
|
||||
B8CCF6422397711F0091D419 /* SettingsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsVC.swift; sourceTree = "<group>"; };
|
||||
B8D8F1372566120F0092EF10 /* Storage+ClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+ClosedGroups.swift"; sourceTree = "<group>"; };
|
||||
B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Jobs.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1375,7 +1362,12 @@
|
|||
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = "<group>"; };
|
||||
C31F812525258FB000DD9FD9 /* Storage+VolumeSamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+VolumeSamples.swift"; sourceTree = "<group>"; };
|
||||
C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairUtilities.swift; sourceTree = "<group>"; };
|
||||
C32A025925A7FC55000ED5D4 /* ClosedGroupsV2Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroupsV2Migration.swift; sourceTree = "<group>"; };
|
||||
C328250E25CA06020062D0A7 /* VoiceMessageViewV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageViewV2.swift; sourceTree = "<group>"; };
|
||||
C328251E25CA3A900062D0A7 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; };
|
||||
C328252F25CA55360062D0A7 /* ContextMenuWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuWindow.swift; sourceTree = "<group>"; };
|
||||
C328253F25CA55880062D0A7 /* ContextMenuVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuVC.swift; sourceTree = "<group>"; };
|
||||
C328254825CA60E60062D0A7 /* ContextMenuVC+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+Action.swift"; sourceTree = "<group>"; };
|
||||
C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+ActionView.swift"; sourceTree = "<group>"; };
|
||||
C32C5A87256DBCF9003C73A2 /* MessageReceiver+Handling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+Handling.swift"; sourceTree = "<group>"; };
|
||||
C32C5B3E256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSQuotedMessage+Conversion.swift"; sourceTree = "<group>"; };
|
||||
C32C5FD5256E0346003C73A2 /* Notification+Thread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Thread.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1827,9 +1819,6 @@
|
|||
C3C2A7702553A41E00C340D1 /* ControlMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlMessage.swift; sourceTree = "<group>"; };
|
||||
C3C2A7822553AAF200C340D1 /* SNProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SNProto.swift; sourceTree = "<group>"; };
|
||||
C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionProtos.pb.swift; sourceTree = "<group>"; };
|
||||
C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionProtocolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C3C2A8642553B41A00C340D1 /* SessionProtocolKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionProtocolKit.h; sourceTree = "<group>"; };
|
||||
C3C2A8652553B41A00C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+SecureRandom.swift"; sourceTree = "<group>"; };
|
||||
C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = "<group>"; };
|
||||
C3C3CF8824D8EED300E1CCE7 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1892,9 +1881,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */,
|
||||
B8D64FC725BA78520029CFC0 /* SessionMessagingKit.framework in Frameworks */,
|
||||
B8D64FC825BA78520029CFC0 /* SessionProtocolKit.framework in Frameworks */,
|
||||
B8D64FC925BA78520029CFC0 /* SessionSnodeKit.framework in Frameworks */,
|
||||
C3D90A5C25773A25002C9DF5 /* SessionUtilitiesKit.framework in Frameworks */,
|
||||
C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */,
|
||||
B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */,
|
||||
|
@ -1907,7 +1895,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B8D64FBB25BA78310029CFC0 /* SessionMessagingKit.framework in Frameworks */,
|
||||
B8D64FBC25BA78310029CFC0 /* SessionProtocolKit.framework in Frameworks */,
|
||||
B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */,
|
||||
B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */,
|
||||
C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */,
|
||||
|
@ -1927,7 +1914,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C32A027E25A80428000ED5D4 /* SessionProtocolKit.framework in Frameworks */,
|
||||
C38EF48A255B7E3F007E1867 /* SessionUIKit.framework in Frameworks */,
|
||||
C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */,
|
||||
C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */,
|
||||
|
@ -1957,28 +1943,17 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C32A027D25A80423000ED5D4 /* SessionProtocolKit.framework in Frameworks */,
|
||||
5DF9AB212C6DB1E8BE70EFF6 /* Pods_SessionMessagingKit.framework in Frameworks */,
|
||||
C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C3C2A85F2553B41A00C340D1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A33A4BA9D050805FE156E3ED /* Pods_SessionProtocolKit.framework in Frameworks */,
|
||||
C3C2AC372553CCE600C340D1 /* SessionUtilitiesKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D221A086169C9E5E00537ABF /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B8FF8DAE25C0D00F004D1F22 /* SessionMessagingKit.framework in Frameworks */,
|
||||
B8FF8DAF25C0D00F004D1F22 /* SessionUtilitiesKit.framework in Frameworks */,
|
||||
C32A027F25A80432000ED5D4 /* SessionProtocolKit.framework in Frameworks */,
|
||||
C37F54DC255BB84A002AEA92 /* SessionSnodeKit.framework in Frameworks */,
|
||||
C37F5414255BAFA7002AEA92 /* SignalUtilitiesKit.framework in Frameworks */,
|
||||
455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */,
|
||||
|
@ -2236,6 +2211,56 @@
|
|||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B8041A7325C8F758003C2166 /* Content Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
34A8B3502190A40E00218A25 /* MediaAlbumView.swift */,
|
||||
B8569AD225CBA13D00DBA3DB /* MediaTextOverlayView.swift */,
|
||||
3488F9352191CC4000E524CC /* MediaView.swift */,
|
||||
B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */,
|
||||
C328251E25CA3A900062D0A7 /* QuoteView.swift */,
|
||||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */,
|
||||
C328250E25CA06020062D0A7 /* VoiceMessageViewV2.swift */,
|
||||
B8569AE225CBB19A00DBA3DB /* DocumentView.swift */,
|
||||
);
|
||||
path = "Content Views";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B835246C25C38AA20089A44F /* Conversations V2 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B835246D25C38ABF0089A44F /* ConversationVC.swift */,
|
||||
B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */,
|
||||
B897621B25D201F7004F83B2 /* ConversationVC+ScrollToBottomButton.swift */,
|
||||
B887C38125C7C79700E11DAE /* Input View */,
|
||||
B835247725C38D190089A44F /* Message Cells */,
|
||||
C328252E25CA54F70062D0A7 /* Context Menu */,
|
||||
);
|
||||
path = "Conversations V2";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B835247725C38D190089A44F /* Message Cells */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B835247825C38D880089A44F /* MessageCell.swift */,
|
||||
B835249A25C3AB650089A44F /* VisibleMessageCell.swift */,
|
||||
B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */,
|
||||
B8041AA625C90927003C2166 /* TypingIndicatorCellV2.swift */,
|
||||
B8041A7325C8F758003C2166 /* Content Views */,
|
||||
);
|
||||
path = "Message Cells";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B887C38125C7C79700E11DAE /* Input View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B8269D2825C7A4B400488AB4 /* InputView.swift */,
|
||||
B8269D3225C7A8C600488AB4 /* InputViewButton.swift */,
|
||||
B8269D3C25C7B34D00488AB4 /* InputTextView.swift */,
|
||||
);
|
||||
path = "Input View";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B8A582AB258C64E800AFD84C /* Database */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2477,6 +2502,17 @@
|
|||
path = Meta;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C328252E25CA54F70062D0A7 /* Context Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C328252F25CA55360062D0A7 /* ContextMenuWindow.swift */,
|
||||
C328253F25CA55880062D0A7 /* ContextMenuVC.swift */,
|
||||
C328254825CA60E60062D0A7 /* ContextMenuVC+Action.swift */,
|
||||
C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */,
|
||||
);
|
||||
path = "Context Menu";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C32B405424A961E1001117B5 /* Dependencies */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2537,6 +2573,7 @@
|
|||
C33FDB48255A580C00E217F9 /* TSOutgoingMessage.h */,
|
||||
C33FDB56255A580D00E217F9 /* TSOutgoingMessage.m */,
|
||||
B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */,
|
||||
34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */,
|
||||
);
|
||||
path = Signal;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2790,8 +2827,6 @@
|
|||
C360969125AD1765008B62B2 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
340FC884204DAC8C007AEB0F /* AboutTableViewController.h */,
|
||||
340FC893204DAC8C007AEB0F /* AboutTableViewController.m */,
|
||||
340FC88B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.h */,
|
||||
340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */,
|
||||
340FC88A204DAC8C007AEB0F /* NotificationSettingsViewController.h */,
|
||||
|
@ -2821,7 +2856,7 @@
|
|||
C360969B25AD180B008B62B2 /* Open Groups */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */,
|
||||
B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */,
|
||||
);
|
||||
path = "Open Groups";
|
||||
sourceTree = "<group>";
|
||||
|
@ -2895,15 +2930,12 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
457F671A20746193000EABCD /* QuotedReplyPreview.swift */,
|
||||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */,
|
||||
4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */,
|
||||
34129B8521EF8779005457A8 /* LinkPreviewView.swift */,
|
||||
34D1F0BB1F8D108C0066283D /* AttachmentUploadView.h */,
|
||||
34D1F0BC1F8D108C0066283D /* AttachmentUploadView.m */,
|
||||
3488F9352191CC4000E524CC /* ConversationMediaView.swift */,
|
||||
34D1F0961F867BFC0066283D /* ConversationViewCell.h */,
|
||||
34D1F0971F867BFC0066283D /* ConversationViewCell.m */,
|
||||
34A8B3502190A40E00218A25 /* MediaAlbumCellView.swift */,
|
||||
34EA693F2194933900702471 /* MediaDownloadView.swift */,
|
||||
34EA69412194DE7F00702471 /* MediaUploadView.swift */,
|
||||
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
|
||||
|
@ -2932,7 +2964,6 @@
|
|||
34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */,
|
||||
34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */,
|
||||
34B6A906218B5240007C4606 /* TypingIndicatorCell.swift */,
|
||||
34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */,
|
||||
C364534F252449260045C478 /* VoiceMessageView.swift */,
|
||||
);
|
||||
path = "Views & Cells";
|
||||
|
@ -3084,7 +3115,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
B8B32044258C117C0020074B /* ContactsMigration.swift */,
|
||||
C32A025925A7FC55000ED5D4 /* ClosedGroupsV2Migration.swift */,
|
||||
C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */,
|
||||
C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */,
|
||||
C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */,
|
||||
|
@ -3404,28 +3434,6 @@
|
|||
path = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C3C2A8632553B41A00C340D1 /* SessionProtocolKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C3C2A8762553B42C00C340D1 /* Meta */,
|
||||
B8CA010A25A293530091AF73 /* ClosedGroupRatchet.swift */,
|
||||
B8CA010025A293260091AF73 /* ClosedGroupSenderKey.swift */,
|
||||
B8CA011425A293800091AF73 /* Configuration.swift */,
|
||||
B8CA011E25A2939F0091AF73 /* SharedSenderKeys.swift */,
|
||||
B8CA014025A293EE0091AF73 /* Storage.swift */,
|
||||
);
|
||||
path = SessionProtocolKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C3C2A8762553B42C00C340D1 /* Meta */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C3C2A8642553B41A00C340D1 /* SessionProtocolKit.h */,
|
||||
C3C2A8652553B41A00C340D1 /* Info.plist */,
|
||||
);
|
||||
path = Meta;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C3CA3AA0255CDA7000F4C6D4 /* Mnemonic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3581,7 +3589,6 @@
|
|||
C33FD9AC255A548A00E217F9 /* SignalUtilitiesKit */,
|
||||
C331FF1C2558F9D300070591 /* SessionUIKit */,
|
||||
C3C2A6F125539DE700C340D1 /* SessionMessagingKit */,
|
||||
C3C2A8632553B41A00C340D1 /* SessionProtocolKit */,
|
||||
C3C2A5A0255385C100C340D1 /* SessionSnodeKit */,
|
||||
C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */,
|
||||
D221A08C169C9E5E00537ABF /* Frameworks */,
|
||||
|
@ -3599,7 +3606,6 @@
|
|||
C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */,
|
||||
C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */,
|
||||
C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */,
|
||||
C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */,
|
||||
C331FF1B2558F9D300070591 /* SessionUIKit.framework */,
|
||||
C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */,
|
||||
);
|
||||
|
@ -3666,6 +3672,7 @@
|
|||
C36096A525AD18D7008B62B2 /* Basic Chats */,
|
||||
C360969C25AD18BA008B62B2 /* Closed Groups */,
|
||||
C36096AE25AD1909008B62B2 /* Conversations */,
|
||||
B835246C25C38AA20089A44F /* Conversations V2 */,
|
||||
C32C5D49256DD522003C73A2 /* Database */,
|
||||
C32B405424A961E1001117B5 /* Dependencies */,
|
||||
C360968E25AD16E8008B62B2 /* Home */,
|
||||
|
@ -3850,14 +3857,6 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C3C2A85D2553B41A00C340D1 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C3C2A8662553B41A00C340D1 /* SessionProtocolKit.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -3874,7 +3873,6 @@
|
|||
);
|
||||
dependencies = (
|
||||
B8D64FC025BA784A0029CFC0 /* PBXTargetDependency */,
|
||||
B8D64FC225BA784A0029CFC0 /* PBXTargetDependency */,
|
||||
B8D64FC425BA784A0029CFC0 /* PBXTargetDependency */,
|
||||
B8D64FC625BA784A0029CFC0 /* PBXTargetDependency */,
|
||||
C3D90A5525773A1A002C9DF5 /* PBXTargetDependency */,
|
||||
|
@ -3898,7 +3896,6 @@
|
|||
);
|
||||
dependencies = (
|
||||
B8D64FB425BA78270029CFC0 /* PBXTargetDependency */,
|
||||
B8D64FB625BA78270029CFC0 /* PBXTargetDependency */,
|
||||
B8D64FB825BA78270029CFC0 /* PBXTargetDependency */,
|
||||
B8D64FBA25BA78270029CFC0 /* PBXTargetDependency */,
|
||||
C3D90A7125773A44002C9DF5 /* PBXTargetDependency */,
|
||||
|
@ -4003,25 +4000,6 @@
|
|||
productReference = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
C3C2A8612553B41A00C340D1 /* SessionProtocolKit */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C3C2A86B2553B41A00C340D1 /* Build configuration list for PBXNativeTarget "SessionProtocolKit" */;
|
||||
buildPhases = (
|
||||
099772F07D67DC2A83009D2F /* [CP] Check Pods Manifest.lock */,
|
||||
C3C2A85D2553B41A00C340D1 /* Headers */,
|
||||
C3C2A85E2553B41A00C340D1 /* Sources */,
|
||||
C3C2A85F2553B41A00C340D1 /* Frameworks */,
|
||||
C3C2A8602553B41A00C340D1 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SessionProtocolKit;
|
||||
productName = SessionProtocolKit;
|
||||
productReference = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
D221A088169C9E5E00537ABF /* Session */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Session" */;
|
||||
|
@ -4042,7 +4020,6 @@
|
|||
C3C2A5A5255385C100C340D1 /* PBXTargetDependency */,
|
||||
C3C2A67F255388CC00C340D1 /* PBXTargetDependency */,
|
||||
C3C2A6F625539DE700C340D1 /* PBXTargetDependency */,
|
||||
C3C2A8682553B41A00C340D1 /* PBXTargetDependency */,
|
||||
C331FF212558F9D300070591 /* PBXTargetDependency */,
|
||||
C33FD9B1255A548A00E217F9 /* PBXTargetDependency */,
|
||||
);
|
||||
|
@ -4117,12 +4094,6 @@
|
|||
LastSwiftMigration = 1210;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
C3C2A8612553B41A00C340D1 = {
|
||||
CreatedOnToolsVersion = 12.1;
|
||||
DevelopmentTeam = SUQ8J2PCT7;
|
||||
LastSwiftMigration = 1220;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D221A088169C9E5E00537ABF = {
|
||||
DevelopmentTeam = SUQ8J2PCT7;
|
||||
LastSwiftMigration = 1020;
|
||||
|
@ -4187,7 +4158,6 @@
|
|||
C33FD9AA255A548A00E217F9 /* SignalUtilitiesKit */,
|
||||
C331FF1A2558F9D300070591 /* SessionUIKit */,
|
||||
C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */,
|
||||
C3C2A8612553B41A00C340D1 /* SessionProtocolKit */,
|
||||
C3C2A59E255385C100C340D1 /* SessionSnodeKit */,
|
||||
C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */,
|
||||
);
|
||||
|
@ -4253,13 +4223,6 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C3C2A8602553B41A00C340D1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D221A087169C9E5E00537ABF /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -4346,28 +4309,6 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
099772F07D67DC2A83009D2F /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-SessionProtocolKit-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
1460156AE01E0DB0949D61FE /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -4759,7 +4700,6 @@
|
|||
C38EF3F9255B6DF7007E1867 /* OWSLayerView.swift in Sources */,
|
||||
C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */,
|
||||
B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */,
|
||||
C32A025A25A7FC55000ED5D4 /* ClosedGroupsV2Migration.swift in Sources */,
|
||||
C38EF3B9255B6DE7007E1867 /* ImageEditorPinchGestureRecognizer.swift in Sources */,
|
||||
C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */,
|
||||
C33FDC27255A581F00E217F9 /* YapDatabase+Promise.swift in Sources */,
|
||||
|
@ -4967,6 +4907,7 @@
|
|||
C352A349255781F400338F3E /* AttachmentDownloadJob.swift in Sources */,
|
||||
C352A30925574D8500338F3E /* Message+Destination.swift in Sources */,
|
||||
C300A5E72554B07300555489 /* ExpirationTimerUpdate.swift in Sources */,
|
||||
B88A1AC725C90A4700E6D421 /* TypingIndicatorInteraction.swift in Sources */,
|
||||
C3D9E3C025676AD70040E4F3 /* TSAttachment.m in Sources */,
|
||||
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
|
||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||
|
@ -5002,22 +4943,11 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C3C2A85E2553B41A00C340D1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B8CA010125A293260091AF73 /* ClosedGroupSenderKey.swift in Sources */,
|
||||
B8CA011525A293800091AF73 /* Configuration.swift in Sources */,
|
||||
B8CA011F25A2939F0091AF73 /* SharedSenderKeys.swift in Sources */,
|
||||
B8CA010B25A293530091AF73 /* ClosedGroupRatchet.swift in Sources */,
|
||||
B8CA014125A293EE0091AF73 /* Storage.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D221A085169C9E5E00537ABF /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B8041AA725C90927003C2166 /* TypingIndicatorCellV2.swift in Sources */,
|
||||
B8CCF63723961D6D0091D419 /* NewPrivateChatVC.swift in Sources */,
|
||||
34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */,
|
||||
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */,
|
||||
|
@ -5037,6 +4967,7 @@
|
|||
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
|
||||
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */,
|
||||
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */,
|
||||
C328255225CA64470062D0A7 /* ContextMenuVC+ActionView.swift in Sources */,
|
||||
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */,
|
||||
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
|
||||
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */,
|
||||
|
@ -5047,6 +4978,7 @@
|
|||
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */,
|
||||
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
|
||||
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,
|
||||
B83524A525C3BA4B0089A44F /* InfoMessageCell.swift in Sources */,
|
||||
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
|
||||
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
|
||||
451166C01FD86B98000739BA /* AccountManager.swift in Sources */,
|
||||
|
@ -5059,7 +4991,7 @@
|
|||
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */,
|
||||
3496955D219B605E00DCFE74 /* PhotoCollectionPickerController.swift in Sources */,
|
||||
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */,
|
||||
34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */,
|
||||
34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */,
|
||||
34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */,
|
||||
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */,
|
||||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */,
|
||||
|
@ -5071,6 +5003,7 @@
|
|||
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */,
|
||||
D221A09A169C9E5E00537ABF /* main.m in Sources */,
|
||||
3496957221A301A100DCFE74 /* OWSBackup.m in Sources */,
|
||||
B835247925C38D880089A44F /* MessageCell.swift in Sources */,
|
||||
C35D0DA125AE582D00B6BF49 /* MultiDeviceVC.swift in Sources */,
|
||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */,
|
||||
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
|
||||
|
@ -5089,9 +5022,11 @@
|
|||
45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */,
|
||||
34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */,
|
||||
B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */,
|
||||
B835246E25C38ABF0089A44F /* ConversationVC.swift in Sources */,
|
||||
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
|
||||
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
|
||||
C328254925CA60E60062D0A7 /* ContextMenuVC+Action.swift in Sources */,
|
||||
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
|
||||
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
|
||||
|
@ -5099,9 +5034,9 @@
|
|||
341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */,
|
||||
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
|
||||
C331FFF32558FF0300070591 /* PathStatusView.swift in Sources */,
|
||||
B8569AD325CBA13D00DBA3DB /* MediaTextOverlayView.swift in Sources */,
|
||||
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
|
||||
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
|
||||
34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */,
|
||||
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */,
|
||||
34EA69402194933900702471 /* MediaDownloadView.swift in Sources */,
|
||||
B8544E3323D50E4900299F14 /* AppearanceUtilities.swift in Sources */,
|
||||
|
@ -5112,7 +5047,7 @@
|
|||
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
|
||||
C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */,
|
||||
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
|
||||
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */,
|
||||
B8CCF63F23975CFB0091D419 /* JoinOpenGroupVC.swift in Sources */,
|
||||
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
|
||||
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
|
||||
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
|
||||
|
@ -5125,18 +5060,22 @@
|
|||
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
|
||||
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
|
||||
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
|
||||
B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */,
|
||||
45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */,
|
||||
B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */,
|
||||
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */,
|
||||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
|
||||
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */,
|
||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
|
||||
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
|
||||
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
|
||||
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,
|
||||
C328250F25CA06020062D0A7 /* VoiceMessageViewV2.swift in Sources */,
|
||||
B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */,
|
||||
3488F9362191CC4000E524CC /* ConversationMediaView.swift in Sources */,
|
||||
3488F9362191CC4000E524CC /* MediaView.swift in Sources */,
|
||||
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,
|
||||
B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */,
|
||||
B8A14D702589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift in Sources */,
|
||||
3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */,
|
||||
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
|
||||
|
@ -5148,12 +5087,13 @@
|
|||
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */,
|
||||
3496957021A301A100DCFE74 /* OWSBackupIO.m in Sources */,
|
||||
34AC0A23211C829F00997B47 /* OWSLabel.m in Sources */,
|
||||
B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */,
|
||||
34EA69422194DE8000702471 /* MediaUploadView.swift in Sources */,
|
||||
B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */,
|
||||
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
|
||||
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */,
|
||||
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */,
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */,
|
||||
340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */,
|
||||
C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */,
|
||||
C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */,
|
||||
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */,
|
||||
|
@ -5161,22 +5101,28 @@
|
|||
C31D1DE9252172D4005D4DA8 /* ContactUtilities.swift in Sources */,
|
||||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
||||
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
|
||||
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */,
|
||||
B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */,
|
||||
B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */,
|
||||
B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */,
|
||||
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */,
|
||||
B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */,
|
||||
340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */,
|
||||
340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */,
|
||||
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */,
|
||||
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */,
|
||||
B897621C25D201F7004F83B2 /* ConversationVC+ScrollToBottomButton.swift in Sources */,
|
||||
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */,
|
||||
45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */,
|
||||
34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */,
|
||||
C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */,
|
||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */,
|
||||
34D1F0861F8678AA0066283D /* ConversationViewController.m in Sources */,
|
||||
C328254025CA55880062D0A7 /* ContextMenuVC.swift in Sources */,
|
||||
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */,
|
||||
B90418E6183E9DD40038554A /* DateUtil.m in Sources */,
|
||||
C33100092558FF6D00070591 /* UserCell.swift in Sources */,
|
||||
B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */,
|
||||
C3645350252449260045C478 /* VoiceMessageView.swift in Sources */,
|
||||
3496956F21A301A100DCFE74 /* OWSBackupLazyRestore.swift in Sources */,
|
||||
);
|
||||
|
@ -5200,11 +5146,6 @@
|
|||
target = C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */;
|
||||
targetProxy = B8D64FB325BA78270029CFC0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
B8D64FB625BA78270029CFC0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C3C2A8612553B41A00C340D1 /* SessionProtocolKit */;
|
||||
targetProxy = B8D64FB525BA78270029CFC0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
B8D64FB825BA78270029CFC0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */;
|
||||
|
@ -5220,11 +5161,6 @@
|
|||
target = C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */;
|
||||
targetProxy = B8D64FBF25BA784A0029CFC0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
B8D64FC225BA784A0029CFC0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C3C2A8612553B41A00C340D1 /* SessionProtocolKit */;
|
||||
targetProxy = B8D64FC125BA784A0029CFC0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
B8D64FC425BA784A0029CFC0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */;
|
||||
|
@ -5260,11 +5196,6 @@
|
|||
target = C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */;
|
||||
targetProxy = C3C2A6F525539DE700C340D1 /* PBXContainerItemProxy */;
|
||||
};
|
||||
C3C2A8682553B41A00C340D1 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C3C2A8612553B41A00C340D1 /* SessionProtocolKit */;
|
||||
targetProxy = C3C2A8672553B41A00C340D1 /* PBXContainerItemProxy */;
|
||||
};
|
||||
C3D90A5525773A1A002C9DF5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C331FF1A2558F9D300070591 /* SessionUIKit */;
|
||||
|
@ -6257,136 +6188,6 @@
|
|||
};
|
||||
name = "App Store Release";
|
||||
};
|
||||
C3C2A86C2553B41A00C340D1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AEA8083C060FF9BAFF6E0C9F /* Pods-SessionProtocolKit.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SessionProtocolKit/Meta/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionProtocolKit";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
C3C2A86D2553B41A00C340D1 /* App Store Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 174BD0AE74771D02DAC2B7A9 /* Pods-SessionProtocolKit.app store release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = SessionProtocolKit/Meta/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionProtocolKit";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = "App Store Release";
|
||||
};
|
||||
D221A0BA169C9E5F00537ABF /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -6736,15 +6537,6 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "App Store Release";
|
||||
};
|
||||
C3C2A86B2553B41A00C340D1 /* Build configuration list for PBXNativeTarget "SessionProtocolKit" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C3C2A86C2553B41A00C340D1 /* Debug */,
|
||||
C3C2A86D2553B41A00C340D1 /* App Store Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "App Store Release";
|
||||
};
|
||||
D221A083169C9E5E00537ABF /* Build configuration list for PBXProject "Session" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -183,7 +183,7 @@ private final class EnterPublicKeyVC : UIViewController {
|
|||
view.backgroundColor = .clear
|
||||
// Explanation label
|
||||
let explanationLabel = UILabel()
|
||||
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
explanationLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
|
||||
explanationLabel.text = NSLocalizedString("vc_enter_public_key_explanation", comment: "")
|
||||
explanationLabel.numberOfLines = 0
|
||||
|
|
|
@ -100,7 +100,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
|
|||
let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.members).isEmpty
|
||||
if (!hasContactsToAdd) {
|
||||
addMembersButton.isUserInteractionEnabled = false
|
||||
let disabledColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
let disabledColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
addMembersButton.layer.borderColor = disabledColor.cgColor
|
||||
addMembersButton.setTitleColor(disabledColor, for: UIControl.State.normal)
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
|
|||
self.members = members.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) }
|
||||
let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.members).isEmpty
|
||||
self.addMembersButton.isUserInteractionEnabled = hasContactsToAdd
|
||||
let color = hasContactsToAdd ? Colors.accent : Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
let color = hasContactsToAdd ? Colors.accent : Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
self.addMembersButton.layer.borderColor = color.cgColor
|
||||
self.addMembersButton.setTitleColor(color, for: UIControl.State.normal)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ final class GroupMembersVC : BaseVC, UITableViewDataSource {
|
|||
setNavBarTitle("Group Members")
|
||||
// Set up explanation label
|
||||
let explanationLabel = UILabel()
|
||||
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
explanationLabel.text = "The ability to add members to a closed group is coming soon."
|
||||
explanationLabel.numberOfLines = 0
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
extension ContextMenuVC {
|
||||
|
||||
struct Action {
|
||||
let icon: UIImage
|
||||
let title: String
|
||||
let work: () -> Void
|
||||
|
||||
static func reply(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate) -> Action {
|
||||
let title = "Reply"
|
||||
return Action(icon: UIImage(named: "ic_reply")!, title: title) { delegate.reply(viewItem) }
|
||||
}
|
||||
|
||||
static func copy(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate) -> Action {
|
||||
let title = "Copy"
|
||||
return Action(icon: UIImage(named: "ic_copy")!, title: title) { delegate.copy(viewItem) }
|
||||
}
|
||||
|
||||
static func copySessionID(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate) -> Action {
|
||||
let title = "Copy Session ID"
|
||||
return Action(icon: UIImage(named: "ic_copy")!, title: title) { delegate.copySessionID(viewItem) }
|
||||
}
|
||||
|
||||
static func delete(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate) -> Action {
|
||||
let title = "Delete"
|
||||
return Action(icon: UIImage(named: "ic_trash")!, title: title) { delegate.delete(viewItem) }
|
||||
}
|
||||
|
||||
static func save(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate) -> Action {
|
||||
let title = "Save"
|
||||
return Action(icon: UIImage(named: "ic_download")!, title: title) { delegate.save(viewItem) }
|
||||
}
|
||||
|
||||
static func ban(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate) -> Action {
|
||||
let title = "Ban User"
|
||||
return Action(icon: UIImage(named: "ic_block")!, title: title) { delegate.ban(viewItem) }
|
||||
}
|
||||
}
|
||||
|
||||
static func actions(for viewItem: ConversationViewItem, delegate: ContextMenuActionDelegate) -> [Action] {
|
||||
func isReplyingAllowed() -> Bool {
|
||||
guard let message = viewItem.interaction as? TSOutgoingMessage else { return true }
|
||||
switch message.messageState {
|
||||
case .failed, .sending: return false
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
switch viewItem.messageCellType {
|
||||
case .textOnlyMessage:
|
||||
var result: [Action] = []
|
||||
if isReplyingAllowed() { result.append(Action.reply(viewItem, delegate)) }
|
||||
result.append(Action.copy(viewItem, delegate))
|
||||
let isGroup = viewItem.isGroupThread
|
||||
if isGroup && viewItem.interaction is TSIncomingMessage { result.append(Action.copySessionID(viewItem, delegate)) }
|
||||
if !isGroup || viewItem.userCanDeleteGroupMessage { result.append(Action.delete(viewItem, delegate)) }
|
||||
if isGroup && viewItem.interaction is TSIncomingMessage && viewItem.userHasModerationPermission { result.append(Action.ban(viewItem, delegate)) }
|
||||
return result
|
||||
case .mediaMessage, .audio, .genericAttachment:
|
||||
var result: [Action] = []
|
||||
if isReplyingAllowed() { result.append(Action.reply(viewItem, delegate)) }
|
||||
if viewItem.canCopyMedia() { result.append(Action.copy(viewItem, delegate)) }
|
||||
if viewItem.canSaveMedia() { result.append(Action.save(viewItem, delegate)) }
|
||||
let isGroup = viewItem.isGroupThread
|
||||
if isGroup && viewItem.interaction is TSIncomingMessage { result.append(Action.copySessionID(viewItem, delegate)) }
|
||||
if !isGroup || viewItem.userCanDeleteGroupMessage { result.append(Action.delete(viewItem, delegate)) }
|
||||
if isGroup && viewItem.interaction is TSIncomingMessage && viewItem.userHasModerationPermission { result.append(Action.ban(viewItem, delegate)) }
|
||||
return result
|
||||
default: return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Delegate
|
||||
protocol ContextMenuActionDelegate {
|
||||
|
||||
func reply(_ viewItem: ConversationViewItem)
|
||||
func copy(_ viewItem: ConversationViewItem)
|
||||
func copySessionID(_ viewItem: ConversationViewItem)
|
||||
func delete(_ viewItem: ConversationViewItem)
|
||||
func save(_ viewItem: ConversationViewItem)
|
||||
func ban(_ viewItem: ConversationViewItem)
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
extension ContextMenuVC {
|
||||
|
||||
final class ActionView : UIView {
|
||||
private let action: Action
|
||||
private let dismiss: () -> Void
|
||||
|
||||
// MARK: Settings
|
||||
private static let iconSize: CGFloat = 16
|
||||
private static let iconImageViewSize: CGFloat = 24
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(for action: Action, dismiss: @escaping () -> Void) {
|
||||
self.action = action
|
||||
self.dismiss = dismiss
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(for:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(for:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
// Icon
|
||||
let iconSize = ActionView.iconSize
|
||||
let iconImageView = UIImageView(image: action.icon.resizedImage(to: CGSize(width: iconSize, height: iconSize))!.withTint(Colors.text))
|
||||
let iconImageViewSize = ActionView.iconImageViewSize
|
||||
iconImageView.set(.width, to: iconImageViewSize)
|
||||
iconImageView.set(.height, to: iconImageViewSize)
|
||||
iconImageView.contentMode = .center
|
||||
// Title
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = action.title
|
||||
titleLabel.textColor = Colors.text
|
||||
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
// Stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [ iconImageView, titleLabel ])
|
||||
stackView.axis = .horizontal
|
||||
stackView.spacing = Values.smallSpacing
|
||||
stackView.alignment = .center
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
let smallSpacing = Values.smallSpacing
|
||||
stackView.layoutMargins = UIEdgeInsets(top: smallSpacing, leading: smallSpacing, bottom: smallSpacing, trailing: Values.mediumSpacing)
|
||||
addSubview(stackView)
|
||||
stackView.pin(to: self)
|
||||
// Tap gesture recognizer
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func handleTap() {
|
||||
action.work()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
117
Session/Conversations V2/Context Menu/ContextMenuVC.swift
Normal file
117
Session/Conversations V2/Context Menu/ContextMenuVC.swift
Normal file
|
@ -0,0 +1,117 @@
|
|||
|
||||
final class ContextMenuVC : UIViewController {
|
||||
private let snapshot: UIView
|
||||
private let viewItem: ConversationViewItem
|
||||
private let frame: CGRect
|
||||
private let delegate: ContextMenuActionDelegate
|
||||
private let dismiss: () -> Void
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var blurView = UIVisualEffectView(effect: nil)
|
||||
|
||||
private lazy var menuView: UIView = {
|
||||
let result = UIView()
|
||||
result.layer.shadowColor = UIColor.black.cgColor
|
||||
result.layer.shadowOffset = CGSize.zero
|
||||
result.layer.shadowOpacity = 0.4
|
||||
result.layer.shadowRadius = 4
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Settings
|
||||
private static let actionViewHeight: CGFloat = 40
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(snapshot: UIView, viewItem: ConversationViewItem, frame: CGRect, delegate: ContextMenuActionDelegate, dismiss: @escaping () -> Void) {
|
||||
self.snapshot = snapshot
|
||||
self.viewItem = viewItem
|
||||
self.frame = frame
|
||||
self.delegate = delegate
|
||||
self.dismiss = dismiss
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
override init(nibName: String?, bundle: Bundle?) {
|
||||
preconditionFailure("Use init(snapshot:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(coder:) instead.")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Background color
|
||||
view.backgroundColor = .clear
|
||||
// Blur
|
||||
view.addSubview(blurView)
|
||||
blurView.pin(to: view)
|
||||
// Snapshot
|
||||
snapshot.layer.shadowColor = UIColor.black.cgColor
|
||||
snapshot.layer.shadowOffset = CGSize.zero
|
||||
snapshot.layer.shadowOpacity = 0.4
|
||||
snapshot.layer.shadowRadius = 4
|
||||
view.addSubview(snapshot)
|
||||
snapshot.pin(.left, to: .left, of: view, withInset: frame.origin.x)
|
||||
snapshot.pin(.top, to: .top, of: view, withInset: frame.origin.y)
|
||||
snapshot.set(.width, to: frame.width)
|
||||
snapshot.set(.height, to: frame.height)
|
||||
// Menu
|
||||
let menuBackgroundView = UIView()
|
||||
menuBackgroundView.backgroundColor = Colors.receivedMessageBackground
|
||||
menuBackgroundView.layer.cornerRadius = Values.messageBubbleCornerRadius
|
||||
menuBackgroundView.layer.masksToBounds = true
|
||||
menuView.addSubview(menuBackgroundView)
|
||||
menuBackgroundView.pin(to: menuView)
|
||||
let actionViews = ContextMenuVC.actions(for: viewItem, delegate: delegate).map { ActionView(for: $0, dismiss: snDismiss) }
|
||||
let menuStackView = UIStackView(arrangedSubviews: actionViews)
|
||||
menuStackView.axis = .vertical
|
||||
menuView.addSubview(menuStackView)
|
||||
menuStackView.pin(to: menuView)
|
||||
view.addSubview(menuView)
|
||||
let menuHeight = CGFloat(actionViews.count) * ContextMenuVC.actionViewHeight
|
||||
let spacing = Values.smallSpacing
|
||||
let margin = max(UIApplication.shared.keyWindow!.safeAreaInsets.bottom, Values.mediumSpacing)
|
||||
if frame.maxY + spacing + menuHeight > UIScreen.main.bounds.height - margin {
|
||||
menuView.pin(.bottom, to: .top, of: snapshot, withInset: -spacing)
|
||||
} else {
|
||||
menuView.pin(.top, to: .bottom, of: snapshot, withInset: spacing)
|
||||
}
|
||||
switch viewItem.interaction.interactionType() {
|
||||
case .outgoingMessage: menuView.pin(.right, to: .right, of: snapshot)
|
||||
case .incomingMessage: menuView.pin(.left, to: .left, of: snapshot)
|
||||
default: break // Should never occur
|
||||
}
|
||||
// Tap gesture
|
||||
let mainTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
view.addGestureRecognizer(mainTapGestureRecognizer)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.blurView.effect = UIBlurEffect(style: .regular)
|
||||
self.menuView.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
menuView.layer.shadowPath = UIBezierPath(roundedRect: menuView.bounds, cornerRadius: Values.messageBubbleCornerRadius).cgPath
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func handleTap() {
|
||||
snDismiss()
|
||||
}
|
||||
|
||||
func snDismiss() {
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.blurView.effect = nil
|
||||
self.menuView.alpha = 0
|
||||
}, completion: { _ in
|
||||
self.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
final class ContextMenuWindow : UIWindow {
|
||||
|
||||
override var windowLevel: UIWindow.Level {
|
||||
get { return UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude - 1) }
|
||||
set { /* Do nothing */ }
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
initialize()
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
override init(windowScene: UIWindowScene) {
|
||||
super.init(windowScene: windowScene)
|
||||
initialize()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
initialize()
|
||||
}
|
||||
|
||||
private func initialize() {
|
||||
backgroundColor = .clear
|
||||
}
|
||||
}
|
192
Session/Conversations V2/ConversationVC+Interaction.swift
Normal file
192
Session/Conversations V2/ConversationVC+Interaction.swift
Normal file
|
@ -0,0 +1,192 @@
|
|||
|
||||
extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuActionDelegate, ScrollToBottomButtonDelegate {
|
||||
|
||||
@objc func openSettings() {
|
||||
let settingsVC = OWSConversationSettingsViewController()
|
||||
settingsVC.configure(with: thread, uiDatabaseConnection: OWSPrimaryStorage.shared().uiDatabaseConnection)
|
||||
navigationController!.pushViewController(settingsVC, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func handleCameraButtonTapped() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func handleLibraryButtonTapped() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func handleGIFButtonTapped() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func handleDocumentButtonTapped() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func handleSendButtonTapped() {
|
||||
// TODO: Attachments
|
||||
let text = snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let thread = self.thread
|
||||
// TODO: Blocking
|
||||
guard !text.isEmpty else { return }
|
||||
let message = VisibleMessage()
|
||||
message.sentTimestamp = NSDate.millisecondTimestamp()
|
||||
message.text = text
|
||||
// TODO: Quotes
|
||||
// TODO: Link previews
|
||||
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
||||
viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
|
||||
Storage.shared.write(with: { transaction in
|
||||
// TODO: Link previews
|
||||
}, completion: { [weak self] in
|
||||
// TODO: Link previews
|
||||
Storage.shared.write { transaction in
|
||||
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
|
||||
}
|
||||
Storage.shared.write { transaction in
|
||||
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||
}
|
||||
// TODO: Sent handling
|
||||
self?.snInputView.text = ""
|
||||
// TODO: Reset mentions
|
||||
})
|
||||
}
|
||||
|
||||
func handleViewItemLongPressed(_ viewItem: ConversationViewItem) {
|
||||
guard let index = viewItems.firstIndex(where: { $0 === viewItem }),
|
||||
let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell,
|
||||
let snapshot = cell.bubbleView.snapshotView(afterScreenUpdates: false), contextMenuWindow == nil,
|
||||
!ContextMenuVC.actions(for: viewItem, delegate: self).isEmpty else { return }
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
let frame = cell.convert(cell.bubbleView.frame, to: UIApplication.shared.keyWindow!)
|
||||
let window = ContextMenuWindow()
|
||||
let contextMenuVC = ContextMenuVC(snapshot: snapshot, viewItem: viewItem, frame: frame, delegate: self) {
|
||||
window.isHidden = true
|
||||
self.contextMenuVC = nil
|
||||
self.contextMenuWindow = nil
|
||||
}
|
||||
self.contextMenuVC = contextMenuVC
|
||||
contextMenuWindow = window
|
||||
window.rootViewController = contextMenuVC
|
||||
window.makeKeyAndVisible()
|
||||
window.backgroundColor = .clear
|
||||
}
|
||||
|
||||
func handleViewItemTapped(_ viewItem: ConversationViewItem, gestureRecognizer: UITapGestureRecognizer) {
|
||||
switch viewItem.messageCellType {
|
||||
case .audio: playOrPauseAudio(for: viewItem)
|
||||
case .mediaMessage:
|
||||
guard let index = viewItems.firstIndex(where: { $0 === viewItem }),
|
||||
let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell, let albumView = cell.albumView else { return }
|
||||
let locationInCell = gestureRecognizer.location(in: cell)
|
||||
if let overlayView = cell.mediaTextOverlayView {
|
||||
let locationInOverlayView = cell.convert(locationInCell, to: overlayView)
|
||||
if let readMoreButton = overlayView.readMoreButton, readMoreButton.frame.contains(locationInOverlayView) {
|
||||
return showFullText(viewItem) // FIXME: Bit of a hack to do it this way
|
||||
}
|
||||
}
|
||||
let locationInAlbumView = cell.convert(locationInCell, to: albumView)
|
||||
guard let mediaView = albumView.mediaView(forLocation: locationInAlbumView) else { return }
|
||||
if albumView.isMoreItemsView(mediaView: mediaView) && viewItem.mediaAlbumHasFailedAttachment() {
|
||||
// TODO: Tapped a failed incoming attachment
|
||||
}
|
||||
let attachment = mediaView.attachment
|
||||
if let pointer = attachment as? TSAttachmentPointer {
|
||||
if pointer.state == .failed {
|
||||
// TODO: Tapped a failed incoming attachment
|
||||
}
|
||||
}
|
||||
guard let stream = attachment as? TSAttachmentStream else { return }
|
||||
let gallery = MediaGallery(thread: thread, options: [ .sliderEnabled, .showAllMediaButton ])
|
||||
gallery.presentDetailView(fromViewController: self, mediaAttachment: stream, replacingView: mediaView)
|
||||
case .genericAttachment:
|
||||
guard let url = viewItem.attachmentStream?.originalMediaURL else { return }
|
||||
let shareVC = UIActivityViewController(activityItems: [ url ], applicationActivities: nil)
|
||||
navigationController!.present(shareVC, animated: true, completion: nil)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
func handleViewItemDoubleTapped(_ viewItem: ConversationViewItem) {
|
||||
switch viewItem.messageCellType {
|
||||
case .audio: speedUpAudio(for: viewItem)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
func showFullText(_ viewItem: ConversationViewItem) {
|
||||
let longMessageVC = LongTextViewController(viewItem: viewItem)
|
||||
navigationController!.pushViewController(longMessageVC, animated: true)
|
||||
}
|
||||
|
||||
func playOrPauseAudio(for viewItem: ConversationViewItem) {
|
||||
guard let attachment = viewItem.attachmentStream else { return }
|
||||
let fileManager = FileManager.default
|
||||
guard let path = attachment.originalFilePath, fileManager.fileExists(atPath: path),
|
||||
let url = attachment.originalMediaURL else { return }
|
||||
if let audioPlayer = audioPlayer {
|
||||
if let owner = audioPlayer.owner as? ConversationViewItem, owner === viewItem {
|
||||
audioPlayer.playbackRate = 1
|
||||
audioPlayer.togglePlayState()
|
||||
return
|
||||
} else {
|
||||
audioPlayer.stop()
|
||||
self.audioPlayer = nil
|
||||
}
|
||||
}
|
||||
let audioPlayer = OWSAudioPlayer(mediaUrl: url, audioBehavior: .audioMessagePlayback, delegate: viewItem)
|
||||
self.audioPlayer = audioPlayer
|
||||
audioPlayer.owner = viewItem
|
||||
audioPlayer.play()
|
||||
audioPlayer.setCurrentTime(Double(viewItem.audioProgressSeconds))
|
||||
}
|
||||
|
||||
func speedUpAudio(for viewItem: ConversationViewItem) {
|
||||
guard let audioPlayer = audioPlayer, let owner = audioPlayer.owner as? ConversationViewItem, owner === viewItem, audioPlayer.isPlaying else { return }
|
||||
audioPlayer.playbackRate = 1.5
|
||||
viewItem.lastAudioMessageView?.showSpeedUpLabel()
|
||||
}
|
||||
|
||||
func reply(_ viewItem: ConversationViewItem) {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func copy(_ viewItem: ConversationViewItem) {
|
||||
if viewItem.canCopyMedia() {
|
||||
viewItem.copyMediaAction()
|
||||
} else {
|
||||
viewItem.copyTextAction()
|
||||
}
|
||||
}
|
||||
|
||||
func copySessionID(_ viewItem: ConversationViewItem) {
|
||||
guard let message = viewItem.interaction as? TSIncomingMessage else { return }
|
||||
UIPasteboard.general.string = message.authorId
|
||||
}
|
||||
|
||||
func delete(_ viewItem: ConversationViewItem) {
|
||||
viewItem.deleteAction()
|
||||
}
|
||||
|
||||
func save(_ viewItem: ConversationViewItem) {
|
||||
guard viewItem.canSaveMedia() else { return }
|
||||
viewItem.saveMediaAction()
|
||||
}
|
||||
|
||||
func ban(_ viewItem: ConversationViewItem) {
|
||||
guard let message = viewItem.interaction as? TSIncomingMessage, message.isOpenGroupMessage else { return }
|
||||
let alert = UIAlertController(title: "Ban This User?", message: nil, preferredStyle: .alert)
|
||||
let threadID = thread.uniqueId!
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
|
||||
guard let openGroup = Storage.shared.getOpenGroup(for: threadID) else { return }
|
||||
let publicKey = message.authorId
|
||||
OpenGroupAPI.ban(publicKey, from: openGroup.server).retainUntilComplete()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func handleScrollToBottomButtonTapped() {
|
||||
scrollToBottom(isAnimated: true)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
extension ConversationVC {
|
||||
|
||||
final class ScrollToBottomButton : UIView {
|
||||
private let delegate: ScrollToBottomButtonDelegate
|
||||
|
||||
// MARK: Settings
|
||||
private static let size: CGFloat = 40
|
||||
private static let iconSize: CGFloat = 16
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(delegate: ScrollToBottomButtonDelegate) {
|
||||
self.delegate = delegate
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(delegate:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(delegate:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
// Background & blur
|
||||
let backgroundView = UIView()
|
||||
backgroundView.backgroundColor = isLightMode ? .white : .black
|
||||
backgroundView.alpha = Values.lowOpacity
|
||||
addSubview(backgroundView)
|
||||
backgroundView.pin(to: self)
|
||||
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||
addSubview(blurView)
|
||||
blurView.pin(to: self)
|
||||
// Size & shape
|
||||
let size = ScrollToBottomButton.size
|
||||
set(.width, to: size)
|
||||
set(.height, to: size)
|
||||
layer.cornerRadius = size / 2
|
||||
layer.masksToBounds = true
|
||||
// Border
|
||||
layer.borderWidth = 1
|
||||
let borderColor = (isLightMode ? UIColor.black : UIColor.white).withAlphaComponent(Values.veryLowOpacity)
|
||||
layer.borderColor = borderColor.cgColor
|
||||
// Icon
|
||||
let tint = isLightMode ? UIColor.black : UIColor.white
|
||||
let icon = UIImage(named: "ic_chevron_down")!.withTint(tint)
|
||||
let iconImageView = UIImageView(image: icon)
|
||||
iconImageView.set(.width, to: ScrollToBottomButton.iconSize)
|
||||
iconImageView.set(.height, to: ScrollToBottomButton.iconSize)
|
||||
iconImageView.contentMode = .scaleAspectFit
|
||||
addSubview(iconImageView)
|
||||
iconImageView.center(in: self)
|
||||
// Gesture recognizer
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func handleTap() {
|
||||
delegate.handleScrollToBottomButtonTapped()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol ScrollToBottomButtonDelegate {
|
||||
|
||||
func handleScrollToBottomButtonTapped()
|
||||
}
|
344
Session/Conversations V2/ConversationVC.swift
Normal file
344
Session/Conversations V2/ConversationVC.swift
Normal file
|
@ -0,0 +1,344 @@
|
|||
|
||||
// TODO
|
||||
// • Tapping replies
|
||||
// • Mentions
|
||||
// • Remaining send logic
|
||||
// • Paging
|
||||
// • Blocking
|
||||
// • Subtitle
|
||||
// • Resending failed messages
|
||||
// • Linkification
|
||||
// • Link previews
|
||||
|
||||
final class ConversationVC : BaseVC, ConversationViewModelDelegate, UITableViewDataSource, UITableViewDelegate {
|
||||
let thread: TSThread
|
||||
private let focusedMessageID: String?
|
||||
var audioPlayer: OWSAudioPlayer?
|
||||
private var didConstrainScrollButton = false
|
||||
// Context menu
|
||||
var contextMenuWindow: ContextMenuWindow?
|
||||
var contextMenuVC: ContextMenuVC?
|
||||
// Scrolling & paging
|
||||
private var isUserScrolling = false { didSet { autoLoadMoreIfNeeded() } }
|
||||
private var hasPerformedInitialScroll = false
|
||||
private var isLoadingMore = false
|
||||
private var contentOffsetYBeforeUpdate: CGFloat?
|
||||
private var contentHeightBeforeUpdate: CGFloat?
|
||||
|
||||
|
||||
private var dbConnection: YapDatabaseConnection { OWSPrimaryStorage.shared().uiDatabaseConnection }
|
||||
var viewItems: [ConversationViewItem] { viewModel.viewState.viewItems }
|
||||
func conversationStyle() -> ConversationStyle { return ConversationStyle(thread: thread) }
|
||||
override var inputAccessoryView: UIView? { snInputView }
|
||||
override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
private var lastPageTop: CGFloat {
|
||||
let bottomInset = -messagesTableView.adjustedContentInset.bottom - ConversationVC.bottomInset
|
||||
let tableViewUnobscuredHeight = messagesTableView.bounds.height + bottomInset
|
||||
return messagesTableView.contentSize.height - tableViewUnobscuredHeight
|
||||
}
|
||||
|
||||
lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: focusedMessageID, delegate: self)
|
||||
|
||||
private lazy var mediaCache: NSCache<NSString, AnyObject> = {
|
||||
let result = NSCache<NSString, AnyObject>()
|
||||
result.countLimit = 24
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: UI Components
|
||||
lazy var messagesTableView: UITableView = {
|
||||
let result = UITableView()
|
||||
result.dataSource = self
|
||||
result.delegate = self
|
||||
result.register(VisibleMessageCell.self, forCellReuseIdentifier: VisibleMessageCell.identifier)
|
||||
result.register(InfoMessageCell.self, forCellReuseIdentifier: InfoMessageCell.identifier)
|
||||
result.register(TypingIndicatorCellV2.self, forCellReuseIdentifier: TypingIndicatorCellV2.identifier)
|
||||
result.separatorStyle = .none
|
||||
result.backgroundColor = .clear
|
||||
result.contentInset = UIEdgeInsets(top: 0, leading: 0, bottom: ConversationVC.bottomInset, trailing: 0)
|
||||
result.showsVerticalScrollIndicator = false
|
||||
result.contentInsetAdjustmentBehavior = .never
|
||||
result.keyboardDismissMode = .interactive
|
||||
return result
|
||||
}()
|
||||
|
||||
lazy var snInputView = InputView(delegate: self)
|
||||
|
||||
private lazy var scrollButton = ScrollToBottomButton(delegate: self)
|
||||
|
||||
// MARK: Settings
|
||||
private static let bottomInset = Values.mediumSpacing
|
||||
private static let loadMoreThreshold: CGFloat = 120
|
||||
/// The button will be fully visible once the user has scrolled this amount from the bottom of the table view.
|
||||
private static let scrollButtonFullVisibilityThreshold: CGFloat = 80
|
||||
/// The button will be invisible until the user has scrolled at least this amount from the bottom of the table view.
|
||||
private static let scrollButtonNoVisibilityThreshold: CGFloat = 20
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(thread: TSThread, focusedMessageID: String? = nil) {
|
||||
self.thread = thread
|
||||
self.focusedMessageID = focusedMessageID
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(thread:) instead.")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Gradient
|
||||
setUpGradientBackground()
|
||||
// Nav bar
|
||||
setUpNavBarStyle()
|
||||
setNavBarTitle(getTitle())
|
||||
updateNavBarButtons()
|
||||
// Constraints
|
||||
view.addSubview(messagesTableView)
|
||||
messagesTableView.pin(to: view)
|
||||
view.addSubview(scrollButton)
|
||||
scrollButton.pin(.right, to: .right, of: view, withInset: -Values.mediumSpacing)
|
||||
// Notifications
|
||||
let notificationCenter = NotificationCenter.default
|
||||
notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillChangeFrameNotification(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillHideNotification(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleAudioDidFinishPlayingNotification(_:)), name: .SNAudioDidFinishPlaying, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if !hasPerformedInitialScroll {
|
||||
scrollToBottom(isAnimated: false)
|
||||
hasPerformedInitialScroll = true
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
guard let lastSortID = viewItems.last?.interaction.sortId else { return }
|
||||
OWSReadReceiptManager.shared().markAsReadLocally(beforeSortId: lastSortID, thread: thread)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
mediaCache.removeAllObjects()
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
// MARK: Table View Data Source
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return viewItems.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let viewItem = viewItems[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: MessageCell.getCellType(for: viewItem).identifier) as! MessageCell
|
||||
cell.delegate = self
|
||||
cell.viewItem = viewItem
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
private func updateNavBarButtons() {
|
||||
let rightBarButtonItem: UIBarButtonItem
|
||||
if thread is TSContactThread {
|
||||
let size = Values.verySmallProfilePictureSize
|
||||
let profilePictureView = ProfilePictureView()
|
||||
profilePictureView.accessibilityLabel = "Settings button"
|
||||
profilePictureView.size = size
|
||||
profilePictureView.update(for: thread)
|
||||
profilePictureView.set(.width, to: size)
|
||||
profilePictureView.set(.height, to: size)
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
|
||||
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
|
||||
rightBarButtonItem = UIBarButtonItem(customView: profilePictureView)
|
||||
} else {
|
||||
rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings))
|
||||
}
|
||||
rightBarButtonItem.accessibilityLabel = "Settings button"
|
||||
rightBarButtonItem.isAccessibilityElement = true
|
||||
navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||
}
|
||||
|
||||
@objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) {
|
||||
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
|
||||
if !didConstrainScrollButton {
|
||||
// Bit of a hack to do this here, but it works out.
|
||||
scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -(newHeight + Values.mediumSpacing))
|
||||
didConstrainScrollButton = true
|
||||
}
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.messagesTableView.contentInset = UIEdgeInsets(top: 0, leading: 0, bottom: newHeight + ConversationVC.bottomInset, trailing: 0)
|
||||
self.scrollButton.alpha = 0
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handleKeyboardWillHideNotification(_ notification: Notification) {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.messagesTableView.contentInset = UIEdgeInsets(top: 0, leading: 0, bottom: ConversationVC.bottomInset, trailing: 0)
|
||||
self.scrollButton.alpha = self.getScrollButtonOpacity()
|
||||
}
|
||||
}
|
||||
|
||||
func conversationViewModelWillUpdate() {
|
||||
|
||||
}
|
||||
|
||||
func conversationViewModelDidUpdate(_ conversationUpdate: ConversationUpdate) {
|
||||
guard self.isViewLoaded else { return }
|
||||
// TODO: Reload the thread if it's a group thread?
|
||||
let updateType = conversationUpdate.conversationUpdateType
|
||||
guard updateType != .minor else { return } // No view items were affected
|
||||
if updateType == .reload {
|
||||
return messagesTableView.reloadData()
|
||||
}
|
||||
var shouldScrollToBottom = false
|
||||
let shouldAnimate = conversationUpdate.shouldAnimateUpdates
|
||||
let batchUpdates: () -> Void = {
|
||||
for update in conversationUpdate.updateItems! {
|
||||
switch update.updateItemType {
|
||||
case .delete:
|
||||
self.messagesTableView.deleteRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .fade)
|
||||
case .insert:
|
||||
// Perform inserts before updates
|
||||
self.messagesTableView.insertRows(at: [ IndexPath(row: Int(update.newIndex), section: 0) ], with: .fade)
|
||||
let viewItem = update.viewItem
|
||||
if viewItem?.interaction is TSOutgoingMessage {
|
||||
shouldScrollToBottom = true
|
||||
}
|
||||
case .update:
|
||||
self.messagesTableView.reloadRows(at: [ IndexPath(row: Int(update.oldIndex), section: 0) ], with: .fade)
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
let batchUpdatesCompletion: (Bool) -> Void = { isFinished in
|
||||
// TODO: Update last visible sort ID?
|
||||
if shouldScrollToBottom {
|
||||
self.scrollToBottom(isAnimated: true)
|
||||
}
|
||||
// TODO: Update last known distance from bottom
|
||||
}
|
||||
if shouldAnimate {
|
||||
messagesTableView.performBatchUpdates(batchUpdates, completion: batchUpdatesCompletion)
|
||||
} else {
|
||||
// HACK: We use `UIView.animateWithDuration:0` rather than `UIView.performWithAnimation` to work around a
|
||||
// UIKit Crash like:
|
||||
//
|
||||
// *** Assertion failure in -[ConversationViewLayout prepareForCollectionViewUpdates:],
|
||||
// /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UICollectionViewLayout.m:760
|
||||
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'While
|
||||
// preparing update a visible view at <NSIndexPath: 0xc000000011c00016> {length = 2, path = 0 - 142}
|
||||
// wasn't found in the current data model and was not in an update animation. This is an internal
|
||||
// error.'
|
||||
//
|
||||
// I'm unclear if this is a bug in UIKit, or if we're doing something crazy in
|
||||
// ConversationViewLayout#prepareLayout. To reproduce, rapidily insert and delete items into the
|
||||
// conversation.
|
||||
UIView.animate(withDuration: 0) {
|
||||
self.messagesTableView.performBatchUpdates(batchUpdates, completion: batchUpdatesCompletion)
|
||||
if shouldScrollToBottom {
|
||||
self.scrollToBottom(isAnimated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Set last reload date?
|
||||
}
|
||||
|
||||
func conversationViewModelWillLoadMoreItems() {
|
||||
contentHeightBeforeUpdate = messagesTableView.contentSize.height
|
||||
contentOffsetYBeforeUpdate = messagesTableView.contentOffset.y
|
||||
}
|
||||
|
||||
func conversationViewModelDidLoadMoreItems() {
|
||||
guard let contentHeightBeforeUpdate = contentHeightBeforeUpdate, let contentOffsetYBeforeUpdate = contentOffsetYBeforeUpdate else { return }
|
||||
view.layoutIfNeeded()
|
||||
messagesTableView.contentOffset.y = (messagesTableView.contentSize.height - contentHeightBeforeUpdate + contentOffsetYBeforeUpdate)
|
||||
isLoadingMore = false
|
||||
}
|
||||
|
||||
func conversationViewModelDidLoadPrevPage() {
|
||||
|
||||
}
|
||||
|
||||
func conversationViewModelRangeDidChange() {
|
||||
|
||||
}
|
||||
|
||||
func conversationViewModelDidReset() {
|
||||
|
||||
}
|
||||
|
||||
// MARK: General
|
||||
func getMediaCache() -> NSCache<NSString, AnyObject> {
|
||||
return mediaCache
|
||||
}
|
||||
|
||||
@objc private func handleAudioDidFinishPlayingNotification(_ notification: Notification) {
|
||||
guard let audioPlayer = audioPlayer, let viewItem = audioPlayer.owner as? ConversationViewItem,
|
||||
let index = viewItems.firstIndex(where: { $0 === viewItem }), index < (viewItems.endIndex - 1) else { return }
|
||||
let nextViewItem = viewItems[index + 1]
|
||||
guard nextViewItem.messageCellType == .audio else { return }
|
||||
playOrPauseAudio(for: nextViewItem)
|
||||
}
|
||||
|
||||
func scrollToBottom(isAnimated: Bool) {
|
||||
guard !isUserScrolling else { 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()
|
||||
let firstContentPageTop = -messagesTableView.adjustedContentInset.top
|
||||
let destinationY = max(firstContentPageTop, lastPageTop)
|
||||
messagesTableView.setContentOffset(CGPoint(x: 0, y: destinationY), animated: isAnimated)
|
||||
// TODO: Did scroll to bottom
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
isUserScrolling = true
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
isUserScrolling = false
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
scrollButton.alpha = getScrollButtonOpacity()
|
||||
autoLoadMoreIfNeeded()
|
||||
}
|
||||
|
||||
private func autoLoadMoreIfNeeded() {
|
||||
let isMainAppAndActive = CurrentAppContext().isMainAppAndActive
|
||||
guard isMainAppAndActive && viewModel.canLoadMoreItems() && !isLoadingMore
|
||||
&& messagesTableView.contentOffset.y < ConversationVC.loadMoreThreshold else { return }
|
||||
isLoadingMore = true
|
||||
print("[Test] LOAD MORE")
|
||||
viewModel.loadAnotherPageOfMessages()
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
private func getTitle() -> String {
|
||||
if let thread = thread as? TSGroupThread {
|
||||
return thread.groupModel.groupName!
|
||||
} else if thread.isNoteToSelf() {
|
||||
return "Note to Self"
|
||||
} else {
|
||||
let sessionID = thread.contactIdentifier()!
|
||||
var result = sessionID
|
||||
Storage.read { transaction in
|
||||
result = Storage.shared.getContact(with: sessionID)?.displayName ?? "Anonymous"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private func getScrollButtonOpacity() -> CGFloat {
|
||||
let contentOffsetY = messagesTableView.contentOffset.y
|
||||
let x = (lastPageTop - ConversationVC.bottomInset - contentOffsetY).clamp(0, .greatestFiniteMagnitude)
|
||||
let a = 1 / (ConversationVC.scrollButtonFullVisibilityThreshold - ConversationVC.scrollButtonNoVisibilityThreshold)
|
||||
return a * x
|
||||
}
|
||||
}
|
72
Session/Conversations V2/Input View/InputTextView.swift
Normal file
72
Session/Conversations V2/Input View/InputTextView.swift
Normal file
|
@ -0,0 +1,72 @@
|
|||
|
||||
public final class InputTextView : UITextView, UITextViewDelegate {
|
||||
private let snDelegate: InputTextViewDelegate
|
||||
private lazy var heightConstraint = self.set(.height, to: minHeight)
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var placeholderLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.text = "Message"
|
||||
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Settings
|
||||
private let minHeight: CGFloat = 22
|
||||
private let maxHeight: CGFloat = 120
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(delegate: InputTextViewDelegate) {
|
||||
snDelegate = delegate
|
||||
super.init(frame: CGRect.zero, textContainer: nil)
|
||||
setUpViewHierarchy()
|
||||
self.delegate = self
|
||||
}
|
||||
|
||||
public override init(frame: CGRect, textContainer: NSTextContainer?) {
|
||||
preconditionFailure("Use init(delegate:) instead.")
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(delegate:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
showsHorizontalScrollIndicator = false
|
||||
showsVerticalScrollIndicator = false
|
||||
backgroundColor = .clear
|
||||
textColor = Colors.text
|
||||
font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
tintColor = Colors.accent
|
||||
keyboardAppearance = isLightMode ? .light : .dark
|
||||
heightConstraint.isActive = true
|
||||
let horizontalInset: CGFloat = 2
|
||||
textContainerInset = UIEdgeInsets(top: 0, left: horizontalInset, bottom: 0, right: horizontalInset)
|
||||
addSubview(placeholderLabel)
|
||||
placeholderLabel.pin(.leading, to: .leading, of: self, withInset: horizontalInset + 3) // Slight visual adjustment
|
||||
placeholderLabel.pin(.top, to: .top, of: self)
|
||||
pin(.trailing, to: .trailing, of: placeholderLabel, withInset: horizontalInset)
|
||||
pin(.bottom, to: .bottom, of: placeholderLabel)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
public func textViewDidChange(_ textView: UITextView) {
|
||||
placeholderLabel.isHidden = !text.isEmpty
|
||||
let width = frame.width
|
||||
let height = frame.height
|
||||
let size = sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
|
||||
// `textView.contentSize` isn't accurate when restoring a multiline draft, so we set it here manually
|
||||
self.contentSize = size
|
||||
let newHeight = size.height.clamp(minHeight, maxHeight)
|
||||
guard newHeight != height else { return }
|
||||
heightConstraint.constant = newHeight
|
||||
snDelegate.inputTextViewDidChangeSize(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Delegate
|
||||
protocol InputTextViewDelegate {
|
||||
|
||||
func inputTextViewDidChangeSize(_ inputTextView: InputTextView)
|
||||
}
|
110
Session/Conversations V2/Input View/InputView.swift
Normal file
110
Session/Conversations V2/Input View/InputView.swift
Normal file
|
@ -0,0 +1,110 @@
|
|||
|
||||
final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate {
|
||||
private let delegate: InputViewDelegate
|
||||
|
||||
var text: String {
|
||||
get { inputTextView.text }
|
||||
set { inputTextView.text = newValue }
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize { CGSize.zero }
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var cameraButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_black"), delegate: self)
|
||||
private lazy var libraryButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_roll_black"), delegate: self)
|
||||
private lazy var gifButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_gif_black"), delegate: self)
|
||||
private lazy var documentButton = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_document_black"), delegate: self)
|
||||
private lazy var sendButton = InputViewButton(icon: #imageLiteral(resourceName: "ArrowUp"), isSendButton: true, delegate: self)
|
||||
|
||||
private lazy var inputTextView = InputTextView(delegate: self)
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(delegate: InputViewDelegate) {
|
||||
self.delegate = delegate
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(delegate:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(delegate:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
autoresizingMask = .flexibleHeight
|
||||
// Background & blur
|
||||
let backgroundView = UIView()
|
||||
backgroundView.backgroundColor = isLightMode ? .white : .black
|
||||
backgroundView.alpha = Values.lowOpacity
|
||||
addSubview(backgroundView)
|
||||
backgroundView.pin(to: self)
|
||||
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||
addSubview(blurView)
|
||||
blurView.pin(to: self)
|
||||
// Separator
|
||||
let separator = UIView()
|
||||
separator.backgroundColor = Colors.text.withAlphaComponent(0.2)
|
||||
separator.set(.height, to: 1 / UIScreen.main.scale)
|
||||
addSubview(separator)
|
||||
separator.pin([ UIView.HorizontalEdge.leading, UIView.VerticalEdge.top, UIView.HorizontalEdge.trailing ], to: self)
|
||||
// Buttons
|
||||
func container(for button: InputViewButton) -> UIView {
|
||||
let result = UIView()
|
||||
result.addSubview(button)
|
||||
result.set(.width, to: InputViewButton.expandedSize)
|
||||
result.set(.height, to: InputViewButton.expandedSize)
|
||||
button.center(in: result)
|
||||
return result
|
||||
}
|
||||
let (cameraButtonContainer, libraryButtonContainer, gifButtonContainer, documentButtonContainer) = (container(for: cameraButton), container(for: libraryButton), container(for: gifButton), container(for: documentButton))
|
||||
let buttonStackView = UIStackView(arrangedSubviews: [ cameraButtonContainer, libraryButtonContainer, gifButtonContainer, documentButtonContainer, UIView.hStretchingSpacer() ])
|
||||
buttonStackView.axis = .horizontal
|
||||
buttonStackView.spacing = Values.smallSpacing
|
||||
// Bottom stack view
|
||||
let bottomStackView = UIStackView(arrangedSubviews: [ inputTextView, container(for: sendButton) ])
|
||||
bottomStackView.axis = .horizontal
|
||||
bottomStackView.spacing = Values.smallSpacing
|
||||
// Main stack view
|
||||
let mainStackView = UIStackView(arrangedSubviews: [ buttonStackView, bottomStackView ])
|
||||
mainStackView.axis = .vertical
|
||||
mainStackView.spacing = 12
|
||||
mainStackView.isLayoutMarginsRelativeArrangement = true
|
||||
let adjustment = (InputViewButton.expandedSize - InputViewButton.size) / 2
|
||||
mainStackView.layoutMargins = UIEdgeInsets(top: Values.smallSpacing, leading: Values.largeSpacing, bottom: Values.smallSpacing, trailing: Values.largeSpacing - adjustment)
|
||||
addSubview(mainStackView)
|
||||
mainStackView.pin(.top, to: .bottom, of: separator)
|
||||
mainStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self)
|
||||
mainStackView.pin(.bottom, to: .bottom, of: self, withInset: -12)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
func inputTextViewDidChangeSize(_ inputTextView: InputTextView) {
|
||||
invalidateIntrinsicContentSize()
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
func handleInputViewButtonTapped(_ inputViewButton: InputViewButton) {
|
||||
if inputViewButton == cameraButton { delegate.handleCameraButtonTapped() }
|
||||
if inputViewButton == libraryButton { delegate.handleLibraryButtonTapped() }
|
||||
if inputViewButton == gifButton { delegate.handleGIFButtonTapped() }
|
||||
if inputViewButton == documentButton { delegate.handleDocumentButtonTapped() }
|
||||
if inputViewButton == sendButton { delegate.handleSendButtonTapped() }
|
||||
}
|
||||
|
||||
override func resignFirstResponder() -> Bool {
|
||||
inputTextView.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Delegate
|
||||
protocol InputViewDelegate {
|
||||
|
||||
func handleCameraButtonTapped()
|
||||
func handleLibraryButtonTapped()
|
||||
func handleGIFButtonTapped()
|
||||
func handleDocumentButtonTapped()
|
||||
func handleSendButtonTapped()
|
||||
}
|
90
Session/Conversations V2/Input View/InputViewButton.swift
Normal file
90
Session/Conversations V2/Input View/InputViewButton.swift
Normal file
|
@ -0,0 +1,90 @@
|
|||
|
||||
final class InputViewButton : UIView {
|
||||
private let icon: UIImage
|
||||
private let isSendButton: Bool
|
||||
private let delegate: InputViewButtonDelegate
|
||||
private lazy var widthConstraint = set(.width, to: InputViewButton.size)
|
||||
private lazy var heightConstraint = set(.height, to: InputViewButton.size)
|
||||
|
||||
// MARK: Settings
|
||||
static let size = CGFloat(40)
|
||||
static let expandedSize = CGFloat(48)
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(icon: UIImage, isSendButton: Bool = false, delegate: InputViewButtonDelegate) {
|
||||
self.icon = icon
|
||||
self.isSendButton = isSendButton
|
||||
self.delegate = delegate
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(icon:delegate:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(icon:delegate:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
backgroundColor = isSendButton ? Colors.accent : Colors.text.withAlphaComponent(0.05)
|
||||
layer.cornerRadius = InputViewButton.size / 2
|
||||
layer.masksToBounds = true
|
||||
isUserInteractionEnabled = true
|
||||
widthConstraint.isActive = true
|
||||
heightConstraint.isActive = true
|
||||
let tint = isSendButton ? UIColor.black : Colors.text
|
||||
let iconImageView = UIImageView(image: icon.withTint(tint))
|
||||
iconImageView.contentMode = .scaleAspectFit
|
||||
iconImageView.set(.width, to: 20)
|
||||
iconImageView.set(.height, to: 20)
|
||||
addSubview(iconImageView)
|
||||
iconImageView.center(in: self)
|
||||
}
|
||||
|
||||
// MARK: Animation
|
||||
private func animate(to size: CGFloat, glowColor: UIColor, backgroundColor: UIColor) {
|
||||
let frame = CGRect(center: center, size: CGSize(width: size, height: size))
|
||||
widthConstraint.constant = size
|
||||
heightConstraint.constant = size
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.layoutIfNeeded()
|
||||
self.frame = frame
|
||||
self.layer.cornerRadius = size / 2
|
||||
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: isLightMode ? 4 : 6)
|
||||
self.setCircularGlow(with: glowConfiguration)
|
||||
self.backgroundColor = backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
private func expand() {
|
||||
animate(to: InputViewButton.expandedSize, glowColor: Colors.expandedButtonGlowColor, backgroundColor: Colors.accent)
|
||||
}
|
||||
|
||||
private func collapse() {
|
||||
let backgroundColor = isSendButton ? Colors.accent : Colors.text.withAlphaComponent(0.05)
|
||||
animate(to: InputViewButton.size, glowColor: .clear, backgroundColor: backgroundColor)
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
expand()
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
collapse()
|
||||
delegate.handleInputViewButtonTapped(self)
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
collapse()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Delegate
|
||||
protocol InputViewButtonDelegate {
|
||||
|
||||
func handleInputViewButtonTapped(_ inputViewButton: InputViewButton)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
final class DocumentView : UIView {
|
||||
private let viewItem: ConversationViewItem
|
||||
private let textColor: UIColor
|
||||
|
||||
// MARK: Settings
|
||||
private static let iconSize: CGFloat = 24
|
||||
private static let iconImageViewSize: CGFloat = 40
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(viewItem: ConversationViewItem, textColor: UIColor) {
|
||||
self.viewItem = viewItem
|
||||
self.textColor = textColor
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(viewItem:textColor:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(viewItem:textColor:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
guard let attachment = viewItem.attachmentStream ?? viewItem.attachmentPointer else { return }
|
||||
// Image view
|
||||
let iconSize = DocumentView.iconSize
|
||||
let icon = UIImage(named: "actionsheet_document_black")?.withTint(textColor)?.resizedImage(to: CGSize(width: iconSize, height: iconSize))
|
||||
let imageView = UIImageView(image: icon)
|
||||
imageView.contentMode = .center
|
||||
let iconImageViewSize = DocumentView.iconImageViewSize
|
||||
imageView.set(.width, to: iconImageViewSize)
|
||||
imageView.set(.height, to: iconImageViewSize)
|
||||
// Body label
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.lineBreakMode = .byTruncatingTail
|
||||
titleLabel.text = attachment.sourceFilename ?? "File"
|
||||
titleLabel.textColor = textColor
|
||||
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||
// Stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ])
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
stackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 12)
|
||||
addSubview(stackView)
|
||||
stackView.pin(to: self, withInset: Values.smallSpacing)
|
||||
}
|
||||
}
|
|
@ -4,15 +4,15 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
@objc(OWSMediaAlbumCellView)
|
||||
public class MediaAlbumCellView: UIStackView {
|
||||
@objc(OWSMediaAlbumView)
|
||||
public class MediaAlbumView: UIStackView {
|
||||
private let items: [ConversationMediaAlbumItem]
|
||||
|
||||
@objc
|
||||
public let itemViews: [ConversationMediaView]
|
||||
public let itemViews: [MediaView]
|
||||
|
||||
@objc
|
||||
public var moreItemsView: ConversationMediaView?
|
||||
public var moreItemsView: MediaView?
|
||||
|
||||
private static let kSpacingPts: CGFloat = 2
|
||||
private static let kMaxItems = 5
|
||||
|
@ -26,22 +26,20 @@ public class MediaAlbumCellView: UIStackView {
|
|||
public required init(mediaCache: NSCache<NSString, AnyObject>,
|
||||
items: [ConversationMediaAlbumItem],
|
||||
isOutgoing: Bool,
|
||||
maxMessageWidth: CGFloat,
|
||||
isOnionRouted: Bool) {
|
||||
maxMessageWidth: CGFloat) {
|
||||
self.items = items
|
||||
self.itemViews = MediaAlbumCellView.itemsToDisplay(forItems: items).map {
|
||||
let result = ConversationMediaView(mediaCache: mediaCache,
|
||||
self.itemViews = MediaAlbumView.itemsToDisplay(forItems: items).map {
|
||||
let result = MediaView(mediaCache: mediaCache,
|
||||
attachment: $0.attachment,
|
||||
isOutgoing: isOutgoing,
|
||||
maxMessageWidth: maxMessageWidth,
|
||||
isOnionRouted: isOnionRouted)
|
||||
maxMessageWidth: maxMessageWidth)
|
||||
return result
|
||||
}
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
// UIStackView's backgroundColor property has no effect.
|
||||
addBackgroundView(withBackgroundColor: Theme.backgroundColor)
|
||||
addBackgroundView(withBackgroundColor: Colors.navigationBarBackground)
|
||||
|
||||
createContents(maxMessageWidth: maxMessageWidth)
|
||||
}
|
||||
|
@ -62,19 +60,19 @@ public class MediaAlbumCellView: UIStackView {
|
|||
case 2:
|
||||
// X X
|
||||
// side-by-side.
|
||||
let imageSize = (maxMessageWidth - MediaAlbumCellView.kSpacingPts) / 2
|
||||
let imageSize = (maxMessageWidth - MediaAlbumView.kSpacingPts) / 2
|
||||
autoSet(viewSize: imageSize, ofViews: itemViews)
|
||||
for itemView in itemViews {
|
||||
addArrangedSubview(itemView)
|
||||
}
|
||||
self.axis = .horizontal
|
||||
self.spacing = MediaAlbumCellView.kSpacingPts
|
||||
self.spacing = MediaAlbumView.kSpacingPts
|
||||
case 3:
|
||||
// x
|
||||
// X x
|
||||
// Big on left, 2 small on right.
|
||||
let smallImageSize = (maxMessageWidth - MediaAlbumCellView.kSpacingPts * 2) / 3
|
||||
let bigImageSize = smallImageSize * 2 + MediaAlbumCellView.kSpacingPts
|
||||
let smallImageSize = (maxMessageWidth - MediaAlbumView.kSpacingPts * 2) / 3
|
||||
let bigImageSize = smallImageSize * 2 + MediaAlbumView.kSpacingPts
|
||||
|
||||
guard let leftItemView = itemViews.first else {
|
||||
owsFailDebug("Missing view")
|
||||
|
@ -88,12 +86,12 @@ public class MediaAlbumCellView: UIStackView {
|
|||
axis: .vertical,
|
||||
viewSize: smallImageSize))
|
||||
self.axis = .horizontal
|
||||
self.spacing = MediaAlbumCellView.kSpacingPts
|
||||
self.spacing = MediaAlbumView.kSpacingPts
|
||||
case 4:
|
||||
// X X
|
||||
// X X
|
||||
// Square
|
||||
let imageSize = (maxMessageWidth - MediaAlbumCellView.kSpacingPts) / 2
|
||||
let imageSize = (maxMessageWidth - MediaAlbumView.kSpacingPts) / 2
|
||||
|
||||
let topViews = Array(itemViews[0..<2])
|
||||
addArrangedSubview(newRow(rowViews: topViews,
|
||||
|
@ -106,13 +104,13 @@ public class MediaAlbumCellView: UIStackView {
|
|||
viewSize: imageSize))
|
||||
|
||||
self.axis = .vertical
|
||||
self.spacing = MediaAlbumCellView.kSpacingPts
|
||||
self.spacing = MediaAlbumView.kSpacingPts
|
||||
default:
|
||||
// X X
|
||||
// xxx
|
||||
// 2 big on top, 3 small on bottom.
|
||||
let bigImageSize = (maxMessageWidth - MediaAlbumCellView.kSpacingPts) / 2
|
||||
let smallImageSize = (maxMessageWidth - MediaAlbumCellView.kSpacingPts * 2) / 3
|
||||
let bigImageSize = (maxMessageWidth - MediaAlbumView.kSpacingPts) / 2
|
||||
let smallImageSize = (maxMessageWidth - MediaAlbumView.kSpacingPts * 2) / 3
|
||||
|
||||
let topViews = Array(itemViews[0..<2])
|
||||
addArrangedSubview(newRow(rowViews: topViews,
|
||||
|
@ -125,9 +123,9 @@ public class MediaAlbumCellView: UIStackView {
|
|||
viewSize: smallImageSize))
|
||||
|
||||
self.axis = .vertical
|
||||
self.spacing = MediaAlbumCellView.kSpacingPts
|
||||
self.spacing = MediaAlbumView.kSpacingPts
|
||||
|
||||
if items.count > MediaAlbumCellView.kMaxItems {
|
||||
if items.count > MediaAlbumView.kMaxItems {
|
||||
guard let lastView = bottomViews.last else {
|
||||
owsFailDebug("Missing lastView")
|
||||
return
|
||||
|
@ -140,7 +138,7 @@ public class MediaAlbumCellView: UIStackView {
|
|||
lastView.addSubview(tintView)
|
||||
tintView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let moreCount = max(1, items.count - MediaAlbumCellView.kMaxItems)
|
||||
let moreCount = max(1, items.count - MediaAlbumView.kMaxItems)
|
||||
let moreCountText = OWSFormat.formatInt(Int32(moreCount))
|
||||
let moreText = String(format: NSLocalizedString("MEDIA_GALLERY_MORE_ITEMS_FORMAT",
|
||||
comment: "Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}."), moreCountText)
|
||||
|
@ -184,24 +182,24 @@ public class MediaAlbumCellView: UIStackView {
|
|||
}
|
||||
|
||||
private func autoSet(viewSize: CGFloat,
|
||||
ofViews views: [ConversationMediaView]) {
|
||||
ofViews views: [MediaView]) {
|
||||
for itemView in views {
|
||||
itemView.autoSetDimensions(to: CGSize(width: viewSize, height: viewSize))
|
||||
}
|
||||
}
|
||||
|
||||
private func newRow(rowViews: [ConversationMediaView],
|
||||
private func newRow(rowViews: [MediaView],
|
||||
axis: NSLayoutConstraint.Axis,
|
||||
viewSize: CGFloat) -> UIStackView {
|
||||
autoSet(viewSize: viewSize, ofViews: rowViews)
|
||||
return newRow(rowViews: rowViews, axis: axis)
|
||||
}
|
||||
|
||||
private func newRow(rowViews: [ConversationMediaView],
|
||||
private func newRow(rowViews: [MediaView],
|
||||
axis: NSLayoutConstraint.Axis) -> UIStackView {
|
||||
let stackView = UIStackView(arrangedSubviews: rowViews)
|
||||
stackView.axis = axis
|
||||
stackView.spacing = MediaAlbumCellView.kSpacingPts
|
||||
stackView.spacing = MediaAlbumView.kSpacingPts
|
||||
return stackView
|
||||
}
|
||||
|
||||
|
@ -267,8 +265,8 @@ public class MediaAlbumCellView: UIStackView {
|
|||
}
|
||||
|
||||
@objc
|
||||
public func mediaView(forLocation location: CGPoint) -> ConversationMediaView? {
|
||||
var bestMediaView: ConversationMediaView?
|
||||
public func mediaView(forLocation location: CGPoint) -> MediaView? {
|
||||
var bestMediaView: MediaView?
|
||||
var bestDistance: CGFloat = 0
|
||||
for itemView in itemViews {
|
||||
let itemCenter = convert(itemView.center, from: itemView.superview)
|
||||
|
@ -283,7 +281,7 @@ public class MediaAlbumCellView: UIStackView {
|
|||
}
|
||||
|
||||
@objc
|
||||
public func isMoreItemsView(mediaView: ConversationMediaView) -> Bool {
|
||||
public func isMoreItemsView(mediaView: MediaView) -> Bool {
|
||||
return moreItemsView == mediaView
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
|
||||
final class MediaLoaderView : UIView {
|
||||
private let bar = UIView()
|
||||
|
||||
private lazy var barLeftConstraint = bar.pin(.left, to: .left, of: self)
|
||||
private lazy var barRightConstraint = bar.pin(.right, to: .right, of: self)
|
||||
|
||||
// MARK: Lifecycle
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
bar.backgroundColor = Colors.accent
|
||||
bar.set(.height, to: 8)
|
||||
addSubview(bar)
|
||||
barLeftConstraint.isActive = true
|
||||
bar.pin(.top, to: .top, of: self)
|
||||
barRightConstraint.isActive = true
|
||||
bar.pin(.bottom, to: .bottom, of: self)
|
||||
step1()
|
||||
}
|
||||
|
||||
// MARK: Animation
|
||||
func step1() {
|
||||
barRightConstraint.constant = -bounds.width
|
||||
UIView.animate(withDuration: 0.5, animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.barRightConstraint.constant = 0
|
||||
self.layoutIfNeeded()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.step2()
|
||||
})
|
||||
}
|
||||
|
||||
func step2() {
|
||||
barLeftConstraint.constant = 0
|
||||
UIView.animate(withDuration: 0.5, animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.barLeftConstraint.constant = self.bounds.width
|
||||
self.layoutIfNeeded()
|
||||
}, completion: { [weak self] _ in
|
||||
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
|
||||
self?.step3()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func step3() {
|
||||
barLeftConstraint.constant = bounds.width
|
||||
UIView.animate(withDuration: 0.5, animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.barLeftConstraint.constant = 0
|
||||
self.layoutIfNeeded()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.step4()
|
||||
})
|
||||
}
|
||||
|
||||
func step4() {
|
||||
barRightConstraint.constant = 0
|
||||
UIView.animate(withDuration: 0.5, animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.barRightConstraint.constant = -self.bounds.width
|
||||
self.layoutIfNeeded()
|
||||
}, completion: { [weak self] _ in
|
||||
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
|
||||
self?.step1()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
|
||||
final class MediaTextOverlayView : UIView {
|
||||
private let viewItem: ConversationViewItem
|
||||
private let albumViewWidth: CGFloat
|
||||
private let delegate: MessageCellDelegate
|
||||
var readMoreButton: UIButton?
|
||||
|
||||
// MARK: Settings
|
||||
private static let maxHeight: CGFloat = 88;
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(viewItem: ConversationViewItem, albumViewWidth: CGFloat, delegate: MessageCellDelegate) {
|
||||
self.viewItem = viewItem
|
||||
self.albumViewWidth = albumViewWidth
|
||||
self.delegate = delegate
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(text:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(text:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
guard let message = viewItem.interaction as? TSMessage, let body = message.body, body.count > 0 else { return }
|
||||
// Shadow
|
||||
let shadowView = GradientView(from: .clear, to: UIColor.black.withAlphaComponent(0.7))
|
||||
addSubview(shadowView)
|
||||
shadowView.pin(to: self)
|
||||
// Line
|
||||
let lineView = UIView()
|
||||
lineView.backgroundColor = Colors.accent
|
||||
lineView.set(.width, to: Values.accentLineThickness)
|
||||
// Body label
|
||||
let bodyLabel = UILabel()
|
||||
bodyLabel.numberOfLines = 0
|
||||
bodyLabel.lineBreakMode = .byTruncatingTail
|
||||
bodyLabel.text = given(body) { MentionUtilities.highlightMentions(in: $0, threadID: viewItem.interaction.uniqueThreadId) }
|
||||
bodyLabel.textColor = .white
|
||||
bodyLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
// Content stack view
|
||||
let contentStackView = UIStackView(arrangedSubviews: [ lineView, bodyLabel ])
|
||||
contentStackView.axis = .horizontal
|
||||
contentStackView.spacing = Values.smallSpacing
|
||||
addSubview(contentStackView)
|
||||
let inset = Values.mediumSpacing
|
||||
contentStackView.pin(.left, to: .left, of: self, withInset: inset)
|
||||
contentStackView.pin(.top, to: .top, of: self, withInset: 3 * inset)
|
||||
contentStackView.pin(.right, to: .right, of: self, withInset: -inset)
|
||||
// Max height
|
||||
bodyLabel.heightAnchor.constraint(lessThanOrEqualToConstant: MediaTextOverlayView.maxHeight).isActive = true
|
||||
// Overflow button
|
||||
let bodyLabelTargetSize = bodyLabel.sizeThatFits(CGSize(width: albumViewWidth - 2 * inset, height: .greatestFiniteMagnitude))
|
||||
if bodyLabelTargetSize.height > MediaTextOverlayView.maxHeight {
|
||||
let readMoreButton = UIButton()
|
||||
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.addTarget(self, action: #selector(readMore), for: UIControl.Event.touchUpInside)
|
||||
addSubview(readMoreButton)
|
||||
readMoreButton.pin(.left, to: .left, of: self, withInset: inset)
|
||||
readMoreButton.pin(.top, to: .bottom, of: contentStackView, withInset: Values.smallSpacing)
|
||||
readMoreButton.pin(.bottom, to: .bottom, of: self, withInset: -Values.smallSpacing)
|
||||
} else {
|
||||
contentStackView.pin(.bottom, to: .bottom, of: self, withInset: -inset)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func readMore() {
|
||||
delegate.showFullText(viewItem)
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
@objc(OWSConversationMediaView)
|
||||
public class ConversationMediaView: UIView {
|
||||
@objc(OWSMediaView)
|
||||
public class MediaView: UIView {
|
||||
|
||||
private enum MediaError {
|
||||
case missing
|
||||
|
@ -22,7 +22,6 @@ public class ConversationMediaView: UIView {
|
|||
private let maxMessageWidth: CGFloat
|
||||
private var loadBlock: (() -> Void)?
|
||||
private var unloadBlock: (() -> Void)?
|
||||
private let isOnionRouted: Bool
|
||||
|
||||
// MARK: - LoadState
|
||||
|
||||
|
@ -85,17 +84,15 @@ public class ConversationMediaView: UIView {
|
|||
public required init(mediaCache: NSCache<NSString, AnyObject>,
|
||||
attachment: TSAttachment,
|
||||
isOutgoing: Bool,
|
||||
maxMessageWidth: CGFloat,
|
||||
isOnionRouted: Bool) {
|
||||
maxMessageWidth: CGFloat) {
|
||||
self.mediaCache = mediaCache
|
||||
self.attachment = attachment
|
||||
self.isOutgoing = isOutgoing
|
||||
self.maxMessageWidth = maxMessageWidth
|
||||
self.isOnionRouted = isOnionRouted
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
backgroundColor = Theme.offBackgroundColor
|
||||
backgroundColor = Colors.unimportant
|
||||
clipsToBounds = true
|
||||
|
||||
createContents()
|
||||
|
@ -152,53 +149,19 @@ public class ConversationMediaView: UIView {
|
|||
configure(forError: .missing)
|
||||
return
|
||||
}
|
||||
guard let attachmentId = attachmentPointer.uniqueId else {
|
||||
owsFailDebug("Attachment missing unique ID.")
|
||||
configure(forError: .invalid)
|
||||
return
|
||||
}
|
||||
/*
|
||||
guard nil != attachmentDownloads.downloadProgress(forAttachmentId: attachmentId) else {
|
||||
// Not being downloaded.
|
||||
configure(forError: .missing)
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
backgroundColor = (Theme.isDarkThemeEnabled ? .ows_gray90 : .ows_gray05)
|
||||
let view: UIView
|
||||
if isOnionRouted { // Loki: Due to the way onion routing works we can't get upload progress for those attachments
|
||||
let activityIndicatorView = UIActivityIndicatorView(style: .white)
|
||||
activityIndicatorView.isHidden = false
|
||||
activityIndicatorView.startAnimating()
|
||||
view = activityIndicatorView
|
||||
} else {
|
||||
view = MediaDownloadView(attachmentId: attachmentId, radius: maxMessageWidth * 0.1)
|
||||
}
|
||||
addSubview(view)
|
||||
view.autoPinEdgesToSuperviewEdges()
|
||||
let loader = MediaLoaderView()
|
||||
addSubview(loader)
|
||||
loader.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.right ], to: self)
|
||||
}
|
||||
|
||||
private func addUploadProgressIfNecessary(_ subview: UIView) -> Bool {
|
||||
guard isOutgoing else { return false }
|
||||
guard let attachmentStream = attachment as? TSAttachmentStream else { return false }
|
||||
guard let attachmentId = attachmentStream.uniqueId else {
|
||||
owsFailDebug("Attachment missing unique ID.")
|
||||
configure(forError: .invalid)
|
||||
return false
|
||||
}
|
||||
guard !attachmentStream.isUploaded else { return false }
|
||||
let view: UIView
|
||||
if isOnionRouted { // Loki: Due to the way onion routing works we can't get upload progress for those attachments
|
||||
let activityIndicatorView = UIActivityIndicatorView(style: .white)
|
||||
activityIndicatorView.isHidden = false
|
||||
activityIndicatorView.startAnimating()
|
||||
view = activityIndicatorView
|
||||
} else {
|
||||
view = MediaUploadView(attachmentId: attachmentId, radius: maxMessageWidth * 0.1)
|
||||
}
|
||||
addSubview(view)
|
||||
view.autoPinEdgesToSuperviewEdges()
|
||||
let loader = MediaLoaderView()
|
||||
addSubview(loader)
|
||||
loader.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.right ], to: self)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -215,7 +178,7 @@ public class ConversationMediaView: UIView {
|
|||
// some performance cost.
|
||||
animatedImageView.layer.minificationFilter = .trilinear
|
||||
animatedImageView.layer.magnificationFilter = .trilinear
|
||||
animatedImageView.backgroundColor = Theme.offBackgroundColor
|
||||
animatedImageView.backgroundColor = Colors.unimportant
|
||||
addSubview(animatedImageView)
|
||||
animatedImageView.autoPinEdgesToSuperviewEdges()
|
||||
_ = addUploadProgressIfNecessary(animatedImageView)
|
||||
|
@ -274,7 +237,7 @@ public class ConversationMediaView: UIView {
|
|||
// some performance cost.
|
||||
stillImageView.layer.minificationFilter = .trilinear
|
||||
stillImageView.layer.magnificationFilter = .trilinear
|
||||
stillImageView.backgroundColor = Theme.offBackgroundColor
|
||||
stillImageView.backgroundColor = Colors.unimportant
|
||||
addSubview(stillImageView)
|
||||
stillImageView.autoPinEdgesToSuperviewEdges()
|
||||
_ = addUploadProgressIfNecessary(stillImageView)
|
||||
|
@ -329,7 +292,7 @@ public class ConversationMediaView: UIView {
|
|||
// some performance cost.
|
||||
stillImageView.layer.minificationFilter = .trilinear
|
||||
stillImageView.layer.magnificationFilter = .trilinear
|
||||
stillImageView.backgroundColor = Theme.offBackgroundColor
|
||||
stillImageView.backgroundColor = Colors.unimportant
|
||||
|
||||
addSubview(stillImageView)
|
||||
stillImageView.autoPinEdgesToSuperviewEdges()
|
||||
|
@ -408,7 +371,7 @@ public class ConversationMediaView: UIView {
|
|||
return
|
||||
}
|
||||
let iconView = UIImageView(image: icon.withRenderingMode(.alwaysTemplate))
|
||||
iconView.tintColor = Theme.primaryColor.withAlphaComponent(0.6)
|
||||
iconView.tintColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
addSubview(iconView)
|
||||
iconView.autoCenterInSuperview()
|
||||
}
|
||||
|
@ -457,7 +420,7 @@ public class ConversationMediaView: UIView {
|
|||
Logger.verbose("media cache miss")
|
||||
|
||||
let threadSafeLoadState = self.threadSafeLoadState
|
||||
ConversationMediaView.loadQueue.async {
|
||||
MediaView.loadQueue.async {
|
||||
guard threadSafeLoadState.get() == .loading else {
|
||||
Logger.verbose("Skipping obsolete load.")
|
||||
return
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
final class QuoteView : UIView {
|
||||
private let viewItem: ConversationViewItem
|
||||
private let maxMessageWidth: CGFloat
|
||||
|
||||
private var direction: Direction {
|
||||
guard let message = viewItem.interaction as? TSMessage else { preconditionFailure() }
|
||||
switch message {
|
||||
case is TSIncomingMessage: return .incoming
|
||||
case is TSOutgoingMessage: return .outgoing
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private var lineColor: UIColor {
|
||||
return .black
|
||||
}
|
||||
|
||||
private var textColor: UIColor {
|
||||
switch (direction, AppModeManager.shared.currentAppMode) {
|
||||
case (.outgoing, .dark), (.incoming, .light): return .white
|
||||
default: return .black
|
||||
}
|
||||
}
|
||||
|
||||
private var snBackgroundColor: UIColor {
|
||||
switch direction {
|
||||
case .outgoing: return Colors.receivedMessageBackground
|
||||
case .incoming: return Colors.sentMessageBackground
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Direction
|
||||
enum Direction { case incoming, outgoing }
|
||||
|
||||
// MARK: Settings
|
||||
static let inset = Values.smallSpacing
|
||||
static let thumbnailSize: CGFloat = 48
|
||||
static let iconSize: CGFloat = 24
|
||||
static let labelStackViewSpacing: CGFloat = 2
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(for viewItem: ConversationViewItem, maxMessageWidth: CGFloat) {
|
||||
self.viewItem = viewItem
|
||||
self.maxMessageWidth = maxMessageWidth
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(for:maxMessageWidth:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(for:maxMessageWidth:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
guard let quote = (viewItem.interaction as? TSMessage)?.quotedMessage else { return }
|
||||
let hasAttachments = !quote.quotedAttachments.isEmpty
|
||||
let thumbnailSize = QuoteView.thumbnailSize
|
||||
let iconSize = QuoteView.iconSize
|
||||
let labelStackViewSpacing = QuoteView.labelStackViewSpacing
|
||||
let smallSpacing = Values.smallSpacing
|
||||
let inset = QuoteView.inset
|
||||
let availableWidth: CGFloat
|
||||
if !hasAttachments {
|
||||
availableWidth = maxMessageWidth - 2 * inset - Values.accentLineThickness - 2 * smallSpacing
|
||||
} else {
|
||||
availableWidth = maxMessageWidth - 2 * inset - thumbnailSize - 2 * smallSpacing
|
||||
}
|
||||
let availableSpace = CGSize(width: availableWidth, height: .greatestFiniteMagnitude)
|
||||
var body = quote.body
|
||||
// Main stack view
|
||||
let mainStackView = UIStackView(arrangedSubviews: [])
|
||||
mainStackView.axis = .horizontal
|
||||
mainStackView.spacing = smallSpacing
|
||||
mainStackView.isLayoutMarginsRelativeArrangement = true
|
||||
mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: smallSpacing)
|
||||
mainStackView.alignment = .center
|
||||
// Content view
|
||||
let contentView = UIView()
|
||||
contentView.backgroundColor = snBackgroundColor
|
||||
contentView.layer.cornerRadius = VisibleMessageCell.smallCornerRadius
|
||||
contentView.layer.masksToBounds = true
|
||||
addSubview(contentView)
|
||||
contentView.pin(to: self, withInset: inset)
|
||||
// Line view
|
||||
let lineView = UIView()
|
||||
lineView.backgroundColor = lineColor
|
||||
lineView.set(.width, to: Values.accentLineThickness)
|
||||
if !hasAttachments {
|
||||
mainStackView.addArrangedSubview(lineView)
|
||||
} else {
|
||||
let image = viewItem.quotedReply?.thumbnailImage
|
||||
let fallbackImage = UIImage(named: "actionsheet_document_black")?.withTint(.white)?.resizedImage(to: CGSize(width: iconSize, height: iconSize))
|
||||
let imageView = UIImageView(image: image ?? fallbackImage)
|
||||
imageView.contentMode = (image != nil) ? .scaleAspectFill : .center
|
||||
imageView.backgroundColor = lineColor
|
||||
imageView.set(.width, to: thumbnailSize)
|
||||
imageView.set(.height, to: thumbnailSize)
|
||||
mainStackView.addArrangedSubview(imageView)
|
||||
body = (image != nil) ? "Image" : "Document"
|
||||
}
|
||||
// Body label
|
||||
let bodyLabel = UILabel()
|
||||
bodyLabel.numberOfLines = 0
|
||||
bodyLabel.lineBreakMode = .byTruncatingTail
|
||||
bodyLabel.text = given(body) { MentionUtilities.highlightMentions(in: $0, threadID: viewItem.interaction.uniqueThreadId) } ?? "Document"
|
||||
bodyLabel.textColor = textColor
|
||||
bodyLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
if hasAttachments {
|
||||
bodyLabel.numberOfLines = 1
|
||||
}
|
||||
let bodyLabelSize = bodyLabel.systemLayoutSizeFitting(availableSpace)
|
||||
// Label stack view
|
||||
if viewItem.isGroupThread {
|
||||
let authorLabel = UILabel()
|
||||
authorLabel.lineBreakMode = .byTruncatingTail
|
||||
authorLabel.text = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: quote.authorId, avoidingWriteTransaction: true)
|
||||
authorLabel.textColor = textColor
|
||||
authorLabel.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
||||
let authorLabelSize = authorLabel.systemLayoutSizeFitting(availableSpace)
|
||||
let labelStackView = UIStackView(arrangedSubviews: [ authorLabel, bodyLabel ])
|
||||
labelStackView.axis = .vertical
|
||||
labelStackView.spacing = labelStackViewSpacing
|
||||
labelStackView.set(.width, to: max(bodyLabelSize.width, authorLabelSize.width))
|
||||
mainStackView.addArrangedSubview(labelStackView)
|
||||
} else {
|
||||
mainStackView.addArrangedSubview(bodyLabel)
|
||||
}
|
||||
// Constraints
|
||||
contentView.addSubview(mainStackView)
|
||||
mainStackView.pin(to: contentView)
|
||||
if !viewItem.isGroupThread {
|
||||
bodyLabel.set(.width, to: bodyLabelSize.width)
|
||||
}
|
||||
let bodyLabelHeight = bodyLabelSize.height
|
||||
let authorLabelHeight: CGFloat = 14.33
|
||||
let isAuthorShown = viewItem.isGroupThread
|
||||
let contentViewHeight: CGFloat
|
||||
if hasAttachments {
|
||||
contentViewHeight = thumbnailSize
|
||||
} else {
|
||||
contentViewHeight = bodyLabelHeight + 2 * smallSpacing + (isAuthorShown ? (authorLabelHeight + labelStackViewSpacing) : 0)
|
||||
}
|
||||
contentView.set(.height, to: contentViewHeight)
|
||||
lineView.set(.height, to: contentViewHeight)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
import NVActivityIndicatorView
|
||||
|
||||
@objc(SNVoiceMessageView)
|
||||
public final class VoiceMessageViewV2 : UIView {
|
||||
private let viewItem: ConversationViewItem
|
||||
private var isShowingSpeedUpLabel = false
|
||||
@objc var progress: Int = 0 { didSet { handleProgressChanged() } }
|
||||
@objc var isPlaying = false { didSet { handleIsPlayingChanged() } }
|
||||
|
||||
private lazy var progressViewRightConstraint = progressView.pin(.right, to: .right, of: self, withInset: -VoiceMessageViewV2.width)
|
||||
|
||||
private var attachment: TSAttachment? { viewItem.attachmentStream ?? viewItem.attachmentPointer }
|
||||
private var duration: Int { Int(viewItem.audioDurationSeconds) }
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var progressView: UIView = {
|
||||
let result = UIView()
|
||||
result.backgroundColor = UIColor.black.withAlphaComponent(0.2)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var toggleImageView: UIImageView = {
|
||||
let result = UIImageView(image: UIImage(named: "Play"))
|
||||
result.set(.width, to: 8)
|
||||
result.set(.height, to: 8)
|
||||
result.contentMode = .scaleAspectFit
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var loader: NVActivityIndicatorView = {
|
||||
let result = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
|
||||
result.set(.width, to: VoiceMessageViewV2.toggleContainerSize + 2)
|
||||
result.set(.height, to: VoiceMessageViewV2.toggleContainerSize + 2)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var countdownLabelContainer: UIView = {
|
||||
let result = UIView()
|
||||
result.backgroundColor = .white
|
||||
result.layer.masksToBounds = true
|
||||
result.set(.height, to: VoiceMessageViewV2.toggleContainerSize)
|
||||
result.set(.width, to: 44)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var countdownLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.textColor = .black
|
||||
result.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
result.text = "00:00"
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var speedUpLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.textColor = .black
|
||||
result.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
result.alpha = 0
|
||||
result.text = "1.5x"
|
||||
result.textAlignment = .center
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Settings
|
||||
private static let width: CGFloat = 160
|
||||
private static let toggleContainerSize: CGFloat = 20
|
||||
private static let inset = Values.smallSpacing
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(viewItem: ConversationViewItem) {
|
||||
self.viewItem = viewItem
|
||||
self.progress = Int(viewItem.audioProgressSeconds)
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
handleProgressChanged()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(viewItem:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(viewItem:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
let toggleContainerSize = VoiceMessageViewV2.toggleContainerSize
|
||||
let inset = VoiceMessageViewV2.inset
|
||||
// Width & height
|
||||
set(.width, to: VoiceMessageViewV2.width)
|
||||
// Toggle
|
||||
let toggleContainer = UIView()
|
||||
toggleContainer.backgroundColor = .white
|
||||
toggleContainer.set(.width, to: toggleContainerSize)
|
||||
toggleContainer.set(.height, to: toggleContainerSize)
|
||||
toggleContainer.addSubview(toggleImageView)
|
||||
toggleImageView.center(in: toggleContainer)
|
||||
toggleContainer.layer.cornerRadius = toggleContainerSize / 2
|
||||
toggleContainer.layer.masksToBounds = true
|
||||
// Line
|
||||
let lineView = UIView()
|
||||
lineView.backgroundColor = .white
|
||||
lineView.set(.height, to: 1)
|
||||
// Countdown label
|
||||
countdownLabelContainer.addSubview(countdownLabel)
|
||||
countdownLabel.center(in: countdownLabelContainer)
|
||||
// Speed up label
|
||||
countdownLabelContainer.addSubview(speedUpLabel)
|
||||
speedUpLabel.center(in: countdownLabelContainer)
|
||||
// Constraints
|
||||
addSubview(progressView)
|
||||
progressView.pin(.left, to: .left, of: self)
|
||||
progressView.pin(.top, to: .top, of: self)
|
||||
progressViewRightConstraint.isActive = true
|
||||
progressView.pin(.bottom, to: .bottom, of: self)
|
||||
addSubview(toggleContainer)
|
||||
toggleContainer.pin(.left, to: .left, of: self, withInset: inset)
|
||||
toggleContainer.pin(.top, to: .top, of: self, withInset: inset)
|
||||
toggleContainer.pin(.bottom, to: .bottom, of: self, withInset: -inset)
|
||||
addSubview(lineView)
|
||||
lineView.pin(.left, to: .right, of: toggleContainer)
|
||||
lineView.center(.vertical, in: self)
|
||||
addSubview(countdownLabelContainer)
|
||||
countdownLabelContainer.pin(.left, to: .right, of: lineView)
|
||||
countdownLabelContainer.pin(.right, to: .right, of: self, withInset: -inset)
|
||||
countdownLabelContainer.center(.vertical, in: self)
|
||||
addSubview(loader)
|
||||
loader.center(in: toggleContainer)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
countdownLabelContainer.layer.cornerRadius = countdownLabelContainer.bounds.height / 2
|
||||
}
|
||||
|
||||
private func handleIsPlayingChanged() {
|
||||
toggleImageView.image = isPlaying ? UIImage(named: "Pause") : UIImage(named: "Play")
|
||||
}
|
||||
|
||||
private func handleProgressChanged() {
|
||||
let isDownloaded = (attachment?.isDownloaded == true)
|
||||
loader.isHidden = isDownloaded
|
||||
if isDownloaded { loader.stopAnimating() } else if !loader.isAnimating { loader.startAnimating() }
|
||||
guard isDownloaded else { return }
|
||||
countdownLabel.text = OWSFormat.formatDurationSeconds(duration - progress)
|
||||
guard viewItem.audioProgressSeconds > 0 && viewItem.audioDurationSeconds > 0 else { return }
|
||||
let fraction = viewItem.audioProgressSeconds / viewItem.audioDurationSeconds
|
||||
progressViewRightConstraint.constant = -(VoiceMessageViewV2.width * (1 - fraction))
|
||||
}
|
||||
|
||||
func showSpeedUpLabel() {
|
||||
guard !isShowingSpeedUpLabel else { return }
|
||||
isShowingSpeedUpLabel = true
|
||||
UIView.animate(withDuration: 0.25) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.countdownLabel.alpha = 0
|
||||
self.speedUpLabel.alpha = 1
|
||||
}
|
||||
Timer.scheduledTimer(withTimeInterval: 1.25, repeats: false) { [weak self] _ in
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
guard let self = self else { return }
|
||||
self.countdownLabel.alpha = 1
|
||||
self.speedUpLabel.alpha = 0
|
||||
}, completion: { _ in
|
||||
self?.isShowingSpeedUpLabel = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
71
Session/Conversations V2/Message Cells/InfoMessageCell.swift
Normal file
71
Session/Conversations V2/Message Cells/InfoMessageCell.swift
Normal file
|
@ -0,0 +1,71 @@
|
|||
|
||||
final class InfoMessageCell : MessageCell {
|
||||
private lazy var iconImageViewWidthConstraint = iconImageView.set(.width, to: InfoMessageCell.iconSize)
|
||||
private lazy var iconImageViewHeightConstraint = iconImageView.set(.height, to: InfoMessageCell.iconSize)
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var iconImageView = UIImageView()
|
||||
|
||||
private lazy var label: UILabel = {
|
||||
let result = UILabel()
|
||||
result.numberOfLines = 0
|
||||
result.lineBreakMode = .byWordWrapping
|
||||
result.font = .boldSystemFont(ofSize: Values.verySmallFontSize)
|
||||
result.textColor = Colors.text
|
||||
result.textAlignment = .center
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var stackView: UIStackView = {
|
||||
let result = UIStackView(arrangedSubviews: [ iconImageView, label ])
|
||||
result.axis = .vertical
|
||||
result.alignment = .center
|
||||
result.spacing = Values.smallSpacing
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Settings
|
||||
private static let iconSize: CGFloat = 16
|
||||
private static let inset = Values.mediumSpacing
|
||||
|
||||
override class var identifier: String { "InfoMessageCell" }
|
||||
|
||||
// MARK: Lifecycle
|
||||
override func setUpViewHierarchy() {
|
||||
super.setUpViewHierarchy()
|
||||
iconImageViewWidthConstraint.isActive = true
|
||||
iconImageViewHeightConstraint.isActive = true
|
||||
addSubview(stackView)
|
||||
stackView.pin(.left, to: .left, of: self, withInset: InfoMessageCell.inset)
|
||||
stackView.pin(.top, to: .top, of: self, withInset: InfoMessageCell.inset)
|
||||
stackView.pin(.right, to: .right, of: self, withInset: -InfoMessageCell.inset)
|
||||
stackView.pin(.bottom, to: .bottom, of: self, withInset: -InfoMessageCell.inset)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
override func update() {
|
||||
guard let message = viewItem?.interaction as? TSInfoMessage else { return }
|
||||
let icon: UIImage?
|
||||
switch message.messageType {
|
||||
case .typeDisappearingMessagesUpdate:
|
||||
var configuration: OWSDisappearingMessagesConfiguration?
|
||||
Storage.read { transaction in
|
||||
configuration = message.thread(with: transaction).disappearingMessagesConfiguration(with: transaction)
|
||||
}
|
||||
if let configuration = configuration {
|
||||
icon = configuration.isEnabled ? UIImage(named: "ic_timer") : UIImage(named: "ic_timer_disabled")
|
||||
} else {
|
||||
icon = nil
|
||||
}
|
||||
default: icon = nil
|
||||
}
|
||||
if let icon = icon {
|
||||
iconImageView.image = icon.withTint(Colors.text)
|
||||
}
|
||||
iconImageViewWidthConstraint.constant = (icon != nil) ? InfoMessageCell.iconSize : 0
|
||||
iconImageViewHeightConstraint.constant = (icon != nil) ? InfoMessageCell.iconSize : 0
|
||||
Storage.read { transaction in
|
||||
self.label.text = message.previewText(with: transaction)
|
||||
}
|
||||
}
|
||||
}
|
58
Session/Conversations V2/Message Cells/MessageCell.swift
Normal file
58
Session/Conversations V2/Message Cells/MessageCell.swift
Normal file
|
@ -0,0 +1,58 @@
|
|||
import UIKit
|
||||
|
||||
class MessageCell : UITableViewCell {
|
||||
var delegate: MessageCellDelegate?
|
||||
var viewItem: ConversationViewItem? { didSet { update() } }
|
||||
|
||||
// MARK: Settings
|
||||
class var identifier: String { preconditionFailure("Must be overridden by subclasses.") }
|
||||
|
||||
// MARK: Lifecycle
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setUpViewHierarchy()
|
||||
setUpGestureRecognizers()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setUpViewHierarchy()
|
||||
setUpGestureRecognizers()
|
||||
}
|
||||
|
||||
func setUpViewHierarchy() {
|
||||
backgroundColor = .clear
|
||||
let selectedBackgroundView = UIView()
|
||||
selectedBackgroundView.backgroundColor = .clear
|
||||
self.selectedBackgroundView = selectedBackgroundView
|
||||
}
|
||||
|
||||
func setUpGestureRecognizers() {
|
||||
// To be overridden by subclasses
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
func update() {
|
||||
preconditionFailure("Must be overridden by subclasses.")
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
static func getCellType(for viewItem: ConversationViewItem) -> MessageCell.Type {
|
||||
switch viewItem.interaction {
|
||||
case is TSIncomingMessage: fallthrough
|
||||
case is TSOutgoingMessage: return VisibleMessageCell.self
|
||||
case is TSInfoMessage: return InfoMessageCell.self
|
||||
case is TypingIndicatorInteraction: return TypingIndicatorCellV2.self
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol MessageCellDelegate {
|
||||
|
||||
func getMediaCache() -> NSCache<NSString, AnyObject>
|
||||
func handleViewItemLongPressed(_ viewItem: ConversationViewItem)
|
||||
func handleViewItemTapped(_ viewItem: ConversationViewItem, gestureRecognizer: UITapGestureRecognizer)
|
||||
func handleViewItemDoubleTapped(_ viewItem: ConversationViewItem)
|
||||
func showFullText(_ viewItem: ConversationViewItem)
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
|
||||
// Assumptions
|
||||
// • We'll never encounter an outgoing typing indicator.
|
||||
// • Typing indicators are only sent in contact threads.
|
||||
|
||||
final class TypingIndicatorCellV2 : MessageCell {
|
||||
|
||||
private var positionInCluster: Position? {
|
||||
guard let viewItem = viewItem else { return nil }
|
||||
if viewItem.isFirstInCluster { return .top }
|
||||
if viewItem.isLastInCluster { return .bottom }
|
||||
return .middle
|
||||
}
|
||||
|
||||
private var isOnlyMessageInCluster: Bool { viewItem?.isFirstInCluster == true && viewItem?.isLastInCluster == true }
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var bubbleView: UIView = {
|
||||
let result = UIView()
|
||||
result.layer.cornerRadius = VisibleMessageCell.smallCornerRadius
|
||||
result.backgroundColor = Colors.receivedMessageBackground
|
||||
return result
|
||||
}()
|
||||
|
||||
private let bubbleViewMaskLayer = CAShapeLayer()
|
||||
|
||||
private lazy var typingIndicatorView = TypingIndicatorView()
|
||||
|
||||
// MARK: Settings
|
||||
override class var identifier: String { "TypingIndicatorCell" }
|
||||
|
||||
// MARK: Direction & Position
|
||||
enum Position { case top, middle, bottom }
|
||||
|
||||
// MARK: Lifecycle
|
||||
override func setUpViewHierarchy() {
|
||||
super.setUpViewHierarchy()
|
||||
// Bubble view
|
||||
addSubview(bubbleView)
|
||||
bubbleView.pin(.left, to: .left, of: self, withInset: VisibleMessageCell.contactThreadHSpacing)
|
||||
bubbleView.pin(.top, to: .top, of: self, withInset: 1)
|
||||
// Typing indicator view
|
||||
bubbleView.addSubview(typingIndicatorView)
|
||||
typingIndicatorView.pin(to: bubbleView, withInset: 12)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
override func update() {
|
||||
guard let viewItem = viewItem, viewItem.interaction is TypingIndicatorInteraction else { return }
|
||||
// Bubble view
|
||||
updateBubbleViewCorners()
|
||||
// Typing indicator view
|
||||
typingIndicatorView.startAnimation()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateBubbleViewCorners()
|
||||
}
|
||||
|
||||
private func updateBubbleViewCorners() {
|
||||
let maskPath = UIBezierPath(roundedRect: bubbleView.bounds, byRoundingCorners: getCornersToRound(),
|
||||
cornerRadii: CGSize(width: VisibleMessageCell.largeCornerRadius, height: VisibleMessageCell.largeCornerRadius))
|
||||
bubbleViewMaskLayer.path = maskPath.cgPath
|
||||
bubbleView.layer.mask = bubbleViewMaskLayer
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
typingIndicatorView.stopAnimation()
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
private func getCornersToRound() -> UIRectCorner {
|
||||
guard !isOnlyMessageInCluster else { return .allCorners }
|
||||
let result: UIRectCorner
|
||||
switch positionInCluster {
|
||||
case .top: result = [ .topLeft, .topRight, .bottomRight ]
|
||||
case .middle: result = [ .topRight, .bottomRight ]
|
||||
case .bottom: result = [ .topRight, .bottomRight, .bottomLeft ]
|
||||
case nil: result = .allCorners
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
395
Session/Conversations V2/Message Cells/VisibleMessageCell.swift
Normal file
395
Session/Conversations V2/Message Cells/VisibleMessageCell.swift
Normal file
|
@ -0,0 +1,395 @@
|
|||
|
||||
final class VisibleMessageCell : MessageCell {
|
||||
private var unloadContent: (() -> Void)?
|
||||
var albumView: MediaAlbumView?
|
||||
var mediaTextOverlayView: MediaTextOverlayView?
|
||||
// Constraints
|
||||
private lazy var headerViewTopConstraint = headerView.pin(.top, to: .top, of: self, withInset: 1)
|
||||
private lazy var profilePictureViewLeftConstraint = profilePictureView.pin(.left, to: .left, of: self, withInset: VisibleMessageCell.groupThreadHSpacing)
|
||||
private lazy var profilePictureViewWidthConstraint = profilePictureView.set(.width, to: Values.verySmallProfilePictureSize)
|
||||
private lazy var bubbleViewLeftConstraint1 = bubbleView.pin(.left, to: .right, of: profilePictureView, withInset: VisibleMessageCell.groupThreadHSpacing)
|
||||
private lazy var bubbleViewLeftConstraint2 = bubbleView.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor, constant: VisibleMessageCell.gutterSize)
|
||||
private lazy var bubbleViewTopConstraint = bubbleView.pin(.top, to: .bottom, of: authorLabel, withInset: VisibleMessageCell.authorLabelBottomSpacing)
|
||||
private lazy var bubbleViewRightConstraint1 = bubbleView.pin(.right, to: .right, of: self, withInset: -VisibleMessageCell.contactThreadHSpacing)
|
||||
private lazy var bubbleViewRightConstraint2 = bubbleView.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -VisibleMessageCell.gutterSize)
|
||||
private lazy var messageStatusImageViewTopConstraint = messageStatusImageView.pin(.top, to: .bottom, of: bubbleView, withInset: 0)
|
||||
private lazy var messageStatusImageViewWidthConstraint = messageStatusImageView.set(.width, to: VisibleMessageCell.messageStatusImageViewSize)
|
||||
private lazy var messageStatusImageViewHeightConstraint = messageStatusImageView.set(.height, to: VisibleMessageCell.messageStatusImageViewSize)
|
||||
|
||||
private var positionInCluster: Position? {
|
||||
guard let viewItem = viewItem else { return nil }
|
||||
if viewItem.isFirstInCluster { return .top }
|
||||
if viewItem.isLastInCluster { return .bottom }
|
||||
return .middle
|
||||
}
|
||||
|
||||
private var isOnlyMessageInCluster: Bool { viewItem?.isFirstInCluster == true && viewItem?.isLastInCluster == true }
|
||||
|
||||
private var direction: Direction {
|
||||
guard let message = viewItem?.interaction as? TSMessage else { preconditionFailure() }
|
||||
switch message {
|
||||
case is TSIncomingMessage: return .incoming
|
||||
case is TSOutgoingMessage: return .outgoing
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private var shouldInsetHeader: Bool {
|
||||
guard let viewItem = viewItem else { preconditionFailure() }
|
||||
return (positionInCluster == .top || isOnlyMessageInCluster) && !viewItem.wasPreviousItemInfoMessage
|
||||
}
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var profilePictureView: ProfilePictureView = {
|
||||
let result = ProfilePictureView()
|
||||
let size = Values.verySmallProfilePictureSize
|
||||
result.set(.height, to: size)
|
||||
result.size = size
|
||||
return result
|
||||
}()
|
||||
|
||||
lazy var bubbleView: UIView = {
|
||||
let result = UIView()
|
||||
result.layer.cornerRadius = VisibleMessageCell.smallCornerRadius
|
||||
return result
|
||||
}()
|
||||
|
||||
private let bubbleViewMaskLayer = CAShapeLayer()
|
||||
|
||||
private lazy var headerView = UIView()
|
||||
|
||||
private lazy var authorLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var snContentView = UIView()
|
||||
|
||||
private lazy var messageStatusImageView: UIImageView = {
|
||||
let result = UIImageView()
|
||||
result.contentMode = .scaleAspectFit
|
||||
result.layer.cornerRadius = VisibleMessageCell.messageStatusImageViewSize / 2
|
||||
result.layer.masksToBounds = true
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Settings
|
||||
private static let messageStatusImageViewSize: CGFloat = 16
|
||||
private static let authorLabelBottomSpacing: CGFloat = 4
|
||||
private static let groupThreadHSpacing: CGFloat = 12
|
||||
private static let profilePictureSize = Values.verySmallProfilePictureSize
|
||||
static let smallCornerRadius: CGFloat = 4
|
||||
static let largeCornerRadius: CGFloat = 18
|
||||
static let contactThreadHSpacing = Values.mediumSpacing
|
||||
|
||||
static var gutterSize: CGFloat { groupThreadHSpacing + profilePictureSize + groupThreadHSpacing }
|
||||
|
||||
private var bodyLabelTextColor: UIColor {
|
||||
switch (direction, AppModeManager.shared.currentAppMode) {
|
||||
case (.outgoing, .dark), (.incoming, .light): return .black
|
||||
default: return .white
|
||||
}
|
||||
}
|
||||
|
||||
override class var identifier: String { "VisibleMessageCell" }
|
||||
|
||||
// MARK: Direction & Position
|
||||
enum Direction { case incoming, outgoing }
|
||||
enum Position { case top, middle, bottom }
|
||||
|
||||
// MARK: Lifecycle
|
||||
override func setUpViewHierarchy() {
|
||||
super.setUpViewHierarchy()
|
||||
isUserInteractionEnabled = true
|
||||
// Header view
|
||||
addSubview(headerView)
|
||||
headerViewTopConstraint.isActive = true
|
||||
headerView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: self)
|
||||
// Author label
|
||||
addSubview(authorLabel)
|
||||
authorLabel.pin(.top, to: .bottom, of: headerView)
|
||||
// Profile picture view
|
||||
addSubview(profilePictureView)
|
||||
profilePictureViewLeftConstraint.isActive = true
|
||||
profilePictureViewWidthConstraint.isActive = true
|
||||
profilePictureView.pin(.bottom, to: .bottom, of: self, withInset: -1)
|
||||
// Bubble view
|
||||
addSubview(bubbleView)
|
||||
bubbleViewLeftConstraint1.isActive = true
|
||||
bubbleViewTopConstraint.isActive = true
|
||||
bubbleViewRightConstraint1.isActive = true
|
||||
// Content view
|
||||
bubbleView.addSubview(snContentView)
|
||||
snContentView.pin(to: bubbleView)
|
||||
// Message status image view
|
||||
addSubview(messageStatusImageView)
|
||||
messageStatusImageViewTopConstraint.isActive = true
|
||||
messageStatusImageView.pin(.right, to: .right, of: bubbleView, withInset: -1)
|
||||
messageStatusImageView.pin(.bottom, to: .bottom, of: self, withInset: -1)
|
||||
messageStatusImageViewWidthConstraint.isActive = true
|
||||
messageStatusImageViewHeightConstraint.isActive = true
|
||||
// Remaining constraints
|
||||
authorLabel.pin(.left, to: .left, of: bubbleView, withInset: 12)
|
||||
}
|
||||
|
||||
override func setUpGestureRecognizers() {
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
|
||||
addGestureRecognizer(longPressRecognizer)
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
tapGestureRecognizer.numberOfTapsRequired = 1
|
||||
addGestureRecognizer(tapGestureRecognizer)
|
||||
let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
|
||||
doubleTapGestureRecognizer.numberOfTapsRequired = 2
|
||||
addGestureRecognizer(doubleTapGestureRecognizer)
|
||||
tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer)
|
||||
}
|
||||
|
||||
// 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()
|
||||
// Profile picture view
|
||||
profilePictureViewLeftConstraint.constant = isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0
|
||||
profilePictureViewWidthConstraint.constant = isGroupThread ? VisibleMessageCell.profilePictureSize : 0
|
||||
let senderSessionID = (message as? TSIncomingMessage)?.authorId
|
||||
profilePictureView.isHidden = !VisibleMessageCell.shouldShowProfilePicture(for: viewItem)
|
||||
if let senderSessionID = senderSessionID {
|
||||
profilePictureView.update(for: senderSessionID)
|
||||
}
|
||||
// Bubble view
|
||||
bubbleViewLeftConstraint1.isActive = (direction == .incoming)
|
||||
bubbleViewLeftConstraint1.constant = isGroupThread ? VisibleMessageCell.groupThreadHSpacing : VisibleMessageCell.contactThreadHSpacing
|
||||
bubbleViewLeftConstraint2.isActive = (direction == .outgoing)
|
||||
bubbleViewTopConstraint.constant = (viewItem.senderName == nil) ? 0 : VisibleMessageCell.authorLabelBottomSpacing
|
||||
bubbleViewRightConstraint1.isActive = (direction == .outgoing)
|
||||
bubbleViewRightConstraint2.isActive = (direction == .incoming)
|
||||
bubbleView.backgroundColor = (direction == .incoming) ? Colors.receivedMessageBackground : Colors.sentMessageBackground
|
||||
updateBubbleViewCorners()
|
||||
// Content view
|
||||
populateContentView(for: viewItem)
|
||||
// Date break
|
||||
headerViewTopConstraint.constant = shouldInsetHeader ? Values.mediumSpacing : 1
|
||||
headerView.subviews.forEach { $0.removeFromSuperview() }
|
||||
if viewItem.shouldShowDate {
|
||||
populateHeader(for: viewItem)
|
||||
}
|
||||
// Author label
|
||||
authorLabel.textColor = Colors.text
|
||||
authorLabel.isHidden = (viewItem.senderName == nil)
|
||||
authorLabel.text = viewItem.senderName?.string // Will only be set if it should be shown
|
||||
// Message status image view
|
||||
let (image, backgroundColor) = getMessageStatusImage(for: message)
|
||||
messageStatusImageView.image = image
|
||||
messageStatusImageView.backgroundColor = backgroundColor
|
||||
if let message = message as? TSOutgoingMessage {
|
||||
messageStatusImageView.isHidden = (message.messageState == .sent && message.thread.lastInteraction != message)
|
||||
} else {
|
||||
messageStatusImageView.isHidden = true
|
||||
}
|
||||
messageStatusImageViewTopConstraint.constant = (messageStatusImageView.isHidden) ? 0 : 5
|
||||
[ messageStatusImageViewWidthConstraint, messageStatusImageViewHeightConstraint ].forEach {
|
||||
$0.constant = (messageStatusImageView.isHidden) ? 0 : VisibleMessageCell.messageStatusImageViewSize
|
||||
}
|
||||
}
|
||||
|
||||
private func populateHeader(for viewItem: ConversationViewItem) {
|
||||
guard viewItem.shouldShowDate else { return }
|
||||
let dateBreakLabel = UILabel()
|
||||
dateBreakLabel.font = .boldSystemFont(ofSize: Values.verySmallFontSize)
|
||||
dateBreakLabel.textColor = Colors.text
|
||||
dateBreakLabel.textAlignment = .center
|
||||
let date = viewItem.interaction.receivedAtDate()
|
||||
let description = DateUtil.formatDate(forConversationDateBreaks: date)
|
||||
dateBreakLabel.text = description
|
||||
headerView.addSubview(dateBreakLabel)
|
||||
dateBreakLabel.pin(.top, to: .top, of: headerView, withInset: Values.smallSpacing)
|
||||
let additionalBottomInset = shouldInsetHeader ? Values.mediumSpacing : 1
|
||||
headerView.pin(.bottom, to: .bottom, of: dateBreakLabel, withInset: Values.smallSpacing + additionalBottomInset)
|
||||
dateBreakLabel.center(.horizontal, in: headerView)
|
||||
}
|
||||
|
||||
private func populateContentView(for viewItem: ConversationViewItem) {
|
||||
snContentView.subviews.forEach { $0.removeFromSuperview() }
|
||||
albumView = nil
|
||||
mediaTextOverlayView = nil
|
||||
let isOutgoing = (viewItem.interaction.interactionType() == .outgoingMessage)
|
||||
switch viewItem.messageCellType {
|
||||
case .textOnlyMessage:
|
||||
guard let message = viewItem.interaction as? TSMessage else { return }
|
||||
// Stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [])
|
||||
stackView.axis = .vertical
|
||||
// Quote label
|
||||
if viewItem.quotedReply != nil {
|
||||
let maxMessageWidth = VisibleMessageCell.getMaxWidth(for: viewItem)
|
||||
let quoteView = QuoteView(for: viewItem, maxMessageWidth: maxMessageWidth)
|
||||
stackView.addArrangedSubview(quoteView)
|
||||
}
|
||||
// Body label
|
||||
let bodyLabel = UILabel()
|
||||
bodyLabel.numberOfLines = 0
|
||||
bodyLabel.lineBreakMode = .byWordWrapping
|
||||
bodyLabel.textColor = bodyLabelTextColor
|
||||
bodyLabel.font = .systemFont(ofSize: getFontSize(for: viewItem))
|
||||
bodyLabel.attributedText = given(message.body) { MentionUtilities.highlightMentions(in: $0, isOutgoingMessage: isOutgoing, threadID: viewItem.interaction.uniqueThreadId, attributes: [:]) }
|
||||
stackView.addArrangedSubview(bodyLabel)
|
||||
// Constraints
|
||||
snContentView.addSubview(stackView)
|
||||
stackView.pin(to: snContentView, withInset: 12)
|
||||
case .mediaMessage:
|
||||
guard let cache = delegate?.getMediaCache() else { preconditionFailure() }
|
||||
let maxMessageWidth = VisibleMessageCell.getMaxWidth(for: viewItem)
|
||||
let albumView = MediaAlbumView(mediaCache: cache, items: viewItem.mediaAlbumItems!, isOutgoing: isOutgoing, maxMessageWidth: maxMessageWidth)
|
||||
self.albumView = albumView
|
||||
snContentView.addSubview(albumView)
|
||||
let size = getSize(for: viewItem)
|
||||
albumView.set(.width, to: size.width)
|
||||
albumView.set(.height, to: size.height)
|
||||
albumView.pin(to: snContentView)
|
||||
albumView.loadMedia()
|
||||
albumView.layer.mask = bubbleViewMaskLayer
|
||||
if let message = viewItem.interaction as? TSMessage, let body = message.body, body.count > 0,
|
||||
let delegate = delegate { // delegate should always be set at this point
|
||||
let overlayView = MediaTextOverlayView(viewItem: viewItem, albumViewWidth: size.width, delegate: delegate)
|
||||
self.mediaTextOverlayView = overlayView
|
||||
snContentView.addSubview(overlayView)
|
||||
overlayView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.right ], to: snContentView)
|
||||
}
|
||||
unloadContent = { albumView.unloadMedia() }
|
||||
case .audio:
|
||||
let voiceMessageView = VoiceMessageViewV2(viewItem: viewItem)
|
||||
snContentView.addSubview(voiceMessageView)
|
||||
voiceMessageView.pin(to: snContentView)
|
||||
viewItem.lastAudioMessageView = voiceMessageView
|
||||
case .genericAttachment:
|
||||
let documentView = DocumentView(viewItem: viewItem, textColor: bodyLabelTextColor)
|
||||
snContentView.addSubview(documentView)
|
||||
documentView.pin(to: snContentView)
|
||||
default: return
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateBubbleViewCorners()
|
||||
}
|
||||
|
||||
private func updateBubbleViewCorners() {
|
||||
let maskPath = UIBezierPath(roundedRect: bubbleView.bounds, byRoundingCorners: getCornersToRound(),
|
||||
cornerRadii: CGSize(width: VisibleMessageCell.largeCornerRadius, height: VisibleMessageCell.largeCornerRadius))
|
||||
bubbleViewMaskLayer.path = maskPath.cgPath
|
||||
bubbleView.layer.mask = bubbleViewMaskLayer
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
unloadContent?()
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func handleLongPress() {
|
||||
guard let viewItem = viewItem else { return }
|
||||
delegate?.handleViewItemLongPressed(viewItem)
|
||||
}
|
||||
|
||||
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
guard let viewItem = viewItem else { return }
|
||||
delegate?.handleViewItemTapped(viewItem, gestureRecognizer: gestureRecognizer)
|
||||
}
|
||||
|
||||
@objc private func handleDoubleTap() {
|
||||
guard let viewItem = viewItem else { return }
|
||||
delegate?.handleViewItemDoubleTapped(viewItem)
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
private func getCornersToRound() -> UIRectCorner {
|
||||
guard !isOnlyMessageInCluster else { return .allCorners }
|
||||
let result: UIRectCorner
|
||||
switch (positionInCluster, direction) {
|
||||
case (.top, .outgoing): result = [ .bottomLeft, .topLeft, .topRight ]
|
||||
case (.middle, .outgoing): result = [ .bottomLeft, .topLeft ]
|
||||
case (.bottom, .outgoing): result = [ .bottomRight, .bottomLeft, .topLeft ]
|
||||
case (.top, .incoming): result = [ .topLeft, .topRight, .bottomRight ]
|
||||
case (.middle, .incoming): result = [ .topRight, .bottomRight ]
|
||||
case (.bottom, .incoming): result = [ .topRight, .bottomRight, .bottomLeft ]
|
||||
case (nil, _): result = .allCorners
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func getFontSize(for viewItem: ConversationViewItem) -> CGFloat {
|
||||
let baselineFontSize = Values.mediumFontSize
|
||||
switch viewItem.displayableBodyText?.jumbomojiCount {
|
||||
case 1: return baselineFontSize + 30
|
||||
case 2: return baselineFontSize + 24
|
||||
case 3, 4, 5: return baselineFontSize + 18
|
||||
default: return baselineFontSize
|
||||
}
|
||||
}
|
||||
|
||||
private func getMessageStatusImage(for message: TSMessage) -> (image: UIImage?, backgroundColor: UIColor?) {
|
||||
guard let message = message as? TSOutgoingMessage else { return (nil, nil) }
|
||||
let image: UIImage
|
||||
var backgroundColor: UIColor? = nil
|
||||
let status = MessageRecipientStatusUtils.recipientStatus(outgoingMessage: message)
|
||||
switch status {
|
||||
case .uploading, .sending: image = #imageLiteral(resourceName: "CircleDotDotDot").asTintedImage(color: Colors.text)!
|
||||
case .sent, .skipped, .delivered: image = #imageLiteral(resourceName: "CircleCheck").asTintedImage(color: Colors.text)!
|
||||
case .read:
|
||||
backgroundColor = isLightMode ? .black : .white
|
||||
image = isLightMode ? #imageLiteral(resourceName: "FilledCircleCheckLightMode") : #imageLiteral(resourceName: "FilledCircleCheckDarkMode")
|
||||
case .failed: image = #imageLiteral(resourceName: "message_status_failed").asTintedImage(color: Colors.text)!
|
||||
}
|
||||
return (image, backgroundColor)
|
||||
}
|
||||
|
||||
|
||||
private func getSize(for viewItem: ConversationViewItem) -> CGSize {
|
||||
guard let albumItems = viewItem.mediaAlbumItems else { preconditionFailure() }
|
||||
let maxMessageWidth = VisibleMessageCell.getMaxWidth(for: viewItem)
|
||||
let defaultSize = MediaAlbumView.layoutSize(forMaxMessageWidth: maxMessageWidth, items: albumItems)
|
||||
guard albumItems.count == 1 else { return defaultSize }
|
||||
// Honor the content aspect ratio for single media
|
||||
let albumItem = albumItems.first!
|
||||
let size = albumItem.mediaSize
|
||||
guard size.width > 0 && size.height > 0 else { return defaultSize }
|
||||
var aspectRatio = (size.width / size.height)
|
||||
// Clamp the aspect ratio so that very thin/wide content still looks alright
|
||||
let minAspectRatio: CGFloat = 0.35
|
||||
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
|
||||
// Don't blow up small images unnecessarily
|
||||
let minSize: CGFloat = 150
|
||||
let shortSourceDimension = min(size.width, size.height)
|
||||
let shortDestinationDimension = min(width, height)
|
||||
if shortDestinationDimension > minSize && shortDestinationDimension > shortSourceDimension {
|
||||
let factor = minSize / shortDestinationDimension
|
||||
width *= factor; height *= factor
|
||||
}
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
static func getMaxWidth(for viewItem: ConversationViewItem) -> CGFloat {
|
||||
let screen = UIScreen.main.bounds
|
||||
switch viewItem.interaction.interactionType() {
|
||||
case .outgoingMessage: return screen.width - contactThreadHSpacing - gutterSize
|
||||
case .incomingMessage:
|
||||
let leftGutterSize = shouldShowProfilePicture(for: viewItem) ? gutterSize : contactThreadHSpacing
|
||||
return screen.width - leftGutterSize - gutterSize
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func shouldShowProfilePicture(for viewItem: ConversationViewItem) -> Bool {
|
||||
guard let message = viewItem.interaction as? TSMessage else { preconditionFailure() }
|
||||
let isGroupThread = message.thread.isGroupThread()
|
||||
let senderSessionID = (message as? TSIncomingMessage)?.authorId
|
||||
return isGroupThread && viewItem.shouldShowSenderProfilePicture && senderSessionID != nil
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
self.placeholderView = [UILabel new];
|
||||
self.placeholderView.text = NSLocalizedString(@"Message", @"");
|
||||
self.placeholderView.textColor = [LKColors.text colorWithAlphaComponent:LKValues.composeViewTextFieldPlaceholderOpacity];
|
||||
self.placeholderView.textColor = [LKColors.text colorWithAlphaComponent:LKValues.lowOpacity];
|
||||
self.placeholderView.userInteractionEnabled = NO;
|
||||
[self addSubview:self.placeholderView];
|
||||
|
||||
|
|
|
@ -256,7 +256,7 @@ const CGFloat kMaxTextViewHeight = 120;
|
|||
self.borderView.backgroundColor = UIColor.clearColor;
|
||||
self.borderView.opaque = NO;
|
||||
self.borderView.layer.borderColor = LKColors.text.CGColor;
|
||||
self.borderView.layer.opacity = LKValues.composeViewTextFieldBorderOpacity;
|
||||
self.borderView.layer.opacity = LKValues.lowOpacity;
|
||||
self.borderView.layer.borderWidth = LKValues.composeViewTextFieldBorderThickness;
|
||||
self.borderView.layer.cornerRadius = vStackRounding;
|
||||
[self addSubview:self.borderView];
|
||||
|
|
|
@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
self.circleView = circleView;
|
||||
circleView.userInteractionEnabled = NO;
|
||||
circleView.layer.cornerRadius = circleSize * 0.5f;
|
||||
circleView.layer.borderColor = [LKColors.text colorWithAlphaComponent:LKValues.composeViewTextFieldBorderOpacity].CGColor;
|
||||
circleView.layer.borderColor = [LKColors.text colorWithAlphaComponent:LKValues.lowOpacity].CGColor;
|
||||
circleView.layer.borderWidth = LKValues.composeViewTextFieldBorderThickness;
|
||||
[circleView autoSetDimension:ALDimensionWidth toSize:circleSize];
|
||||
[circleView autoSetDimension:ALDimensionHeight toSize:circleSize];
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString *const SNAudioDidFinishPlayingNotification;
|
||||
|
||||
typedef NS_ENUM(NSInteger, OWSMessageCellType) {
|
||||
OWSMessageCellType_Unknown,
|
||||
OWSMessageCellType_TextOnlyMessage,
|
||||
|
@ -23,7 +25,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
|||
@class ContactShareViewModel;
|
||||
@class ConversationViewCell;
|
||||
@class DisplayableText;
|
||||
@class LKVoiceMessageView;
|
||||
@class SNVoiceMessageView;
|
||||
@class OWSLinkPreview;
|
||||
@class OWSQuotedReplyModel;
|
||||
@class OWSUnreadIndicator;
|
||||
|
@ -79,11 +81,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
|||
@property (nonatomic, readonly) BOOL isExpiringMessage;
|
||||
|
||||
@property (nonatomic) BOOL shouldShowDate;
|
||||
@property (nonatomic) BOOL shouldShowSenderAvatar;
|
||||
@property (nonatomic) BOOL shouldShowSenderProfilePicture;
|
||||
@property (nonatomic, nullable) NSAttributedString *senderName;
|
||||
@property (nonatomic) BOOL shouldHideFooter;
|
||||
@property (nonatomic) BOOL isFirstInCluster;
|
||||
@property (nonatomic) BOOL isOnlyMessageInCluster;
|
||||
@property (nonatomic) BOOL isLastInCluster;
|
||||
@property (nonatomic) BOOL wasPreviousItemInfoMessage;
|
||||
|
||||
@property (nonatomic, nullable) OWSUnreadIndicator *unreadIndicator;
|
||||
|
||||
|
@ -98,7 +102,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
|||
|
||||
#pragma mark - Audio Playback
|
||||
|
||||
@property (nonatomic, weak) LKVoiceMessageView *lastAudioMessageView;
|
||||
@property (nonatomic, weak) SNVoiceMessageView *lastAudioMessageView;
|
||||
|
||||
@property (nonatomic, readonly) CGFloat audioDurationSeconds;
|
||||
@property (nonatomic, readonly) CGFloat audioProgressSeconds;
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString *const SNAudioDidFinishPlayingNotification = @"SNAudioDidFinishPlayingNotification";
|
||||
|
||||
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
{
|
||||
switch (cellType) {
|
||||
|
@ -111,13 +113,15 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
@implementation ConversationInteractionViewItem
|
||||
|
||||
@synthesize shouldShowDate = _shouldShowDate;
|
||||
@synthesize shouldShowSenderAvatar = _shouldShowSenderAvatar;
|
||||
@synthesize shouldShowSenderProfilePicture = _shouldShowSenderProfilePicture;
|
||||
@synthesize unreadIndicator = _unreadIndicator;
|
||||
@synthesize didCellMediaFailToLoad = _didCellMediaFailToLoad;
|
||||
@synthesize interaction = _interaction;
|
||||
@synthesize isFirstInCluster = _isFirstInCluster;
|
||||
@synthesize isGroupThread = _isGroupThread;
|
||||
@synthesize isOnlyMessageInCluster = _isOnlyMessageInCluster;
|
||||
@synthesize isLastInCluster = _isLastInCluster;
|
||||
@synthesize wasPreviousItemInfoMessage = _wasPreviousItemInfoMessage;
|
||||
@synthesize lastAudioMessageView = _lastAudioMessageView;
|
||||
@synthesize senderName = _senderName;
|
||||
@synthesize shouldHideFooter = _shouldHideFooter;
|
||||
|
@ -228,13 +232,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)setShouldShowSenderAvatar:(BOOL)shouldShowSenderAvatar
|
||||
- (void)setShouldShowSenderAvatar:(BOOL)shouldShowSenderProfilePicture
|
||||
{
|
||||
if (_shouldShowSenderAvatar == shouldShowSenderAvatar) {
|
||||
if (_shouldShowSenderProfilePicture == shouldShowSenderProfilePicture) {
|
||||
return;
|
||||
}
|
||||
|
||||
_shouldShowSenderAvatar = shouldShowSenderAvatar;
|
||||
_shouldShowSenderProfilePicture = shouldShowSenderProfilePicture;
|
||||
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
@ -446,7 +450,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
|
||||
self.audioProgressSeconds = progress;
|
||||
|
||||
[self.lastAudioMessageView setProgress:progress / duration];
|
||||
[self.lastAudioMessageView setProgress:(int)(progress)];
|
||||
}
|
||||
|
||||
- (void)showInvalidAudioFileAlert
|
||||
|
@ -458,6 +462,12 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
@"Message for the alert indicating that an audio file is invalid.")];
|
||||
}
|
||||
|
||||
- (void)audioPlayerDidFinishPlaying:(OWSAudioPlayer *)player successfully:(BOOL)flag
|
||||
{
|
||||
if (!flag) { return; }
|
||||
[NSNotificationCenter.defaultCenter postNotificationName:SNAudioDidFinishPlayingNotification object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Displayable Text
|
||||
|
||||
// TODO: Now that we're caching the displayable text on the view items,
|
||||
|
|
|
@ -1083,13 +1083,11 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
[TSInteraction fetchObjectWithUniqueID:uniqueId transaction:transaction];
|
||||
if (!interaction) {
|
||||
OWSFailDebug(@"missing interaction in message mapping: %@.", uniqueId);
|
||||
// TODO: Add analytics.
|
||||
hasError = YES;
|
||||
continue;
|
||||
}
|
||||
if (!interaction.uniqueId) {
|
||||
OWSFailDebug(@"invalid interaction in message mapping: %@.", interaction);
|
||||
// TODO: Add analytics.
|
||||
hasError = YES;
|
||||
continue;
|
||||
}
|
||||
|
@ -1226,7 +1224,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
id<ConversationViewItem> viewItem = viewItems[i];
|
||||
id<ConversationViewItem> _Nullable previousViewItem = (i > 0 ? viewItems[i - 1] : nil);
|
||||
id<ConversationViewItem> _Nullable nextViewItem = (i + 1 < viewItems.count ? viewItems[i + 1] : nil);
|
||||
BOOL shouldShowSenderAvatar = NO;
|
||||
BOOL shouldShowSenderProfilePicture = NO;
|
||||
BOOL shouldHideFooter = NO;
|
||||
BOOL isFirstInCluster = YES;
|
||||
BOOL isLastInCluster = YES;
|
||||
|
@ -1322,9 +1320,8 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
}
|
||||
|
||||
if (viewItem.isGroupThread) {
|
||||
// Show the sender name for incoming group messages unless
|
||||
// the previous message has the same sender name and
|
||||
// no "date break" separates us.
|
||||
// Show the sender name for incoming group messages unless the
|
||||
// previous message has the same sender and no "date break" separates us.
|
||||
BOOL shouldShowSenderName = YES;
|
||||
NSString *_Nullable previousIncomingSenderId = nil;
|
||||
if (previousViewItem && previousViewItem.interaction.interactionType == interactionType) {
|
||||
|
@ -1333,37 +1330,18 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
previousIncomingSenderId = previousIncomingMessage.authorId;
|
||||
OWSAssertDebug(previousIncomingSenderId.length > 0);
|
||||
|
||||
shouldShowSenderName
|
||||
= (![NSObject isNullableObject:previousIncomingSenderId equalTo:incomingSenderId]
|
||||
|| viewItem.hasCellHeader);
|
||||
shouldShowSenderName = (![NSObject isNullableObject:previousIncomingSenderId equalTo:incomingSenderId] || viewItem.hasCellHeader);
|
||||
}
|
||||
|
||||
if (shouldShowSenderName) {
|
||||
senderName = [[NSAttributedString alloc] initWithString:[SSKEnvironment.shared.profileManager profileNameForRecipientWithID:incomingSenderId avoidingWriteTransaction:YES]];
|
||||
|
||||
if ([self.thread isKindOfClass:[TSGroupThread class]]) {
|
||||
TSGroupThread *groupThread = (TSGroupThread *)self.thread;
|
||||
NSData *groupId = groupThread.groupModel.groupId;
|
||||
NSString *stringGroupId = [[NSString alloc] initWithData:groupId encoding:NSUTF8StringEncoding];
|
||||
|
||||
if (stringGroupId != nil) {
|
||||
NSString __block *displayName;
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
displayName = [transaction objectForKey:incomingSenderId inCollection:stringGroupId];
|
||||
}];
|
||||
if (displayName != nil) {
|
||||
senderName = [[NSAttributedString alloc] initWithString:displayName attributes:[OWSMessageBubbleView senderNamePrimaryAttributes]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the sender avatar for incoming group messages unless
|
||||
// the next message has the same sender avatar and
|
||||
// no "date break" separates us.
|
||||
shouldShowSenderAvatar = YES;
|
||||
if (previousViewItem && previousViewItem.interaction.interactionType == interactionType) {
|
||||
shouldShowSenderAvatar = (![NSObject isNullableObject:previousIncomingSenderId equalTo:incomingSenderId]);
|
||||
// Show the sender profile picture for incoming group messages unless the
|
||||
// next message has the same sender and no "date break" separates us.
|
||||
shouldShowSenderProfilePicture = YES;
|
||||
if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) {
|
||||
shouldShowSenderProfilePicture = (![NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1374,9 +1352,10 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
|
||||
viewItem.isFirstInCluster = isFirstInCluster;
|
||||
viewItem.isLastInCluster = isLastInCluster;
|
||||
viewItem.shouldShowSenderAvatar = shouldShowSenderAvatar;
|
||||
viewItem.shouldShowSenderProfilePicture = shouldShowSenderProfilePicture;
|
||||
viewItem.shouldHideFooter = shouldHideFooter;
|
||||
viewItem.senderName = senderName;
|
||||
viewItem.wasPreviousItemInfoMessage = (previousViewItem.interaction.interactionType == OWSInteractionType_Info);
|
||||
}
|
||||
|
||||
self.viewState = [[ConversationViewState alloc] initWithViewItems:viewItems];
|
||||
|
|
|
@ -404,7 +404,7 @@ class MenuActionView: UIButton {
|
|||
var image = action.image
|
||||
image = image.withRenderingMode(.alwaysTemplate)
|
||||
let imageView = UIImageView(image: image)
|
||||
imageView.tintColor = textColor.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
imageView.tintColor = textColor.withAlphaComponent(Values.mediumOpacity)
|
||||
let imageWidth: CGFloat = 24
|
||||
imageView.autoSetDimensions(to: CGSize(width: imageWidth, height: imageWidth))
|
||||
imageView.isUserInteractionEnabled = false
|
||||
|
@ -417,7 +417,7 @@ class MenuActionView: UIButton {
|
|||
|
||||
let subtitleLabel = UILabel()
|
||||
subtitleLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
subtitleLabel.textColor = textColor.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
subtitleLabel.textColor = textColor.withAlphaComponent(Values.mediumOpacity)
|
||||
subtitleLabel.text = action.subtitle
|
||||
subtitleLabel.isUserInteractionEnabled = false
|
||||
|
||||
|
|
|
@ -628,7 +628,7 @@ public class LinkPreviewView: UIStackView {
|
|||
displayDomain.count > 0 {
|
||||
let label = UILabel()
|
||||
label.text = displayDomain
|
||||
label.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
||||
label.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
|
||||
label.font = .systemFont(ofSize: Values.verySmallFontSize)
|
||||
textStack.addArrangedSubview(label)
|
||||
}
|
||||
|
|
|
@ -608,7 +608,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(match.range.length >= ConversationSearchController.kMinimumSearchTextLength);
|
||||
UIColor *highlightColor;
|
||||
if (LKAppModeUtilities.isLightMode) {
|
||||
highlightColor = isOutgoingMessage ? UIColor.whiteColor : [LKColors.accent colorWithAlphaComponent:LKValues.unimportantElementOpacity];
|
||||
highlightColor = isOutgoingMessage ? UIColor.whiteColor : [LKColors.accent colorWithAlphaComponent:LKValues.mediumOpacity];
|
||||
} else {
|
||||
highlightColor = UIColor.whiteColor;
|
||||
}
|
||||
|
@ -637,7 +637,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(self.senderNameLabel);
|
||||
OWSAssertDebug(self.shouldShowSenderName);
|
||||
|
||||
self.senderNameLabel.textColor = [LKColors.text colorWithAlphaComponent:LKValues.unimportantElementOpacity];
|
||||
self.senderNameLabel.textColor = [LKColors.text colorWithAlphaComponent:LKValues.mediumOpacity];
|
||||
self.senderNameLabel.font = OWSMessageBubbleView.senderNameFont;
|
||||
self.senderNameLabel.text = self.viewItem.senderName.string;
|
||||
self.senderNameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
|
@ -695,12 +695,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
OWSAssertDebug(self.viewItem.mediaAlbumItems);
|
||||
|
||||
OWSMediaAlbumCellView *albumView =
|
||||
[[OWSMediaAlbumCellView alloc] initWithMediaCache:self.cellMediaCache
|
||||
OWSMediaAlbumView *albumView =
|
||||
[[OWSMediaAlbumView alloc] initWithMediaCache:self.cellMediaCache
|
||||
items:self.viewItem.mediaAlbumItems
|
||||
isOutgoing:self.isOutgoing
|
||||
maxMessageWidth:self.conversationStyle.maxMessageWidth
|
||||
isOnionRouted:YES];
|
||||
maxMessageWidth:self.conversationStyle.maxMessageWidth];
|
||||
self.loadCellContentBlock = ^{
|
||||
[albumView loadMedia];
|
||||
};
|
||||
|
@ -729,12 +728,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(attachment);
|
||||
OWSAssertDebug([attachment isAudio]);
|
||||
|
||||
LKVoiceMessageView *voiceMessageView = [[LKVoiceMessageView alloc] initWithVoiceMessage:attachment isOutgoing:self.isOutgoing];
|
||||
[voiceMessageView setDuration:(int)self.viewItem.audioDurationSeconds];
|
||||
[voiceMessageView setProgress:self.viewItem.audioProgressSeconds / self.viewItem.audioDurationSeconds];
|
||||
[voiceMessageView initialize];
|
||||
UIView *voiceMessageView = [UIView new];
|
||||
|
||||
self.viewItem.lastAudioMessageView = voiceMessageView;
|
||||
// LKVoiceMessageView *voiceMessageView = [[LKVoiceMessageView alloc] initWithVoiceMessage:attachment isOutgoing:self.isOutgoing];
|
||||
// [voiceMessageView setDuration:(int)self.viewItem.audioDurationSeconds];
|
||||
// [voiceMessageView setProgress:self.viewItem.audioProgressSeconds / self.viewItem.audioDurationSeconds];
|
||||
// [voiceMessageView initialize];
|
||||
|
||||
// self.viewItem.lastAudioMessageView = voiceMessageView;
|
||||
|
||||
self.loadCellContentBlock = ^{
|
||||
// Do nothing.
|
||||
|
@ -946,7 +947,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
break;
|
||||
}
|
||||
case OWSMessageCellType_MediaMessage:
|
||||
result = [OWSMediaAlbumCellView layoutSizeForMaxMessageWidth:maxMessageWidth
|
||||
result = [OWSMediaAlbumView layoutSizeForMaxMessageWidth:maxMessageWidth
|
||||
items:self.viewItem.mediaAlbumItems];
|
||||
|
||||
if (self.viewItem.mediaAlbumItems.count == 1) {
|
||||
|
@ -1314,19 +1315,19 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(self.bodyMediaView);
|
||||
OWSAssertDebug(self.viewItem.mediaAlbumItems.count > 0);
|
||||
|
||||
if (![self.bodyMediaView isKindOfClass:[OWSMediaAlbumCellView class]]) {
|
||||
if (![self.bodyMediaView isKindOfClass:[OWSMediaAlbumView class]]) {
|
||||
OWSFailDebug(@"Unexpected body media view: %@", self.bodyMediaView.class);
|
||||
return;
|
||||
}
|
||||
OWSMediaAlbumCellView *_Nullable mediaAlbumCellView = (OWSMediaAlbumCellView *)self.bodyMediaView;
|
||||
OWSMediaAlbumView *_Nullable MediaAlbumView = (OWSMediaAlbumView *)self.bodyMediaView;
|
||||
CGPoint location = [self convertPoint:locationInMessageBubble toView:self.bodyMediaView];
|
||||
OWSConversationMediaView *_Nullable mediaView = [mediaAlbumCellView mediaViewForLocation:location];
|
||||
OWSMediaView *_Nullable mediaView = [MediaAlbumView mediaViewForLocation:location];
|
||||
if (!mediaView) {
|
||||
OWSFailDebug(@"Missing media view.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ([mediaAlbumCellView isMoreItemsViewWithMediaView:mediaView]
|
||||
if ([MediaAlbumView isMoreItemsViewWithMediaView:mediaView]
|
||||
&& self.viewItem.mediaAlbumHasFailedAttachment) {
|
||||
[self.delegate didTapFailedIncomingAttachment:self.viewItem];
|
||||
return;
|
||||
|
@ -1355,12 +1356,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
switch (self.cellType) {
|
||||
case OWSMessageCellType_Audio: {
|
||||
LKVoiceMessageView *voiceMessageView = self.viewItem.lastAudioMessageView;
|
||||
NSTimeInterval currentTime = [voiceMessageView getCurrentTime:sender];
|
||||
[self.viewItem setAudioProgress:((CGFloat)currentTime) duration:self.viewItem.audioDurationSeconds];
|
||||
CGFloat progress = self.viewItem.audioProgressSeconds / self.viewItem.audioDurationSeconds;
|
||||
[voiceMessageView setProgress:progress];
|
||||
[self.delegate didPanAudioViewItemToCurrentTime:currentTime];
|
||||
// SNVoiceMessageView *voiceMessageView = self.viewItem.lastAudioMessageView;
|
||||
// NSTimeInterval currentTime = [voiceMessageView getCurrentTime:sender];
|
||||
// [self.viewItem setAudioProgress:((CGFloat)currentTime) duration:self.viewItem.audioDurationSeconds];
|
||||
// CGFloat progress = self.viewItem.audioProgressSeconds / self.viewItem.audioDurationSeconds;
|
||||
// [voiceMessageView setProgress:progress];
|
||||
// [self.delegate didPanAudioViewItemToCurrentTime:currentTime];
|
||||
return;
|
||||
}
|
||||
default: return;
|
||||
|
|
|
@ -265,7 +265,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// Returns YES IFF the avatar view is appropriate and configured.
|
||||
- (BOOL)updateAvatarView
|
||||
{
|
||||
if (!self.viewItem.shouldShowSenderAvatar) {
|
||||
if (!self.viewItem.shouldShowSenderProfilePicture) {
|
||||
return NO;
|
||||
}
|
||||
if (!self.viewItem.isGroupThread) {
|
||||
|
@ -314,7 +314,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
if (!self.viewItem.shouldShowSenderAvatar) {
|
||||
if (!self.viewItem.shouldShowSenderProfilePicture) {
|
||||
return;
|
||||
}
|
||||
if (!self.viewItem.isGroupThread) {
|
||||
|
@ -496,7 +496,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
{
|
||||
LKVoiceMessageView *voiceMessageView = self.viewItem.lastAudioMessageView;
|
||||
SNVoiceMessageView *voiceMessageView = self.viewItem.lastAudioMessageView;
|
||||
if (![gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class] || voiceMessageView == nil) { return NO; }
|
||||
UIPanGestureRecognizer *panGestureRecognizer = (UIPanGestureRecognizer *)gestureRecognizer;
|
||||
CGPoint location = [panGestureRecognizer locationInView:voiceMessageView];
|
||||
|
|
|
@ -383,7 +383,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 4;
|
|||
kRemotelySourcedContentRowMargin,
|
||||
kRemotelySourcedContentRowMargin);
|
||||
|
||||
UIColor *backgroundColor = LKAppModeUtilities.isLightMode ? [UIColor.whiteColor colorWithAlphaComponent:LKValues.unimportantElementOpacity] : [LKColors.text colorWithAlphaComponent:LKValues.unimportantElementOpacity];
|
||||
UIColor *backgroundColor = LKAppModeUtilities.isLightMode ? [UIColor.whiteColor colorWithAlphaComponent:LKValues.mediumOpacity] : [LKColors.text colorWithAlphaComponent:LKValues.mediumOpacity];
|
||||
[sourceRow addBackgroundViewWithBackgroundColor:backgroundColor];
|
||||
|
||||
return sourceRow;
|
||||
|
|
|
@ -13,7 +13,7 @@ final class VoiceMessageView : UIView {
|
|||
@objc var isPlaying = false { didSet { updateToggleImageView() } }
|
||||
|
||||
// MARK: Components
|
||||
private lazy var toggleImageView = UIImageView(image: #imageLiteral(resourceName: "Play"))
|
||||
private lazy var toggleImageView = UIImageView(image: UIImage(named: "Play"))
|
||||
|
||||
private lazy var spinner = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: .black, padding: nil)
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ public class VoiceMemoLockView: UIView {
|
|||
view.autoSetDimension(.width, toSize: width)
|
||||
view.backgroundColor = Colors.composeViewBackground
|
||||
view.layer.cornerRadius = width / 2
|
||||
view.layer.borderColor = Colors.text.withAlphaComponent(Values.composeViewTextFieldBorderOpacity).cgColor
|
||||
view.layer.borderColor = Colors.text.withAlphaComponent(Values.lowOpacity).cgColor
|
||||
view.layer.borderWidth = Values.composeViewTextFieldBorderThickness
|
||||
|
||||
return view
|
||||
|
|
|
@ -36,7 +36,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIViewC
|
|||
result.backgroundColor = .clear
|
||||
result.separatorStyle = .none
|
||||
result.register(ConversationCell.self, forCellReuseIdentifier: ConversationCell.reuseIdentifier)
|
||||
let bottomInset = Values.newConversationButtonBottomOffset + Values.newConversationButtonExpandedSize + Values.largeSpacing + Values.newConversationButtonCollapsedSize
|
||||
let bottomInset = Values.newConversationButtonBottomOffset + NewConversationButtonSet.expandedButtonSize + Values.largeSpacing + NewConversationButtonSet.collapsedButtonSize
|
||||
result.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0)
|
||||
result.showsVerticalScrollIndicator = false
|
||||
return result
|
||||
|
@ -342,8 +342,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIViewC
|
|||
if let presentedVC = self.presentedViewController {
|
||||
presentedVC.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
let conversationVC = ConversationViewController()
|
||||
conversationVC.configure(for: thread, action: action, focusMessageId: highlightedMessageID)
|
||||
let conversationVC = ConversationVC(thread: thread)
|
||||
// conversationVC.configure(for: thread, action: action, focusMessageId: highlightedMessageID)
|
||||
self.navigationController?.setViewControllers([ self, conversationVC ], animated: true)
|
||||
}
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIViewC
|
|||
}
|
||||
|
||||
@objc func joinOpenGroup() {
|
||||
let joinOpenGroupVC = JoinPublicChatVC()
|
||||
let joinOpenGroupVC = JoinOpenGroupVC()
|
||||
let navigationController = OWSNavigationController(rootViewController: joinOpenGroupVC)
|
||||
present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ final class NewConversationButtonSet : UIView {
|
|||
private let iconSize = CGFloat(24)
|
||||
private let maxDragDistance = CGFloat(56)
|
||||
private let dragMargin = CGFloat(16)
|
||||
static let collapsedButtonSize = CGFloat(60)
|
||||
static let expandedButtonSize = CGFloat(72)
|
||||
|
||||
// MARK: Components
|
||||
private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize)))
|
||||
|
@ -39,7 +41,7 @@ final class NewConversationButtonSet : UIView {
|
|||
createNewClosedGroupButton.isAccessibilityElement = true
|
||||
joinOpenGroupButton.accessibilityLabel = "Join open group button"
|
||||
joinOpenGroupButton.isAccessibilityElement = true
|
||||
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
|
||||
let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2
|
||||
addSubview(joinOpenGroupButton)
|
||||
horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset)
|
||||
verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
|
||||
|
@ -52,9 +54,9 @@ final class NewConversationButtonSet : UIView {
|
|||
addSubview(mainButton)
|
||||
mainButton.center(.horizontal, in: self)
|
||||
mainButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
|
||||
let width = 2 * Values.newConversationButtonExpandedSize + 2 * spacing + Values.newConversationButtonCollapsedSize
|
||||
let width = 2 * NewConversationButtonSet.expandedButtonSize + 2 * spacing + NewConversationButtonSet.collapsedButtonSize
|
||||
set(.width, to: width)
|
||||
let height = Values.newConversationButtonExpandedSize + spacing + Values.newConversationButtonCollapsedSize
|
||||
let height = NewConversationButtonSet.expandedButtonSize + spacing + NewConversationButtonSet.collapsedButtonSize
|
||||
set(.height, to: height)
|
||||
collapse(withAnimation: false)
|
||||
isUserInteractionEnabled = true
|
||||
|
@ -75,8 +77,8 @@ final class NewConversationButtonSet : UIView {
|
|||
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
buttons.forEach { $0.alpha = 1 }
|
||||
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
|
||||
let size = Values.newConversationButtonCollapsedSize
|
||||
let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2
|
||||
let size = NewConversationButtonSet.collapsedButtonSize
|
||||
self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
|
||||
self.createNewPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size))
|
||||
self.createNewClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
|
||||
|
@ -91,14 +93,14 @@ final class NewConversationButtonSet : UIView {
|
|||
UIView.animate(withDuration: isAnimated ? 0.25 : 0) {
|
||||
buttons.forEach { button in
|
||||
button.alpha = 0
|
||||
let size = Values.newConversationButtonCollapsedSize
|
||||
let size = NewConversationButtonSet.collapsedButtonSize
|
||||
button.frame = CGRect(center: self.mainButton.center, size: CGSize(width: size, height: size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - Values.newConversationButtonExpandedSize / 2)
|
||||
let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - NewConversationButtonSet.expandedButtonSize / 2)
|
||||
let mainButtonSize = mainButton.frame.size
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.mainButton.frame = CGRect(center: mainButtonLocationInSelfCoordinates, size: mainButtonSize)
|
||||
|
@ -120,7 +122,7 @@ final class NewConversationButtonSet : UIView {
|
|||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
guard let touch = touches.first, isUserDragging else { return }
|
||||
let mainButtonSize = mainButton.frame.size
|
||||
let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - Values.newConversationButtonExpandedSize / 2)
|
||||
let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - NewConversationButtonSet.expandedButtonSize / 2)
|
||||
let touchLocationInSelfCoordinates = touch.location(in: self)
|
||||
mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize)
|
||||
mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance)
|
||||
|
@ -159,7 +161,7 @@ final class NewConversationButtonSet : UIView {
|
|||
private func expand(_ button: NewConversationButton) {
|
||||
if let horizontalConstraint = horizontalButtonConstraints[button] { horizontalConstraint.constant = 0 }
|
||||
if let verticalConstraint = verticalButtonConstraints[button] { verticalConstraint.constant = 0 }
|
||||
let size = Values.newConversationButtonExpandedSize
|
||||
let size = NewConversationButtonSet.expandedButtonSize
|
||||
let frame = CGRect(center: button.center, size: CGSize(width: size, height: size))
|
||||
button.widthConstraint.constant = size
|
||||
button.heightConstraint.constant = size
|
||||
|
@ -167,7 +169,7 @@ final class NewConversationButtonSet : UIView {
|
|||
self.layoutIfNeeded()
|
||||
button.frame = frame
|
||||
button.layer.cornerRadius = size / 2
|
||||
let glowColor = Colors.newConversationButtonShadow
|
||||
let glowColor = Colors.expandedButtonGlowColor
|
||||
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: isLightMode ? 4 : 6)
|
||||
button.setCircularGlow(with: glowConfiguration)
|
||||
button.backgroundColor = Colors.accent
|
||||
|
@ -175,7 +177,7 @@ final class NewConversationButtonSet : UIView {
|
|||
}
|
||||
|
||||
private func collapse(_ button: NewConversationButton) {
|
||||
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
|
||||
let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2
|
||||
if joinOpenGroupButton == expandedButton {
|
||||
horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset
|
||||
verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset
|
||||
|
@ -185,7 +187,7 @@ final class NewConversationButtonSet : UIView {
|
|||
horizontalButtonConstraints[createNewClosedGroupButton]!.constant = -inset
|
||||
verticalButtonConstraints[createNewClosedGroupButton]!.constant = -inset
|
||||
}
|
||||
let size = Values.newConversationButtonCollapsedSize
|
||||
let size = NewConversationButtonSet.collapsedButtonSize
|
||||
let frame = CGRect(center: button.center, size: CGSize(width: size, height: size))
|
||||
button.widthConstraint.constant = size
|
||||
button.heightConstraint.constant = size
|
||||
|
@ -249,9 +251,9 @@ private final class NewConversationButton : UIImageView {
|
|||
private func setUpViewHierarchy(isUpdate: Bool = false) {
|
||||
let newConversationButtonCollapsedBackground = isLightMode ? UIColor(hex: 0xF5F5F5) : UIColor(hex: 0x1F1F1F)
|
||||
backgroundColor = isMainButton ? Colors.accent : newConversationButtonCollapsedBackground
|
||||
let size = Values.newConversationButtonCollapsedSize
|
||||
let size = NewConversationButtonSet.collapsedButtonSize
|
||||
layer.cornerRadius = size / 2
|
||||
let glowColor = isMainButton ? Colors.newConversationButtonShadow : (isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black)
|
||||
let glowColor = isMainButton ? Colors.expandedButtonGlowColor : (isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black)
|
||||
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: false, radius: isLightMode ? 4 : 6)
|
||||
setCircularGlow(with: glowConfiguration)
|
||||
layer.masksToBounds = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Gear.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
125
Session/Meta/Images.xcassets/Session/ArrowUp.imageset/ArrowUp.pdf
vendored
Normal file
125
Session/Meta/Images.xcassets/Session/ArrowUp.imageset/ArrowUp.pdf
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 16.000000 2.500000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
-1.500000 3.000000 m
|
||||
-1.500000 2.171572 -0.828427 1.500000 0.000000 1.500000 c
|
||||
0.828427 1.500000 1.500000 2.171572 1.500000 3.000000 c
|
||||
-1.500000 3.000000 l
|
||||
h
|
||||
1.500000 24.000000 m
|
||||
1.500000 24.828426 0.828427 25.500000 0.000000 25.500000 c
|
||||
-0.828427 25.500000 -1.500000 24.828426 -1.500000 24.000000 c
|
||||
1.500000 24.000000 l
|
||||
h
|
||||
1.500000 3.000000 m
|
||||
1.500000 24.000000 l
|
||||
-1.500000 24.000000 l
|
||||
-1.500000 3.000000 l
|
||||
1.500000 3.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 7.000000 14.207108 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
-1.060660 4.353553 m
|
||||
-1.646447 3.767766 -1.646447 2.818019 -1.060660 2.232232 c
|
||||
-0.474874 1.646446 0.474874 1.646446 1.060660 2.232232 c
|
||||
-1.060660 4.353553 l
|
||||
h
|
||||
10.060660 11.232232 m
|
||||
10.646446 11.818019 10.646446 12.767766 10.060660 13.353553 c
|
||||
9.474874 13.939339 8.525126 13.939339 7.939340 13.353553 c
|
||||
10.060660 11.232232 l
|
||||
h
|
||||
1.060660 2.232232 m
|
||||
10.060660 11.232232 l
|
||||
7.939340 13.353553 l
|
||||
-1.060660 4.353553 l
|
||||
1.060660 2.232232 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 16.000000 14.207108 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.060660 13.353553 m
|
||||
0.474874 13.939339 -0.474874 13.939339 -1.060660 13.353553 c
|
||||
-1.646447 12.767766 -1.646447 11.818019 -1.060660 11.232232 c
|
||||
1.060660 13.353553 l
|
||||
h
|
||||
7.939340 2.232232 m
|
||||
8.525126 1.646445 9.474874 1.646445 10.060660 2.232232 c
|
||||
10.646447 2.818019 10.646447 3.767766 10.060660 4.353552 c
|
||||
7.939340 2.232232 l
|
||||
h
|
||||
-1.060660 11.232232 m
|
||||
7.939340 2.232232 l
|
||||
10.060660 4.353552 l
|
||||
1.060660 13.353553 l
|
||||
-1.060660 11.232232 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1618
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 32.000000 32.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001708 00000 n
|
||||
0000001731 00000 n
|
||||
0000001904 00000 n
|
||||
0000001978 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2037
|
||||
%%EOF
|
12
Session/Meta/Images.xcassets/Session/ArrowUp.imageset/Contents.json
vendored
Normal file
12
Session/Meta/Images.xcassets/Session/ArrowUp.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ArrowUp.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
12
Session/Meta/Images.xcassets/Session/Gear.imageset/Contents.json
vendored
Normal file
12
Session/Meta/Images.xcassets/Session/Gear.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Gear.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
203
Session/Meta/Images.xcassets/Session/Gear.imageset/Gear.pdf
vendored
Normal file
203
Session/Meta/Images.xcassets/Session/Gear.imageset/Gear.pdf
vendored
Normal file
|
@ -0,0 +1,203 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
12.753094 0.000000 m
|
||||
11.246860 0.000000 l
|
||||
10.028625 0.000000 9.037453 0.991125 9.037453 2.209358 c
|
||||
9.037453 2.718937 l
|
||||
8.519531 2.884407 8.016328 3.093281 7.532906 3.343452 c
|
||||
7.171782 2.982327 l
|
||||
6.297141 2.106609 4.896094 2.132298 4.046859 2.982656 c
|
||||
2.982281 4.047188 l
|
||||
2.131547 4.896984 2.107078 6.297564 2.982563 7.172110 c
|
||||
3.343406 7.532953 l
|
||||
3.093234 8.016376 2.884406 8.519484 2.718891 9.037499 c
|
||||
2.209359 9.037499 l
|
||||
0.991172 9.037499 0.000000 10.028625 0.000000 11.246861 c
|
||||
0.000000 12.753140 l
|
||||
0.000000 13.971375 0.991172 14.962500 2.209406 14.962500 c
|
||||
2.718938 14.962500 l
|
||||
2.884453 15.480469 3.093282 15.983624 3.343453 16.467047 c
|
||||
2.982328 16.828125 l
|
||||
2.107359 17.702156 2.131500 19.102875 2.982610 19.953047 c
|
||||
4.047281 21.017672 l
|
||||
4.898485 21.870047 6.299203 21.891329 7.172156 21.017391 c
|
||||
7.532953 20.656593 l
|
||||
8.016376 20.906719 8.519578 21.115593 9.037500 21.281109 c
|
||||
9.037500 21.790640 l
|
||||
9.037500 23.008875 10.028625 24.000000 11.246906 24.000000 c
|
||||
12.753139 24.000000 l
|
||||
13.971375 24.000000 14.962501 23.008875 14.962501 21.790640 c
|
||||
14.962501 21.281063 l
|
||||
15.480422 21.115593 15.983624 20.906719 16.467047 20.656548 c
|
||||
16.828173 21.017672 l
|
||||
17.702812 21.893391 19.103859 21.867702 19.953093 21.017344 c
|
||||
21.017673 19.952812 l
|
||||
21.868406 19.103016 21.892876 17.702438 21.017391 16.827890 c
|
||||
20.656548 16.467047 l
|
||||
20.906719 15.983624 21.115547 15.480515 21.281063 14.962500 c
|
||||
21.790594 14.962500 l
|
||||
23.008827 14.962500 24.000000 13.971375 24.000000 12.753140 c
|
||||
24.000000 11.246861 l
|
||||
24.000000 10.028625 23.008827 9.037499 21.790594 9.037499 c
|
||||
21.281063 9.037499 l
|
||||
21.115547 8.519530 20.906719 8.016376 20.656548 7.532953 c
|
||||
21.017673 7.171827 l
|
||||
21.892641 6.297796 21.868500 4.897079 21.017391 4.046907 c
|
||||
19.952719 2.982281 l
|
||||
19.101515 2.129908 17.700796 2.108625 16.827843 2.982563 c
|
||||
16.467047 3.343361 l
|
||||
15.983624 3.093235 15.480422 2.884359 14.962501 2.718845 c
|
||||
14.962501 2.209267 l
|
||||
14.962501 0.991125 13.971375 0.000000 12.753094 0.000000 c
|
||||
12.753094 0.000000 l
|
||||
h
|
||||
7.767984 4.820156 m
|
||||
8.439562 4.422983 9.162375 4.122936 9.916313 3.928360 c
|
||||
10.226812 3.848249 10.443750 3.568218 10.443750 3.247547 c
|
||||
10.443750 2.209358 l
|
||||
10.443750 1.766531 10.804078 1.406250 11.246906 1.406250 c
|
||||
12.753139 1.406250 l
|
||||
13.195968 1.406250 13.556296 1.766531 13.556296 2.209358 c
|
||||
13.556296 3.247547 l
|
||||
13.556296 3.568218 13.773234 3.848249 14.083735 3.928360 c
|
||||
14.837672 4.122936 15.560484 4.422983 16.232063 4.820156 c
|
||||
16.508390 4.983562 16.860188 4.939125 17.087204 4.712109 c
|
||||
17.822578 3.976688 l
|
||||
18.139641 3.659250 18.648796 3.666611 18.958078 3.976360 c
|
||||
20.023405 5.041641 l
|
||||
20.331936 5.349796 20.342249 5.859047 20.023687 6.177187 c
|
||||
19.287983 6.912891 l
|
||||
19.061016 7.139860 19.016579 7.491703 19.179937 7.767984 c
|
||||
19.577108 8.439516 19.877110 9.162329 20.071688 9.916313 c
|
||||
20.151844 10.226812 20.431875 10.443704 20.752501 10.443704 c
|
||||
21.790642 10.443704 l
|
||||
22.233469 10.443704 22.593798 10.803985 22.593798 11.246813 c
|
||||
22.593798 12.753094 l
|
||||
22.593798 13.195922 22.233469 13.556204 21.790642 13.556204 c
|
||||
20.752501 13.556204 l
|
||||
20.431828 13.556204 20.151844 13.773141 20.071688 14.083593 c
|
||||
19.877110 14.837578 19.577063 15.560390 19.179937 16.231922 c
|
||||
19.016579 16.508204 19.061016 16.860046 19.287983 17.087015 c
|
||||
20.023405 17.822437 l
|
||||
20.341312 18.139969 20.333015 18.649031 20.023687 18.957985 c
|
||||
18.958452 20.023218 l
|
||||
18.649687 20.332407 18.140436 20.341454 17.822906 20.023500 c
|
||||
17.087250 19.287796 l
|
||||
16.860283 19.060781 16.508345 19.016344 16.232109 19.179750 c
|
||||
15.560532 19.576921 14.837719 19.876968 14.083782 20.071547 c
|
||||
13.773282 20.151657 13.556343 20.431688 13.556343 20.752359 c
|
||||
13.556343 21.790640 l
|
||||
13.556343 22.233469 13.196015 22.593750 12.753187 22.593750 c
|
||||
11.246953 22.593750 l
|
||||
10.804125 22.593750 10.443796 22.233469 10.443796 21.790640 c
|
||||
10.443796 20.752453 l
|
||||
10.443796 20.431782 10.226859 20.151751 9.916359 20.071640 c
|
||||
9.162422 19.877062 8.439610 19.577015 7.768031 19.179844 c
|
||||
7.491656 19.016483 7.139860 19.060921 6.912891 19.287891 c
|
||||
6.177516 20.023312 l
|
||||
5.860453 20.340750 5.351250 20.333391 5.042016 20.023640 c
|
||||
3.976687 18.958359 l
|
||||
3.668156 18.650204 3.657844 18.141001 3.976406 17.822813 c
|
||||
4.712110 17.087109 l
|
||||
4.939078 16.860140 4.983516 16.508297 4.820156 16.232016 c
|
||||
4.422985 15.560485 4.122984 14.837671 3.928406 14.083687 c
|
||||
3.848250 13.773188 3.568219 13.556297 3.247594 13.556297 c
|
||||
2.209406 13.556297 l
|
||||
1.766578 13.556251 1.406250 13.195969 1.406250 12.753140 c
|
||||
1.406250 11.246861 l
|
||||
1.406250 10.804032 1.766578 10.443749 2.209406 10.443749 c
|
||||
3.247547 10.443749 l
|
||||
3.568219 10.443749 3.848203 10.226813 3.928360 9.916360 c
|
||||
4.122938 9.162375 4.422985 8.439562 4.820109 7.768030 c
|
||||
4.983469 7.491749 4.939031 7.139906 4.712063 6.912937 c
|
||||
3.976641 6.177515 l
|
||||
3.658734 5.859983 3.667031 5.350922 3.976359 5.041969 c
|
||||
5.041594 3.976734 l
|
||||
5.350359 3.667547 5.859610 3.658499 6.177141 3.976452 c
|
||||
6.912797 4.712156 l
|
||||
7.080047 4.879360 7.428000 5.021250 7.767984 4.820156 c
|
||||
7.767984 4.820156 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.778137 6.778126 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
5.221874 0.000000 m
|
||||
2.342484 0.000000 0.000000 2.342530 0.000000 5.221874 c
|
||||
0.000000 8.101217 2.342484 10.443748 5.221874 10.443748 c
|
||||
8.101265 10.443748 10.443748 8.101217 10.443748 5.221874 c
|
||||
10.443748 2.342530 8.101265 0.000000 5.221874 0.000000 c
|
||||
5.221874 0.000000 l
|
||||
h
|
||||
5.221874 9.037498 m
|
||||
3.117890 9.037498 1.406250 7.325811 1.406250 5.221874 c
|
||||
1.406250 3.117937 3.117937 1.406250 5.221874 1.406250 c
|
||||
7.325811 1.406250 9.037498 3.117937 9.037498 5.221874 c
|
||||
9.037498 7.325811 7.325859 9.037498 5.221874 9.037498 c
|
||||
5.221874 9.037498 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
5666
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000005756 00000 n
|
||||
0000005779 00000 n
|
||||
0000005952 00000 n
|
||||
0000006026 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
6085
|
||||
%%EOF
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue