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:
parent
58558b36de
commit
5dde17d939
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue