diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 073510bce..b891df777 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -430,7 +430,9 @@ struct AnimatingCellHeight: AnimatableModifier { } List { - if !viewModel.hideFeatureSection, viewModel.items.count > 0, + if viewModel.listConfig.hasFeatureCards, + !viewModel.hideFeatureSection, + viewModel.items.count > 0, viewModel.searchTerm.isEmpty, viewModel.selectedLabels.isEmpty, viewModel.negatedLabels.isEmpty { @@ -449,41 +451,16 @@ struct AnimatingCellHeight: AnimatableModifier { .contextMenu { menuItems(for: item) } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - if !item.isArchived { - Button(action: { - withAnimation(.linear(duration: 0.4)) { - viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true) - } - }, label: { - Label("Archive", systemImage: "archivebox") - }).tint(.green) - } else { - Button(action: { - 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: .leading, allowsFullSwipe: true) { + ForEach(viewModel.listConfig.leadingSwipeActions, id: \.self) { action in + swipeActionButton(action: action, item: item) + } + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + ForEach(viewModel.listConfig.trailingSwipeActions, id: \.self) { action in + swipeActionButton(action: action, item: item) } - Button( - action: { - itemToRemove = item - confirmationShown = true - }, - label: { - Image(systemName: "trash") - } - ).tint(.red) } -// .swipeActions(edge: .leading, allowsFullSwipe: true) { -// Button { -// viewModel.pinItem(dataService, item) -// } label: { -// Label { Text("Pin") } icon: { Image(systemName: "pin.fill") } -// }.tint(Color(hex: "#0A84FF")) -// } } } .padding(0) @@ -510,6 +487,50 @@ struct AnimatingCellHeight: AnimatableModifier { Button(LocalText.cancelGeneric, role: .cancel) { self.showHideFeatureAlert = false } } } + + func swipeActionButton(action: SwipeAction, item: LinkedItem) -> AnyView { + switch action { + case .pin: + return AnyView(Button(action: { + viewModel.addLabel(dataService: dataService, item: item, label: "Pinned") + }, label: { + VStack { + Image(systemName: "pin.fill") + .rotationEffect(Angle(degrees: 90)) + Text("Pin") + } + }).tint(Color(hex: "#0A84FF"))) + case .archive: + return AnyView(Button(action: { + withAnimation(.linear(duration: 0.4)) { + viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: !item.isArchived) + } + }, label: { + Label(!item.isArchived ? "Archive" : "Unarchive", systemImage: !item.isArchived ? "archivebox" : "tray.and.arrow.down.fill") + }) + .tint(!item.isArchived ? .green : .indigo)) + case .delete: + return AnyView(Button( + action: { + itemToRemove = item + confirmationShown = true + }, + label: { + Label("Delete", systemImage: "trash") + } + ).tint(.red)) + case .moveToInbox: + return AnyView(Button( + action: { + viewModel.addLabel(dataService: dataService, item: item, label: "Inbox") + + }, + label: { + Label("Move to Inbox", systemImage: "tray.fill") + } + ).tint(Color(hex: "#0A84FF"))) + } + } } struct HomeFeedGridView: View { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 279d1325a..9b94b5a63 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -35,6 +35,8 @@ import Views @Published var showCommunityModal = false @Published var featureItems = [LinkedItem]() + @Published var listConfig: LibraryListConfig + var cursor: String? // These are used to make sure we handle search result @@ -48,6 +50,11 @@ import Views @AppStorage(UserDefaultKey.lastSelectedLinkedItemFilter.rawValue) var appliedFilter = LinkedItemFilter.inbox.rawValue @AppStorage(UserDefaultKey.lastSelectedFeaturedItemFilter.rawValue) var featureFilter = FeaturedItemFilter.continueReading.rawValue + init(listConfig: LibraryListConfig) { + self.listConfig = listConfig + super.init() + } + func setItems(_ items: [LinkedItem]) { self.items = items } @@ -56,6 +63,7 @@ import Views Task { if let filter = filter { featureItems = await loadFeatureItems(dataService: dataService, predicate: filter.predicate, sort: filter.sortDescriptor) + print("updated feature items: ", featureItems.count) } else { featureItems = [] } @@ -247,8 +255,6 @@ import Views showLoadingBar = false } - func pinItem(dataService _: DataService, item _: LinkedItem) async {} - func loadFeatureItems(dataService: DataService, predicate: NSPredicate, sort: NSSortDescriptor) async -> [LinkedItem] { let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() fetchRequest.fetchLimit = 25 @@ -322,6 +328,15 @@ import Views dataService.removeLink(objectID: objectID) } + func addLabel(dataService: DataService, item: LinkedItem, label: String) { + Task { + if let label = LinkedItemLabel.named(label, inContext: dataService.viewContext) { + dataService.updateItemLabels(itemID: item.unwrappedID, labelIDs: [label.unwrappedID]) + } + updateFeatureFilter(dataService: dataService, filter: FeaturedItemFilter(rawValue: featureFilter)) + } + } + func snoozeUntil(dataService: DataService, linkId: String, until: Date, successMessage: String?) async { isLoading = true diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListConfig.swift b/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListConfig.swift new file mode 100644 index 000000000..46200aa35 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/Home/LibraryListConfig.swift @@ -0,0 +1,20 @@ +// +// File.swift +// +// +// Created by Jackson Harper on 7/1/23. +// + +import Foundation + +enum CardStyle { + case library + case highlights +} + +struct LibraryListConfig { + var hasFeatureCards = false + var leadingSwipeActions = [SwipeAction]() + var trailingSwipeActions = [SwipeAction]() + var cardStyle = CardStyle.library +} diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/SwipeAction.swift b/apple/OmnivoreKit/Sources/App/Views/Home/SwipeAction.swift new file mode 100644 index 000000000..240b8a777 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/Home/SwipeAction.swift @@ -0,0 +1,18 @@ +// +// SwipeAction.swift +// +// +// Created by Jackson Harper on 7/1/23. +// + +import Foundation +import Models +import Services +import SwiftUI + +enum SwipeAction: String, CaseIterable { + case pin + case archive + case delete + case moveToInbox +} diff --git a/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift b/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift index 4ea972e51..08347b872 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift @@ -10,19 +10,44 @@ import Models import SwiftUI struct LibraryTabView: View { - @StateObject private var viewModel = HomeFeedViewModel() + @StateObject private var subViewModel = HomeFeedViewModel( + listConfig: LibraryListConfig( + hasFeatureCards: false, + leadingSwipeActions: [.moveToInbox], + trailingSwipeActions: [.delete, .archive], + cardStyle: .library + ) + ) + + @StateObject private var libraryViewModel = HomeFeedViewModel( + listConfig: LibraryListConfig( + hasFeatureCards: true, + leadingSwipeActions: [.pin], + trailingSwipeActions: [.delete, .archive], + cardStyle: .library + ) + ) + + @StateObject private var highlightsViewModel = HomeFeedViewModel( + listConfig: LibraryListConfig( + hasFeatureCards: true, + leadingSwipeActions: [.pin], + trailingSwipeActions: [.delete, .archive], + cardStyle: .highlights + ) + ) var body: some View { NavigationView { ZStack { NavigationLink( - destination: LinkDestination(selectedItem: viewModel.selectedItem), - isActive: $viewModel.linkIsActive + destination: LinkDestination(selectedItem: libraryViewModel.selectedItem), + isActive: $libraryViewModel.linkIsActive ) { EmptyView() } TabView { - HomeView(viewModel: viewModel) + HomeView(viewModel: subViewModel) .tabItem { Label { Text("Subscriptions") @@ -30,7 +55,7 @@ struct LibraryTabView: View { Image.tabSubscriptions } } - HomeView(viewModel: viewModel) + HomeView(viewModel: libraryViewModel) .tabItem { Label { Text("Library") @@ -38,7 +63,7 @@ struct LibraryTabView: View { Image.tabLibrary } } - HomeView(viewModel: viewModel) + HomeView(viewModel: highlightsViewModel) .tabItem { Label { Text("Highlights") diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift index 537521fde..60268e8c4 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItemLabel.swift @@ -60,6 +60,21 @@ extension LinkedItemLabel { public var unwrappedID: String { id ?? "" } public var unwrappedName: String { name ?? "" } + public static func named(_ name: String, inContext context: NSManagedObjectContext) -> LinkedItemLabel? { + let fetchRequest: NSFetchRequest = LinkedItemLabel.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "name == %@", name + ) + + var label: LinkedItemLabel? + + context.performAndWait { + label = (try? context.fetch(fetchRequest))?.first + } + + return label + } + static func lookup(byID id: String, inContext context: NSManagedObjectContext) -> LinkedItemLabel? { let fetchRequest: NSFetchRequest = LinkedItemLabel.fetchRequest() fetchRequest.predicate = NSPredicate(