diff --git a/apple/OmnivoreKit/Sources/Binders/PrimaryContentCategory.swift b/apple/OmnivoreKit/Sources/Binders/PrimaryContentCategory.swift index ea2e75b33..60ddf6863 100644 --- a/apple/OmnivoreKit/Sources/Binders/PrimaryContentCategory.swift +++ b/apple/OmnivoreKit/Sources/Binders/PrimaryContentCategory.swift @@ -1,9 +1,8 @@ import SwiftUI import Views -// TODO: maybe this can be removed?? enum PrimaryContentCategory: Identifiable, Hashable, Equatable { - case feed(viewModel: HomeFeedViewModel) + case feed case profile static func == (lhs: PrimaryContentCategory, rhs: PrimaryContentCategory) -> Bool { @@ -38,8 +37,8 @@ enum PrimaryContentCategory: Identifiable, Hashable, Equatable { @ViewBuilder var destinationView: some View { switch self { - case let .feed(viewModel: viewModel): - HomeFeedView(viewModel: viewModel) + case .feed: + HomeFeedView() case .profile: ProfileContainerView() } diff --git a/apple/OmnivoreKit/Sources/Binders/RootViewModel.swift b/apple/OmnivoreKit/Sources/Binders/RootViewModel.swift index 662d83dbe..72ceaf526 100644 --- a/apple/OmnivoreKit/Sources/Binders/RootViewModel.swift +++ b/apple/OmnivoreKit/Sources/Binders/RootViewModel.swift @@ -145,7 +145,7 @@ public struct RootView: View { @ViewBuilder private var innerBody: some View { if authenticator.isLoggedIn { - PrimaryContentView(services: viewModel.services) + PrimaryContentView() .onAppear { viewModel.triggerPushNotificationRequestIfNeeded() } diff --git a/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift b/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift index 83bedbee2..f87f7eadc 100644 --- a/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift +++ b/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift @@ -6,40 +6,39 @@ import UserNotifications import Utils import Views -extension HomeFeedViewModel { - static func make(services: Services) -> HomeFeedViewModel { - let viewModel = HomeFeedViewModel() - viewModel.bind(services: services) - viewModel.loadItems(dataService: services.dataService, searchQuery: nil, isRefresh: false) - return viewModel - } +final class HomeFeedViewModel: ObservableObject { + var currentDetailViewModel: LinkItemDetailViewModel? - func bind(services: Services) { - performActionSubject.sink { [weak self] action in - switch action { - case let .refreshItems(query: query): - self?.loadItems(dataService: services.dataService, searchQuery: query, isRefresh: true) - case let .loadItems(query): - self?.loadItems(dataService: services.dataService, searchQuery: query, isRefresh: false) - case let .archive(linkId): - self?.setLinkArchived(dataService: services.dataService, linkId: linkId, archived: true) - case let .unarchive(linkId): - self?.setLinkArchived(dataService: services.dataService, linkId: linkId, archived: false) - case let .remove(linkId): - self?.removeLink(dataService: services.dataService, linkId: linkId) - case let .snooze(linkId, until, successMessage): - self?.snoozeUntil( - dataService: services.dataService, - linkId: linkId, - until: until, - successMessage: successMessage - ) - } + @Published var items = [FeedItem]() + @Published var isLoading = false + @Published var showPushNotificationPrimer = false + var cursor: String? + + // These are used to make sure we handle search result + // responses in the right order + var searchIdx = 0 + var receivedIdx = 0 + + var subscriptions = Set() + + init() {} + + func itemAppeared(item: FeedItem, searchQuery: String, dataService: DataService) { + if isLoading { return } + let itemIndex = items.firstIndex(where: { $0.id == item.id }) + let thresholdIndex = items.index(items.endIndex, offsetBy: -5) + + // Check if user has scrolled to the last five items in the list + if let itemIndex = itemIndex, itemIndex > thresholdIndex, items.count < thresholdIndex + 10 { + loadItems(dataService: dataService, searchQuery: searchQuery, isRefresh: false) } - .store(in: &subscriptions) } - private func loadItems(dataService: DataService, searchQuery: String?, isRefresh: Bool) { + func pushFeedItem(item: FeedItem) { + items.insert(item, at: 0) + } + + func loadItems(dataService: DataService, searchQuery: String?, isRefresh: Bool) { // Clear offline highlights since we'll be populating new FeedItems with the correct highlights set dataService.clearHighlights() @@ -90,7 +89,7 @@ extension HomeFeedViewModel { .store(in: &subscriptions) } - private func setLinkArchived(dataService: DataService, linkId: String, archived: Bool) { + func setLinkArchived(dataService: DataService, linkId: String, archived: Bool) { isLoading = true startNetworkActivityIndicator() @@ -120,7 +119,7 @@ extension HomeFeedViewModel { .store(in: &subscriptions) } - private func removeLink(dataService: DataService, linkId: String) { + func removeLink(dataService: DataService, linkId: String) { isLoading = true startNetworkActivityIndicator() @@ -146,7 +145,7 @@ extension HomeFeedViewModel { .store(in: &subscriptions) } - private func snoozeUntil(dataService: DataService, linkId: String, until: Date, successMessage: String?) { + func snoozeUntil(dataService: DataService, linkId: String, until: Date, successMessage: String?) { isLoading = true startNetworkActivityIndicator() @@ -178,55 +177,10 @@ extension HomeFeedViewModel { } } -// TODO: remove this view model -final class HomeFeedViewModel: ObservableObject { - var currentDetailViewModel: LinkItemDetailViewModel? - - @Published var items = [FeedItem]() - @Published var isLoading = false - @Published var showPushNotificationPrimer = false - var cursor: String? - - // These are used to make sure we handle search result - // responses in the right order - var searchIdx = 0 - var receivedIdx = 0 - - enum Action { - case refreshItems(query: String) - case loadItems(query: String) - case archive(linkId: String) - case unarchive(linkId: String) - case remove(linkId: String) - case snooze(linkId: String, until: Date, successMessage: String?) - } - - var subscriptions = Set() - let performActionSubject = PassthroughSubject() - - init() {} - - func itemAppeared(item: FeedItem, searchQuery: String) { - if isLoading { return } - let itemIndex = items.firstIndex(where: { $0.id == item.id }) - let thresholdIndex = items.index(items.endIndex, offsetBy: -5) - - // Check if user has scrolled to the last five items in the list - if let itemIndex = itemIndex, itemIndex > thresholdIndex, items.count < thresholdIndex + 10 { - performActionSubject.send(.loadItems(query: searchQuery)) - } - } - - func pushFeedItem(item: FeedItem) { - items.insert(item, at: 0) - } -} - struct HomeFeedView: View { -// @EnvironmentObject var authenticator: Authenticator -// @EnvironmentObject var dataService: DataService + @EnvironmentObject var dataService: DataService - @ObservedObject private var viewModel: HomeFeedViewModel + @ObservedObject private var viewModel = HomeFeedViewModel() @State private var selectedLinkItem: FeedItem? @State private var searchQuery = "" @State private var itemToRemove: FeedItem? @@ -234,10 +188,6 @@ struct HomeFeedView: View { @State private var snoozePresented = false @State private var itemToSnooze: FeedItem? - init(viewModel: HomeFeedViewModel) { - self.viewModel = viewModel - } - @ViewBuilder var conditionalInnerBody: some View { #if os(iOS) if #available(iOS 15.0, *) { @@ -301,14 +251,14 @@ struct HomeFeedView: View { .opacity(0) .buttonStyle(PlainButtonStyle()) .onAppear { - viewModel.itemAppeared(item: item, searchQuery: searchQuery) + viewModel.itemAppeared(item: item, searchQuery: searchQuery, dataService: dataService) } FeedCard(item: item) }.contextMenu { if !item.isArchived { Button(action: { withAnimation(.linear(duration: 0.4)) { - viewModel.performActionSubject.send(.archive(linkId: item.id)) + viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: true) if item == selectedLinkItem { selectedLinkItem = nil } @@ -317,7 +267,7 @@ struct HomeFeedView: View { } else { Button(action: { withAnimation(.linear(duration: 0.4)) { - viewModel.performActionSubject.send(.unarchive(linkId: item.id)) + viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: false) } }, label: { Label("Unarchive", systemImage: "tray.and.arrow.down.fill") }) } @@ -335,7 +285,7 @@ struct HomeFeedView: View { if !item.isArchived { Button { withAnimation(.linear(duration: 0.4)) { - viewModel.performActionSubject.send(.archive(linkId: item.id)) + viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: true) } } label: { Label("Archive", systemImage: "archivebox") @@ -343,7 +293,7 @@ struct HomeFeedView: View { } else { Button { withAnimation(.linear(duration: 0.4)) { - viewModel.performActionSubject.send(.unarchive(linkId: item.id)) + viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: false) } } label: { Label("Unarchive", systemImage: "tray.and.arrow.down.fill") @@ -365,7 +315,7 @@ struct HomeFeedView: View { Button("Remove Link", role: .destructive) { if let itemToRemove = itemToRemove { withAnimation { - viewModel.performActionSubject.send(.remove(linkId: itemToRemove.id)) + viewModel.removeLink(dataService: dataService, linkId: itemToRemove.id) } } self.itemToRemove = nil @@ -417,8 +367,11 @@ struct HomeFeedView: View { } .formSheet(isPresented: $snoozePresented) { SnoozeView(snoozePresented: $snoozePresented, itemToSnooze: $itemToSnooze) { - viewModel.performActionSubject.send( - .snooze(linkId: $0.feedItemId, until: $0.snoozeUntilDate, successMessage: $0.successMessage) + viewModel.snoozeUntil( + dataService: dataService, + linkId: $0.feedItemId, + until: $0.snoozeUntilDate, + successMessage: $0.successMessage ) } } @@ -459,7 +412,7 @@ struct HomeFeedView: View { } private func refresh() { - viewModel.performActionSubject.send(.refreshItems(query: searchQuery)) + viewModel.loadItems(dataService: dataService, searchQuery: searchQuery, isRefresh: true) } } diff --git a/apple/OmnivoreKit/Sources/Binders/Views/PrimaryContentView.swift b/apple/OmnivoreKit/Sources/Binders/Views/PrimaryContentView.swift index e0aa59060..422e770cb 100644 --- a/apple/OmnivoreKit/Sources/Binders/Views/PrimaryContentView.swift +++ b/apple/OmnivoreKit/Sources/Binders/Views/PrimaryContentView.swift @@ -4,33 +4,22 @@ import SwiftUI import Views public struct PrimaryContentView: View { - let homeFeedViewModel: HomeFeedViewModel - - public init(services: Services) { - self.homeFeedViewModel = HomeFeedViewModel.make(services: services) - } - public var body: some View { #if os(iOS) if UIDevice.isIPad { regularView } else { - compactView + HomeFeedView() } #elseif os(macOS) regularView #endif } - // iphone view container - private var compactView: some View { - HomeFeedView(viewModel: homeFeedViewModel) - } - // ipad and mac view container private var regularView: some View { let categories = [ - PrimaryContentCategory.feed(viewModel: homeFeedViewModel), + PrimaryContentCategory.feed, PrimaryContentCategory.profile ] diff --git a/apple/OmnivoreKit/Sources/Binders/Views/ProfileContainerView.swift b/apple/OmnivoreKit/Sources/Binders/Views/ProfileContainerView.swift index 907ba3418..111483915 100644 --- a/apple/OmnivoreKit/Sources/Binders/Views/ProfileContainerView.swift +++ b/apple/OmnivoreKit/Sources/Binders/Views/ProfileContainerView.swift @@ -67,7 +67,7 @@ struct ProfileContainerView: View { #if os(iOS) Button( action: { - viewModel.performActionSubject.send(.showIntercomMessenger) + DataService.showIntercomMessenger?() }, label: { Text("Feedback") } )