From d04f9111db13247d2fcfaa9bac0b35b25cf234a9 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 23 Oct 2017 10:18:36 -0400 Subject: [PATCH 1/5] Rework attachment approval UI. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 +- .../cancel-cross-white.imageset/Contents.json | 23 +++ .../cancel-cross-white@1x.png | Bin 0 -> 1385 bytes .../cancel-cross-white@2x.png | Bin 0 -> 1582 bytes .../cancel-cross-white@3x.png | Bin 0 -> 1783 bytes Signal/src/Models/SignalAttachment.swift | 46 ++++- .../ConversationInputToolbar.m | 175 ++++++++++-------- .../src/ViewControllers/HomeViewController.m | 5 + .../ViewControllers/MediaMessageView.swift | 108 +++++++++-- .../Messages/Interactions/TSOutgoingMessage.m | 3 + SignalServiceKit/src/Util/DataSource.m | 1 + 11 files changed, 264 insertions(+), 103 deletions(-) create mode 100644 Signal/Images.xcassets/cancel-cross-white.imageset/Contents.json create mode 100644 Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@1x.png create mode 100644 Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@2x.png create mode 100644 Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@3x.png diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 34f3e1ede..e12e9fd99 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -2561,11 +2561,7 @@ "DEBUG=1", "$(inherited)", ); - "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( - "DEBUG=1", - "$(inherited)", - "SSK_BUILDING_FOR_TESTS=1", - ); + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1"; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/Signal/Images.xcassets/cancel-cross-white.imageset/Contents.json b/Signal/Images.xcassets/cancel-cross-white.imageset/Contents.json new file mode 100644 index 000000000..314f0d128 --- /dev/null +++ b/Signal/Images.xcassets/cancel-cross-white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "cancel-cross-white@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "cancel-cross-white@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "cancel-cross-white@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@1x.png b/Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c60575b5078f1f74f4ab46c2f3dd5f6452e6218 GIT binary patch literal 1385 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}oCO|{#S9GG z!XV7ZFl&wk0|SdvW=KRygs+cPa(=E}VoH8es$NBI0Z=sqgH44MkeQoWlBiITo0C^; zRbi_HR$&EXgM{^!6u?SKvTcwsYk*NEcCio^nlW#B-B_{|37a;u=! z;{2RaP!NRXWtP|(*?>KSE{q5fh%V>++=8Oi;$omSJ5#6@WHEI05eRGS%wcvQ3!-cA zFUkb^G!f)3J42`i$YSW~Be7Y4EQq856!caBnH8xy5iXg)00sNP5a=qb66hM8^K`Ye5o1R}Ho%5|=ed5=a_TlC6qMld@8iOORp<7-!(L@06IXk0flPk5#n| zFeO;|=BH$)Rk|dWq}mx77@F%E8t58Yh8P-H0g;8VjXs(hBo}~WoQqNuOY)0C^7C`- zz`n=@aTSd9%pp3F<)K<_^g+1@DOW+VELaqnXYIIvI$`F+@W& zccL{Hlc7MHua^kWRgEeUe}yiwEW!PebMFes{5WAh zd1lJz^`+-Tdkk|k9Fx}W-JEu?Zsrnx^9Ttp*$X!ml-|09C#;;kCRtLVigjZ23P;XA zX+h@G8#$Jo>^JqiQM2TlN51xc8%!U$L)+-zI>&*W<46bhZcWHU@24EzZW3hfV)m6g!pwye(d_pnAjS rZ|94g1>Gh%JZm!x6#l-DSry^7odplSvNn+hu+GdHy)QK2F?C$HG5 z!d3~a!V1U+3F|8Y3;nDA{o-C@9zzrKDK}xwt{K19`Se z86_nJR{Hwo<>h+i#(Mch>H3D2mX`VkM*2oZx=P7{9O-#x!EwNQn0$BtH5OLR{C|l=&b@WD^hbJTrz%~CF$Vr>(qFn(mMX_%6fm_FUyL${Jew}wOB(a}ZwD0jzF@gs2^3HlvV zUzZE{7p2XdDH~(+!T9;F|C>*r$y@vF{Itk_Hs`C4yiQ;_w@K&srmqvu$IQK@7Qw9l zV9w1srAa0!3Gy|}Te;_K`_>XxHSOR+zQ60(*C<&THpMM;JRCh|0f(ug{l#a8ZiWAo zYMtwJu`NzeD(Q*Q|1{QZx{HsOyR4W#k^AU8(ToW@rsN!PS;9Ka;l`R3#wE~idvcPyDKJyGoTo^QEZCsZrE zdlgeSS6j{JPv`%{M-yh>GM}C9uKE0qT}QEhnvdLj{wG=?dO?kMx=L(*%f)Y*a{Uv_ z<++nw-+NuNsuJ^C;YO s@p5Jd%a>FCQ#jlb7-Bx1$a#8+|FS6mx{S9S)u2M$)78&qol`;+0P_qV^8f$< literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@3x.png b/Signal/Images.xcassets/cancel-cross-white.imageset/cancel-cross-white@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..67c01b0e658c42c274537f7dbe273af81c423252 GIT binary patch literal 1783 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`a29w(7Bet# z3xhBt!>lsd^Q;1whpd3^o;3KxS@gNuokUZcbjY zRfVk*ScMgk4HDK@QUEI{$+lIB@C{IK&M!(;Fx4~BGf=YQQczH^DN0GR3UYCSY6tRc zl`=|73as??%gf94%8m8%i_-NCEiEne4UF`SjC6r2bc-wVN)jt{^NN)rhQQ2mNi9w; z$}A|!%+FH*nVXoDUs__Tqy(}E4j}F<$xK7olvfP(7SMzGAQ^o_Jp+Ag+EObracBd{ z;MA6q4EI$*X;BW?PbtazsX3{+sd**EKz)W-tpkQFTqBAjD-sJJmVpBi;x`+R%dLV^ zi}Q0zK|v6jmsw(GWCQjPx-cR{AiA9Ma|?=6i;IEU>`b9rkj2o|M26ltP#OUZS+C82q{-VvMg8>m}l*{fI4CM-;QfSg95O$V9N7!aSVyz zJUiXcTgFl3_+Ab%Pf>0~w$&Mlnh}yp98%W16B4-;Z+Qe|PxYR7Az&6dPxwqN~wTjcw+@_Toxo*bM%v-0=7`nq>#@2#z^{U3Sd-KUgDGoL&z z-i5^_5v#B6GcJ>@us`y|Qp%OLq~$wj^791`Wp_E$&Djv~=8oX}qsdaTzmJ`%U*v0Y z;hD=PU9O|?dTX|rx~jdX?M(R`kn?oSimIl4t*fGcs%TW5PB;>`M_opwJA6_vhpjhv=p5+C;#hUti@BzHJz)T}Ys^tF%Q#N$!OrnVOx zuKVZAlxLII-0POi=b@(ZMCPR0vfU!-oUo0Yqv4i;>WgkSDHxhYdu}iZny7Ug3Su8b0MY% za#L(>Y5E6E-nZ-5lwST*Tzg)ppP7Bns%%!EhJMvm*80R(<{Rdz{+t)JvSkI6MC+-) zXH*V`a$L3*;rqlj@zd=kv)kQ%&&@h`WZ%nHGrd#Coxd`;Ii6m)qNh?Z+_`?u=8EPN zVegiO1(l6K=X!p#Ewg``?zUiBk-u!G`wQ(&ZOwtsJV6Vd#BW&sN^$-A;xM6C_Lj}o z!t0hg)E1aYtdVTI^{&S_+gE78y1DFU*Yi3oHOR_o`L(F_(?nb6aKV6!23dIxv#!m% zJ@MFiRfjZ*y9!KM^Jmqq4PU@GDf$Kfr^N@8l*8w2IN;oObZP#bS?>+)_@asmh2APL v-gbL@FzGSt9=8kCU$|u#NG^7LfBHXT`Ngt*oK5|HpxVgO)z4*}Q$iB}G*Ep! literal 0 HcmV?d00001 diff --git a/Signal/src/Models/SignalAttachment.swift b/Signal/src/Models/SignalAttachment.swift index 2905ada67..7cac391d2 100644 --- a/Signal/src/Models/SignalAttachment.swift +++ b/Signal/src/Models/SignalAttachment.swift @@ -57,6 +57,7 @@ enum TSImageQuality { class SignalAttachment: NSObject { static let TAG = "[SignalAttachment]" + let TAG = "[SignalAttachment]" // MARK: Properties @@ -95,7 +96,8 @@ class SignalAttachment: NSObject { // To avoid redundant work of repeatedly compressing/uncompressing // images, we cache the UIImage associated with this attachment if // possible. - public var image: UIImage? + private var cachedImage: UIImage? + private var cachedVideoPreview: UIImage? private(set) public var isVoiceMessage = false @@ -152,6 +154,42 @@ class SignalAttachment: NSObject { return SignalAttachmentError.missingData.errorDescription } + public func image() -> UIImage? { + if let cachedImage = cachedImage { + return cachedImage + } + guard let image = UIImage(data:dataSource.data()) else { + return nil + } + cachedImage = image + return image + } + + public func videoPreview() -> UIImage? { + if let cachedVideoPreview = cachedVideoPreview { + return cachedVideoPreview + } + + guard let mediaUrl = dataUrl else { + return nil + } + + do { + let asset = AVURLAsset(url:mediaUrl) + let generator = AVAssetImageGenerator(asset: asset) + generator.appliesPreferredTrackTransform = true + let cgImage = try generator.copyCGImage(at: CMTimeMake(0, 1), actualTime: nil) + let image = UIImage(cgImage: cgImage) + + cachedVideoPreview = image + return image + + } catch let error { + Logger.verbose("\(TAG) Could not generate video thumbnail: \(error.localizedDescription)") + return nil + } + } + // Returns the MIME type for this attachment or nil if no MIME type // can be identified. var mimeType: String { @@ -454,7 +492,7 @@ class SignalAttachment: NSObject { attachment.error = .couldNotParseImage return attachment } - attachment.image = image + attachment.cachedImage = image if isInputImageValidOutputImage(image: image, dataSource: dataSource, dataUTI: dataUTI) { Logger.verbose("\(TAG) Sending raw \(attachment.mimeType)") @@ -513,7 +551,7 @@ class SignalAttachment: NSObject { let dataSource = DataSourceValue.emptyDataSource() dataSource.sourceFilename = filename let attachment = SignalAttachment(dataSource : dataSource, dataUTI: dataUTI) - attachment.image = image + attachment.cachedImage = image Logger.verbose("\(TAG) Writing \(attachment.mimeType) as image/jpeg") return compressImageAsJPEG(image : image, attachment : attachment, filename:filename) @@ -545,7 +583,7 @@ class SignalAttachment: NSObject { if UInt(jpgImageData.count) <= kMaxFileSizeImage { let recompressedAttachment = SignalAttachment(dataSource : dataSource, dataUTI: kUTTypeJPEG as String) - recompressedAttachment.image = dstImage + recompressedAttachment.cachedImage = dstImage return recompressedAttachment } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index caf420545..a40058dad 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -42,9 +42,9 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex #pragma mark - Attachment Approval -@property (nonatomic) UIView *attachmentApprovalView; @property (nonatomic, nullable) MediaMessageView *attachmentView; @property (nonatomic, nullable) SignalAttachment *attachmentToApprove; +@property (nonatomic) BOOL isLargeAttachment; @end @@ -132,10 +132,6 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex self.voiceMemoButton.imageView.tintColor = [UIColor ows_materialBlueColor]; [self.rightButtonWrapper addSubview:self.voiceMemoButton]; - _attachmentApprovalView = [UIView containerView]; - [self addSubview:self.attachmentApprovalView]; - [self.attachmentApprovalView autoPinToSuperviewEdges]; - // We want to be permissive about the voice message gesture, so we hang // the long press GR on the button's wrapper, not the button itself. UILongPressGestureRecognizer *longPressGestureRecognizer = @@ -215,12 +211,51 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex { [NSLayoutConstraint deactivateConstraints:self.contentContraints]; + const int textViewVInset = 5; + const int contentHInset = 6; + const int contentHSpacing = 6; + + // We want to grow the text input area to fit its content within reason. + const CGFloat kMinTextViewHeight = ceil(self.inputTextView.font.lineHeight + + self.inputTextView.textContainerInset.top + self.inputTextView.textContainerInset.bottom + + self.inputTextView.contentInset.top + self.inputTextView.contentInset.bottom); + const CGFloat kMaxTextViewHeight = 100.f; + const CGFloat textViewDesiredHeight = (self.inputTextView.contentSize.height + self.inputTextView.contentInset.top + + self.inputTextView.contentInset.bottom); + const CGFloat textViewHeight = ceil(MAX(kMinTextViewHeight, MIN(kMaxTextViewHeight, textViewDesiredHeight))); + const CGFloat kMinContentHeight = kMinTextViewHeight + textViewVInset * 2; + if (self.attachmentToApprove) { - self.contentView.hidden = YES; - self.attachmentApprovalView.hidden = NO; + OWSAssert(self.attachmentView); + + self.inputTextView.hidden = YES; + self.attachmentButton.hidden = YES; + self.voiceMemoButton.hidden = YES; + UIButton *rightButton = self.sendButton; + rightButton.enabled = YES; + rightButton.hidden = NO; + + [rightButton setContentHuggingHigh]; + [rightButton setCompressionResistanceHigh]; + [self.attachmentView setContentHuggingLow]; + + OWSAssert(rightButton.superview == self.rightButtonWrapper); self.contentContraints = @[ - [self.attachmentApprovalView autoSetDimension:ALDimensionHeight toSize:300.f], + [self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:textViewVInset], + [self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:textViewVInset], + [self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:contentHInset], + [self.attachmentView autoSetDimension:ALDimensionHeight toSize:(self.isLargeAttachment ? 300.f : 150.f)], + + [self.rightButtonWrapper autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:self.attachmentView], + [self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeRight], + [self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeTop], + [self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeBottom], + + [rightButton autoSetDimension:ALDimensionHeight toSize:kMinContentHeight], + [rightButton autoPinLeadingToSuperviewWithMargin:contentHSpacing], + [rightButton autoPinTrailingToSuperviewWithMargin:contentHInset], + [rightButton autoPinEdgeToSuperviewEdge:ALEdgeBottom], ]; [self setNeedsLayout]; @@ -235,26 +270,11 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex return; } - self.contentView.hidden = NO; - self.attachmentApprovalView.hidden = YES; + self.inputTextView.hidden = NO; + self.attachmentButton.hidden = NO; + self.voiceMemoButton.hidden = NO; + [self.attachmentView removeFromSuperview]; self.attachmentView = nil; - for (UIView *subview in self.attachmentApprovalView.subviews) { - [subview removeFromSuperview]; - } - - const int textViewVInset = 5; - const int contentHInset = 6; - const int contentHSpacing = 6; - - // We want to grow the text input area to fit its content within reason. - const CGFloat kMinTextViewHeight = ceil(self.inputTextView.font.lineHeight - + self.inputTextView.textContainerInset.top + self.inputTextView.textContainerInset.bottom - + self.inputTextView.contentInset.top + self.inputTextView.contentInset.bottom); - const CGFloat kMaxTextViewHeight = 100.f; - const CGFloat textViewDesiredHeight = (self.inputTextView.contentSize.height + self.inputTextView.contentInset.top - + self.inputTextView.contentInset.bottom); - const CGFloat textViewHeight = ceil(MAX(kMinTextViewHeight, MIN(kMaxTextViewHeight, textViewDesiredHeight))); - const CGFloat kMinContentHeight = kMinTextViewHeight + textViewVInset * 2; UIButton *leftButton = self.attachmentButton; UIButton *rightButton = (self.shouldShowVoiceMemoButton ? self.voiceMemoButton : self.sendButton); @@ -321,7 +341,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex - (void)ensureShouldShowVoiceMemoButton { - self.shouldShowVoiceMemoButton = self.inputTextView.trimmedText.length < 1; + self.shouldShowVoiceMemoButton = (self.attachmentToApprove != nil && self.inputTextView.trimmedText.length < 1); } - (void)handleLongPress:(UIGestureRecognizer *)sender @@ -619,7 +639,11 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex { OWSAssert(self.inputToolbarDelegate); - [self.inputToolbarDelegate sendButtonPressed]; + if (self.attachmentToApprove) { + [self attachmentApprovalSendPressed]; + } else { + [self.inputToolbarDelegate sendButtonPressed]; + } } - (void)attachmentButtonPressed @@ -696,61 +720,60 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex OWSAssert(attachment); self.attachmentToApprove = attachment; + self.isLargeAttachment = (attachment.isImage || attachment.isAnimatedImage); MediaMessageView *attachmentView = [[MediaMessageView alloc] initWithAttachment:attachment]; self.attachmentView = attachmentView; - [self.attachmentApprovalView addSubview:attachmentView]; - [attachmentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:10]; - [attachmentView autoPinWidthToSuperviewWithMargin:20]; - - UIView *buttonRow = [UIView containerView]; - [self.attachmentApprovalView addSubview:buttonRow]; - [buttonRow autoPinWidthToSuperviewWithMargin:20]; - [buttonRow autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:attachmentView withOffset:10]; - [buttonRow autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10]; - - // We use this invisible subview to ensure that the buttons are centered - // horizontally. - UIView *buttonSpacer = [UIView new]; - [buttonRow addSubview:buttonSpacer]; - // Vertical positioning of this view doesn't matter. - [buttonSpacer autoPinEdgeToSuperviewEdge:ALEdgeTop]; - [buttonSpacer autoSetDimension:ALDimensionWidth toSize:ScaleFromIPhone5To7Plus(20, 30)]; - [buttonSpacer autoSetDimension:ALDimensionHeight toSize:0]; - [buttonSpacer autoHCenterInSuperview]; - - UIView *cancelButton = [self createAttachmentApprovalButton:[CommonStrings cancelButton] - color:[UIColor ows_destructiveRedColor] - selector:@selector(attachmentApprovalCancelPressed)]; - [buttonRow addSubview:cancelButton]; - [cancelButton autoPinHeightToSuperview]; - [cancelButton autoPinEdge:ALEdgeRight toEdge:ALEdgeLeft ofView:buttonSpacer]; - - UIView *sendButton = - [self createAttachmentApprovalButton:NSLocalizedString( - @"ATTACHMENT_APPROVAL_SEND_BUTTON", comment - : @"Label for 'send' button in the 'attachment approval' dialog.") - color:[UIColor colorWithRGBHex:0x2ecc71] - selector:@selector(attachmentApprovalSendPressed)]; - [buttonRow addSubview:sendButton]; - [sendButton autoPinHeightToSuperview]; - [sendButton autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:buttonSpacer]; + [self.contentView addSubview:attachmentView]; + + UIView *cancelButtonWrapper = [UIView containerView]; + [cancelButtonWrapper + addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(cancelButtonWrapperTapped:)]]; + UIView *_Nullable cancelButtonSuperview = [self.attachmentView contentView]; + if (cancelButtonSuperview) { + cancelButtonSuperview.layer.borderColor = self.inputTextView.layer.borderColor; + cancelButtonSuperview.layer.borderWidth = self.inputTextView.layer.borderWidth; + cancelButtonSuperview.layer.cornerRadius = self.inputTextView.layer.cornerRadius; + cancelButtonSuperview.clipsToBounds = YES; + } else { + cancelButtonSuperview = self.attachmentView; + } + [cancelButtonSuperview addSubview:cancelButtonWrapper]; + [cancelButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeTop]; + [cancelButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeRight]; + + UIImage *cancelIcon = [UIImage imageNamed:@"cancel-cross-white"]; + OWSAssert(cancelIcon); + UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [cancelButton setImage:cancelIcon forState:UIControlStateNormal]; + [cancelButton setBackgroundColor:[UIColor ows_materialBlueColor]]; + OWSAssert(cancelIcon.size.width == cancelIcon.size.height); + CGFloat cancelIconSize = MIN(cancelIcon.size.width, cancelIcon.size.height); + CGFloat cancelIconInset = round(cancelIconSize * 0.35f); + [cancelButton + setContentEdgeInsets:UIEdgeInsetsMake(cancelIconInset, cancelIconInset, cancelIconInset, cancelIconInset)]; + CGFloat cancelButtonRadius = cancelIconInset + cancelIconSize * 0.5f; + cancelButton.layer.cornerRadius = cancelButtonRadius; + CGFloat cancelButtonInset = 10.f; + [cancelButton addTarget:self + action:@selector(attachmentApprovalCancelPressed) + forControlEvents:UIControlEventTouchUpInside]; + [cancelButtonWrapper addSubview:cancelButton]; + [cancelButton autoPinWidthToSuperviewWithMargin:cancelButtonInset]; + [cancelButton autoPinHeightToSuperviewWithMargin:cancelButtonInset]; + CGFloat cancelButtonSize = cancelIconSize + 2 * cancelIconInset; + [cancelButton autoSetDimension:ALDimensionWidth toSize:cancelButtonSize]; + [cancelButton autoSetDimension:ALDimensionHeight toSize:cancelButtonSize]; [self ensureContentConstraints]; } -- (UIView *)createAttachmentApprovalButton:(NSString *)title color:(UIColor *)color selector:(SEL)selector +- (void)cancelButtonWrapperTapped:(UIGestureRecognizer *)sender { - const CGFloat buttonWidth = ScaleFromIPhone5To7Plus(110, 140); - const CGFloat buttonHeight = ScaleFromIPhone5To7Plus(35, 45); - - return [OWSFlatButton buttonWithTitle:title - titleColor:[UIColor whiteColor] - backgroundColor:color - width:buttonWidth - height:buttonHeight - target:self - selector:selector]; + if (sender.state == UIGestureRecognizerStateRecognized) { + [self attachmentApprovalCancelPressed]; + } } - (void)attachmentApprovalCancelPressed diff --git a/Signal/src/ViewControllers/HomeViewController.m b/Signal/src/ViewControllers/HomeViewController.m index 901294943..0058049ab 100644 --- a/Signal/src/ViewControllers/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeViewController.m @@ -277,6 +277,11 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; } [self updateBarButtonItems]; + + dispatch_async(dispatch_get_main_queue(), ^{ + TSThread *thread = [self threadForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + [self presentThread:thread keyboardOnViewAppearing:NO callOnViewAppearing:NO]; + }); } - (void)updateBarButtonItems diff --git a/Signal/src/ViewControllers/MediaMessageView.swift b/Signal/src/ViewControllers/MediaMessageView.swift index 4cc2fdc40..6ca66713b 100644 --- a/Signal/src/ViewControllers/MediaMessageView.swift +++ b/Signal/src/ViewControllers/MediaMessageView.swift @@ -30,6 +30,8 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { var audioProgressSeconds: CGFloat = 0 var audioDurationSeconds: CGFloat = 0 + var contentView: UIView? + // MARK: Initializers @available(*, unavailable, message:"use attachment: constructor instead.") @@ -49,6 +51,10 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { createViews() } + deinit { + NotificationCenter.default.removeObserver(self) + } + // MARK: View Lifecycle func viewWillAppear(_ animated: Bool) { @@ -154,19 +160,31 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { createGenericPreview() return } + guard image.size.width > 0 && image.size.height > 0 else { + createGenericPreview() + return + } let animatedImageView = YYAnimatedImageView() animatedImageView.image = image - animatedImageView.contentMode = .scaleAspectFit - self.addSubview(animatedImageView) - animatedImageView.autoPinToSuperviewEdges() + let aspectRatio = image.size.width / image.size.height + addSubviewWithScaleAspectFitLayout(view:animatedImageView, aspectRatio:aspectRatio) + contentView = animatedImageView + } + + private func addSubviewWithScaleAspectFitLayout(view: UIView, aspectRatio: CGFloat) { + self.addSubview(view) + view.autoCenterInSuperview() + view.autoPin(toAspectRatio:aspectRatio) + view.autoMatch(.width, to: .width, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual) + view.autoMatch(.height, to: .height, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual) } private func createImagePreview() { - var image = attachment.image - if image == nil { - image = UIImage(data: attachment.data) + guard let image = attachment.image() else { + createGenericPreview() + return } - guard image != nil else { + guard image.size.width > 0 && image.size.height > 0 else { createGenericPreview() return } @@ -174,28 +192,35 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { let imageView = UIImageView(image: image) imageView.layer.minificationFilter = kCAFilterTrilinear imageView.layer.magnificationFilter = kCAFilterTrilinear - imageView.contentMode = .scaleAspectFit - self.addSubview(imageView) - imageView.autoPinToSuperviewEdges() + let aspectRatio = image.size.width / image.size.height + addSubviewWithScaleAspectFitLayout(view:imageView, aspectRatio:aspectRatio) + contentView = imageView } private func createVideoPreview() { - guard let dataUrl = attachment.dataUrl else { + guard let image = attachment.videoPreview() else { createGenericPreview() return } - guard let videoPlayer = MPMoviePlayerController(contentURL: dataUrl) else { + guard image.size.width > 0 && image.size.height > 0 else { createGenericPreview() return } - videoPlayer.prepareToPlay() - videoPlayer.controlStyle = .default - videoPlayer.shouldAutoplay = false + let imageView = UIImageView(image: image) + imageView.layer.minificationFilter = kCAFilterTrilinear + imageView.layer.magnificationFilter = kCAFilterTrilinear + let aspectRatio = image.size.width / image.size.height + addSubviewWithScaleAspectFitLayout(view:imageView, aspectRatio:aspectRatio) + contentView = imageView - self.addSubview(videoPlayer.view) - self.videoPlayer = videoPlayer - videoPlayer.view.autoPinToSuperviewEdges() + let videoPlayIcon = UIImage(named:"play_button") + let videoPlayButton = UIImageView(image:videoPlayIcon) + imageView.addSubview(videoPlayButton) + videoPlayButton.autoCenterInSuperview() + + imageView.isUserInteractionEnabled = true + imageView.addGestureRecognizer(UITapGestureRecognizer(target:self, action:#selector(videoTapped))) } private func createGenericPreview() { @@ -365,4 +390,51 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { audioPlayButton?.setImage(image, for: .normal) audioPlayButton?.imageView?.tintColor = UIColor.ows_materialBlue() } + + // MARK: - Video Playback + + func videoTapped(sender: UIGestureRecognizer) { + guard let dataUrl = attachment.dataUrl else { + return + } + guard sender.state == .recognized else { + return + } + guard let videoPlayer = MPMoviePlayerController(contentURL: dataUrl) else { + return + } + videoPlayer.prepareToPlay() + + NotificationCenter.default.addObserver(forName: .MPMoviePlayerWillExitFullscreen, object: nil, queue: nil) { [weak self] _ in + self?.moviePlayerWillExitFullscreen() + } + NotificationCenter.default.addObserver(forName: .MPMoviePlayerDidExitFullscreen, object: nil, queue: nil) { [weak self] _ in + self?.moviePlayerDidExitFullscreen() + } + + videoPlayer.controlStyle = .default + videoPlayer.shouldAutoplay = true + + self.addSubview(videoPlayer.view) + videoPlayer.view.frame = self.bounds + self.videoPlayer = videoPlayer + videoPlayer.view.autoPinToSuperviewEdges() + ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(true) + videoPlayer.setFullscreen(true, animated:false) + } + + private func moviePlayerWillExitFullscreen() { + clearVideoPlayer() + } + + private func moviePlayerDidExitFullscreen() { + clearVideoPlayer() + } + + private func clearVideoPlayer() { + videoPlayer?.stop() + videoPlayer?.view.removeFromSuperview() + videoPlayer = nil + ViewControllerUtils.setAudioIgnoresHardwareMuteSwitch(false) + } } diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 936ac699a..46ea0aaea 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -200,6 +200,9 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec - (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { + DDLogError(@"%@ %zd %@", self.tag, self.attachmentIds.count, self.attachmentIds.firstObject); + [DDLog flushLog]; + if (!(self.groupMetaMessage == TSGroupMessageDeliver || self.groupMetaMessage == TSGroupMessageNone)) { DDLogDebug(@"%@ Skipping save for group meta message.", self.tag); return; diff --git a/SignalServiceKit/src/Util/DataSource.m b/SignalServiceKit/src/Util/DataSource.m index fa10e14e9..a22ada20a 100755 --- a/SignalServiceKit/src/Util/DataSource.m +++ b/SignalServiceKit/src/Util/DataSource.m @@ -297,6 +297,7 @@ NS_ASSUME_NONNULL_BEGIN DataSourcePath *instance = [DataSourcePath new]; instance.filePath = filePath; + OWSAssert(!instance.shouldDeleteOnDeallocation); return instance; } From cbb0030b1223f55fa028c436e1c4e3555ccf2740 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 23 Oct 2017 11:56:09 -0400 Subject: [PATCH 2/5] Rework attachment approval UI. // FREEBIE --- .../ConversationInputToolbar.m | 23 +++++++++++-------- .../ConversationViewController.m | 10 ++++---- .../GifPicker/GifPickerViewController.swift | 9 ++------ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index a40058dad..85d6277b3 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -730,18 +730,21 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex [cancelButtonWrapper addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cancelButtonWrapperTapped:)]]; - UIView *_Nullable cancelButtonSuperview = [self.attachmentView contentView]; - if (cancelButtonSuperview) { - cancelButtonSuperview.layer.borderColor = self.inputTextView.layer.borderColor; - cancelButtonSuperview.layer.borderWidth = self.inputTextView.layer.borderWidth; - cancelButtonSuperview.layer.cornerRadius = self.inputTextView.layer.cornerRadius; - cancelButtonSuperview.clipsToBounds = YES; + UIView *_Nullable attachmentContentView = [self.attachmentView contentView]; + // Place the cancel button inside the attachment view's content area, + // if possible. If not, just place it inside the attachment view. + UIView *cancelButtonReferenceView = attachmentContentView; + if (attachmentContentView) { + attachmentContentView.layer.borderColor = self.inputTextView.layer.borderColor; + attachmentContentView.layer.borderWidth = self.inputTextView.layer.borderWidth; + attachmentContentView.layer.cornerRadius = self.inputTextView.layer.cornerRadius; + attachmentContentView.clipsToBounds = YES; } else { - cancelButtonSuperview = self.attachmentView; + cancelButtonReferenceView = self.attachmentView; } - [cancelButtonSuperview addSubview:cancelButtonWrapper]; - [cancelButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeTop]; - [cancelButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeRight]; + [self.contentView addSubview:cancelButtonWrapper]; + [cancelButtonWrapper autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:cancelButtonReferenceView]; + [cancelButtonWrapper autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:cancelButtonReferenceView]; UIImage *cancelIcon = [UIImage imageNamed:@"cancel-cross-white"]; OWSAssert(cancelIcon); diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index bc8afeff2..00d7189dd 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2318,15 +2318,13 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { #pragma mark GifPickerViewControllerDelegate -- (void)gifPickerWillSend +- (void)gifPickerDidSelectWithAttachment:(SignalAttachment *)attachment { - [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; -} + OWSAssert(attachment); -- (void)gifPickerDidSendWithOutgoingMessage:(TSOutgoingMessage *)message -{ - [self messageWasSent:message]; + [self tryToSendAttachmentIfApproved:attachment]; + [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; [self ensureDynamicInteractions]; } diff --git a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift index 07da5294d..0ce0b91a5 100644 --- a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift +++ b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift @@ -6,8 +6,7 @@ import Foundation @objc protocol GifPickerViewControllerDelegate: class { - func gifPickerWillSend() - func gifPickerDidSend(outgoingMessage: TSOutgoingMessage) + func gifPickerDidSelect(attachment: SignalAttachment) } class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegate, GifPickerLayoutDelegate { @@ -359,11 +358,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect } let attachment = SignalAttachment(dataSource: dataSource, dataUTI: asset.rendition.utiType) - strongSelf.delegate?.gifPickerWillSend() - - let outgoingMessage = ThreadUtil.sendMessage(with: attachment, in: strongSelf.thread, messageSender: strongSelf.messageSender) - - strongSelf.delegate?.gifPickerDidSend(outgoingMessage: outgoingMessage) + strongSelf.delegate?.gifPickerDidSelect(attachment: attachment) strongSelf.dismiss(animated: true, completion: nil) }.catch { [weak self] error in From 2fa3cf1bc6de3e27c6d00779b4c2f4ded744566e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 23 Oct 2017 12:36:58 -0400 Subject: [PATCH 3/5] Rework attachment approval UI. // FREEBIE --- .../ConversationInputToolbar.m | 28 ++++++++++--------- .../src/ViewControllers/HomeViewController.m | 6 ---- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index 85d6277b3..bde8022fc 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -43,8 +43,8 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex #pragma mark - Attachment Approval @property (nonatomic, nullable) MediaMessageView *attachmentView; +@property (nonatomic, nullable) UIView *cancelAttachmentWrapper; @property (nonatomic, nullable) SignalAttachment *attachmentToApprove; -@property (nonatomic) BOOL isLargeAttachment; @end @@ -228,8 +228,8 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex if (self.attachmentToApprove) { OWSAssert(self.attachmentView); + self.leftButtonWrapper.hidden = YES; self.inputTextView.hidden = YES; - self.attachmentButton.hidden = YES; self.voiceMemoButton.hidden = YES; UIButton *rightButton = self.sendButton; rightButton.enabled = YES; @@ -245,7 +245,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex [self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:textViewVInset], [self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:textViewVInset], [self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:contentHInset], - [self.attachmentView autoSetDimension:ALDimensionHeight toSize:(self.isLargeAttachment ? 300.f : 150.f)], + [self.attachmentView autoSetDimension:ALDimensionHeight toSize:300.f], [self.rightButtonWrapper autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:self.attachmentView], [self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeRight], @@ -270,11 +270,13 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex return; } + self.leftButtonWrapper.hidden = NO; self.inputTextView.hidden = NO; - self.attachmentButton.hidden = NO; self.voiceMemoButton.hidden = NO; [self.attachmentView removeFromSuperview]; self.attachmentView = nil; + [self.cancelAttachmentWrapper removeFromSuperview]; + self.cancelAttachmentWrapper = nil; UIButton *leftButton = self.attachmentButton; UIButton *rightButton = (self.shouldShowVoiceMemoButton ? self.voiceMemoButton : self.sendButton); @@ -720,16 +722,16 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex OWSAssert(attachment); self.attachmentToApprove = attachment; - self.isLargeAttachment = (attachment.isImage || attachment.isAnimatedImage); MediaMessageView *attachmentView = [[MediaMessageView alloc] initWithAttachment:attachment]; self.attachmentView = attachmentView; [self.contentView addSubview:attachmentView]; - UIView *cancelButtonWrapper = [UIView containerView]; - [cancelButtonWrapper + UIView *cancelAttachmentWrapper = [UIView containerView]; + self.cancelAttachmentWrapper = cancelAttachmentWrapper; + [cancelAttachmentWrapper addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(cancelButtonWrapperTapped:)]]; + action:@selector(cancelAttachmentWrapperTapped:)]]; UIView *_Nullable attachmentContentView = [self.attachmentView contentView]; // Place the cancel button inside the attachment view's content area, // if possible. If not, just place it inside the attachment view. @@ -742,9 +744,9 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex } else { cancelButtonReferenceView = self.attachmentView; } - [self.contentView addSubview:cancelButtonWrapper]; - [cancelButtonWrapper autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:cancelButtonReferenceView]; - [cancelButtonWrapper autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:cancelButtonReferenceView]; + [self.contentView addSubview:cancelAttachmentWrapper]; + [cancelAttachmentWrapper autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:cancelButtonReferenceView]; + [cancelAttachmentWrapper autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:cancelButtonReferenceView]; UIImage *cancelIcon = [UIImage imageNamed:@"cancel-cross-white"]; OWSAssert(cancelIcon); @@ -762,7 +764,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex [cancelButton addTarget:self action:@selector(attachmentApprovalCancelPressed) forControlEvents:UIControlEventTouchUpInside]; - [cancelButtonWrapper addSubview:cancelButton]; + [cancelAttachmentWrapper addSubview:cancelButton]; [cancelButton autoPinWidthToSuperviewWithMargin:cancelButtonInset]; [cancelButton autoPinHeightToSuperviewWithMargin:cancelButtonInset]; CGFloat cancelButtonSize = cancelIconSize + 2 * cancelIconInset; @@ -772,7 +774,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex [self ensureContentConstraints]; } -- (void)cancelButtonWrapperTapped:(UIGestureRecognizer *)sender +- (void)cancelAttachmentWrapperTapped:(UIGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateRecognized) { [self attachmentApprovalCancelPressed]; diff --git a/Signal/src/ViewControllers/HomeViewController.m b/Signal/src/ViewControllers/HomeViewController.m index 0058049ab..17955823f 100644 --- a/Signal/src/ViewControllers/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeViewController.m @@ -256,7 +256,6 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; // after mappings have been set up in `showInboxGrouping` [self tableViewSetUp]; - self.segmentedControl = [[UISegmentedControl alloc] initWithItems:@[ NSLocalizedString(@"WHISPER_NAV_BAR_TITLE", nil), NSLocalizedString(@"ARCHIVE_NAV_BAR_TITLE", nil) @@ -277,11 +276,6 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; } [self updateBarButtonItems]; - - dispatch_async(dispatch_get_main_queue(), ^{ - TSThread *thread = [self threadForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; - [self presentThread:thread keyboardOnViewAppearing:NO callOnViewAppearing:NO]; - }); } - (void)updateBarButtonItems From bf8d694eb4745337b3dfeafc881d5836c1468db8 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 23 Oct 2017 12:40:51 -0400 Subject: [PATCH 4/5] Rework attachment approval UI. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 +++++- .../src/Messages/Interactions/TSOutgoingMessage.m | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index e12e9fd99..34f3e1ede 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -2561,7 +2561,11 @@ "DEBUG=1", "$(inherited)", ); - "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1"; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + "DEBUG=1", + "$(inherited)", + "SSK_BUILDING_FOR_TESTS=1", + ); GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 46ea0aaea..936ac699a 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -200,9 +200,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec - (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { - DDLogError(@"%@ %zd %@", self.tag, self.attachmentIds.count, self.attachmentIds.firstObject); - [DDLog flushLog]; - if (!(self.groupMetaMessage == TSGroupMessageDeliver || self.groupMetaMessage == TSGroupMessageNone)) { DDLogDebug(@"%@ Skipping save for group meta message.", self.tag); return; From c1f35a0ea6bbbb90a647cb577c902b76f0141334 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 24 Oct 2017 09:41:03 -0400 Subject: [PATCH 5/5] Respond to CR. // FREEBIE --- .../ConversationView/ConversationInputToolbar.m | 3 ++- Signal/src/ViewControllers/MediaMessageView.swift | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index bde8022fc..a0a675f4d 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -4,6 +4,7 @@ #import "ConversationInputToolbar.h" #import "ConversationInputTextView.h" +#import "OWSMath.h" #import "Signal-Swift.h" #import "UIColor+OWS.h" #import "UIFont+OWS.h" @@ -222,7 +223,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex const CGFloat kMaxTextViewHeight = 100.f; const CGFloat textViewDesiredHeight = (self.inputTextView.contentSize.height + self.inputTextView.contentInset.top + self.inputTextView.contentInset.bottom); - const CGFloat textViewHeight = ceil(MAX(kMinTextViewHeight, MIN(kMaxTextViewHeight, textViewDesiredHeight))); + const CGFloat textViewHeight = ceil(Clamp(textViewDesiredHeight, kMinTextViewHeight, kMaxTextViewHeight)); const CGFloat kMinContentHeight = kMinTextViewHeight + textViewVInset * 2; if (self.attachmentToApprove) { diff --git a/Signal/src/ViewControllers/MediaMessageView.swift b/Signal/src/ViewControllers/MediaMessageView.swift index 6ca66713b..2449d6b00 100644 --- a/Signal/src/ViewControllers/MediaMessageView.swift +++ b/Signal/src/ViewControllers/MediaMessageView.swift @@ -173,6 +173,11 @@ class MediaMessageView: UIView, OWSAudioAttachmentPlayerDelegate { private func addSubviewWithScaleAspectFitLayout(view: UIView, aspectRatio: CGFloat) { self.addSubview(view) + // This emulates the behavior of contentMode = .scaleAspectFit using + // iOS auto layout constraints. + // + // This allows ConversationInputToolbar to place the "cancel" button + // in the upper-right hand corner of the preview content. view.autoCenterInSuperview() view.autoPin(toAspectRatio:aspectRatio) view.autoMatch(.width, to: .width, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual)