mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
#1803 (add interactively swipe right anywhere on the screen to return to the feed list)
- Added third-party code to support a “lazy pop”. - Fixed some issues with that code.
This commit is contained in:
parent
8e98ae51d5
commit
43ff718cd6
4 changed files with 266 additions and 0 deletions
|
@ -144,6 +144,9 @@ struct FeedDetailGridView: View {
|
|||
})
|
||||
}
|
||||
.background(Color.themed([0xE0E0E0, 0xFFF8CA, 0x363636, 0x101010]))
|
||||
.if(cache.isGrid) { view in
|
||||
view.lazyPop()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
70
clients/ios/Lazy Pop/SlideAnimatedTransitioning.swift
Normal file
70
clients/ios/Lazy Pop/SlideAnimatedTransitioning.swift
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// SlideAnimatedTransitioning.swift
|
||||
// SwipeRightToPopController
|
||||
//
|
||||
// Created by Warif Akhand Rishi on 2/19/16.
|
||||
// Copyright © 2016 Warif Akhand Rishi. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SlideAnimatedTransitioning: NSObject {
|
||||
|
||||
}
|
||||
|
||||
extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning {
|
||||
|
||||
// NOTE: includes DJS modifications to fix some issues.
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
guard let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view,
|
||||
let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view else {
|
||||
return
|
||||
}
|
||||
|
||||
let width = containerView.frame.width
|
||||
|
||||
var offsetLeft = fromView.frame
|
||||
offsetLeft.origin.x = width
|
||||
|
||||
var offscreenRight = toView.frame
|
||||
offscreenRight.origin.x = -width / 3.33;
|
||||
|
||||
toView.frame = offscreenRight;
|
||||
|
||||
fromView.layer.shadowRadius = 5.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:{
|
||||
|
||||
toView.frame.origin.x = fromView.frame.origin.x
|
||||
toView.frame.size.width = fromView.frame.size.width
|
||||
fromView.frame = offsetLeft
|
||||
|
||||
toView.layer.opacity = 1.0
|
||||
fromView.layer.shadowOpacity = 0.1
|
||||
|
||||
}, completion: { finished in
|
||||
toView.layer.opacity = 1.0
|
||||
toView.layer.shadowOpacity = 0
|
||||
fromView.layer.opacity = 1.0
|
||||
fromView.layer.shadowOpacity = 0
|
||||
|
||||
// when cancelling or completing the animation, ios simulator seems to sometimes flash black backgrounds during the animation. on devices, this doesn't seem to happen though.
|
||||
// containerView.backgroundColor = [UIColor whiteColor];
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
|
||||
return 0.3
|
||||
}
|
||||
|
||||
}
|
173
clients/ios/Lazy Pop/SwipeRightToPopViewController.swift
Normal file
173
clients/ios/Lazy Pop/SwipeRightToPopViewController.swift
Normal file
|
@ -0,0 +1,173 @@
|
|||
//
|
||||
// SwipeRightToPopViewController.swift
|
||||
// SwipeRightToPopController
|
||||
//
|
||||
// Created by Warif Akhand Rishi on 2/19/16.
|
||||
// Copyright © 2016 Warif Akhand Rishi. All rights reserved.
|
||||
//
|
||||
// Modified by Joseph Hinkle on 12/1/19.
|
||||
// Modified version allows use in SwiftUI by subclassing UIHostingController.
|
||||
// Copyright © 2019 Joseph Hinkle. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (l?, r?):
|
||||
return l < r
|
||||
case (nil, _?):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (l?, r?):
|
||||
return l > r
|
||||
default:
|
||||
return rhs < lhs
|
||||
}
|
||||
}
|
||||
|
||||
class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINavigationControllerDelegate where Content : View {
|
||||
|
||||
fileprivate var lazyPopContent: LazyPop<Content>?
|
||||
private var percentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition?
|
||||
private var panGestureRecognizer: UIPanGestureRecognizer!
|
||||
private var parentNavigationControllerToUse: UINavigationController?
|
||||
private var gestureAdded = false
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
// You need to add gesture events after every subview layout to protect against weird edge cases
|
||||
// One notable edgecase is if you are in a splitview in landscape. In this case, there will be
|
||||
// no nav controller with 2 vcs, so our addGesture will fail. After rotating back to portrait,
|
||||
// the splitview will combine into one view with the details pushed on top. So only then would
|
||||
// would the addGesture find a parent nav controller with 2 view controllers. I don't know if
|
||||
// there are other edge cases, but running addGesture on every viewDidLayoutSubviews seems safe.
|
||||
addGesture()
|
||||
}
|
||||
|
||||
public func addGesture() {
|
||||
if !gestureAdded {
|
||||
// attempt to find a parent navigationController
|
||||
var currentVc: UIViewController = self
|
||||
while true {
|
||||
if (currentVc.navigationController != nil) &&
|
||||
currentVc.navigationController?.viewControllers.count > 1
|
||||
{
|
||||
parentNavigationControllerToUse = currentVc.navigationController
|
||||
break
|
||||
}
|
||||
guard let parent = currentVc.parent else {
|
||||
return
|
||||
}
|
||||
currentVc = parent
|
||||
}
|
||||
guard parentNavigationControllerToUse?.viewControllers.count > 1 else {
|
||||
return
|
||||
}
|
||||
|
||||
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeRightToPopViewController.handlePanGesture(_:)))
|
||||
self.view.addGestureRecognizer(panGestureRecognizer)
|
||||
gestureAdded = true
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
|
||||
|
||||
// if the parentNavigationControllerToUse has a width value, use that because it's more accurate. Otherwise use this view's width as a backup
|
||||
let total = parentNavigationControllerToUse?.view.frame.width ?? view.frame.width
|
||||
let percent = max(panGesture.translation(in: view).x, 0) / total
|
||||
|
||||
switch panGesture.state {
|
||||
|
||||
case .began:
|
||||
if lazyPopContent?.isEnabled == true {
|
||||
parentNavigationControllerToUse?.delegate = self
|
||||
_ = parentNavigationControllerToUse?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
case .changed:
|
||||
if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition {
|
||||
percentDrivenInteractiveTransition.update(percent)
|
||||
}
|
||||
|
||||
case .ended:
|
||||
let velocity = panGesture.velocity(in: view).x
|
||||
|
||||
// Continue if drag more than 50% of screen width or velocity is higher than 100
|
||||
if percent > 0.5 || velocity > 100 {
|
||||
percentDrivenInteractiveTransition?.finish()
|
||||
} else {
|
||||
percentDrivenInteractiveTransition?.cancel()
|
||||
}
|
||||
|
||||
case .cancelled, .failed:
|
||||
percentDrivenInteractiveTransition?.cancel()
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
}
|
||||
|
||||
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
|
||||
return SlideAnimatedTransitioning()
|
||||
}
|
||||
|
||||
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
||||
|
||||
parentNavigationControllerToUse?.delegate = nil
|
||||
navigationController.delegate = nil
|
||||
|
||||
if panGestureRecognizer.state == .began {
|
||||
percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition()
|
||||
percentDrivenInteractiveTransition?.completionCurve = .easeOut
|
||||
} else {
|
||||
percentDrivenInteractiveTransition = nil
|
||||
}
|
||||
|
||||
return percentDrivenInteractiveTransition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Lazy Pop SwiftUI Component
|
||||
//
|
||||
// Created by Joseph Hinkle on 12/1/19.
|
||||
// Copyright © 2019 Joseph Hinkle. All rights reserved.
|
||||
//
|
||||
|
||||
fileprivate struct LazyPop<Content: View>: UIViewControllerRepresentable {
|
||||
let rootView: Content
|
||||
@Binding var isEnabled: Bool
|
||||
|
||||
init(_ rootView: Content, isEnabled: (Binding<Bool>)? = nil) {
|
||||
self.rootView = rootView
|
||||
self._isEnabled = isEnabled ?? Binding<Bool>(get: { return true }, set: { _ in })
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> UIViewController {
|
||||
let vc = SwipeRightToPopViewController(rootView: rootView)
|
||||
vc.lazyPopContent = self
|
||||
return vc
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
|
||||
if let host = uiViewController as? UIHostingController<Content> {
|
||||
host.rootView = rootView
|
||||
}
|
||||
}
|
||||
}
|
||||
extension View {
|
||||
public func lazyPop(isEnabled: (Binding<Bool>)? = nil) -> some View {
|
||||
return LazyPop(self, isEnabled: isEnabled)
|
||||
}
|
||||
}
|
|
@ -844,6 +844,10 @@
|
|||
17F39EBA26478538004B46D1 /* content_preview_small@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17F39EB726478538004B46D1 /* content_preview_small@2x.png */; };
|
||||
17F39EBB26478538004B46D1 /* content_preview_large@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17F39EB826478538004B46D1 /* content_preview_large@2x.png */; };
|
||||
17FB51D723AC81C500F5D5BF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 17FB51D923AC81C500F5D5BF /* InfoPlist.strings */; };
|
||||
17FBFA982A6AEA4100149651 /* SlideAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17FBFA962A6AEA4100149651 /* SlideAnimatedTransitioning.swift */; };
|
||||
17FBFA992A6AEA4100149651 /* SlideAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17FBFA962A6AEA4100149651 /* SlideAnimatedTransitioning.swift */; };
|
||||
17FBFA9A2A6AEA4100149651 /* SwipeRightToPopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17FBFA972A6AEA4100149651 /* SwipeRightToPopViewController.swift */; };
|
||||
17FBFA9B2A6AEA4100149651 /* SwipeRightToPopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17FBFA972A6AEA4100149651 /* SwipeRightToPopViewController.swift */; };
|
||||
17FF4F7D27DA9645000526E6 /* ShareAddSiteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17FF4F7C27DA9645000526E6 /* ShareAddSiteCell.swift */; };
|
||||
17FF4F7E27DAAA6E000526E6 /* ak-icon-allstories.png in Resources */ = {isa = PBXBuildFile; fileRef = FF85BF7216D6A972002D334D /* ak-icon-allstories.png */; };
|
||||
17FF4F7F27DAAA78000526E6 /* g_icn_folder.png in Resources */ = {isa = PBXBuildFile; fileRef = FFDD847216E887D3000AA0A2 /* g_icn_folder.png */; };
|
||||
|
@ -1603,6 +1607,8 @@
|
|||
17F39EB726478538004B46D1 /* content_preview_small@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "content_preview_small@2x.png"; sourceTree = "<group>"; };
|
||||
17F39EB826478538004B46D1 /* content_preview_large@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "content_preview_large@2x.png"; sourceTree = "<group>"; };
|
||||
17FB51D823AC81C500F5D5BF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
17FBFA962A6AEA4100149651 /* SlideAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlideAnimatedTransitioning.swift; sourceTree = "<group>"; };
|
||||
17FBFA972A6AEA4100149651 /* SwipeRightToPopViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeRightToPopViewController.swift; sourceTree = "<group>"; };
|
||||
17FF4F7C27DA9645000526E6 /* ShareAddSiteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAddSiteCell.swift; sourceTree = "<group>"; };
|
||||
1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
1D3623240D0F684500981E51 /* NewsBlurAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = NewsBlurAppDelegate.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
|
@ -2489,6 +2495,15 @@
|
|||
path = "Old Widget Extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
17FBFA952A6AEA4100149651 /* Lazy Pop */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
17FBFA962A6AEA4100149651 /* SlideAnimatedTransitioning.swift */,
|
||||
17FBFA972A6AEA4100149651 /* SwipeRightToPopViewController.swift */,
|
||||
);
|
||||
path = "Lazy Pop";
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
19C28FACFE9D520D11CA2CBB /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2536,6 +2551,7 @@
|
|||
FF22FE5316E53ADC0046165A /* Underscore */,
|
||||
FF2924CE1E93293F00FCFA63 /* PINCache */,
|
||||
FF34FD2A1E9D93CB0062F8ED /* InAppSettingsKit */,
|
||||
17FBFA952A6AEA4100149651 /* Lazy Pop */,
|
||||
43A4C3E615B0099B008787B5 /* NewsBlur_Prefix.pch */,
|
||||
179DD9CC23DFD20E007BFD21 /* BridgingHeader.h */,
|
||||
43A4C3B915B00966008787B5 /* ABTableViewCell.h */,
|
||||
|
@ -4863,6 +4879,7 @@
|
|||
175792372930605500490924 /* FeedTableCell.m in Sources */,
|
||||
175792382930605500490924 /* StoryPagesViewController.swift in Sources */,
|
||||
175792392930605500490924 /* NewsBlurAppDelegate.m in Sources */,
|
||||
17FBFA9B2A6AEA4100149651 /* SwipeRightToPopViewController.swift in Sources */,
|
||||
1757923A2930605500490924 /* FeedChooserTitleView.m in Sources */,
|
||||
1757923B2930605500490924 /* FeedsObjCViewController.m in Sources */,
|
||||
1757923C2930605500490924 /* NBURLCache.m in Sources */,
|
||||
|
@ -4887,6 +4904,7 @@
|
|||
1785524D2A21693300A8CD92 /* FeedDetailLoadingView.swift in Sources */,
|
||||
175792502930605500490924 /* IASKSwitch.m in Sources */,
|
||||
175792512930605500490924 /* NBBarButtonItem.m in Sources */,
|
||||
17FBFA992A6AEA4100149651 /* SlideAnimatedTransitioning.swift in Sources */,
|
||||
175792522930605500490924 /* MoveSiteViewController.m in Sources */,
|
||||
175792532930605500490924 /* FirstTimeUserViewController.m in Sources */,
|
||||
1785524A2A1F115800A8CD92 /* FeedDetailTableCell.m in Sources */,
|
||||
|
@ -5045,6 +5063,7 @@
|
|||
43F44B1C159D8DBC00F48F8A /* FeedTableCell.m in Sources */,
|
||||
171B6FFD25C4C7C8008638A9 /* StoryPagesViewController.swift in Sources */,
|
||||
1D3623260D0F684500981E51 /* NewsBlurAppDelegate.m in Sources */,
|
||||
17FBFA9A2A6AEA4100149651 /* SwipeRightToPopViewController.swift in Sources */,
|
||||
17432C861C5343C0003F8FD6 /* FeedChooserTitleView.m in Sources */,
|
||||
28D7ACF80DDB3853001CB0EB /* FeedsObjCViewController.m in Sources */,
|
||||
FFFF683D19D628000081904A /* NBURLCache.m in Sources */,
|
||||
|
@ -5069,6 +5088,7 @@
|
|||
1785524C2A21693300A8CD92 /* FeedDetailLoadingView.swift in Sources */,
|
||||
FF34FD6D1E9D93CB0062F8ED /* IASKSwitch.m in Sources */,
|
||||
FF9B8BB217F2351A0036A41C /* NBBarButtonItem.m in Sources */,
|
||||
17FBFA982A6AEA4100149651 /* SlideAnimatedTransitioning.swift in Sources */,
|
||||
FF2D8CE514893BC000057B80 /* MoveSiteViewController.m in Sources */,
|
||||
433323CD158968ED0025064D /* FirstTimeUserViewController.m in Sources */,
|
||||
178552492A1F115800A8CD92 /* FeedDetailTableCell.m in Sources */,
|
||||
|
|
Loading…
Add table
Reference in a new issue