mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Interactive/Cancelable slide left for details
// FREEBIE
This commit is contained in:
parent
ac8d59bb7d
commit
d87f000051
|
@ -147,6 +147,8 @@
|
|||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; };
|
||||
4521C3C11F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; };
|
||||
4523149C1F7D7F81003A428C /* OWSMessagesBubbleImageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149B1F7D7F81003A428C /* OWSMessagesBubbleImageFactory.swift */; };
|
||||
4523149E1F7E916B003A428C /* SlideOffAnimatedTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */; };
|
||||
452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; };
|
||||
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
|
||||
452C46901E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
|
||||
452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */; };
|
||||
|
@ -615,6 +617,8 @@
|
|||
4520D8D41D417D8E00123472 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
|
||||
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldHelper.swift; sourceTree = "<group>"; };
|
||||
4523149B1F7D7F81003A428C /* OWSMessagesBubbleImageFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSMessagesBubbleImageFactory.swift; sourceTree = "<group>"; };
|
||||
4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SlideOffAnimatedTransition.swift; path = UserInterface/SlideOffAnimatedTransition.swift; sourceTree = "<group>"; };
|
||||
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionalPanGestureRecognizer.swift; sourceTree = "<group>"; };
|
||||
452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = "<group>"; };
|
||||
452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MesssagesBubblesSizeCalculatorTest.swift; path = Models/MesssagesBubblesSizeCalculatorTest.swift; sourceTree = "<group>"; };
|
||||
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1211,6 +1215,7 @@
|
|||
34B3F8331E8DF1700035BE1A /* ViewControllers */,
|
||||
76EB052B18170B33006006FC /* Views */,
|
||||
4523149B1F7D7F81003A428C /* OWSMessagesBubbleImageFactory.swift */,
|
||||
4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */,
|
||||
);
|
||||
name = UserInterface;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1519,6 +1524,7 @@
|
|||
45A6DAD51EBBF85500893231 /* ReminderView.swift */,
|
||||
450D19111F85236600970622 /* RemoteVideoView.h */,
|
||||
450D19121F85236600970622 /* RemoteVideoView.m */,
|
||||
4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */,
|
||||
);
|
||||
name = Views;
|
||||
path = views;
|
||||
|
@ -2283,6 +2289,7 @@
|
|||
76EB068618170B34006006FC /* ContactTableViewCell.m in Sources */,
|
||||
3497DBEF1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m in Sources */,
|
||||
34B3F8881E8DF1700035BE1A /* OversizeTextMessageViewController.swift in Sources */,
|
||||
452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */,
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
|
||||
34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */,
|
||||
34CA1C271F7156F300E51C51 /* MessageMetadataViewController.swift in Sources */,
|
||||
|
@ -2317,6 +2324,7 @@
|
|||
34B3F8911E8DF1710035BE1A /* ShowGroupMembersViewController.m in Sources */,
|
||||
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
|
||||
45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */,
|
||||
4523149E1F7E916B003A428C /* SlideOffAnimatedTransition.swift in Sources */,
|
||||
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */,
|
||||
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */,
|
||||
34B3F8711E8DF1700035BE1A /* AboutTableViewController.m in Sources */,
|
||||
|
|
51
Signal/src/UserInterface/SlideOffAnimatedTransition.swift
Normal file
51
Signal/src/UserInterface/SlideOffAnimatedTransition.swift
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc
|
||||
class SlideOffAnimatedTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
guard let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)?.view else {
|
||||
owsFail("No fromView")
|
||||
return
|
||||
}
|
||||
guard let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)?.view else {
|
||||
owsFail("No toView")
|
||||
return
|
||||
}
|
||||
|
||||
let width = containerView.frame.width
|
||||
let offsetLeft = fromView.frame.offsetBy(dx: -width, dy: 0)
|
||||
toView.frame = fromView.frame
|
||||
|
||||
fromView.layer.shadowRadius = 15.0
|
||||
fromView.layer.shadowOpacity = 1.0
|
||||
toView.layer.opacity = 0.9
|
||||
|
||||
containerView.insertSubview(toView, belowSubview: fromView)
|
||||
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay:0, options: .curveLinear, animations: {
|
||||
fromView.frame = offsetLeft
|
||||
|
||||
toView.layer.opacity = 1.0
|
||||
fromView.layer.shadowOpacity = 0.1
|
||||
}, completion: { _ in
|
||||
toView.layer.opacity = 1.0
|
||||
toView.layer.shadowOpacity = 0
|
||||
|
||||
fromView.layer.opacity = 1.0
|
||||
fromView.layer.shadowOpacity = 0
|
||||
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.3
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem
|
||||
attachmentPointer:(TSAttachmentPointer *)attachmentPointer;
|
||||
- (void)didTapFailedOutgoingMessage:(TSOutgoingMessage *)message;
|
||||
- (void)didPanWithGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
|
||||
viewItem:(ConversationViewItem *)conversationItem;
|
||||
|
||||
- (void)showMetadataViewForMessage:(TSMessage *)message;
|
||||
|
||||
|
|
|
@ -162,6 +162,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
UILongPressGestureRecognizer *longPress =
|
||||
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
|
||||
[self addGestureRecognizer:longPress];
|
||||
|
||||
PanDirectionGestureRecognizer *panGesture =
|
||||
[[PanDirectionGestureRecognizer alloc] initWithDirection:PanDirectionHorizontal
|
||||
target:self
|
||||
action:@selector(handlePanGesture:)];
|
||||
[self addGestureRecognizer:panGesture];
|
||||
}
|
||||
|
||||
+ (NSString *)cellReuseIdentifier
|
||||
|
@ -1029,6 +1035,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
}
|
||||
|
||||
- (void)handlePanGesture:(UIPanGestureRecognizer *)panRecognizer
|
||||
{
|
||||
OWSAssert(self.delegate);
|
||||
|
||||
[self.delegate didPanWithGestureRecognizer:panRecognizer viewItem:self.viewItem];
|
||||
}
|
||||
|
||||
#pragma mark - UIMenuController
|
||||
|
||||
- (void)showMenuController:(CGPoint)fromLocation
|
||||
|
|
|
@ -136,6 +136,10 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
ConversationInputToolbarDelegate,
|
||||
GifPickerViewControllerDelegate>
|
||||
|
||||
// Show message info animation
|
||||
@property (nullable, nonatomic) UIPercentDrivenInteractiveTransition *showMessageDetailsTransition;
|
||||
@property (nullable, nonatomic) UIPanGestureRecognizer *currentShowMessageDetailsPanGesture;
|
||||
|
||||
@property (nonatomic) TSThread *thread;
|
||||
@property (nonatomic) YapDatabaseConnection *editingDatabaseConnection;
|
||||
|
||||
|
@ -988,9 +992,14 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
self.isViewVisible = NO;
|
||||
|
||||
[self.inputToolbar viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewDidDisappear:animated];
|
||||
self.userHasScrolled = NO;
|
||||
self.isViewVisible = NO;
|
||||
|
||||
[self.audioAttachmentPlayer stop];
|
||||
self.audioAttachmentPlayer = nil;
|
||||
|
@ -1005,12 +1014,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
self.isUserScrolling = NO;
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewDidDisappear:animated];
|
||||
self.userHasScrolled = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Initiliazers
|
||||
|
||||
- (void)setNavigationTitle
|
||||
|
@ -4036,6 +4039,110 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - swipe to show message details
|
||||
|
||||
- (void)didPanWithGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
|
||||
viewItem:(ConversationViewItem *)conversationItem
|
||||
{
|
||||
self.currentShowMessageDetailsPanGesture = gestureRecognizer;
|
||||
|
||||
const CGFloat leftTranslation = -1 * [gestureRecognizer translationInView:self.view].x;
|
||||
const CGFloat percent = MAX(leftTranslation, 0) / self.view.frame.size.width;
|
||||
|
||||
switch (gestureRecognizer.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
TSInteraction *interaction = conversationItem.interaction;
|
||||
if ([interaction isKindOfClass:[TSIncomingMessage class]] ||
|
||||
[interaction isKindOfClass:[TSOutgoingMessage class]]) {
|
||||
|
||||
// Canary check in case we later have another reason to set navigationController.delegate - we don't
|
||||
// want to inadvertently clobber it here.
|
||||
OWSAssert(self.navigationController.delegate == nil) self.navigationController.delegate = self;
|
||||
TSMessage *message = (TSMessage *)interaction;
|
||||
MessageMetadataViewController *view = [[MessageMetadataViewController alloc] initWithMessage:message];
|
||||
[self.navigationController pushViewController:view animated:YES];
|
||||
} else {
|
||||
OWSFail(@"%@ Can't show message metadata for message of type: %@", self.tag, [interaction class]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
UIPercentDrivenInteractiveTransition *transition = self.showMessageDetailsTransition;
|
||||
if (!transition) {
|
||||
DDLogVerbose(@"%@ transition not set up yet", self.tag);
|
||||
return;
|
||||
}
|
||||
[transition updateInteractiveTransition:percent];
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded: {
|
||||
const CGFloat velocity = [gestureRecognizer velocityInView:self.view].x;
|
||||
|
||||
UIPercentDrivenInteractiveTransition *transition = self.showMessageDetailsTransition;
|
||||
if (!transition) {
|
||||
DDLogVerbose(@"%@ transition not set up yet", self.tag);
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the transition if moved sufficiently far or fast
|
||||
// Note this is trickier for incoming, since you are already on the left, and have less space.
|
||||
if (percent > 0.3 || velocity < -800) {
|
||||
[transition finishInteractiveTransition];
|
||||
} else {
|
||||
[transition cancelInteractiveTransition];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateCancelled:
|
||||
case UIGestureRecognizerStateFailed: {
|
||||
UIPercentDrivenInteractiveTransition *transition = self.showMessageDetailsTransition;
|
||||
if (!transition) {
|
||||
DDLogVerbose(@"%@ transition not set up yet", self.tag);
|
||||
return;
|
||||
}
|
||||
|
||||
[transition cancelInteractiveTransition];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable id<UIViewControllerAnimatedTransitioning>)navigationController:
|
||||
(UINavigationController *)navigationController
|
||||
animationControllerForOperation:(UINavigationControllerOperation)operation
|
||||
fromViewController:(UIViewController *)fromVC
|
||||
toViewController:(UIViewController *)toVC
|
||||
{
|
||||
return [SlideOffAnimatedTransition new];
|
||||
}
|
||||
|
||||
- (nullable id<UIViewControllerInteractiveTransitioning>)
|
||||
navigationController:(UINavigationController *)navigationController
|
||||
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
|
||||
{
|
||||
// We needed to be the navigation controller delegate to specify the interactive "slide left for message details"
|
||||
// animation But we may not want to be the navigation controller delegate permanently.
|
||||
self.navigationController.delegate = nil;
|
||||
|
||||
DDLogInfo(@"%@ >>>> in %s", self.tag, __PRETTY_FUNCTION__);
|
||||
UIPanGestureRecognizer *recognizer = self.currentShowMessageDetailsPanGesture;
|
||||
if (recognizer == nil) {
|
||||
OWSFail(@"currentShowMessageDetailsPanGesture was unexpectedly nil");
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (recognizer.state == UIGestureRecognizerStateBegan) {
|
||||
self.showMessageDetailsTransition = [UIPercentDrivenInteractiveTransition new];
|
||||
self.showMessageDetailsTransition.completionCurve = UIViewAnimationCurveEaseOut;
|
||||
} else {
|
||||
self.showMessageDetailsTransition = nil;
|
||||
}
|
||||
|
||||
return self.showMessageDetailsTransition;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegate
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView
|
||||
|
|
38
Signal/src/views/DirectionalPanGestureRecognizer.swift
Normal file
38
Signal/src/views/DirectionalPanGestureRecognizer.swift
Normal file
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit.UIGestureRecognizerSubclass
|
||||
|
||||
@objc
|
||||
enum PanDirection: Int {
|
||||
case vertical
|
||||
case horizontal
|
||||
}
|
||||
|
||||
@objc
|
||||
class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
|
||||
|
||||
let direction: PanDirection
|
||||
|
||||
init(direction: PanDirection, target: AnyObject, action: Selector) {
|
||||
self.direction = direction
|
||||
super.init(target: target, action: action)
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if state == .began {
|
||||
let vel = velocity(in: view)
|
||||
switch direction {
|
||||
case .horizontal where fabs(vel.y) > fabs(vel.x):
|
||||
state = .cancelled
|
||||
case .vertical where fabs(vel.x) > fabs(vel.y):
|
||||
state = .cancelled
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue