Use lazy pop to work around swipe gesture issues with iOS 17

This commit is contained in:
Jackson Harper 2023-09-11 17:50:10 +08:00
parent d1eca8b6fd
commit dfaf61cf8c
11 changed files with 406 additions and 221 deletions

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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()) {

View file

@ -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
})
}
}

View file

@ -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

View file

@ -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 })

View file

@ -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

View file

@ -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 < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (lll?, rrr?):
return lll < rrr
case (nil, _?):
return true
default:
return false
}
}
private func > <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (lll?, rrr?):
return lll > rrr
default:
return rhs < lhs
}
}
class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINavigationControllerDelegate where Content: View {
fileprivate var pop: (() -> Void)?
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(_:)))
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<Content: View>: UIViewControllerRepresentable {
let rootView: Content
let pop: () -> Void
@Binding var isEnabled: Bool
init(_ rootView: Content, pop: @escaping () -> Void, isEnabled: (Binding<Bool>)? = nil) {
self.rootView = rootView
self.pop = pop
self._isEnabled = isEnabled ?? Binding<Bool>(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<Content> {
host.rootView = rootView
}
}
}
public extension View {
func lazyPop(pop: @escaping () -> Void, isEnabled: (Binding<Bool>)? = nil) -> some View {
LazyPop(self, pop: pop, isEnabled: isEnabled)
}
}

View file

@ -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<PresentationMode>
@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
}

View file

@ -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)

View file

@ -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)
}