Redesign conversation screen part 1

This commit is contained in:
Niels Andriesse 2021-01-29 11:46:32 +11:00
parent f2f3025554
commit 90e53e5cef
221 changed files with 3071 additions and 1362 deletions

11
Podfile
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View 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
}
}

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View 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
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View file

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

View 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

View file

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

View file

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

View 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