Rework attachment captioning.

This commit is contained in:
Matthew Chen 2019-03-13 16:15:33 -04:00
parent 625656deb9
commit d80f086f31
6 changed files with 449 additions and 186 deletions

View file

@ -20,10 +20,11 @@
340872C92239563500CB25B0 /* AttachmentItemCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C42239563500CB25B0 /* AttachmentItemCollection.swift */; }; 340872C92239563500CB25B0 /* AttachmentItemCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C42239563500CB25B0 /* AttachmentItemCollection.swift */; };
340872CA2239563500CB25B0 /* AttachmentApprovalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C52239563500CB25B0 /* AttachmentApprovalViewController.swift */; }; 340872CA2239563500CB25B0 /* AttachmentApprovalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C52239563500CB25B0 /* AttachmentApprovalViewController.swift */; };
340872CB2239563500CB25B0 /* AttachmentPrepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C62239563500CB25B0 /* AttachmentPrepViewController.swift */; }; 340872CB2239563500CB25B0 /* AttachmentPrepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C62239563500CB25B0 /* AttachmentPrepViewController.swift */; };
340872CC2239563500CB25B0 /* AttachmentCaptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872C72239563500CB25B0 /* AttachmentCaptionViewController.swift */; };
340872CE2239596100CB25B0 /* AttachmentApprovalInputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872CD2239596000CB25B0 /* AttachmentApprovalInputAccessoryView.swift */; }; 340872CE2239596100CB25B0 /* AttachmentApprovalInputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872CD2239596000CB25B0 /* AttachmentApprovalInputAccessoryView.swift */; };
340872DD22399F9100CB25B0 /* AttachmentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872DB22399F9100CB25B0 /* AttachmentTextView.swift */; }; 340872D02239787F00CB25B0 /* AttachmentTextToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872CF2239787F00CB25B0 /* AttachmentTextToolbar.swift */; };
340872DE22399F9100CB25B0 /* AttachmentTextToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872DC22399F9100CB25B0 /* AttachmentTextToolbar.swift */; }; 340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872D522397E6800CB25B0 /* AttachmentCaptionToolbar.swift */; };
340872D822397F4600CB25B0 /* AttachmentCaptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872D722397F4500CB25B0 /* AttachmentCaptionViewController.swift */; };
340872DA22397FEB00CB25B0 /* AttachmentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872D922397FEB00CB25B0 /* AttachmentTextView.swift */; };
340B02BA1FA0D6C700F9CFEC /* ConversationViewItemTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */; }; 340B02BA1FA0D6C700F9CFEC /* ConversationViewItemTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */; };
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */; }; 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */; };
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; }; 340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; };
@ -657,10 +658,11 @@
340872C42239563500CB25B0 /* AttachmentItemCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentItemCollection.swift; sourceTree = "<group>"; }; 340872C42239563500CB25B0 /* AttachmentItemCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentItemCollection.swift; sourceTree = "<group>"; };
340872C52239563500CB25B0 /* AttachmentApprovalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentApprovalViewController.swift; sourceTree = "<group>"; }; 340872C52239563500CB25B0 /* AttachmentApprovalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentApprovalViewController.swift; sourceTree = "<group>"; };
340872C62239563500CB25B0 /* AttachmentPrepViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPrepViewController.swift; sourceTree = "<group>"; }; 340872C62239563500CB25B0 /* AttachmentPrepViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPrepViewController.swift; sourceTree = "<group>"; };
340872C72239563500CB25B0 /* AttachmentCaptionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentCaptionViewController.swift; sourceTree = "<group>"; };
340872CD2239596000CB25B0 /* AttachmentApprovalInputAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentApprovalInputAccessoryView.swift; sourceTree = "<group>"; }; 340872CD2239596000CB25B0 /* AttachmentApprovalInputAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentApprovalInputAccessoryView.swift; sourceTree = "<group>"; };
340872DB22399F9100CB25B0 /* AttachmentTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentTextView.swift; sourceTree = "<group>"; }; 340872CF2239787F00CB25B0 /* AttachmentTextToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentTextToolbar.swift; sourceTree = "<group>"; };
340872DC22399F9100CB25B0 /* AttachmentTextToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentTextToolbar.swift; sourceTree = "<group>"; }; 340872D522397E6800CB25B0 /* AttachmentCaptionToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentCaptionToolbar.swift; sourceTree = "<group>"; };
340872D722397F4500CB25B0 /* AttachmentCaptionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentCaptionViewController.swift; sourceTree = "<group>"; };
340872D922397FEB00CB25B0 /* AttachmentTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentTextView.swift; sourceTree = "<group>"; };
340B02B61F9FD31800F9CFEC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = translations/he.lproj/Localizable.strings; sourceTree = "<group>"; }; 340B02B61F9FD31800F9CFEC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = translations/he.lproj/Localizable.strings; sourceTree = "<group>"; };
340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationViewItemTest.m; sourceTree = "<group>"; }; 340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationViewItemTest.m; sourceTree = "<group>"; };
340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; }; 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; };
@ -1492,11 +1494,12 @@
340872C32239563500CB25B0 /* ApprovalRailCellView.swift */, 340872C32239563500CB25B0 /* ApprovalRailCellView.swift */,
340872CD2239596000CB25B0 /* AttachmentApprovalInputAccessoryView.swift */, 340872CD2239596000CB25B0 /* AttachmentApprovalInputAccessoryView.swift */,
340872C52239563500CB25B0 /* AttachmentApprovalViewController.swift */, 340872C52239563500CB25B0 /* AttachmentApprovalViewController.swift */,
340872C72239563500CB25B0 /* AttachmentCaptionViewController.swift */, 340872D522397E6800CB25B0 /* AttachmentCaptionToolbar.swift */,
340872D722397F4500CB25B0 /* AttachmentCaptionViewController.swift */,
340872C42239563500CB25B0 /* AttachmentItemCollection.swift */, 340872C42239563500CB25B0 /* AttachmentItemCollection.swift */,
340872C62239563500CB25B0 /* AttachmentPrepViewController.swift */, 340872C62239563500CB25B0 /* AttachmentPrepViewController.swift */,
340872DC22399F9100CB25B0 /* AttachmentTextToolbar.swift */, 340872CF2239787F00CB25B0 /* AttachmentTextToolbar.swift */,
340872DB22399F9100CB25B0 /* AttachmentTextView.swift */, 340872D922397FEB00CB25B0 /* AttachmentTextView.swift */,
); );
path = AttachmentApproval; path = AttachmentApproval;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3399,6 +3402,7 @@
34BBC857220C7ADA00857249 /* ImageEditorItem.swift in Sources */, 34BBC857220C7ADA00857249 /* ImageEditorItem.swift in Sources */,
34480B641FD0A98800BC14EF /* UIView+OWS.m in Sources */, 34480B641FD0A98800BC14EF /* UIView+OWS.m in Sources */,
34AC0A1C211B39EA00997B47 /* OWSFlatButton.swift in Sources */, 34AC0A1C211B39EA00997B47 /* OWSFlatButton.swift in Sources */,
340872D822397F4600CB25B0 /* AttachmentCaptionViewController.swift in Sources */,
34C3C7932040B0DD0000134C /* OWSAudioPlayer.m in Sources */, 34C3C7932040B0DD0000134C /* OWSAudioPlayer.m in Sources */,
34AC09E5211B39B100997B47 /* ScreenLockViewController.m in Sources */, 34AC09E5211B39B100997B47 /* ScreenLockViewController.m in Sources */,
34AC09F7211B39B100997B47 /* MediaMessageView.swift in Sources */, 34AC09F7211B39B100997B47 /* MediaMessageView.swift in Sources */,
@ -3406,10 +3410,8 @@
3461293A1FD1B47300532771 /* OWSPreferences.m in Sources */, 3461293A1FD1B47300532771 /* OWSPreferences.m in Sources */,
34AC09E6211B39B100997B47 /* SelectRecipientViewController.m in Sources */, 34AC09E6211B39B100997B47 /* SelectRecipientViewController.m in Sources */,
4C858A52212DC5E1001B45D3 /* UIImage+OWS.swift in Sources */, 4C858A52212DC5E1001B45D3 /* UIImage+OWS.swift in Sources */,
340872CC2239563500CB25B0 /* AttachmentCaptionViewController.swift in Sources */,
34480B671FD0AA9400BC14EF /* UIFont+OWS.m in Sources */, 34480B671FD0AA9400BC14EF /* UIFont+OWS.m in Sources */,
346129E61FD5C0C600532771 /* OWSDatabaseMigrationRunner.m in Sources */, 346129E61FD5C0C600532771 /* OWSDatabaseMigrationRunner.m in Sources */,
340872DD22399F9100CB25B0 /* AttachmentTextView.swift in Sources */,
34AC0A11211B39EA00997B47 /* OWSLayerView.swift in Sources */, 34AC0A11211B39EA00997B47 /* OWSLayerView.swift in Sources */,
34AC0A1B211B39EA00997B47 /* GradientView.swift in Sources */, 34AC0A1B211B39EA00997B47 /* GradientView.swift in Sources */,
34AC09E2211B39B100997B47 /* ReturnToCallViewController.swift in Sources */, 34AC09E2211B39B100997B47 /* ReturnToCallViewController.swift in Sources */,
@ -3430,6 +3432,7 @@
346129AD1FD1F34E00532771 /* ImageCache.swift in Sources */, 346129AD1FD1F34E00532771 /* ImageCache.swift in Sources */,
452C7CA72037628B003D51A5 /* Weak.swift in Sources */, 452C7CA72037628B003D51A5 /* Weak.swift in Sources */,
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */, 34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */,
340872D02239787F00CB25B0 /* AttachmentTextToolbar.swift in Sources */,
34AC09F8211B39B100997B47 /* CountryCodeViewController.m in Sources */, 34AC09F8211B39B100997B47 /* CountryCodeViewController.m in Sources */,
451F8A341FD710C3005CB9DA /* FullTextSearcher.swift in Sources */, 451F8A341FD710C3005CB9DA /* FullTextSearcher.swift in Sources */,
34080F04222858DC0087E99F /* OWSViewController+ImageEditor.swift in Sources */, 34080F04222858DC0087E99F /* OWSViewController+ImageEditor.swift in Sources */,
@ -3466,6 +3469,7 @@
34BEDB0B21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift in Sources */, 34BEDB0B21C2FA3D007B0EAE /* OWS114RemoveDynamicInteractions.swift in Sources */,
34AC0A1A211B39EA00997B47 /* CommonStrings.swift in Sources */, 34AC0A1A211B39EA00997B47 /* CommonStrings.swift in Sources */,
34AC0A19211B39EA00997B47 /* OWSAlerts.swift in Sources */, 34AC0A19211B39EA00997B47 /* OWSAlerts.swift in Sources */,
340872DA22397FEB00CB25B0 /* AttachmentTextView.swift in Sources */,
34FDB29221FF986600A01202 /* UIView+OWS.swift in Sources */, 34FDB29221FF986600A01202 /* UIView+OWS.swift in Sources */,
34BBC859220C7ADA00857249 /* ImageEditorStrokeItem.swift in Sources */, 34BBC859220C7ADA00857249 /* ImageEditorStrokeItem.swift in Sources */,
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */, 451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */,
@ -3482,7 +3486,6 @@
34BBC84B220B2CB200857249 /* ImageEditorTextViewController.swift in Sources */, 34BBC84B220B2CB200857249 /* ImageEditorTextViewController.swift in Sources */,
34AC09FA211B39B100997B47 /* SharingThreadPickerViewController.m in Sources */, 34AC09FA211B39B100997B47 /* SharingThreadPickerViewController.m in Sources */,
45F59A082028E4FB00E8D2B0 /* OWSAudioSession.swift in Sources */, 45F59A082028E4FB00E8D2B0 /* OWSAudioSession.swift in Sources */,
340872DE22399F9100CB25B0 /* AttachmentTextToolbar.swift in Sources */,
34612A071FD7238600532771 /* OWSSyncManager.m in Sources */, 34612A071FD7238600532771 /* OWSSyncManager.m in Sources */,
450C801220AD1D5B00F3A091 /* UIDevice+featureSupport.swift in Sources */, 450C801220AD1D5B00F3A091 /* UIDevice+featureSupport.swift in Sources */,
451F8A471FD715BA005CB9DA /* OWSAvatarBuilder.m in Sources */, 451F8A471FD715BA005CB9DA /* OWSAvatarBuilder.m in Sources */,
@ -3497,6 +3500,7 @@
349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */, 349EA07C2162AEA800F7B17F /* OWS111UDAttributesMigration.swift in Sources */,
34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */, 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */,
459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */, 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */,
340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */,
34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */, 34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */,
4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */, 4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */,
4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */, 4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */,

View file

@ -95,6 +95,9 @@
/* placeholder text for an empty captioning field */ /* placeholder text for an empty captioning field */
"ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER" = "Add a caption…"; "ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER" = "Add a caption…";
/* Title for 'caption' mode of the attachment approval view. */
"ATTACHMENT_APPROVAL_CAPTION_TITLE" = "Caption";
/* Format string for file extension label in call interstitial view */ /* Format string for file extension label in call interstitial view */
"ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@"; "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@";
@ -305,15 +308,9 @@
/* Label for the 'next' button. */ /* Label for the 'next' button. */
"BUTTON_NEXT" = "Next"; "BUTTON_NEXT" = "Next";
/* Label for redo button. */
"BUTTON_REDO" = "Redo";
/* Button text to enable batch selection mode */ /* Button text to enable batch selection mode */
"BUTTON_SELECT" = "Select"; "BUTTON_SELECT" = "Select";
/* Label for undo button. */
"BUTTON_UNDO" = "Undo";
/* Label for button that lets users call a contact again. */ /* Label for button that lets users call a contact again. */
"CALL_AGAIN_BUTTON_TITLE" = "Call Again"; "CALL_AGAIN_BUTTON_TITLE" = "Call Again";
@ -1068,13 +1065,13 @@
/* Placeholder text for search bar which filters conversations. */ /* Placeholder text for search bar which filters conversations. */
"HOME_VIEW_CONVERSATION_SEARCHBAR_PLACEHOLDER" = "Search"; "HOME_VIEW_CONVERSATION_SEARCHBAR_PLACEHOLDER" = "Search";
/* Format string for a label offering to start a new conversation with your contacts, if you have 1 Signal contact. Embeds: {{The name of 1 of your Signal contacts}}. */ /* Format string for a label offering to start a new conversation with your contacts, if you have 1 Signal contact. Embeds {{The name of 1 of your Signal contacts}}. */
"HOME_VIEW_FIRST_CONVERSATION_OFFER_1_CONTACT_FORMAT" = "Some of your contacts are already on Signal, including %@."; "HOME_VIEW_FIRST_CONVERSATION_OFFER_1_CONTACT_FORMAT" = "Some of your contacts are already on Signal, including %@.";
/* Format string for a label offering to start a new conversation with your contacts, if you have 2 Signal contacts. Embeds: {{The names of 2 of your Signal contacts}}. */ /* Format string for a label offering to start a new conversation with your contacts, if you have 2 Signal contacts. Embeds {{The names of 2 of your Signal contacts}}. */
"HOME_VIEW_FIRST_CONVERSATION_OFFER_2_CONTACTS_FORMAT" = "Some of your contacts are already on Signal, including %@ and %@"; "HOME_VIEW_FIRST_CONVERSATION_OFFER_2_CONTACTS_FORMAT" = "Some of your contacts are already on Signal, including %@ and %@";
/* Format string for a label offering to start a new conversation with your contacts, if you have at least 3 Signal contacts. Embeds: {{The names of 3 of your Signal contacts}}. */ /* Format string for a label offering to start a new conversation with your contacts, if you have at least 3 Signal contacts. Embeds {{The names of 3 of your Signal contacts}}. */
"HOME_VIEW_FIRST_CONVERSATION_OFFER_3_CONTACTS_FORMAT" = "Some of your contacts are already on Signal, including %@, %@ and %@"; "HOME_VIEW_FIRST_CONVERSATION_OFFER_3_CONTACTS_FORMAT" = "Some of your contacts are already on Signal, including %@, %@ and %@";
/* A label offering to start a new conversation with your contacts, if you have no Signal contacts. */ /* A label offering to start a new conversation with your contacts, if you have no Signal contacts. */
@ -1089,21 +1086,6 @@
/* Title for the home view's default mode. */ /* Title for the home view's default mode. */
"HOME_VIEW_TITLE_INBOX" = "Signal"; "HOME_VIEW_TITLE_INBOX" = "Signal";
/* Label for brush button in image editor. */
"IMAGE_EDITOR_BRUSH_BUTTON" = "Brush";
/* Label for crop button in image editor. */
"IMAGE_EDITOR_CROP_BUTTON" = "Crop";
/* Label for button that resets crop & rotation state. */
"IMAGE_EDITOR_RESET_BUTTON" = "Reset";
/* Label for button that rotates image 45 degrees. */
"IMAGE_EDITOR_ROTATE_45_BUTTON" = "Rotate 45°";
/* Label for button that rotates image 90 degrees. */
"IMAGE_EDITOR_ROTATE_90_BUTTON" = "Rotate 90°";
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */ /* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items."; "IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
@ -1811,12 +1793,6 @@
/* Alert message explaining what happens if you forget your 'two-factor auth pin'. */ /* Alert message explaining what happens if you forget your 'two-factor auth pin'. */
"REGISTER_2FA_FORGOT_PIN_ALERT_MESSAGE" = "Registration of this phone number will be possible without your Registration Lock PIN after 7 days have passed since the phone number was last active on Signal."; "REGISTER_2FA_FORGOT_PIN_ALERT_MESSAGE" = "Registration of this phone number will be possible without your Registration Lock PIN after 7 days have passed since the phone number was last active on Signal.";
/* Instructions to enter the 'two-factor auth pin' in the 2FA registration view. */
"REGISTER_2FA_INSTRUCTIONS" = "This phone number has Registration Lock enabled. Please enter the Registration Lock PIN.\n\nYour Registration Lock PIN is separate from the automated verification code that was sent to your phone during the last step.";
/* Title for alert indicating that attempt to register with 'two-factor auth' failed. */
"REGISTER_2FA_REGISTRATION_FAILED_ALERT_TITLE" = "Registration Failed";
/* Label for 'submit' button in the 2FA registration view. */ /* Label for 'submit' button in the 2FA registration view. */
"REGISTER_2FA_SUBMIT_BUTTON" = "Submit"; "REGISTER_2FA_SUBMIT_BUTTON" = "Submit";
@ -1838,9 +1814,6 @@
/* Label for the country code field */ /* Label for the country code field */
"REGISTRATION_DEFAULT_COUNTRY_NAME" = "Country Code"; "REGISTRATION_DEFAULT_COUNTRY_NAME" = "Country Code";
/* Navigation title shown when user is re-registering after having enabled registration lock */
"REGISTRATION_ENTER_LOCK_PIN_NAV_TITLE" = "Registration Lock";
/* Placeholder text for the phone number textfield */ /* Placeholder text for the phone number textfield */
"REGISTRATION_ENTERNUMBER_DEFAULT_TEXT" = "Enter Number"; "REGISTRATION_ENTERNUMBER_DEFAULT_TEXT" = "Enter Number";
@ -1859,15 +1832,6 @@
/* alert title when registering an iPad */ /* alert title when registering an iPad */
"REGISTRATION_IPAD_CONFIRM_TITLE" = "Already have a Signal account?"; "REGISTRATION_IPAD_CONFIRM_TITLE" = "Already have a Signal account?";
/* one line label below submit button on registration screen, which links to an external webpage. */
"REGISTRATION_LEGAL_TERMS_LINK" = "Terms & Privacy Policy";
/* legal disclaimer, embeds a tappable {{link title}} which is styled as a hyperlink */
"REGISTRATION_LEGAL_TOP_MATTER_FORMAT" = "By registering this device, you agree to Signal's %@";
/* embedded in legal topmatter, styled as a link */
"REGISTRATION_LEGAL_TOP_MATTER_LINK_TITLE" = "terms";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"REGISTRATION_NON_VALID_NUMBER" = "This phone number format is not supported, please contact support."; "REGISTRATION_NON_VALID_NUMBER" = "This phone number format is not supported, please contact support.";
@ -1877,9 +1841,6 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"REGISTRATION_RESTRICTED_MESSAGE" = "You need to register before you can send a message."; "REGISTRATION_RESTRICTED_MESSAGE" = "You need to register before you can send a message.";
/* No comment provided by engineer. */
"REGISTRATION_TITLE_LABEL" = "Your Phone Number";
/* Alert view title */ /* Alert view title */
"REGISTRATION_VERIFICATION_FAILED_TITLE" = "Verification Failed"; "REGISTRATION_VERIFICATION_FAILED_TITLE" = "Verification Failed";
@ -1889,9 +1850,6 @@
/* Error message indicating that registration failed due to a missing or incorrect 2FA PIN. */ /* Error message indicating that registration failed due to a missing or incorrect 2FA PIN. */
"REGISTRATION_VERIFICATION_FAILED_WRONG_PIN" = "Incorrect Registration Lock PIN."; "REGISTRATION_VERIFICATION_FAILED_WRONG_PIN" = "Incorrect Registration Lock PIN.";
/* No comment provided by engineer. */
"REGISTRATION_VERIFY_DEVICE" = "Register";
/* Message of alert indicating that users needs to enter a valid phone number to register. */ /* Message of alert indicating that users needs to enter a valid phone number to register. */
"REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_MESSAGE" = "Please enter a valid phone number to register."; "REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_MESSAGE" = "Please enter a valid phone number to register.";
@ -2525,24 +2483,6 @@
/* Title for the alert indicating that user should upgrade iOS. */ /* Title for the alert indicating that user should upgrade iOS. */
"UPGRADE_IOS_ALERT_TITLE" = "Upgrade iOS"; "UPGRADE_IOS_ALERT_TITLE" = "Upgrade iOS";
/* button text for back button on verification view */
"VERIFICATION_BACK_BUTTON" = "Back";
/* Text field placeholder for SMS verification code during registration */
"VERIFICATION_CHALLENGE_DEFAULT_TEXT" = "Verification Code";
/* button text during registration to request phone number verification be done via phone call */
"VERIFICATION_CHALLENGE_SEND_VIA_VOICE" = "Call Me Instead";
/* button text during registration to request another SMS code be sent */
"VERIFICATION_CHALLENGE_SUBMIT_AGAIN" = "Resend Code by SMS";
/* button text during registration to submit your SMS verification code. */
"VERIFICATION_CHALLENGE_SUBMIT_CODE" = "Submit";
/* Label indicating the phone number currently being verified. */
"VERIFICATION_PHONE_NUMBER_FORMAT" = "Enter the verification code we sent to %@.";
/* Format for info message indicating that the verification state was unverified on this device. Embeds {{user's name or phone number}}. */ /* Format for info message indicating that the verification state was unverified on this device. Embeds {{user's name or phone number}}. */
"VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_LOCAL" = "You marked %@ as not verified."; "VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_LOCAL" = "You marked %@ as not verified.";

View file

@ -5,42 +5,148 @@
import Foundation import Foundation
import UIKit import UIKit
protocol AttachmentApprovalInputAccessoryViewDelegate: class {
func attachmentApprovalInputUpdateMediaRail()
func attachmentApprovalInputEditCaptions()
}
// MARK: -
class AttachmentApprovalInputAccessoryView: UIView { class AttachmentApprovalInputAccessoryView: UIView {
weak var delegate: AttachmentApprovalInputAccessoryViewDelegate?
let attachmentTextToolbar: AttachmentTextToolbar let attachmentTextToolbar: AttachmentTextToolbar
let attachmentCaptionToolbar: AttachmentCaptionToolbar
let galleryRailView: GalleryRailView let galleryRailView: GalleryRailView
let currentCaptionLabel = UILabel()
let currentCaptionWrapper = UIView()
var isEditingMediaMessage: Bool { var isEditingMediaMessage: Bool {
return attachmentTextToolbar.textView.isFirstResponder return attachmentTextToolbar.textView.isFirstResponder
} }
private var isEditingCaptions: Bool = false
private var currentAttachmentItem: SignalAttachmentItem?
let kGalleryRailViewHeight: CGFloat = 72 let kGalleryRailViewHeight: CGFloat = 72
required init(isAddMoreVisible: Bool) { required init(isAddMoreVisible: Bool) {
attachmentTextToolbar = AttachmentTextToolbar(isAddMoreVisible: isAddMoreVisible) attachmentTextToolbar = AttachmentTextToolbar(isAddMoreVisible: isAddMoreVisible)
attachmentCaptionToolbar = AttachmentCaptionToolbar()
galleryRailView = GalleryRailView() galleryRailView = GalleryRailView()
galleryRailView.scrollFocusMode = .keepWithinBounds galleryRailView.scrollFocusMode = .keepWithinBounds
galleryRailView.autoSetDimension(.height, toSize: kGalleryRailViewHeight) galleryRailView.autoSetDimension(.height, toSize: kGalleryRailViewHeight)
super.init(frame: .zero) super.init(frame: .zero)
createContents()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createContents() {
// Specifying auto-resizing mask and an intrinsic content size allows proper // Specifying auto-resizing mask and an intrinsic content size allows proper
// sizing when used as an input accessory view. // sizing when used as an input accessory view.
self.autoresizingMask = .flexibleHeight self.autoresizingMask = .flexibleHeight
self.translatesAutoresizingMaskIntoConstraints = false self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = .clear
backgroundColor = UIColor.black.withAlphaComponent(0.6)
preservesSuperviewLayoutMargins = true preservesSuperviewLayoutMargins = true
let stackView = UIStackView(arrangedSubviews: [self.galleryRailView, self.attachmentTextToolbar]) // Use a background view that extends below the keyboard to avoid animation glitches.
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.6)
addSubview(backgroundView)
backgroundView.autoPinEdge(toSuperviewEdge: .top)
backgroundView.autoPinEdge(toSuperviewEdge: .leading)
backgroundView.autoPinEdge(toSuperviewEdge: .trailing)
backgroundView.autoPinEdge(toSuperviewEdge: .bottom, withInset: -200)
currentCaptionLabel.textColor = UIColor(white: 1, alpha: 0.8)
currentCaptionLabel.font = UIFont.ows_dynamicTypeBody
currentCaptionLabel.numberOfLines = 5
currentCaptionLabel.lineBreakMode = .byWordWrapping
currentCaptionWrapper.isUserInteractionEnabled = true
currentCaptionWrapper.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(captionTapped)))
currentCaptionWrapper.addSubview(currentCaptionLabel)
currentCaptionLabel.autoPinEdgesToSuperviewMargins()
attachmentCaptionToolbar.attachmentCaptionToolbarDelegate = self
let stackView = UIStackView(arrangedSubviews: [currentCaptionWrapper, attachmentCaptionToolbar, galleryRailView, attachmentTextToolbar])
stackView.axis = .vertical stackView.axis = .vertical
addSubview(stackView) addSubview(stackView)
stackView.autoPinEdgesToSuperviewEdges() stackView.autoPinEdgesToSuperviewEdges()
} }
required init?(coder aDecoder: NSCoder) { // MARK: - Events
fatalError("init(coder:) has not been implemented")
@objc func captionTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
delegate?.attachmentApprovalInputEditCaptions()
}
// MARK:
public var shouldHideControls = false {
didSet {
updateContents()
}
}
private func updateContents() {
var hasCurrentCaption = false
if let currentAttachmentItem = currentAttachmentItem,
let captionText = currentAttachmentItem.captionText {
hasCurrentCaption = captionText.count > 0
attachmentCaptionToolbar.textView.text = captionText
currentCaptionLabel.text = captionText
} else {
attachmentCaptionToolbar.textView.text = nil
currentCaptionLabel.text = nil
}
attachmentCaptionToolbar.isHidden = !isEditingCaptions
currentCaptionWrapper.isHidden = isEditingCaptions || !hasCurrentCaption
attachmentTextToolbar.isHidden = isEditingCaptions
if (shouldHideControls) {
if attachmentCaptionToolbar.textView.isFirstResponder {
attachmentCaptionToolbar.textView.resignFirstResponder()
} else if attachmentTextToolbar.textView.isFirstResponder {
attachmentTextToolbar.textView.resignFirstResponder()
}
} else if (isEditingCaptions) {
if !attachmentCaptionToolbar.textView.isFirstResponder {
attachmentCaptionToolbar.textView.becomeFirstResponder()
}
} else {
if !attachmentTextToolbar.textView.isFirstResponder {
attachmentTextToolbar.textView.becomeFirstResponder()
}
}
invalidateIntrinsicContentSize()
layoutSubviews()
}
public func update(isEditingCaptions: Bool,
currentAttachmentItem: SignalAttachmentItem?) {
self.isEditingCaptions = isEditingCaptions
self.currentAttachmentItem = currentAttachmentItem
updateContents()
} }
// MARK: // MARK:
@ -53,3 +159,18 @@ class AttachmentApprovalInputAccessoryView: UIView {
} }
} }
} }
// MARK: -
extension AttachmentApprovalInputAccessoryView: AttachmentCaptionToolbarDelegate {
public func attachmentCaptionToolbarDidEdit(_ attachmentCaptionToolbar: AttachmentCaptionToolbar) {
guard let currentAttachmentItem = currentAttachmentItem else {
owsFailDebug("Missing currentAttachmentItem.")
return
}
currentAttachmentItem.attachment.captionText = attachmentCaptionToolbar.textView.text
delegate?.attachmentApprovalInputUpdateMediaRail()
}
}

View file

@ -34,6 +34,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate? public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate?
public var isEditingCaptions = false {
didSet {
updateContents()
}
}
// MARK: - Initializers // MARK: - Initializers
@available(*, unavailable, message:"use attachment: constructor instead.") @available(*, unavailable, message:"use attachment: constructor instead.")
@ -86,6 +92,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
lazy var bottomToolView: AttachmentApprovalInputAccessoryView = { lazy var bottomToolView: AttachmentApprovalInputAccessoryView = {
let isAddMoreVisible = mode == .sharedNavigation let isAddMoreVisible = mode == .sharedNavigation
let bottomToolView = AttachmentApprovalInputAccessoryView(isAddMoreVisible: isAddMoreVisible) let bottomToolView = AttachmentApprovalInputAccessoryView(isAddMoreVisible: isAddMoreVisible)
bottomToolView.delegate = self
return bottomToolView return bottomToolView
}() }()
@ -133,8 +140,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
navigationBar.overrideTheme(type: .clear) navigationBar.overrideTheme(type: .clear)
updateNavigationBar() updateContents()
updateControlVisibility()
} }
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
@ -142,8 +148,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
super.viewDidAppear(animated) super.viewDidAppear(animated)
updateNavigationBar() updateContents()
updateControlVisibility()
} }
override public func viewWillDisappear(_ animated: Bool) { override public func viewWillDisappear(_ animated: Bool) {
@ -151,6 +156,14 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
super.viewWillDisappear(animated) super.viewWillDisappear(animated)
} }
private func updateContents() {
updateNavigationBar()
updateControlVisibility()
updateInputAccessory()
}
// MARK: - Input Accessory
override public var inputAccessoryView: UIView? { override public var inputAccessoryView: UIView? {
bottomToolView.layoutIfNeeded() bottomToolView.layoutIfNeeded()
return bottomToolView return bottomToolView
@ -160,6 +173,15 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return !shouldHideControls return !shouldHideControls
} }
public func updateInputAccessory() {
var currentPageViewController: AttachmentPrepViewController?
if pageViewControllers.count == 1 {
currentPageViewController = pageViewControllers.first
}
let currentAttachmentItem: SignalAttachmentItem? = currentPageViewController?.attachmentItem
bottomToolView.update(isEditingCaptions: isEditingCaptions, currentAttachmentItem: currentAttachmentItem)
}
// MARK: - Navigation Bar // MARK: - Navigation Bar
public func updateNavigationBar() { public func updateNavigationBar() {
@ -169,21 +191,36 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
return return
} }
guard !isEditingCaptions else {
// Hide all navigation bar items while the caption view is open.
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_TITLE", comment: "Title for 'caption' mode of the attachment approval view."), style: .plain, target: nil, action: nil)
let doneButton = navigationBarButton(imageName: "image_editor_checkmark_full",
selector: #selector(didTapCaptionDone(sender:)))
let navigationBarItems = [doneButton]
updateNavigationBar(navigationBarItems: navigationBarItems)
return
}
var navigationBarItems = [UIView]() var navigationBarItems = [UIView]()
var isShowingCaptionView = false
if let viewControllers = viewControllers, if let viewControllers = viewControllers,
viewControllers.count == 1, viewControllers.count == 1,
let firstViewController = viewControllers.first as? AttachmentPrepViewController { let firstViewController = viewControllers.first as? AttachmentPrepViewController {
navigationBarItems = firstViewController.navigationBarItems() navigationBarItems = firstViewController.navigationBarItems()
isShowingCaptionView = firstViewController.isShowingCaptionView
}
guard !isShowingCaptionView else { // Show the caption UI if there's more than one attachment
// Hide all navigation bar items while the caption view is open. // OR if the attachment already has a caption.
self.navigationItem.leftBarButtonItem = nil let attachmentCount = attachmentItemCollection.count
self.navigationItem.rightBarButtonItem = nil var shouldShowCaptionUI = attachmentCount > 0
return if let captionText = firstViewController.attachmentItem.captionText, captionText.count > 0 {
shouldShowCaptionUI = true
}
if shouldShowCaptionUI {
let captionButton = navigationBarButton(imageName: "image_editor_caption",
selector: #selector(didTapCaption(sender:)))
navigationBarItems.append(captionButton)
}
} }
updateNavigationBar(navigationBarItems: navigationBarItems) updateNavigationBar(navigationBarItems: navigationBarItems)
@ -264,15 +301,10 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
private func updateControlVisibility() { private func updateControlVisibility() {
if shouldHideControls { if !shouldHideControls {
if isFirstResponder { self.becomeFirstResponder()
resignFirstResponder()
}
} else {
if !isFirstResponder {
becomeFirstResponder()
}
} }
bottomToolView.shouldHideControls = shouldHideControls
} }
// MARK: - View Helpers // MARK: - View Helpers
@ -351,8 +383,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
} }
} }
updateNavigationBar() updateContents()
updateControlVisibility()
} }
// MARK: - UIPageViewControllerDataSource // MARK: - UIPageViewControllerDataSource
@ -564,8 +595,22 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC
private func cancelPressed() { private func cancelPressed() {
self.approvalDelegate?.attachmentApproval(self, didCancelAttachments: attachments) self.approvalDelegate?.attachmentApproval(self, didCancelAttachments: attachments)
} }
@objc func didTapCaption(sender: UIButton) {
Logger.verbose("")
isEditingCaptions = true
}
@objc func didTapCaptionDone(sender: UIButton) {
Logger.verbose("")
isEditingCaptions = false
}
} }
// MARK: -
extension AttachmentApprovalViewController: AttachmentTextToolbarDelegate { extension AttachmentApprovalViewController: AttachmentTextToolbarDelegate {
func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar) { func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar) {
currentPageViewController.setAttachmentViewScale(.compact, animated: true) currentPageViewController.setAttachmentViewScale(.compact, animated: true)
@ -594,12 +639,6 @@ extension AttachmentApprovalViewController: AttachmentTextToolbarDelegate {
// MARK: - // MARK: -
extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate { extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate {
func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) {
self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment)
updateMediaRail()
}
func prepViewControllerUpdateNavigationBar() { func prepViewControllerUpdateNavigationBar() {
updateNavigationBar() updateNavigationBar()
} }
@ -607,10 +646,6 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate
func prepViewControllerUpdateControls() { func prepViewControllerUpdateControls() {
updateControlVisibility() updateControlVisibility()
} }
func prepViewControllerAttachmentCount() -> Int {
return attachmentItemCollection.count
}
} }
// MARK: GalleryRail // MARK: GalleryRail
@ -671,3 +706,15 @@ extension AttachmentApprovalViewController: ApprovalRailCellViewDelegate {
remove(attachmentItem: attachmentItem) remove(attachmentItem: attachmentItem)
} }
} }
// MARK: -
extension AttachmentApprovalViewController: AttachmentApprovalInputAccessoryViewDelegate {
public func attachmentApprovalInputUpdateMediaRail() {
updateMediaRail()
}
public func attachmentApprovalInputEditCaptions() {
isEditingCaptions = true
}
}

View file

@ -0,0 +1,222 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
protocol AttachmentCaptionToolbarDelegate: class {
func attachmentCaptionToolbarDidEdit(_ attachmentCaptionToolbar: AttachmentCaptionToolbar)
}
// MARK: -
class AttachmentCaptionToolbar: UIView, UITextViewDelegate {
private let kMaxCaptionCharacterCount = 240
weak var attachmentCaptionToolbarDelegate: AttachmentCaptionToolbarDelegate?
var messageText: String? {
get { return textView.text }
set {
textView.text = newValue
}
}
// Layout Constants
let kMinTextViewHeight: CGFloat = 38
var maxTextViewHeight: CGFloat {
// About ~4 lines in portrait and ~3 lines in landscape.
// Otherwise we risk obscuring too much of the content.
return UIDevice.current.orientation.isPortrait ? 160 : 100
}
var textViewHeightConstraint: NSLayoutConstraint!
var textViewHeight: CGFloat
// MARK: - Initializers
init() {
self.textViewHeight = kMinTextViewHeight
super.init(frame: CGRect.zero)
// Specifying autorsizing mask and an intrinsic content size allows proper
// sizing when used as an input accessory view.
self.autoresizingMask = .flexibleHeight
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.clear
textView.delegate = self
// Layout
let kToolbarMargin: CGFloat = 8
self.textViewHeightConstraint = textView.autoSetDimension(.height, toSize: kMinTextViewHeight)
lengthLimitLabel.setContentHuggingHigh()
lengthLimitLabel.setCompressionResistanceHigh()
let contentView = UIStackView(arrangedSubviews: [textContainer, lengthLimitLabel])
// We have to wrap the toolbar items in a content view because iOS (at least on iOS10.3) assigns the inputAccessoryView.layoutMargins
// when resigning first responder (verified by auditing with `layoutMarginsDidChange`).
// The effect of this is that if we were to assign these margins to self.layoutMargins, they'd be blown away if the
// user dismisses the keyboard, giving the input accessory view a wonky layout.
contentView.layoutMargins = UIEdgeInsets(top: kToolbarMargin, left: kToolbarMargin, bottom: kToolbarMargin, right: kToolbarMargin)
contentView.axis = .vertical
addSubview(contentView)
contentView.autoPinEdgesToSuperviewEdges()
}
required init?(coder aDecoder: NSCoder) {
notImplemented()
}
// MARK: - UIView Overrides
override var intrinsicContentSize: CGSize {
get {
// Since we have `self.autoresizingMask = UIViewAutoresizingFlexibleHeight`, we must specify
// an intrinsicContentSize. Specifying CGSize.zero causes the height to be determined by autolayout.
return CGSize.zero
}
}
// MARK: - Subviews
private lazy var lengthLimitLabel: UILabel = {
let lengthLimitLabel = UILabel()
// Length Limit Label shown when the user inputs too long of a message
lengthLimitLabel.textColor = .white
lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the media message field.")
lengthLimitLabel.textAlignment = .center
// Add shadow in case overlayed on white content
lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor
lengthLimitLabel.layer.shadowOffset = .zero
lengthLimitLabel.layer.shadowOpacity = 0.8
lengthLimitLabel.layer.shadowRadius = 2.0
lengthLimitLabel.isHidden = true
return lengthLimitLabel
}()
lazy var textView: UITextView = {
let textView = buildTextView()
textView.returnKeyType = .done
textView.scrollIndicatorInsets = UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 3)
return textView
}()
private lazy var textContainer: UIView = {
let textContainer = UIView()
textContainer.clipsToBounds = true
textContainer.addSubview(textView)
textView.autoPinEdgesToSuperviewEdges()
return textContainer
}()
private func buildTextView() -> UITextView {
let textView = AttachmentTextView()
textView.keyboardAppearance = Theme.darkThemeKeyboardAppearance
textView.backgroundColor = .clear
textView.tintColor = Theme.darkThemePrimaryColor
textView.font = UIFont.ows_dynamicTypeBody
textView.textColor = Theme.darkThemePrimaryColor
textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
return textView
}
// MARK: - UITextViewDelegate
public func textViewDidChange(_ textView: UITextView) {
updateHeight(textView: textView)
attachmentCaptionToolbarDelegate?.attachmentCaptionToolbarDidEdit(self)
}
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if !FeatureFlags.sendingMediaWithOversizeText {
let existingText: String = textView.text ?? ""
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
// Don't complicate things by mixing media attachments with oversize text attachments
guard proposedText.utf8.count < kOversizeTextMessageSizeThreshold else {
Logger.debug("long text was truncated")
self.lengthLimitLabel.isHidden = false
// `range` represents the section of the existing text we will replace. We can re-use that space.
// Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be
// represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is
// to just measure the utf8 encoded bytes of the replaced substring.
let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count
// Accept as much of the input as we can
let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete
if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) {
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
}
return false
}
self.lengthLimitLabel.isHidden = true
// After verifying the byte-length is sufficiently small, verify the character count is within bounds.
guard proposedText.count < kMaxCaptionCharacterCount else {
Logger.debug("hit attachment message body character count limit")
self.lengthLimitLabel.isHidden = false
// `range` represents the section of the existing text we will replace. We can re-use that space.
let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count
// Accept as much of the input as we can
let charBudget: Int = Int(kMaxCaptionCharacterCount) - charsAfterDelete
if charBudget >= 0 {
let acceptableNewText = String(text.prefix(charBudget))
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
}
return false
}
}
// Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button
// allows the user to get the keyboard out of the way while in the attachment approval view.
if text == "\n" {
textView.resignFirstResponder()
return false
} else {
return true
}
}
// MARK: - Helpers
private func updateHeight(textView: UITextView) {
// compute new height assuming width is unchanged
let currentSize = textView.frame.size
let newHeight = clampedTextViewHeight(fixedWidth: currentSize.width)
if newHeight != textViewHeight {
Logger.debug("TextView height changed: \(textViewHeight) -> \(newHeight)")
textViewHeight = newHeight
textViewHeightConstraint?.constant = textViewHeight
invalidateIntrinsicContentSize()
}
}
private func clampedTextViewHeight(fixedWidth: CGFloat) -> CGFloat {
let contentSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
return CGFloatClamp(contentSize.height, kMinTextViewHeight, maxTextViewHeight)
}
}

View file

@ -7,13 +7,9 @@ import UIKit
import AVFoundation import AVFoundation
protocol AttachmentPrepViewControllerDelegate: class { protocol AttachmentPrepViewControllerDelegate: class {
func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem)
func prepViewControllerUpdateNavigationBar() func prepViewControllerUpdateNavigationBar()
func prepViewControllerUpdateControls() func prepViewControllerUpdateControls()
func prepViewControllerAttachmentCount() -> Int
} }
// MARK: - // MARK: -
@ -42,13 +38,6 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
private(set) var playVideoButton: UIView? private(set) var playVideoButton: UIView?
private var imageEditorView: ImageEditorView? private var imageEditorView: ImageEditorView?
public var isShowingCaptionView = false {
didSet {
prepDelegate?.prepViewControllerUpdateNavigationBar()
prepDelegate?.prepViewControllerUpdateControls()
}
}
public var shouldHideControls: Bool { public var shouldHideControls: Bool {
guard let imageEditorView = imageEditorView else { guard let imageEditorView = imageEditorView else {
return false return false
@ -189,8 +178,6 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
playButton.autoCenterInSuperview() playButton.autoCenterInSuperview()
} }
// Caption
view.addSubview(touchInterceptorView) view.addSubview(touchInterceptorView)
touchInterceptorView.autoPinEdgesToSuperviewEdges() touchInterceptorView.autoPinEdgesToSuperviewEdges()
touchInterceptorView.isHidden = true touchInterceptorView.isHidden = true
@ -227,52 +214,10 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
// MARK: - Navigation Bar // MARK: - Navigation Bar
public func navigationBarItems() -> [UIView] { public func navigationBarItems() -> [UIView] {
let captionButton = navigationBarButton(imageName: "image_editor_caption",
selector: #selector(didTapCaption(sender:)))
guard let imageEditorView = imageEditorView else { guard let imageEditorView = imageEditorView else {
// Show the "add caption" button for non-image attachments if
// there is more than one attachment.
if let prepDelegate = prepDelegate,
prepDelegate.prepViewControllerAttachmentCount() > 1 {
return [captionButton]
}
return [] return []
} }
var navigationBarItems = imageEditorView.navigationBarItems() return imageEditorView.navigationBarItems()
// Show the caption UI if there's more than one attachment
// OR if the attachment already has a caption.
var shouldShowCaptionUI = attachmentCount() > 0
if let captionText = attachmentItem.captionText, captionText.count > 0 {
shouldShowCaptionUI = true
}
if shouldShowCaptionUI {
navigationBarItems.append(captionButton)
}
return navigationBarItems
}
private func attachmentCount() -> Int {
guard let prepDelegate = prepDelegate else {
owsFailDebug("Missing prepDelegate.")
return 0
}
return prepDelegate.prepViewControllerAttachmentCount()
}
@objc func didTapCaption(sender: UIButton) {
Logger.verbose("")
presentCaptionView()
}
private func presentCaptionView() {
let view = AttachmentCaptionViewController(delegate: self, attachmentItem: attachmentItem)
self.imageEditor(presentFullScreenView: view, isTransparent: true)
isShowingCaptionView = true
} }
// MARK: - Event Handlers // MARK: - Event Handlers
@ -435,22 +380,6 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
// MARK: - // MARK: -
extension AttachmentPrepViewController: AttachmentCaptionDelegate {
func captionView(_ captionView: AttachmentCaptionViewController, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) {
let attachment = attachmentItem.attachment
attachment.captionText = captionText
prepDelegate?.prepViewController(self, didUpdateCaptionForAttachmentItem: attachmentItem)
isShowingCaptionView = false
}
func captionViewDidCancel() {
isShowingCaptionView = false
}
}
// MARK: -
extension AttachmentPrepViewController: UIScrollViewDelegate { extension AttachmentPrepViewController: UIScrollViewDelegate {
public func viewForZooming(in scrollView: UIScrollView) -> UIView? { public func viewForZooming(in scrollView: UIScrollView) -> UIView? {