Work on multi tab iOS client

This commit is contained in:
Jackson Harper 2023-07-05 09:36:55 -07:00
parent 7cf0c37c88
commit 2d021c2683
6 changed files with 156 additions and 42 deletions

View file

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

View file

@ -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<Models.LinkedItem> = 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

View file

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

View file

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

View file

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

View file

@ -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<Models.LinkedItemLabel> = 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<Models.LinkedItemLabel> = LinkedItemLabel.fetchRequest()
fetchRequest.predicate = NSPredicate(