Handle grid cards

This commit is contained in:
Jackson Harper 2023-09-11 20:54:10 +08:00
parent dfaf61cf8c
commit dcdb01171c
13 changed files with 190 additions and 187 deletions

View file

@ -13,38 +13,47 @@ 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
guard
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
else {
return
}
let width = containerView.frame.width
var offsetLeft = fromView?.frame
var offsetLeft = fromVC.view?.frame
offsetLeft?.origin.x = width
var offscreenRight = toView?.frame
var offscreenRight = fromVC.view?.frame
offscreenRight?.origin.x = -width / 3.33
toView?.frame = offscreenRight!
toVC.view?.frame = offscreenRight!
fromView?.layer.shadowRadius = 5.0
fromView?.layer.shadowOpacity = 1.0
toView?.layer.opacity = 0.9
fromVC.view?.layer.shadowRadius = 5.0
fromVC.view?.layer.shadowOpacity = 1.0
toVC.view?.layer.opacity = 0.9
containerView.insertSubview(toView!, belowSubview: fromView!)
transitionContext.containerView.addSubview(toVC.view)
transitionContext.containerView.addSubview(fromVC.view)
// containerView.insertSubview(toView!, belowSubview: fromView!)
print("CONTAINER VIEW: ", containerView)
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveLinear, animations: {
toView?.frame = (fromView?.frame)!
fromView?.frame = offsetLeft!
toVC.view?.frame = (fromVC.view?.frame)!
fromVC.view?.frame = offsetLeft!
toView?.layer.opacity = 1.0
fromView?.layer.shadowOpacity = 0.1
toVC.view?.layer.opacity = 1.0
fromVC.view?.layer.shadowOpacity = 0.1
}, completion: { _ in
toView?.layer.opacity = 1.0
toView?.layer.shadowOpacity = 0
fromView?.layer.opacity = 1.0
fromView?.layer.shadowOpacity = 0
toVC.view?.layer.opacity = 1.0
toVC.view?.layer.shadowOpacity = 0
fromVC.view?.layer.opacity = 1.0
fromVC.view?.layer.shadowOpacity = 0
fromVC.view.removeFromSuperview()
// 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)

View file

@ -66,28 +66,16 @@ struct GridCardNavigationLink: View {
@ObservedObject var viewModel: HomeFeedViewModel
var body: some View {
ZStack {
Button {
if isContextMenuOpen {
isContextMenuOpen = false
} else {
// viewModel.selectedItem = item
// viewModel.linkIsActive = true
}
} label: {
NavigationLink(destination: EmptyView()) {
EmptyView()
}
.opacity(0)
.buttonStyle(PlainButtonStyle())
.onAppear {
Task { await viewModel.itemAppeared(item: item, dataService: dataService) }
}
GridCard(item: item, isContextMenuOpen: $isContextMenuOpen, actionHandler: actionHandler)
}
NavigationLink(destination: LinkItemDetailView(
linkedItemObjectID: item.objectID,
isPDF: item.isPDF
)) {
GridCard(item: item, isContextMenuOpen: $isContextMenuOpen, actionHandler: actionHandler)
}
.aspectRatio(1.8, contentMode: .fill)
.scaleEffect(scale)
.onAppear {
Task { await viewModel.itemAppeared(item: item, dataService: dataService) }
}
.aspectRatio(1.0, contentMode: .fill)
.background(
Color.secondarySystemGroupedBackground
.onTapGesture {

View file

@ -20,7 +20,10 @@ struct LibraryFeatureCardNavigationLink: View {
@State var showFeatureActions = false
var body: some View {
NavigationLink(destination: EmptyView()) {
NavigationLink(destination: LinkItemDetailView(
linkedItemObjectID: item.objectID,
isPDF: item.isPDF
)) {
LibraryFeatureCard(item: item, viewer: dataService.currentViewer)
}
.confirmationDialog("", isPresented: $showFeatureActions) {

View file

@ -159,8 +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
viewModel.selectedItem = linkedItem
viewModel.linkIsActive = true
}
.onOpenURL { url in
viewModel.linkRequest = nil
@ -228,6 +228,19 @@ struct AnimatingCellHeight: AnimatableModifier {
var body: some View {
VStack(spacing: 0) {
if let linkRequest = viewModel.linkRequest {
NavigationLink(
destination: WebReaderLoadingContainer(requestID: linkRequest.serverID),
tag: linkRequest,
selection: $viewModel.linkRequest
) {
EmptyView()
}
}
NavigationLink(destination: LinkDestination(selectedItem: viewModel.selectedItem), isActive: $viewModel.linkIsActive) {
EmptyView()
}
if prefersListLayout || !enableGrid {
HomeFeedListView(listTitle: $listTitle, isListScrolled: $isListScrolled, prefersListLayout: $prefersListLayout, viewModel: viewModel)
} else {
@ -702,7 +715,7 @@ struct AnimatingCellHeight: AnimatableModifier {
.padding(.leading, 16)
.padding(.bottom, 25)
LazyVGrid(columns: [GridItem(.adaptive(minimum: 325), spacing: 16)], alignment: .leading, spacing: 16) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 325, maximum: 400), spacing: 16)], alignment: .center, spacing: 30) {
ForEach(viewModel.items) { item in
GridCardNavigationLink(
item: item,
@ -710,9 +723,9 @@ struct AnimatingCellHeight: AnimatableModifier {
isContextMenuOpen: $isContextMenuOpen,
viewModel: viewModel
)
.contextMenu {
libraryItemMenu(dataService: dataService, viewModel: viewModel, item: item)
}
// .contextMenu {
// libraryItemMenu(dataService: dataService, viewModel: viewModel, item: item)
// }
}
Spacer()
}

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

View file

@ -43,50 +43,14 @@ struct LibraryTabView: View {
)
var body: some View {
NavigationView {
// ZStack {
// NavigationLink(
// destination: LinkDestination(selectedItem: libraryViewModel.selectedItem),
// isActive: $libraryViewModel.linkIsActive
// ) {
// EmptyView()
// }
// TabView(selection: $selection) {
// BriefingView(
// articleId: "98e017a3-79d5-4049-97bc-ff170153792a"
// )
// .tabItem {
// Label {
// Text("Your Briefing")
// } icon: {
// Image.tabBriefing.padding(.trailing, 5)
// }
// }.tag(0)
HomeView(viewModel: libraryViewModel)
// .tabItem {
// Label {
// Text("Library")
// } icon: {
// Image.tabLibrary
// }
// }.tag(1)
// HomeView(viewModel: highlightsViewModel)
// .tabItem {
// Label {
// Text("Highlights")
// } icon: {
// Image.tabHighlights
// }
// }.tag(2)
// }
if #available(iOS 16.0, *) {
NavigationView {
HomeView(viewModel: libraryViewModel)
.navigationBarHidden(false)
}
} else {
// Fallback on earlier versions
EmptyView()
}
// }.introspectNavigationController { controller in
// print("INTROSPECTING CONTROLLER: ", controller.transitionCoordinator)
// controller.transitionCoordinator?.notifyWhenInteractionChanges { context in
// print("interaction changed: ", context)
// }
// }
}
}

View file

@ -89,15 +89,15 @@ struct LinkItemDetailView: View {
pdfContainerView
} else if let item = viewModel.item {
WebReaderContainerView(item: item, pop: { dismiss() })
.navigationBarHidden(true)
.lazyPop(pop: {
dismiss()
}, isEnabled: $isEnabled)
}
}
.task {
await viewModel.loadItem(linkedItemObjectID: linkedItemObjectID, dataService: dataService)
}
.navigationBarHidden(true)
.lazyPop(pop: {
dismiss()
}, isEnabled: $isEnabled)
}
@ViewBuilder private var pdfContainerView: some View {
@ -117,30 +117,3 @@ struct LinkItemDetailView: View {
}
}
}
//
// #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

@ -49,22 +49,22 @@ struct InnerRootView: View {
@ViewBuilder private var innerBody: some View {
if authenticator.isLoggedIn {
GeometryReader { geo in
PrimaryContentView()
#if os(iOS)
.miniPlayer()
.formSheet(isPresented: $viewModel.showNewFeaturePrimer,
modalSize: CGSize(width: geo.size.width * 0.66, height: geo.size.width * 0.66)) {
FeaturePrimer.recommendationsPrimer
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
viewModel.showNewFeaturePrimer = viewModel.shouldShowNewFeaturePrimer
viewModel.shouldShowNewFeaturePrimer = false
}
}
#endif
}
// GeometryReader { geo in
PrimaryContentView()
// #if os(iOS)
// .miniPlayer()
// .formSheet(isPresented: $viewModel.showNewFeaturePrimer,
// modalSize: CGSize(width: geo.size.width * 0.66, height: geo.size.width * 0.66)) {
// FeaturePrimer.recommendationsPrimer
// }
// .onAppear {
// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
// viewModel.showNewFeaturePrimer = viewModel.shouldShowNewFeaturePrimer
// viewModel.shouldShowNewFeaturePrimer = false
// }
// }
// #endif
// }
} else {
WelcomeView()
.accessibilityElement()

View file

@ -121,7 +121,11 @@ class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINa
from _: UIViewController,
to _: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
SlideAnimatedTransitioning()
if #available(iOS 17.0, *) {
return nil
} else {
return SlideAnimatedTransitioning()
}
}
func navigationController(_: UINavigationController,
@ -129,7 +133,7 @@ class SwipeRightToPopViewController<Content>: UIHostingController<Content>, UINa
-> UIViewControllerInteractiveTransitioning?
{
parentNavigationControllerToUse?.delegate = nil
// navigationController.delegate = nil
// navigationController.delegate = nil
if panGestureRecognizer.state == .began {
percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition()

View file

@ -34,8 +34,11 @@ import Views
public struct WebReaderLoadingContainer: View {
let requestID: String
@Environment(\.dismiss) private var dismiss
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioController: AudioController
@State var lazyPopIsEnabled = true
@StateObject var viewModel = WebReaderLoadingContainerViewModel()
public var body: some View {
@ -53,9 +56,11 @@ public struct WebReaderLoadingContainer: View {
}
#endif
} else {
WebReaderContainerView(item: item, pop: {})
WebReaderContainerView(item: item, pop: { dismiss() })
#if os(iOS)
.navigationViewStyle(.stack)
.navigationBarHidden(true)
.lazyPop(pop: { dismiss() }, isEnabled: $lazyPopIsEnabled)
#endif
.accentColor(.appGrayTextContrast)
.task { viewModel.trackReadEvent() }

View file

@ -69,20 +69,67 @@ public struct GridCard: View {
}
}
var imageBox: some View {
GeometryReader { geo in
ZStack(alignment: .bottomLeading) {
if let imageURL = item.imageURL {
AsyncImage(url: imageURL) { phase in
switch phase {
case .empty:
Color.systemBackground
.frame(maxWidth: .infinity, maxHeight: geo.size.height)
case let .success(image):
image.resizable()
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: geo.size.height)
.clipped()
case .failure:
fallbackImage
@unknown default:
// Since the AsyncImagePhase enum isn't frozen,
// we need to add this currently unused fallback
// to handle any new cases that might be added
// in the future:
Color.systemBackground
.frame(maxWidth: .infinity, maxHeight: geo.size.height)
}
}
} else {
fallbackImage
}
Color(hex: "#D9D9D9")?.opacity(0.65).frame(width: geo.size.width, height: 5)
Color(hex: "#FFD234").frame(width: geo.size.width * (item.readingProgress / 100), height: 5)
}
}
.cornerRadius(5)
}
var fallbackImage: some View {
GeometryReader { geo in
HStack {
Text(item.unwrappedTitle.prefix(1))
.font(Font.system(size: 128, weight: .bold))
.offset(CGSize(width: -48, height: 12))
.frame(alignment: .bottomLeading)
.foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0))
.background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom))
.frame(width: geo.size.width, height: geo.size.height)
}
}
public var body: some View {
GeometryReader { geo in
VStack(alignment: .leading, spacing: 0) {
// Progress Bar
Group {
ProgressView(value: min(abs(item.readingProgress) / 100, 1))
.tint(.appYellow48)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.bottom, 16)
}
// .onTapGesture { tapHandler() }
VStack {
// Title, Subtitle, Menu Button
imageBox
.frame(height: geo.size.height / 2.0)
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(item.unwrappedTitle)
@ -111,8 +158,9 @@ public struct GridCard: View {
// .onTapGesture { tapHandler() }
}
.frame(height: 30)
.padding(.horizontal)
.padding(.bottom, 16)
.padding(.horizontal, 10)
.padding(.bottom, 10)
.padding(.top, 10)
// Link description and image
HStack(alignment: .top) {
@ -123,27 +171,8 @@ public struct GridCard: View {
.multilineTextAlignment(.leading)
Spacer()
if let imageURL = item.imageURL {
AsyncImage(url: imageURL) { phase in
if let image = phase.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width / 3, height: (geo.size.width * 2) / 9)
.cornerRadius(3)
} else if phase.error != nil {
EmptyView()
} else {
Color.appButtonBackground
.frame(width: geo.size.width / 3, height: (geo.size.width * 2) / 9)
.cornerRadius(3)
}
}
}
}
.padding(.horizontal)
// .onTapGesture { tapHandler() }
.padding(.horizontal, 10)
// Category Labels
if item.hasLabels {
@ -154,7 +183,7 @@ public struct GridCard: View {
}
Spacer()
}
.padding(.horizontal)
.padding(.horizontal, 10)
}
// .onTapGesture { tapHandler() }
}
@ -165,7 +194,6 @@ public struct GridCard: View {
}
.padding(.horizontal, 0)
.padding(.top, 0)
.padding(.bottom, 8)
}
.contextMenu { contextMenuView }
}

View file

@ -149,7 +149,7 @@ public struct LibraryItemCard: View {
}
var imageBox: some View {
Group {
ZStack(alignment: .bottomLeading) {
if let imageURL = item.imageURL {
AsyncImage(url: imageURL) { phase in
if let image = phase.image {
@ -166,8 +166,24 @@ public struct LibraryItemCard: View {
.padding(.top, 2)
}
}
} else {
fallbackImage
}
}
.cornerRadius(5)
}
var fallbackImage: some View {
HStack {
Text(item.unwrappedTitle.prefix(1))
.font(Font.system(size: 32, weight: .bold))
.frame(alignment: .bottomLeading)
.foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0))
.background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom))
.frame(width: 74 * 0.666, height: 74)
}
var bylineStr: String {

View file

@ -1,12 +1,12 @@
#if os(iOS)
import UIKit
extension UINavigationController {
// Remove back button text
override open func viewWillLayoutSubviews() {
navigationBar.topItem?.backButtonDisplayMode = .minimal
}
}
#endif
// #if os(iOS)
//
// import UIKit
//
// extension UINavigationController {
// // Remove back button text
// override open func viewWillLayoutSubviews() {
// navigationBar.topItem?.backButtonDisplayMode = .minimal
// }
// }
//
// #endif