Files
omnivore/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift
Jackson Harper 85caaa7ea3 Use transmission for snackbar, fix issue with ownership of currently viewed item
The list object of the root library view had ownership of the
currently selected item, so object modifications that removed
the item from the current library list (like move or archive)
could cause the object to be released and the current screen
to continue operating on an invalid object.
2024-02-06 11:43:43 +08:00

174 lines
5.1 KiB
Swift

//
// File.swift
//
//
// Created by Jackson Harper on 6/29/23.
//
import Foundation
import Models
import Services
import SwiftUI
import Transmission
import Utils
import Views
@MainActor
struct LibraryTabView: View {
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioController: AudioController
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox"
@State var isEditMode: EditMode = .inactive
@State var showExpandedAudioPlayer = false
private let syncManager = LibrarySyncManager()
@MainActor
public init() {
UITabBar.appearance().isHidden = true
}
@StateObject private var inboxViewModel = HomeFeedViewModel(
filterKey: "lastSelectedFilter-inbox",
fetcher: LibraryItemFetcher(),
folderConfigs: [
"inbox": LibraryListConfig(
hasFeatureCards: true,
hasReadNowSection: true,
leadingSwipeActions: [.pin],
trailingSwipeActions: [.archive, .delete],
cardStyle: .library
)
]
)
@StateObject private var followingViewModel = HomeFeedViewModel(
filterKey: "lastSelectedFilter-following",
fetcher: LibraryItemFetcher(),
folderConfigs: [
"following": LibraryListConfig(
hasFeatureCards: false,
hasReadNowSection: false,
leadingSwipeActions: [.moveToInbox],
trailingSwipeActions: [.delete],
cardStyle: .library
)
]
)
var currentViewModel: HomeFeedViewModel? {
switch selectedTab {
case "inbox":
return inboxViewModel
case "following":
return followingViewModel
default:
return nil
}
}
@State var showOperationToast = false
@State var operationStatus: OperationStatus = .none
@State var operationMessage: String?
var body: some View {
VStack(spacing: 0) {
WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $showOperationToast) {
OperationToast(operationMessage: $operationMessage, showOperationToast: $showOperationToast, operationStatus: $operationStatus)
} label: {
EmptyView()
}.buttonStyle(.plain)
TabView(selection: $selectedTab) {
if !hideFollowingTab {
NavigationView {
HomeFeedContainerView(viewModel: followingViewModel, isEditMode: $isEditMode)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("following")
}
NavigationView {
HomeFeedContainerView(viewModel: inboxViewModel, isEditMode: $isEditMode)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("inbox")
NavigationView {
ProfileView()
.navigationViewStyle(.stack)
}.tag("profile")
}
if let audioProperties = audioController.itemAudioProperties {
MiniPlayerViewer(itemAudioProperties: audioProperties)
.onTapGesture {
showExpandedAudioPlayer = true
}
.padding(0)
Color(hex: "#3D3D3D")
.frame(height: 1)
.frame(maxWidth: .infinity)
}
if isEditMode != .active {
CustomTabBar(selectedTab: $selectedTab, hideFollowingTab: hideFollowingTab)
.padding(0)
}
}
.sheet(isPresented: $showExpandedAudioPlayer) {
ExpandedAudioPlayer(
delete: {
showExpandedAudioPlayer = false
audioController.stop()
currentViewModel?.removeLibraryItem(dataService: dataService, objectID: $0)
},
archive: {
showExpandedAudioPlayer = false
audioController.stop()
currentViewModel?.setLinkArchived(dataService: dataService, objectID: $0, archived: true)
},
viewArticle: { itemID in
if let article = try? dataService.viewContext.existingObject(with: itemID) as? Models.LibraryItem {
currentViewModel?.pushFeedItem(item: article)
}
}
)
}
.navigationBarHidden(true)
.onReceive(NSNotification.performSyncPublisher) { _ in
Task {
await syncManager.syncUpdates(dataService: dataService)
}
}
.onOpenURL { url in
inboxViewModel.linkRequest = nil
withoutAnimation {
NotificationCenter.default.post(Notification(name: Notification.Name("PopToRoot")))
}
if let deepLink = DeepLink.make(from: url) {
switch deepLink {
case let .search(query):
inboxViewModel.searchTerm = query
case let .savedSearch(named):
if let filter = inboxViewModel.findFilter(dataService, named: named) {
inboxViewModel.appliedFilter = filter
}
case let .webAppLinkRequest(requestID):
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
withoutAnimation {
inboxViewModel.linkRequest = LinkRequest(id: UUID(), serverID: requestID)
inboxViewModel.presentWebContainer = true
}
}
}
}
selectedTab = "inbox"
}
}
}