mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
fix context menus so all actions are included across devices
This commit is contained in:
parent
2868e95716
commit
b19ac104f4
7 changed files with 160 additions and 136 deletions
|
|
@ -1,44 +0,0 @@
|
|||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
struct FeedItemContextMenuView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
let item: FeedItem
|
||||
|
||||
@Binding var selectedLinkItem: FeedItem?
|
||||
@Binding var snoozePresented: Bool
|
||||
@Binding var itemToSnooze: FeedItem?
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
var body: some View {
|
||||
if !item.isArchived {
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: true)
|
||||
if item == selectedLinkItem {
|
||||
selectedLinkItem = nil
|
||||
}
|
||||
}
|
||||
}, label: { Label("Archive", systemImage: "archivebox") })
|
||||
} else {
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: false)
|
||||
}
|
||||
}, label: { Label("Unarchive", systemImage: "tray.and.arrow.down.fill") })
|
||||
}
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
itemToSnooze = item
|
||||
snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,13 +152,31 @@ import Views
|
|||
viewModel: viewModel
|
||||
)
|
||||
.contextMenu {
|
||||
FeedItemContextMenuView(
|
||||
item: item,
|
||||
selectedLinkItem: $selectedLinkItem,
|
||||
snoozePresented: $snoozePresented,
|
||||
itemToSnooze: $itemToSnooze,
|
||||
viewModel: viewModel
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
|
||||
}
|
||||
}, label: {
|
||||
Label(
|
||||
item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: { Label("Delete Link", systemImage: "trash") }
|
||||
)
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
itemToSnooze = item
|
||||
snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
link
|
||||
|
|
|
|||
|
|
@ -22,18 +22,54 @@ import Views
|
|||
List {
|
||||
Section {
|
||||
ForEach(viewModel.items) { item in
|
||||
FeedCardNavigationLink(
|
||||
item: item,
|
||||
searchQuery: searchQuery,
|
||||
selectedLinkItem: $selectedLinkItem,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.contextMenu {
|
||||
FeedItemContextMenuView(
|
||||
if #available(macOS 12.0, *) {
|
||||
FeedCardNavigationLink(
|
||||
item: item,
|
||||
searchQuery: searchQuery,
|
||||
selectedLinkItem: $selectedLinkItem,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
|
||||
}, label: {
|
||||
Label(
|
||||
item.isArchived ? "Unarchive" : "Archive",
|
||||
systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
},
|
||||
label: { Label("Delete Link", systemImage: "trash") }
|
||||
)
|
||||
if FeatureFlag.enableSnooze {
|
||||
Button {
|
||||
itemToSnooze = item
|
||||
snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Are you sure?", isPresented: $confirmationShown) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.id)
|
||||
self.itemToRemove = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) { self.itemToRemove = nil }
|
||||
}
|
||||
} else {
|
||||
FeedCardNavigationLink(
|
||||
item: item,
|
||||
searchQuery: searchQuery,
|
||||
selectedLinkItem: $selectedLinkItem,
|
||||
snoozePresented: $snoozePresented,
|
||||
itemToSnooze: $itemToSnooze,
|
||||
viewModel: viewModel
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@ public extension Color {
|
|||
#if os(iOS)
|
||||
static var systemBackground: Color { Color(.systemBackground) }
|
||||
static var systemPlaceholder: Color { Color(.placeholderText) }
|
||||
static var secondarySystemGroupedBackground: Color { Color(.secondarySystemGroupedBackground) }
|
||||
#elseif os(macOS)
|
||||
static var systemBackground: Color { Color(.windowBackgroundColor) }
|
||||
static var systemPlaceholder: Color { Color(.placeholderTextColor) }
|
||||
|
||||
// Just for compilation. secondarySystemGroupedBackground shouldn't be used on macOS
|
||||
static var secondarySystemGroupedBackground: Color { Color(.windowBackgroundColor) }
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,54 +2,6 @@ import Models
|
|||
import SwiftUI
|
||||
import Utils
|
||||
|
||||
public struct FeedCard: View {
|
||||
let item: FeedItem
|
||||
|
||||
public init(item: FeedItem) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(alignment: .top, spacing: 6) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(item.title)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.lineLimit(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
if let author = item.author {
|
||||
Text("By \(author)")
|
||||
.font(.appCaption)
|
||||
.foregroundColor(.appGrayText)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if let publisherURL = item.publisherHostname {
|
||||
Text(publisherURL)
|
||||
.font(.appCaption)
|
||||
.foregroundColor(.appGrayText)
|
||||
.underline()
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(0)
|
||||
|
||||
Group {
|
||||
if let imageURL = item.imageURL {
|
||||
AsyncImage(url: imageURL, isResizable: true)
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.frame(width: 80, height: 80)
|
||||
.cornerRadius(6)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 100, idealHeight: 100, maxHeight: 100)
|
||||
}
|
||||
}
|
||||
|
||||
public enum GridCardAction {
|
||||
case toggleArchiveStatus
|
||||
case delete
|
||||
|
|
@ -110,14 +62,20 @@ public struct GridCard: View {
|
|||
VStack(alignment: .leading, spacing: 16) {
|
||||
// Progress Bar
|
||||
Group {
|
||||
if #available(iOS 15.0, *) {
|
||||
ProgressView(value: min(abs(item.readingProgress) / 100, 1))
|
||||
.tint(.appYellow48)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
// Remove os check when dropping macOS 11
|
||||
#if os(iOS)
|
||||
if #available(iOS 15.0, *) {
|
||||
ProgressView(value: min(abs(item.readingProgress) / 100, 1))
|
||||
.tint(.appYellow48)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
ProgressView(value: max(abs(item.readingProgress) / 100, 1))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
#else
|
||||
ProgressView(value: max(abs(item.readingProgress) / 100, 1))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.onTapGesture { tapHandler() }
|
||||
|
||||
|
|
@ -199,31 +157,10 @@ public struct GridCard: View {
|
|||
}
|
||||
}
|
||||
.background(
|
||||
Color(.secondarySystemGroupedBackground)
|
||||
Color.secondarySystemGroupedBackground
|
||||
.onTapGesture { tapHandler() }
|
||||
)
|
||||
.cornerRadius(6)
|
||||
.contextMenu { contextMenuView }
|
||||
}
|
||||
}
|
||||
|
||||
struct TextChip: View {
|
||||
let text: String
|
||||
let color: Color
|
||||
let cornerRadius = 20.0
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(color)
|
||||
.lineLimit(1)
|
||||
.background(color.opacity(0.1))
|
||||
.cornerRadius(cornerRadius)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(color.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import Models
|
||||
import SwiftUI
|
||||
import Utils
|
||||
|
||||
public struct FeedCard: View {
|
||||
let item: FeedItem
|
||||
|
||||
public init(item: FeedItem) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(alignment: .top, spacing: 6) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(item.title)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.lineLimit(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
if let author = item.author {
|
||||
Text("By \(author)")
|
||||
.font(.appCaption)
|
||||
.foregroundColor(.appGrayText)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
if let publisherURL = item.publisherHostname {
|
||||
Text(publisherURL)
|
||||
.font(.appCaption)
|
||||
.foregroundColor(.appGrayText)
|
||||
.underline()
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(0)
|
||||
|
||||
Group {
|
||||
if let imageURL = item.imageURL {
|
||||
AsyncImage(url: imageURL, isResizable: true)
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
.frame(width: 80, height: 80)
|
||||
.cornerRadius(6)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 100, idealHeight: 100, maxHeight: 100)
|
||||
}
|
||||
}
|
||||
22
apple/OmnivoreKit/Sources/Views/TextChip.swift
Normal file
22
apple/OmnivoreKit/Sources/Views/TextChip.swift
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TextChip: View {
|
||||
let text: String
|
||||
let color: Color
|
||||
let cornerRadius = 20.0
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(color)
|
||||
.lineLimit(1)
|
||||
.background(color.opacity(0.1))
|
||||
.cornerRadius(cornerRadius)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(color.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue