mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1187 from omnivore-app/fix/recently-read-nav-pop
Fix: iOS Nav Pop
This commit is contained in:
commit
4d34142d9d
4 changed files with 206 additions and 190 deletions
|
|
@ -12,32 +12,21 @@ struct FeedCardNavigationLink: View {
|
|||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
var body: some View {
|
||||
let destination = LinkItemDetailView(
|
||||
linkedItemObjectID: item.objectID,
|
||||
isPDF: item.isPDF
|
||||
)
|
||||
#if os(iOS)
|
||||
let modifiedDestination = destination
|
||||
.navigationTitle("")
|
||||
#else
|
||||
let modifiedDestination = destination
|
||||
#endif
|
||||
|
||||
return ZStack {
|
||||
NavigationLink(
|
||||
destination: modifiedDestination,
|
||||
tag: item.objectID,
|
||||
selection: $viewModel.selectedLinkItem
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.onAppear {
|
||||
Task { await viewModel.itemAppeared(item: item, dataService: dataService, audioSession: audioSession) }
|
||||
}
|
||||
FeedCard(item: item) {
|
||||
viewModel.selectedLinkItem = item.objectID
|
||||
ZStack {
|
||||
Button(
|
||||
action: {
|
||||
viewModel.selectedItem = item
|
||||
viewModel.linkIsActive = true
|
||||
}) {
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.onAppear {
|
||||
Task { await viewModel.itemAppeared(item: item, dataService: dataService, audioSession: audioSession) }
|
||||
}
|
||||
FeedCard(item: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,28 +49,14 @@ struct GridCardNavigationLink: View {
|
|||
scale = 0.95
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150)) {
|
||||
scale = 1.0
|
||||
viewModel.selectedLinkItem = item.objectID
|
||||
viewModel.selectedItem = item
|
||||
viewModel.linkIsActive = true
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let destination = LinkItemDetailView(
|
||||
linkedItemObjectID: item.objectID,
|
||||
isPDF: item.isPDF
|
||||
)
|
||||
#if os(iOS)
|
||||
let modifiedDestination = destination
|
||||
.navigationTitle("")
|
||||
#else
|
||||
let modifiedDestination = destination
|
||||
#endif
|
||||
|
||||
return ZStack {
|
||||
NavigationLink(
|
||||
destination: modifiedDestination,
|
||||
tag: item.objectID,
|
||||
selection: $viewModel.selectedLinkItem
|
||||
) {
|
||||
ZStack {
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
EmptyView()
|
||||
}
|
||||
GridCard(item: item, isContextMenuOpen: $isContextMenuOpen, actionHandler: actionHandler, tapAction: {
|
||||
|
|
|
|||
|
|
@ -111,11 +111,12 @@ import Views
|
|||
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.selectedLinkItem = linkedItem.objectID
|
||||
viewModel.selectedItem = linkedItem
|
||||
viewModel.linkIsActive = true
|
||||
}
|
||||
.onReceive(NSNotification.pushReaderItemPublisher) { notification in
|
||||
if let objectID = notification.userInfo?["objectID"] as? NSManagedObjectID {
|
||||
viewModel.handleReaderItemNotification(objectID: objectID)
|
||||
viewModel.handleReaderItemNotification(objectID: objectID, dataService: dataService)
|
||||
}
|
||||
}
|
||||
.onOpenURL { url in
|
||||
|
|
@ -268,115 +269,123 @@ import Views
|
|||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
ForEach(viewModel.items) { item in
|
||||
FeedCardNavigationLink(
|
||||
item: item,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.contextMenu {
|
||||
Button(
|
||||
action: { viewModel.itemUnderTitleEdit = item },
|
||||
label: { Label("Edit Title/Description", systemImage: "textbox") }
|
||||
ZStack {
|
||||
NavigationLink(
|
||||
destination: LinkDestination(selectedItem: viewModel.selectedItem),
|
||||
isActive: $viewModel.linkIsActive
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
List {
|
||||
Section {
|
||||
ForEach(viewModel.items) { item in
|
||||
FeedCardNavigationLink(
|
||||
item: item,
|
||||
viewModel: viewModel
|
||||
)
|
||||
Button(
|
||||
action: { viewModel.itemUnderLabelEdit = item },
|
||||
label: { Label("Edit Labels", systemImage: "tag") }
|
||||
)
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(
|
||||
dataService: dataService,
|
||||
objectID: item.objectID,
|
||||
archived: !item.isArchived
|
||||
)
|
||||
}
|
||||
}, label: {
|
||||
Label(
|
||||
item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
.contextMenu {
|
||||
Button(
|
||||
action: { viewModel.itemUnderTitleEdit = item },
|
||||
label: { Label("Edit Title/Description", systemImage: "textbox") }
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: { Label("Delete", systemImage: "trash") }
|
||||
)
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
Button(
|
||||
action: { viewModel.downloadAudio(audioSession: audioSession, item: item) },
|
||||
label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") }
|
||||
)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !item.isArchived {
|
||||
Button {
|
||||
Button(
|
||||
action: { viewModel.itemUnderLabelEdit = item },
|
||||
label: { Label("Edit Labels", systemImage: "tag") }
|
||||
)
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true)
|
||||
viewModel.setLinkArchived(
|
||||
dataService: dataService,
|
||||
objectID: item.objectID,
|
||||
archived: !item.isArchived
|
||||
)
|
||||
}
|
||||
} label: {
|
||||
Label("Archive", systemImage: "archivebox")
|
||||
}.tint(.green)
|
||||
} else {
|
||||
Button {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: false)
|
||||
}
|
||||
} label: {
|
||||
Label("Unarchive", systemImage: "tray.and.arrow.down.fill")
|
||||
}.tint(.indigo)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(
|
||||
role: .destructive,
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
)
|
||||
}.alert("Are you sure?", isPresented: $confirmationShown) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID)
|
||||
}, label: {
|
||||
Label(
|
||||
item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: { Label("Delete", systemImage: "trash") }
|
||||
)
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
self.itemToRemove = nil
|
||||
Button(
|
||||
action: { viewModel.downloadAudio(audioSession: audioSession, item: item) },
|
||||
label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") }
|
||||
)
|
||||
}
|
||||
Button("Cancel", role: .cancel) { self.itemToRemove = nil }
|
||||
}
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}.tint(.appYellow48)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !item.isArchived {
|
||||
Button {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true)
|
||||
}
|
||||
} label: {
|
||||
Label("Archive", systemImage: "archivebox")
|
||||
}.tint(.green)
|
||||
} else {
|
||||
Button {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: false)
|
||||
}
|
||||
} label: {
|
||||
Label("Unarchive", systemImage: "tray.and.arrow.down.fill")
|
||||
}.tint(.indigo)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(
|
||||
role: .destructive,
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
)
|
||||
}.alert("Are you sure?", isPresented: $confirmationShown) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID)
|
||||
}
|
||||
}
|
||||
self.itemToRemove = nil
|
||||
}
|
||||
Button("Cancel", role: .cancel) { self.itemToRemove = nil }
|
||||
}
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}.tint(.appYellow48)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
LoadingSection()
|
||||
if viewModel.isLoading {
|
||||
LoadingSection()
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -411,47 +420,56 @@ import Views
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 325), spacing: 16)], spacing: 16) {
|
||||
ForEach(viewModel.items) { item in
|
||||
GridCardNavigationLink(
|
||||
item: item,
|
||||
actionHandler: { contextMenuActionHandler(item: item, action: $0) },
|
||||
isContextMenuOpen: $isContextMenuOpen,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.alert("Are you sure?", isPresented: $confirmationShown) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID)
|
||||
}
|
||||
}
|
||||
self.itemToRemove = nil
|
||||
}
|
||||
Button("Cancel", role: .cancel) { self.itemToRemove = nil }
|
||||
}
|
||||
ZStack {
|
||||
ScrollView {
|
||||
NavigationLink(
|
||||
destination: LinkDestination(selectedItem: viewModel.selectedItem),
|
||||
isActive: $viewModel.linkIsActive
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
GeometryReader {
|
||||
Color(.systemGroupedBackground).preference(
|
||||
key: ScrollViewOffsetPreferenceKey.self,
|
||||
value: $0.frame(in: .global).origin.y
|
||||
)
|
||||
}
|
||||
)
|
||||
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { offset in
|
||||
DispatchQueue.main.async {
|
||||
if !viewModel.isLoading, offset > 240 {
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.items.isEmpty, viewModel.isLoading {
|
||||
LoadingSection()
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 325), spacing: 16)], spacing: 16) {
|
||||
ForEach(viewModel.items) { item in
|
||||
GridCardNavigationLink(
|
||||
item: item,
|
||||
actionHandler: { contextMenuActionHandler(item: item, action: $0) },
|
||||
isContextMenuOpen: $isContextMenuOpen,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.alert("Are you sure?", isPresented: $confirmationShown) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID)
|
||||
}
|
||||
}
|
||||
self.itemToRemove = nil
|
||||
}
|
||||
Button("Cancel", role: .cancel) { self.itemToRemove = nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
GeometryReader {
|
||||
Color(.systemGroupedBackground).preference(
|
||||
key: ScrollViewOffsetPreferenceKey.self,
|
||||
value: $0.frame(in: .global).origin.y
|
||||
)
|
||||
}
|
||||
)
|
||||
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { offset in
|
||||
DispatchQueue.main.async {
|
||||
if !viewModel.isLoading, offset > 240 {
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.items.isEmpty, viewModel.isLoading {
|
||||
LoadingSection()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -480,3 +498,27 @@ struct ScrollViewOffsetPreferenceKey: PreferenceKey {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct LinkDestination: View {
|
||||
let selectedItem: LinkedItem?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let selectedItem = selectedItem {
|
||||
let destination = LinkItemDetailView(
|
||||
linkedItemObjectID: selectedItem.objectID,
|
||||
isPDF: selectedItem.isPDF
|
||||
)
|
||||
#if os(iOS)
|
||||
let modifiedDestination = destination
|
||||
.navigationTitle("")
|
||||
#else
|
||||
let modifiedDestination = destination
|
||||
#endif
|
||||
modifiedDestination
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ import Views
|
|||
@Published var negatedLabels = [LinkedItemLabel]()
|
||||
@Published var snoozePresented = false
|
||||
@Published var itemToSnoozeID: String?
|
||||
@Published var selectedLinkItem: NSManagedObjectID?
|
||||
@Published var linkRequest: LinkRequest?
|
||||
@Published var showLoadingBar = false
|
||||
@Published var appliedSort = LinkedItemSort.newest.rawValue
|
||||
|
||||
@Published var selectedItem: LinkedItem?
|
||||
@Published var linkIsActive = false
|
||||
|
||||
@AppStorage(UserDefaultKey.audioInfoAlertShown.rawValue) var showAudioInfoAlert = false
|
||||
|
||||
@AppStorage(UserDefaultKey.lastSelectedLinkedItemFilter.rawValue) var appliedFilter = LinkedItemFilter.inbox.rawValue
|
||||
|
|
@ -34,8 +37,9 @@ import Views
|
|||
from: Date(timeIntervalSinceReferenceDate: 0)
|
||||
)
|
||||
|
||||
func handleReaderItemNotification(objectID: NSManagedObjectID) {
|
||||
selectedLinkItem = objectID
|
||||
func handleReaderItemNotification(objectID: NSManagedObjectID, dataService: DataService) {
|
||||
selectedItem = dataService.viewContext.object(with: objectID) as? LinkedItem
|
||||
linkIsActive = true
|
||||
}
|
||||
|
||||
var cursor: String?
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@ import SwiftUI
|
|||
import Utils
|
||||
|
||||
public struct FeedCard: View {
|
||||
let tapHandler: () -> Void
|
||||
@ObservedObject var item: LinkedItem
|
||||
|
||||
public init(item: LinkedItem, tapHandler: @escaping () -> Void) {
|
||||
public init(item: LinkedItem) {
|
||||
self.item = item
|
||||
self.tapHandler = tapHandler
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
|
@ -77,9 +75,6 @@ public struct FeedCard: View {
|
|||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.onTapGesture {
|
||||
tapHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 10)
|
||||
|
|
|
|||
Loading…
Reference in a new issue