diff --git a/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift b/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift index 4843f995c..6c781c48d 100644 --- a/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/BriefingView.swift @@ -82,7 +82,7 @@ struct BriefingView: View { var body: some View { ZStack { // Using ZStack so .task can be used on if/else body if let item = viewModel.item { - WebReaderContainerView(item: item) + WebReaderContainerView(item: item, pop: {}) } } .task { diff --git a/apple/OmnivoreKit/Sources/App/Views/File.swift b/apple/OmnivoreKit/Sources/App/Views/File.swift index 2110ece3a..9a6b8f4d3 100644 --- a/apple/OmnivoreKit/Sources/App/Views/File.swift +++ b/apple/OmnivoreKit/Sources/App/Views/File.swift @@ -1,8 +1,57 @@ // -// File.swift -// +// SlideAnimatedTransitioning.swift +// SwipeRightToPopController // -// Created by Jackson Harper on 9/11/23. +// Created by Warif Akhand Rishi on 2/19/16. +// Copyright © 2016 Warif Akhand Rishi. All rights reserved. // -import Foundation +import UIKit + +class SlideAnimatedTransitioning: NSObject {} + +extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning { + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let containerView = transitionContext.containerView + let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view + let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view + + 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 = (fromView?.frame)! + 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 + + // 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 _: UIViewControllerContextTransitioning?) -> TimeInterval { + 0.3 + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift index 507a1f069..c57a9013e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift @@ -44,7 +44,7 @@ struct FeedCardNavigationLink: View { isPDF: item.isPDF ), label: { LibraryItemCard(item: item, viewer: dataService.currentViewer) - .padding(10) + .padding(.top, 15) }) .onAppear { Task { await viewModel.itemAppeared(item: item, dataService: dataService) } @@ -71,8 +71,8 @@ struct GridCardNavigationLink: View { if isContextMenuOpen { isContextMenuOpen = false } else { - viewModel.selectedItem = item - viewModel.linkIsActive = true + // viewModel.selectedItem = item + // viewModel.linkIsActive = true } } label: { NavigationLink(destination: EmptyView()) { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift index 05c9d4b50..21bbbf243 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryFeatureCardNavigationLink.swift @@ -20,49 +20,38 @@ struct LibraryFeatureCardNavigationLink: View { @State var showFeatureActions = false var body: some View { - ZStack { - Button { + NavigationLink(destination: EmptyView()) { + LibraryFeatureCard(item: item, viewer: dataService.currentViewer) + } + .confirmationDialog("", isPresented: $showFeatureActions) { + if FeaturedItemFilter(rawValue: viewModel.featureFilter) == .pinned { + Button("Unpin", action: { + viewModel.unpinItem(dataService: dataService, item: item) + }) + } + Button("Pin", action: { + viewModel.pinItem(dataService: dataService, item: item) + }) + Button("Archive", action: { + viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true) + }) + Button("Remove", action: { + viewModel.removeLink(dataService: dataService, objectID: item.objectID) + }) + if FeaturedItemFilter(rawValue: viewModel.featureFilter) != .pinned { + Button("Mark Read", action: { + viewModel.markRead(dataService: dataService, item: item) + }) + Button("Mark Unread", action: { + viewModel.markUnread(dataService: dataService, item: item) + }) + } + Button("Dismiss", role: .cancel, action: { showFeatureActions = false - viewModel.selectedItem = item - viewModel.linkIsActive = true - } label: { - NavigationLink(destination: EmptyView()) { - EmptyView() - } - .opacity(0) - .buttonStyle(PlainButtonStyle()) - LibraryFeatureCard(item: item, viewer: dataService.currentViewer) - } - .confirmationDialog("", isPresented: $showFeatureActions) { - if FeaturedItemFilter(rawValue: viewModel.featureFilter) == .pinned { - Button("Unpin", action: { - viewModel.unpinItem(dataService: dataService, item: item) - }) - } - Button("Pin", action: { - viewModel.pinItem(dataService: dataService, item: item) - }) - Button("Archive", action: { - viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true) - }) - Button("Remove", action: { - viewModel.removeLink(dataService: dataService, objectID: item.objectID) - }) - if FeaturedItemFilter(rawValue: viewModel.featureFilter) != .pinned { - Button("Mark Read", action: { - viewModel.markRead(dataService: dataService, item: item) - }) - Button("Mark Unread", action: { - viewModel.markUnread(dataService: dataService, item: item) - }) - } - Button("Dismiss", role: .cancel, action: { - showFeatureActions = false - }) - } - .delayedGesture(LongPressGesture().onEnded { _ in - showFeatureActions = true }) } + .delayedGesture(LongPressGesture().onEnded { _ in + showFeatureActions = true + }) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 62d83945f..b127f0114 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -44,121 +44,110 @@ struct AnimatingCellHeight: AnimatableModifier { } var body: some View { - ZStack { -// if let linkRequest = viewModel.linkRequest { -// NavigationLink( -// destination: WebReaderLoadingContainer(requestID: linkRequest.serverID), -// tag: linkRequest, -// selection: $viewModel.linkRequest -// ) { -// EmptyView() -// } -// } - HomeFeedView( - listTitle: $listTitle, - isListScrolled: $isListScrolled, - prefersListLayout: $prefersListLayout, - viewModel: viewModel - ) - .refreshable { - loadItems(isRefresh: true) + HomeFeedView( + listTitle: $listTitle, + isListScrolled: $isListScrolled, + prefersListLayout: $prefersListLayout, + viewModel: viewModel + ) + .refreshable { + loadItems(isRefresh: true) + } + .onChange(of: viewModel.searchTerm) { _ in + // Maybe we should debounce this, but + // it feels like it works ok without + loadItems(isRefresh: true) + } + .onChange(of: viewModel.selectedLabels) { _ in + loadItems(isRefresh: true) + } + .onChange(of: viewModel.negatedLabels) { _ in + loadItems(isRefresh: true) + } + .onChange(of: viewModel.appliedFilter) { _ in + loadItems(isRefresh: true) + } + .onChange(of: viewModel.appliedSort) { _ in + loadItems(isRefresh: true) + } + .sheet(item: $viewModel.itemUnderLabelEdit) { item in + ApplyLabelsView(mode: .item(item), onSave: nil) + } + .sheet(item: $viewModel.itemUnderTitleEdit) { item in + LinkedItemMetadataEditView(item: item) + } + .sheet(item: $viewModel.itemForHighlightsView) { item in + NotebookView(itemObjectID: item.objectID, hasHighlightMutations: $hasHighlightMutations) + } + .sheet(isPresented: $viewModel.showFiltersModal) { + NavigationView { + FilterSelectorView(viewModel: viewModel) } - .onChange(of: viewModel.searchTerm) { _ in - // Maybe we should debounce this, but - // it feels like it works ok without - loadItems(isRefresh: true) - } - .onChange(of: viewModel.selectedLabels) { _ in - loadItems(isRefresh: true) - } - .onChange(of: viewModel.negatedLabels) { _ in - loadItems(isRefresh: true) - } - .onChange(of: viewModel.appliedFilter) { _ in - loadItems(isRefresh: true) - } - .onChange(of: viewModel.appliedSort) { _ in - loadItems(isRefresh: true) - } - .sheet(item: $viewModel.itemUnderLabelEdit) { item in - ApplyLabelsView(mode: .item(item), onSave: nil) - } - .sheet(item: $viewModel.itemUnderTitleEdit) { item in - LinkedItemMetadataEditView(item: item) - } - .sheet(item: $viewModel.itemForHighlightsView) { item in - NotebookView(itemObjectID: item.objectID, hasHighlightMutations: $hasHighlightMutations) - } - .sheet(isPresented: $viewModel.showFiltersModal) { - NavigationView { - FilterSelectorView(viewModel: viewModel) - } - } - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .barLeading) { - VStack(alignment: .leading) { - let title = (LinkedItemFilter(rawValue: viewModel.appliedFilter) ?? LinkedItemFilter.inbox).displayName + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .barLeading) { + VStack(alignment: .leading) { + let title = (LinkedItemFilter(rawValue: viewModel.appliedFilter) ?? LinkedItemFilter.inbox).displayName - Text(title) - .font(Font.system(size: isListScrolled ? 10 : 18, weight: .semibold)) + Text(title) + .font(Font.system(size: isListScrolled ? 10 : 18, weight: .semibold)) - if isListScrolled { - Text(listTitle) - .font(Font.system(size: 15, weight: .regular)) - .foregroundColor(Color.appGrayText) - } - }.frame(maxWidth: .infinity, alignment: .leading) - } - ToolbarItem(placement: .barTrailing) { - Button("", action: {}) - .disabled(true) - .overlay { - if viewModel.isLoading, !prefersListLayout, enableGrid { - ProgressView() - } - } - } - ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { - if enableGrid { - Button( - action: { prefersListLayout.toggle() }, - label: { - Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") - } - ) - } else { - EmptyView() + if isListScrolled { + Text(listTitle) + .font(Font.system(size: 15, weight: .regular)) + .foregroundColor(Color.appGrayText) } - } - ToolbarItem(placement: .barTrailing) { + }.frame(maxWidth: .infinity, alignment: .leading) + } + ToolbarItem(placement: .barTrailing) { + Button("", action: {}) + .disabled(true) + .overlay { + if viewModel.isLoading, !prefersListLayout, enableGrid { + ProgressView() + } + } + } + ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { + if enableGrid { Button( - action: { searchPresented = true }, + action: { prefersListLayout.toggle() }, label: { - Image(systemName: "magnifyingglass") - .resizable() - .frame(width: 18, height: 18) - .padding(.vertical) - .foregroundColor(.appGrayTextContrast) + Label("Toggle Feed Layout", systemImage: prefersListLayout ? "square.grid.2x2" : "list.bullet") } ) + } else { + EmptyView() } - ToolbarItem(placement: .barTrailing) { - if UIDevice.isIPhone { - Menu(content: { - Button(action: { settingsPresented = true }, label: { - Label(LocalText.genericProfile, systemImage: "person.circle") - }) - Button(action: { addLinkPresented = true }, label: { - Label("Add Link", systemImage: "plus.circle") - }) - }, label: { - Image.utilityMenu - }) + } + ToolbarItem(placement: .barTrailing) { + Button( + action: { searchPresented = true }, + label: { + Image(systemName: "magnifyingglass") + .resizable() + .frame(width: 18, height: 18) + .padding(.vertical) .foregroundColor(.appGrayTextContrast) - } else { - EmptyView() } + ) + } + ToolbarItem(placement: .barTrailing) { + if UIDevice.isIPhone { + Menu(content: { + Button(action: { settingsPresented = true }, label: { + Label(LocalText.genericProfile, systemImage: "person.circle") + }) + Button(action: { addLinkPresented = true }, label: { + Label("Add Link", systemImage: "plus.circle") + }) + }, label: { + Image.utilityMenu + }) + .foregroundColor(.appGrayTextContrast) + } else { + EmptyView() } } } @@ -170,13 +159,8 @@ struct AnimatingCellHeight: AnimatableModifier { guard let objectID = dataService.persist(jsonArticle: jsonArticle) else { return } guard let linkedItem = dataService.viewContext.object(with: objectID) as? LinkedItem else { return } viewModel.pushFeedItem(item: linkedItem) - viewModel.selectedItem = linkedItem - viewModel.linkIsActive = true - } - .onReceive(NSNotification.pushReaderItemPublisher) { notification in - if let objectID = notification.userInfo?["objectID"] as? NSManagedObjectID { - viewModel.handleReaderItemNotification(objectID: objectID, dataService: dataService) - } + // viewModel.selectedItem = linkedItem + // viewModel.linkIsActive = true } .onOpenURL { url in viewModel.linkRequest = nil diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 1dd059b5c..2cb2a8c0d 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -27,7 +27,7 @@ import Views @Published var appliedSort = LinkedItemSort.newest.rawValue @Published var selectedLinkItem: NSManagedObjectID? // used by mac app only - @Published var selectedItem: LinkedItem? + // @Published var selectedItem: LinkedItem? @Published var linkIsActive = false @Published var showLabelsSheet = false @@ -79,35 +79,6 @@ import Views } } - func handleReaderItemNotification(objectID: NSManagedObjectID, dataService: DataService) { - // Pop the current selected item if needed - if selectedItem != nil, selectedItem?.objectID != objectID { - // Temporarily disable animation to avoid excessive animations - #if os(iOS) - UIView.setAnimationsEnabled(false) - #endif - - linkIsActive = false - selectedItem = nil - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { - self.selectedLinkItem = objectID - self.selectedItem = dataService.viewContext.object(with: objectID) as? LinkedItem - self.linkIsActive = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { - #if os(iOS) - UIView.setAnimationsEnabled(true) - #endif - } - } else { - selectedLinkItem = objectID - selectedItem = dataService.viewContext.object(with: objectID) as? LinkedItem - linkIsActive = true - } - } - func itemAppeared(item: LinkedItem, dataService: DataService) async { if isLoading { return } let itemIndex = items.firstIndex(where: { $0.id == item.id }) diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index e2504bb47..fa6aea0f6 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -75,25 +75,29 @@ struct LinkItemDetailView: View { @StateObject private var viewModel = LinkItemDetailViewModel() + @State var isEnabled = true + @Environment(\.dismiss) var dismiss + init(linkedItemObjectID: NSManagedObjectID, isPDF: Bool) { self.linkedItemObjectID = linkedItemObjectID self.isPDF = isPDF } var body: some View { - ZStack { // Using ZStack so .task can be used on if/else body + ZStack { if isPDF { pdfContainerView } else if let item = viewModel.item { - WebReaderContainerView(item: item) + WebReaderContainerView(item: item, pop: { dismiss() }) } } .task { await viewModel.loadItem(linkedItemObjectID: linkedItemObjectID, dataService: dataService) } - #if os(iOS) - .navigationBarHidden(true) - #endif + .navigationBarHidden(true) + .lazyPop(pop: { + dismiss() + }, isEnabled: $isEnabled) } @ViewBuilder private var pdfContainerView: some View { @@ -114,16 +118,29 @@ struct LinkItemDetailView: View { } } -#if os(iOS) - // Enable swipe to go back behavior if nav bar is hidden - extension UINavigationController: UIGestureRecognizerDelegate { - override open func viewDidLoad() { - super.viewDidLoad() - interactivePopGestureRecognizer?.delegate = self - } - - public func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { - viewControllers.count > 1 - } - } -#endif +// +// #if os(iOS) +// // Enable swipe to go back behavior if nav bar is hidden +// extension UINavigationController: UIGestureRecognizerDelegate, UINavigationControllerDelegate { +// override open func viewDidLoad() { +// super.viewDidLoad() +// print("INIT: ", viewControllers) +// delegate = self +// interactivePopGestureRecognizer?.delegate = nil +// } +//// +//// public func gestureRecognizerShouldBegin(_ gesture: UIGestureRecognizer) -> Bool { +//// print("SHOULD BEGIN: ", viewControllers, gesture) +//// return viewControllers.count > 1 +//// } +//// +//// public func navigationController(_ navigationController: UINavigationController, willShow _: UIViewController, animated _: Bool) { +//// navigationController.transitionCoordinator?.notifyWhenInteractionChanges { context in +//// print("DID SHOW CONTEXT: ", context.percentComplete, navigationController.viewControllers) +//// navigationController.interactivePopGestureRecognizer?.delegate = nil +//// navigationController.popToRootViewController(animated: false) +//// // interactivePopGestureRecognizer. +//// } +//// } +// } +// #endif diff --git a/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift b/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift index 2110ece3a..94eff4279 100644 --- a/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift +++ b/apple/OmnivoreKit/Sources/App/Views/SwipeRightToPopViewController.swift @@ -1,8 +1,181 @@ // -// File.swift -// +// SwipeRightToPopViewController.swift +// SwipeRightToPopController // -// Created by Jackson Harper on 9/11/23. +// 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 Foundation +import SwiftUI +private func < (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (lll?, rrr?): + return lll < rrr + case (nil, _?): + return true + default: + return false + } +} + +private func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (lll?, rrr?): + return lll > rrr + default: + return rhs < lhs + } +} + +class SwipeRightToPopViewController: UIHostingController, UINavigationControllerDelegate where Content: View { + fileprivate var pop: (() -> Void)? + fileprivate var lazyPopContent: LazyPop? + 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(_:))) + 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 + if let pop = self.pop { + pop() + } + } + + 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(_: UINavigationController, + animationControllerFor _: UINavigationController.Operation, + from _: UIViewController, + to _: UIViewController) -> UIViewControllerAnimatedTransitioning? + { + SlideAnimatedTransitioning() + } + + func navigationController(_: UINavigationController, + interactionControllerFor _: 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. +// + +private struct LazyPop: UIViewControllerRepresentable { + let rootView: Content + let pop: () -> Void + @Binding var isEnabled: Bool + + init(_ rootView: Content, pop: @escaping () -> Void, isEnabled: (Binding)? = nil) { + self.rootView = rootView + self.pop = pop + self._isEnabled = isEnabled ?? Binding(get: { true }, set: { _ in }) + } + + func makeUIViewController(context _: Context) -> UIViewController { + let vc = SwipeRightToPopViewController(rootView: rootView) + vc.pop = pop + vc.lazyPopContent = self + return vc + } + + func updateUIViewController(_ uiViewController: UIViewController, context _: Context) { + if let host = uiViewController as? UIHostingController { + host.rootView = rootView + } + } +} + +public extension View { + func lazyPop(pop: @escaping () -> Void, isEnabled: (Binding)? = nil) -> some View { + LazyPop(self, pop: pop, isEnabled: isEnabled) + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 3edc2e333..d062b013a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -10,6 +10,7 @@ import WebKit // swiftlint:disable file_length type_body_length struct WebReaderContainerView: View { let item: LinkedItem + let pop: () -> Void @State private var showPreferencesPopover = false @State private var showPreferencesFormsheet = false @@ -40,9 +41,9 @@ struct WebReaderContainerView: View { @EnvironmentObject var dataService: DataService @EnvironmentObject var audioController: AudioController - @Environment(\.presentationMode) var presentationMode: Binding @Environment(\.openURL) var openURL @StateObject var viewModel = WebReaderViewModel() + @Environment(\.dismiss) var dismiss func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) { if let replyHandler = replyHandler { @@ -271,7 +272,9 @@ struct WebReaderContainerView: View { HStack(alignment: .center, spacing: 10) { #if os(iOS) Button( - action: { self.presentationMode.wrappedValue.dismiss() }, + action: { + pop() + }, label: { Image.chevronRight .padding(.horizontal, 10) @@ -620,7 +623,7 @@ struct WebReaderContainerView: View { func archive() { dataService.archiveLink(objectID: item.objectID, archived: !item.isArchived) #if os(iOS) - presentationMode.wrappedValue.dismiss() + pop() #endif } @@ -651,7 +654,7 @@ struct WebReaderContainerView: View { removeLibraryItemAction(dataService: dataService, objectID: item.objectID) #if os(iOS) DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { - presentationMode.wrappedValue.dismiss() + pop() } #endif } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift index fb7ce6101..b0202e141 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift @@ -53,9 +53,8 @@ public struct WebReaderLoadingContainer: View { } #endif } else { - WebReaderContainerView(item: item) + WebReaderContainerView(item: item, pop: {}) #if os(iOS) - .navigationBarHidden(true) .navigationViewStyle(.stack) #endif .accentColor(.appGrayTextContrast) diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift index e4cb66b27..7c9e3214d 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift @@ -27,8 +27,8 @@ public struct LibraryItemCard: View { public var body: some View { VStack { HStack(alignment: .top, spacing: 10) { - articleInfo imageBox + articleInfo } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -156,12 +156,12 @@ public struct LibraryItemCard: View { image .resizable() .aspectRatio(contentMode: .fill) - .frame(width: 40, height: 40) + .frame(width: 74 * 0.666, height: 74) .cornerRadius(5) .padding(.top, 2) } else { Color.systemBackground - .frame(width: 40, height: 40) + .frame(width: 74 * 0.666, height: 74) .cornerRadius(5) .padding(.top, 2) }