Show approval/caption view in app.

ApprovalView/Captioning is shown for:
- Images/Videos from Library
- Images/Video from Camera
- Document Picker
- GIFs

Voice notes are intentionally not captionable.

Also, in main app, hide status bar when ApprovalView is presented

// FREEBIE
This commit is contained in:
Michael Kirk 2018-01-16 17:55:53 -05:00
parent 58558b36de
commit 5dde17d939
8 changed files with 37 additions and 186 deletions

View File

@ -22,10 +22,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha;
#pragma mark - Attachment Approval
- (void)didApproveAttachment:(SignalAttachment *)attachment;
@end
#pragma mark -
@ -60,12 +56,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)cancelVoiceMemoIfNecessary;
#pragma mark - Attachment Approval
- (void)showApprovalUIForAttachment:(SignalAttachment *)attachment;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END

View File

@ -44,12 +44,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
@property (nonatomic) BOOL isRecordingVoiceMemo;
@property (nonatomic) CGPoint voiceMemoGestureStartLocation;
#pragma mark - Attachment Approval
@property (nonatomic, nullable) MediaMessageView *attachmentView;
@property (nonatomic, nullable) UIView *cancelAttachmentWrapper;
@property (nonatomic, nullable) SignalAttachment *attachmentToApprove;
@end
#pragma mark -
@ -264,58 +258,9 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
self.textViewHeight = textViewHeight;
self.toolbarHeight = textViewHeight + textViewVInset * 2;
if (self.attachmentToApprove) {
OWSAssert(self.attachmentView);
self.leftButtonWrapper.hidden = YES;
self.inputTextView.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.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:textViewVInset],
[self.attachmentView autoPinBottomToSuperviewWithMargin:textViewVInset],
[self.attachmentView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:contentHInset],
[self.attachmentView autoSetDimension:ALDimensionHeight toSize:150.f],
[self.rightButtonWrapper autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:self.attachmentView],
[self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeRight],
[self.rightButtonWrapper autoPinEdgeToSuperviewEdge:ALEdgeTop],
[self.rightButtonWrapper autoPinBottomToSuperviewWithMargin:0],
[rightButton autoSetDimension:ALDimensionHeight toSize:kMinContentHeight],
[rightButton autoPinLeadingToSuperviewWithMargin:contentHSpacing],
[rightButton autoPinTrailingToSuperviewWithMargin:contentHInset],
[rightButton autoPinEdgeToSuperviewEdge:ALEdgeBottom],
];
[self setNeedsLayout];
[self layoutIfNeeded];
// Ensure the keyboard is dismissed.
//
// NOTE: We need to do this _last_ or the layout changes in the input toolbar
// will be inadvertently animated.
[self.inputTextView resignFirstResponder];
return;
}
self.leftButtonWrapper.hidden = NO;
self.inputTextView.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);
@ -382,7 +327,7 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
- (void)ensureShouldShowVoiceMemoButton
{
self.shouldShowVoiceMemoButton = (self.attachmentToApprove == nil && self.inputTextView.trimmedText.length < 1);
self.shouldShowVoiceMemoButton = self.inputTextView.trimmedText.length < 1;
}
- (void)handleLongPress:(UIGestureRecognizer *)sender
@ -680,11 +625,7 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
{
OWSAssert(self.inputToolbarDelegate);
if (self.attachmentToApprove) {
[self attachmentApprovalSendPressed];
} else {
[self.inputToolbarDelegate sendButtonPressed];
}
[self.inputToolbarDelegate sendButtonPressed];
}
- (void)attachmentButtonPressed
@ -750,105 +691,6 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5;
}
}
#pragma mark - Attachment Approval
- (void)showApprovalUIForAttachment:(SignalAttachment *)attachment
{
OWSAssert(attachment);
self.attachmentToApprove = attachment;
MediaMessageView *attachmentView =
[[MediaMessageView alloc] initWithAttachment:attachment mode:MediaMessageViewModeSmall];
self.attachmentView = attachmentView;
[self.contentView addSubview:attachmentView];
UIView *cancelAttachmentWrapper = [UIView containerView];
self.cancelAttachmentWrapper = cancelAttachmentWrapper;
[cancelAttachmentWrapper
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
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.
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 {
cancelButtonReferenceView = self.attachmentView;
}
[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);
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];
[cancelAttachmentWrapper 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];
[self ensureShouldShowVoiceMemoButton];
}
- (void)cancelAttachmentWrapperTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self attachmentApprovalCancelPressed];
}
}
- (void)attachmentApprovalCancelPressed
{
self.attachmentToApprove = nil;
[self ensureContentConstraints];
[self ensureShouldShowVoiceMemoButton];
}
- (void)attachmentApprovalSendPressed
{
SignalAttachment *attachment = self.attachmentToApprove;
self.attachmentToApprove = nil;
if (attachment) {
[self.inputToolbarDelegate didApproveAttachment:attachment];
}
[self ensureContentConstraints];
[self ensureShouldShowVoiceMemoButton];
}
- (void)viewWillAppear:(BOOL)animated
{
[self.attachmentView viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self.attachmentView viewWillDisappear:animated];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -123,7 +123,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
#pragma mark -
@interface ConversationViewController () <AVAudioPlayerDelegate,
@interface ConversationViewController () <AttachmentApprovalViewControllerDelegate,
AVAudioPlayerDelegate,
ContactsViewHelperDelegate,
ContactEditingDelegate,
CNContactViewControllerDelegate,
@ -607,8 +608,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
// or on another device.
[self hideInputIfNeeded];
[self.inputToolbar viewWillAppear:animated];
self.isViewVisible = YES;
// We should have already requested contact access at this point, so this should be a no-op
@ -1035,7 +1034,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[super viewWillDisappear:animated];
self.isViewCompletelyAppeared = NO;
[self.inputToolbar viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
@ -2562,13 +2560,12 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
if ([mediaType isEqualToString:(__bridge NSString *)kUTTypeMovie]) {
// Video picked from library or captured with camera
BOOL isFromCamera = picker.sourceType == UIImagePickerControllerSourceTypeCamera;
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
[self dismissViewControllerAnimated:YES
completion:^{
[self sendQualityAdjustedAttachmentForVideo:videoURL
filename:filename
skipApprovalDialog:isFromCamera];
skipApprovalDialog:NO];
}];
} else if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
// Static Image captured from camera
@ -2594,7 +2591,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
[self showErrorAlertForAttachment:attachment];
failedToPickAttachment(nil);
} else {
[self tryToSendAttachmentIfApproved:attachment skipApprovalDialog:YES];
[self tryToSendAttachmentIfApproved:attachment skipApprovalDialog:NO];
}
} else {
failedToPickAttachment(nil);
@ -3637,7 +3634,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
} else if (skipApprovalDialog) {
[self sendMessageAttachment:attachment];
} else {
[self.inputToolbar showApprovalUIForAttachment:attachment];
AttachmentApprovalViewController *approvalVC = [[AttachmentApprovalViewController alloc] initWithAttachment:attachment delegate:self];
[self presentViewController:approvalVC animated:YES completion:nil];
}
});
}
@ -3731,11 +3729,15 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
}
}
- (void)didApproveAttachment:(SignalAttachment *)attachment
- (void)attachmentApproval:(AttachmentApprovalViewController *)attachmentApproval didApproveAttachment:(SignalAttachment * _Nonnull)attachment
{
OWSAssert(attachment);
[self sendMessageAttachment:attachment];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)attachmentApproval:(AttachmentApprovalViewController *)attachmentApproval didCancelAttachment:(SignalAttachment * _Nonnull)attachment
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)showErrorAlertForAttachment:(SignalAttachment *_Nullable)attachment

View File

@ -372,11 +372,12 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
owsFail("\(strongSelf.TAG) couldn't load asset.")
return
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: asset.rendition.utiType)
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: asset.rendition.utiType, imageQuality: .original)
strongSelf.delegate?.gifPickerDidSelect(attachment: attachment)
strongSelf.dismiss(animated: true, completion: nil)
strongSelf.dismiss(animated: true) {
// Delegate presents view controllers, so it's important that *this* controller be dismissed before that occurs.
strongSelf.delegate?.gifPickerDidSelect(attachment: attachment)
}
}.catch { [weak self] error in
guard let strongSelf = self else {
Logger.info("\(GifPickerViewController.TAG) ignoring failure, since VC was dismissed before fetching finished.")

View File

@ -120,6 +120,11 @@ NS_ASSUME_NONNULL_BEGIN
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle];
}
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated
{
[[UIApplication sharedApplication] setStatusBarHidden:isHidden animated:isAnimated];
}
- (BOOL)isInBackground
{
return [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;

View File

@ -78,6 +78,8 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool
Logger.debug("\(logTag) in \(#function)")
super.viewWillAppear(animated)
CurrentAppContext().setStatusBarHidden(true, animated: animated)
mediaMessageView.viewWillAppear(animated)
}
@ -91,6 +93,10 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool
super.viewWillDisappear(animated)
mediaMessageView.viewWillDisappear(animated)
// Since this VC is being dismissed, the "show status bar" animation would feel like
// it's occuring on the presenting view controller - it's better not to animate at all.
CurrentAppContext().setStatusBarHidden(false, animated: false)
}
// MARK: - Create Views

View File

@ -55,8 +55,8 @@ typedef void (^BackgroundTaskExpirationHandler)(void);
// Should only be called if isMainApp is YES.
- (void)setMainAppBadgeNumber:(NSInteger)value;
- (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle;
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated;
// Returns the VC that should be used to present alerts, modals, etc.
- (nullable UIViewController *)frontmostViewController;

View File

@ -128,6 +128,11 @@ NS_ASSUME_NONNULL_BEGIN
DDLogInfo(@"Ignoring request to set status bar style since we're in an app extension");
}
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated
{
DDLogInfo(@"Ignoring request to show/hide status bar style since we're in an app extension");
}
- (BOOL)isInBackground
{
return self.isSAEInBackground;