diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift index dbe50286e..0176a7dd7 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppView.swift @@ -10,6 +10,7 @@ import WebKit let rawAuthCookie: String? let openLinkAction: (URL) -> Void let webViewActionHandler: (WKScriptMessage) -> Void + let navBarVisibilityRatioUpdater: (Double) -> Void @Binding var annotation: String @Binding var annotationSaveTransactionID: UUID? @Binding var sendIncreaseFontSignal: Bool @@ -28,11 +29,12 @@ import WebKit let webView = WebView(frame: CGRect.zero) let contentController = WKUserContentController() - webView.scrollView.isScrollEnabled = true + webView.scrollView.contentInset.top = LinkItemDetailView.navBarHeight webView.navigationDelegate = context.coordinator webView.isOpaque = false webView.backgroundColor = UIColor.clear webView.configuration.userContentController = contentController + webView.scrollView.delegate = context.coordinator for action in WebViewAction.allCases { webView.configuration.userContentController.add(context.coordinator, name: action.rawValue) @@ -53,6 +55,7 @@ import WebKit context.coordinator.linkHandler = openLinkAction context.coordinator.webViewActionHandler = webViewActionHandler + context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater return webView } diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift index 28609f510..bbc3e7591 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift @@ -1,17 +1,28 @@ -// import Models import SwiftUI -// import Utils import WebKit final class WebAppViewCoordinator: NSObject { + let navBarHeight = LinkItemDetailView.navBarHeight var webViewActionHandler: (WKScriptMessage) -> Void = { _ in } var linkHandler: (URL) -> Void = { _ in } var needsReload = true var lastSavedAnnotationID: UUID? + var updateNavBarVisibilityRatio: (Double) -> Void = { _ in } + private var yOffsetAtStartOfDrag: Double? + private var lastYOffset: Double = 0 + private var hasDragged = false + private var isNavBarHidden = false override init() { super.init() } + + var navBarVisibilityRatio: Double = 1.0 { + didSet { + isNavBarHidden = navBarVisibilityRatio == 0 + updateNavBarVisibilityRatio(navBarVisibilityRatio) + } + } } extension WebAppViewCoordinator: WKScriptMessageHandler { @@ -34,6 +45,60 @@ extension WebAppViewCoordinator: WKNavigationDelegate { } } +extension WebAppViewCoordinator: UIScrollViewDelegate { + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + hasDragged = true + yOffsetAtStartOfDrag = scrollView.contentOffset.y + scrollView.contentInset.top + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + guard hasDragged else { return } + + let yOffset = scrollView.contentOffset.y + + if yOffset == 0 { + scrollView.contentInset.top = navBarHeight + navBarVisibilityRatio = 1 + return + } + + if yOffset < 0 { + navBarVisibilityRatio = 1 + scrollView.contentInset.top = navBarHeight + return + } + + if yOffset < navBarHeight { + let isScrollingUp = yOffsetAtStartOfDrag ?? 0 > yOffset + navBarVisibilityRatio = isScrollingUp || yOffset < 0 ? 1 : min(1, 1 - (yOffset / navBarHeight)) + scrollView.contentInset.top = navBarVisibilityRatio * navBarHeight + return + } + + guard let yOffsetAtStartOfDrag = yOffsetAtStartOfDrag else { return } + + if yOffset > yOffsetAtStartOfDrag, !isNavBarHidden { + let translation = yOffset - yOffsetAtStartOfDrag + let ratio = translation < navBarHeight ? 1 - (translation / navBarHeight) : 0 + navBarVisibilityRatio = min(ratio, 1) + scrollView.contentInset.top = navBarVisibilityRatio * navBarHeight + } + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if decelerate, scrollView.contentOffset.y + scrollView.contentInset.top < (yOffsetAtStartOfDrag ?? 0) { + scrollView.contentInset.top = navBarHeight + navBarVisibilityRatio = 1 + } + } + + func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { + scrollView.contentInset.top = navBarHeight + navBarVisibilityRatio = 1 + return false + } +} + struct WebViewConfig { let url: URL let themeId: String diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift index f8bae2f3f..f87b67573 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift @@ -35,9 +35,11 @@ public struct WebAppWrapperView: View { @State private var annotation = String() @State var annotationSaveTransactionID: UUID? @State var safariWebLink: SafariWebLink? + let navBarVisibilityRatioUpdater: (Double) -> Void - public init(viewModel: WebAppWrapperViewModel) { + public init(viewModel: WebAppWrapperViewModel, navBarVisibilityRatioUpdater: ((Double) -> Void)? = nil) { self.viewModel = viewModel + self.navBarVisibilityRatioUpdater = navBarVisibilityRatioUpdater ?? { _ in } } public var body: some View { @@ -53,6 +55,7 @@ public struct WebAppWrapperView: View { #endif }, webViewActionHandler: webViewActionHandler, + navBarVisibilityRatioUpdater: navBarVisibilityRatioUpdater, annotation: $annotation, annotationSaveTransactionID: $annotationSaveTransactionID, sendIncreaseFontSignal: $viewModel.sendIncreaseFontSignal, diff --git a/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift b/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift index 7147ab60d..deff8c4f8 100644 --- a/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift +++ b/apple/OmnivoreKit/Sources/Views/FontSizeAdjustmentPopoverView.swift @@ -23,6 +23,7 @@ public struct FontSizeAdjustmentPopoverView: View { Image(systemName: "minus") #if os(iOS) .foregroundColor(.appGraySolid) + .padding() #endif } ) @@ -37,6 +38,7 @@ public struct FontSizeAdjustmentPopoverView: View { Image(systemName: "plus") #if os(iOS) .foregroundColor(.appGraySolid) + .padding() #endif } ) diff --git a/apple/OmnivoreKit/Sources/Views/LinkedItemDetail/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/Views/LinkedItemDetail/LinkItemDetailView.swift index 0af2bd8e2..c107daf51 100644 --- a/apple/OmnivoreKit/Sources/Views/LinkedItemDetail/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/Views/LinkedItemDetail/LinkItemDetailView.swift @@ -25,8 +25,12 @@ public final class LinkItemDetailViewModel: ObservableObject { } public struct LinkItemDetailView: View { + @Environment(\.presentationMode) var presentationMode: Binding + + static let navBarHeight = 50.0 @ObservedObject private var viewModel: LinkItemDetailViewModel @State private var showFontSizePopover = false + @State private var navBarVisibilityRatio = 1.0 public init(viewModel: LinkItemDetailViewModel) { self.viewModel = viewModel @@ -50,13 +54,105 @@ public struct LinkItemDetailView: View { ) } + var fontAdjustmentPopoverView: some View { + FontSizeAdjustmentPopoverView( + increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true }, + decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true } + ) + } + public var body: some View { - innerBody #if os(iOS) - .navigationBarTitleDisplayMode(.inline) + if UIDevice.isIPhone, !viewModel.item.isPDF { + compactInnerBody + } else { + innerBody + } + #else + innerBody #endif } + var navBar: some View { + HStack(alignment: .center) { + Button( + action: { self.presentationMode.wrappedValue.dismiss() }, + label: { + Image(systemName: "chevron.backward") + .font(.appTitleTwo) + .foregroundColor(.appGrayTextContrast) + .padding(.horizontal) + } + ) + .scaleEffect(navBarVisibilityRatio) + Spacer() + Button( + action: { showFontSizePopover.toggle() }, + label: { + Image(systemName: "textformat.size") + .font(.appTitleTwo) + } + ) + .padding(.horizontal) + .scaleEffect(navBarVisibilityRatio) + } + .frame(height: LinkItemDetailView.navBarHeight * navBarVisibilityRatio) + .opacity(navBarVisibilityRatio) + .background(Color.systemBackground) + } + + @ViewBuilder private var compactInnerBody: some View { + if let webAppWrapperViewModel = viewModel.webAppWrapperViewModel { + ZStack { + WebAppWrapperView( + viewModel: webAppWrapperViewModel, + navBarVisibilityRatioUpdater: { + if $0 < 1 { + showFontSizePopover = false + } + navBarVisibilityRatio = $0 + } + ) + if showFontSizePopover { + VStack { + Color.clear + .contentShape(Rectangle()) + .frame(height: LinkItemDetailView.navBarHeight) + HStack { + Spacer() + fontAdjustmentPopoverView + .background(Color.appButtonBackground) + .cornerRadius(8) + .padding(.trailing, 5) + } + Spacer() + } + .background( + Color.clear + .contentShape(Rectangle()) + .onTapGesture { + showFontSizePopover = false + } + ) + } + VStack(spacing: 0) { + navBar + Spacer() + } + } + .navigationBarHidden(true) + } else { + VStack(spacing: 0) { + navBar + Spacer() + } + .onAppear { + viewModel.performActionSubject.send(.load) + } + .navigationBarHidden(true) + } + } + @ViewBuilder private var innerBody: some View { if let pdfURL = viewModel.item.pdfURL { #if os(iOS) @@ -76,17 +172,11 @@ public struct LinkItemDetailView: View { ) #if os(iOS) .fittedPopover(isPresented: $showFontSizePopover) { - FontSizeAdjustmentPopoverView( - increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true }, - decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true } - ) + fontAdjustmentPopoverView } #else .popover(isPresented: $showFontSizePopover) { - FontSizeAdjustmentPopoverView( - increaseFontAction: { viewModel.webAppWrapperViewModel?.sendIncreaseFontSignal = true }, - decreaseFontAction: { viewModel.webAppWrapperViewModel?.sendDecreaseFontSignal = true } - ) + fontAdjustmentPopoverView } #endif } @@ -103,3 +193,15 @@ public struct LinkItemDetailView: View { } } } + +// 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 + } +}