2023-06-29 21:37:13 +00:00
|
|
|
import Foundation
|
2023-07-01 21:42:57 +00:00
|
|
|
import Models
|
2023-07-12 01:55:47 +00:00
|
|
|
import Services
|
2023-07-01 04:09:24 +00:00
|
|
|
import SwiftUI
|
2023-12-04 04:11:34 +00:00
|
|
|
import Transmission
|
2023-11-30 09:58:13 +00:00
|
|
|
import Utils
|
2023-07-24 04:11:21 +00:00
|
|
|
import Views
|
2023-07-01 04:09:24 +00:00
|
|
|
|
2023-10-30 07:34:25 +00:00
|
|
|
@MainActor
|
2023-07-01 04:09:24 +00:00
|
|
|
struct LibraryTabView: View {
|
2023-07-12 01:55:47 +00:00
|
|
|
@EnvironmentObject var dataService: DataService
|
2023-12-06 06:01:43 +00:00
|
|
|
@EnvironmentObject var audioController: AudioController
|
|
|
|
|
|
2023-12-08 10:31:09 +00:00
|
|
|
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
|
2023-11-30 09:58:13 +00:00
|
|
|
@AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox"
|
2024-01-09 06:20:04 +00:00
|
|
|
|
|
|
|
|
@State var isEditMode: EditMode = .inactive
|
2024-04-22 22:52:28 +00:00
|
|
|
@State var showLibraryDigest = false
|
2023-12-06 06:01:43 +00:00
|
|
|
@State var showExpandedAudioPlayer = false
|
2024-03-27 02:37:03 +00:00
|
|
|
@State var presentPushContainer = true
|
|
|
|
|
@State var pushLinkRequest: String?
|
2023-12-06 06:01:43 +00:00
|
|
|
|
2023-12-23 08:12:33 +00:00
|
|
|
private let syncManager = LibrarySyncManager()
|
|
|
|
|
|
2023-11-16 08:07:50 +00:00
|
|
|
@MainActor
|
|
|
|
|
public init() {
|
|
|
|
|
UITabBar.appearance().isHidden = true
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 09:35:08 +00:00
|
|
|
@StateObject private var inboxViewModel = HomeFeedViewModel(
|
2023-12-27 06:16:50 +00:00
|
|
|
filterKey: "lastSelectedFilter-inbox",
|
2023-11-30 09:58:13 +00:00
|
|
|
fetcher: LibraryItemFetcher(),
|
2023-12-27 06:16:50 +00:00
|
|
|
folderConfigs: [
|
|
|
|
|
"inbox": LibraryListConfig(
|
|
|
|
|
hasFeatureCards: true,
|
|
|
|
|
hasReadNowSection: true,
|
|
|
|
|
leadingSwipeActions: [.pin],
|
|
|
|
|
trailingSwipeActions: [.archive, .delete],
|
|
|
|
|
cardStyle: .library
|
|
|
|
|
)
|
|
|
|
|
]
|
2023-07-05 16:36:55 +00:00
|
|
|
)
|
|
|
|
|
|
2023-12-18 03:31:59 +00:00
|
|
|
@StateObject private var followingViewModel = HomeFeedViewModel(
|
2023-12-27 06:16:50 +00:00
|
|
|
filterKey: "lastSelectedFilter-following",
|
2023-11-30 09:58:13 +00:00
|
|
|
fetcher: LibraryItemFetcher(),
|
2023-12-27 06:16:50 +00:00
|
|
|
folderConfigs: [
|
|
|
|
|
"following": LibraryListConfig(
|
|
|
|
|
hasFeatureCards: false,
|
|
|
|
|
hasReadNowSection: false,
|
|
|
|
|
leadingSwipeActions: [.moveToInbox],
|
|
|
|
|
trailingSwipeActions: [.delete],
|
|
|
|
|
cardStyle: .library
|
|
|
|
|
)
|
|
|
|
|
]
|
2023-07-05 16:36:55 +00:00
|
|
|
)
|
|
|
|
|
|
2023-12-23 08:12:33 +00:00
|
|
|
var currentViewModel: HomeFeedViewModel? {
|
|
|
|
|
switch selectedTab {
|
|
|
|
|
case "inbox":
|
|
|
|
|
return inboxViewModel
|
|
|
|
|
case "following":
|
|
|
|
|
return followingViewModel
|
|
|
|
|
default:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-15 07:22:47 +00:00
|
|
|
|
2024-02-03 11:06:49 +00:00
|
|
|
@State var showOperationToast = false
|
|
|
|
|
@State var operationStatus: OperationStatus = .none
|
|
|
|
|
@State var operationMessage: String?
|
2024-02-05 09:16:12 +00:00
|
|
|
|
2024-04-15 12:27:36 +00:00
|
|
|
var displayTabs: [String] {
|
|
|
|
|
var res = [String]()
|
|
|
|
|
if !hideFollowingTab {
|
|
|
|
|
res.append("following")
|
|
|
|
|
}
|
|
|
|
|
res.append("inbox")
|
2024-04-22 22:57:08 +00:00
|
|
|
res.append("profile")
|
2024-04-15 12:27:36 +00:00
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-01 04:09:24 +00:00
|
|
|
var body: some View {
|
2023-12-06 06:01:43 +00:00
|
|
|
VStack(spacing: 0) {
|
2024-02-03 11:06:49 +00:00
|
|
|
WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $showOperationToast) {
|
2024-03-27 02:37:03 +00:00
|
|
|
OperationToast(operationMessage: $operationMessage,
|
2024-02-05 09:16:12 +00:00
|
|
|
showOperationToast: $showOperationToast,
|
|
|
|
|
operationStatus: $operationStatus)
|
2024-02-03 11:06:49 +00:00
|
|
|
} label: {
|
|
|
|
|
EmptyView()
|
|
|
|
|
}.buttonStyle(.plain)
|
|
|
|
|
|
2024-03-27 02:37:03 +00:00
|
|
|
if let pushLinkRequest = pushLinkRequest {
|
|
|
|
|
PresentationLink(
|
|
|
|
|
transition: PresentationLinkTransition.slide(
|
|
|
|
|
options: PresentationLinkTransition.SlideTransitionOptions(
|
|
|
|
|
edge: .trailing,
|
|
|
|
|
options: PresentationLinkTransition.Options(
|
|
|
|
|
modalPresentationCapturesStatusBarAppearance: true,
|
|
|
|
|
preferredPresentationBackgroundColor: ThemeManager.currentBgColor
|
|
|
|
|
))),
|
|
|
|
|
isPresented: $presentPushContainer,
|
|
|
|
|
destination: {
|
|
|
|
|
WebReaderLoadingContainer(requestID: pushLinkRequest)
|
|
|
|
|
.background(ThemeManager.currentBgColor)
|
|
|
|
|
.environmentObject(dataService)
|
|
|
|
|
.environmentObject(audioController)
|
|
|
|
|
}, label: {
|
|
|
|
|
EmptyView()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-16 08:07:50 +00:00
|
|
|
TabView(selection: $selectedTab) {
|
2023-12-08 10:31:09 +00:00
|
|
|
if !hideFollowingTab {
|
|
|
|
|
NavigationView {
|
2024-01-09 06:20:04 +00:00
|
|
|
HomeFeedContainerView(viewModel: followingViewModel, isEditMode: $isEditMode)
|
2023-12-08 10:31:09 +00:00
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
|
|
.navigationViewStyle(.stack)
|
|
|
|
|
}.tag("following")
|
|
|
|
|
}
|
2023-11-16 08:07:50 +00:00
|
|
|
|
2024-04-22 22:57:08 +00:00
|
|
|
NavigationView {
|
|
|
|
|
HomeFeedContainerView(viewModel: inboxViewModel, isEditMode: $isEditMode)
|
|
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
|
|
.navigationViewStyle(.stack)
|
|
|
|
|
}.tag("inbox")
|
|
|
|
|
|
|
|
|
|
NavigationView {
|
2024-04-23 00:40:50 +00:00
|
|
|
ProfileView()
|
|
|
|
|
.navigationViewStyle(.stack)
|
|
|
|
|
}.tag("profile")
|
2023-11-16 08:07:50 +00:00
|
|
|
}
|
2024-04-23 00:40:50 +00:00
|
|
|
|
2024-04-16 05:34:12 +00:00
|
|
|
if audioController.itemAudioProperties != nil {
|
|
|
|
|
MiniPlayerViewer()
|
2023-12-06 06:01:43 +00:00
|
|
|
.onTapGesture {
|
2024-04-22 22:52:28 +00:00
|
|
|
if audioController.itemAudioProperties?.audioItemType == .digest {
|
|
|
|
|
showLibraryDigest = true
|
|
|
|
|
} else {
|
|
|
|
|
showExpandedAudioPlayer = true
|
|
|
|
|
}
|
2023-12-06 06:01:43 +00:00
|
|
|
}
|
|
|
|
|
.padding(0)
|
|
|
|
|
Color(hex: "#3D3D3D")
|
|
|
|
|
.frame(height: 1)
|
|
|
|
|
.frame(maxWidth: .infinity)
|
|
|
|
|
}
|
2024-01-09 06:20:04 +00:00
|
|
|
if isEditMode != .active {
|
2024-04-15 12:27:36 +00:00
|
|
|
CustomTabBar(
|
|
|
|
|
displayTabs: displayTabs,
|
|
|
|
|
selectedTab: $selectedTab)
|
2024-01-09 06:20:04 +00:00
|
|
|
.padding(0)
|
|
|
|
|
}
|
2023-12-06 06:01:43 +00:00
|
|
|
}
|
2024-01-10 09:00:38 +00:00
|
|
|
.sheet(isPresented: $showExpandedAudioPlayer) {
|
2023-12-23 08:12:33 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
2023-07-01 04:09:24 +00:00
|
|
|
}
|
2024-04-22 22:52:28 +00:00
|
|
|
.fullScreenCover(isPresented: $showLibraryDigest) {
|
|
|
|
|
if #available(iOS 17.0, *) {
|
|
|
|
|
NavigationView {
|
|
|
|
|
FullScreenDigestView(dataService: dataService, audioController: audioController)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Text("Sorry digest is only available on iOS 17 and above")
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-04 04:11:34 +00:00
|
|
|
.navigationBarHidden(true)
|
2023-12-15 07:22:47 +00:00
|
|
|
.onReceive(NSNotification.performSyncPublisher) { _ in
|
|
|
|
|
Task {
|
2023-12-26 06:23:39 +00:00
|
|
|
await syncManager.syncUpdates(dataService: dataService)
|
2023-12-15 07:22:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-03-08 13:11:16 +00:00
|
|
|
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("PushLibraryItem"))) { notification in
|
|
|
|
|
guard let libraryItemId = notification.userInfo?["libraryItemId"] as? String else { return }
|
2024-03-27 02:37:03 +00:00
|
|
|
pushLinkRequest = libraryItemId
|
|
|
|
|
presentPushContainer = true
|
2024-03-08 13:11:16 +00:00
|
|
|
}
|
2023-12-18 03:31:59 +00:00
|
|
|
.onOpenURL { url in
|
2023-12-18 09:35:08 +00:00
|
|
|
inboxViewModel.linkRequest = nil
|
2024-01-10 13:32:49 +00:00
|
|
|
|
|
|
|
|
withoutAnimation {
|
|
|
|
|
NotificationCenter.default.post(Notification(name: Notification.Name("PopToRoot")))
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 03:31:59 +00:00
|
|
|
if let deepLink = DeepLink.make(from: url) {
|
|
|
|
|
switch deepLink {
|
|
|
|
|
case let .search(query):
|
2023-12-18 09:35:08 +00:00
|
|
|
inboxViewModel.searchTerm = query
|
2023-12-18 03:31:59 +00:00
|
|
|
case let .savedSearch(named):
|
2023-12-18 09:35:08 +00:00
|
|
|
if let filter = inboxViewModel.findFilter(dataService, named: named) {
|
|
|
|
|
inboxViewModel.appliedFilter = filter
|
2023-12-18 03:31:59 +00:00
|
|
|
}
|
|
|
|
|
case let .webAppLinkRequest(requestID):
|
2024-01-10 13:32:49 +00:00
|
|
|
|
2023-12-18 03:31:59 +00:00
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
|
2024-03-08 13:11:16 +00:00
|
|
|
inboxViewModel.pushLinkedRequest(request: LinkRequest(id: UUID(), serverID: requestID))
|
2023-12-18 03:31:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
selectedTab = "inbox"
|
|
|
|
|
}
|
2024-05-10 13:36:55 +00:00
|
|
|
.task {
|
|
|
|
|
await dataService.tryUpdateFeatureFlags()
|
|
|
|
|
}
|
2023-07-01 04:09:24 +00:00
|
|
|
}
|
|
|
|
|
}
|