diff --git a/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift b/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift index 5b0706d57..7cd4c6ee2 100644 --- a/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift +++ b/apple/OmnivoreKit/Sources/Binders/Views/HomeFeedView.swift @@ -8,9 +8,7 @@ import Views extension HomeFeedViewModel { static func make(services: Services) -> HomeFeedViewModel { - let viewModel = HomeFeedViewModel { feedItem in - LinkItemDetailViewModel.make(feedItem: feedItem, services: services) - } + let viewModel = HomeFeedViewModel() #if os(iOS) if UIDevice.isIPhone { @@ -201,7 +199,6 @@ private func stopNetworkActivityIndicator() { // TODO: remove this view model final class HomeFeedViewModel: ObservableObject { - let detailViewModelCreator: (FeedItem) -> LinkItemDetailViewModel var currentDetailViewModel: LinkItemDetailViewModel? var profileContainerViewModel: ProfileContainerViewModel? @@ -227,9 +224,7 @@ final class HomeFeedViewModel: ObservableObject { var subscriptions = Set() let performActionSubject = PassthroughSubject() - init(detailViewModelCreator: @escaping (FeedItem) -> LinkItemDetailViewModel) { - self.detailViewModelCreator = detailViewModelCreator - } + init() {} func itemAppeared(item: FeedItem, searchQuery: String) { if isLoading { return } @@ -248,6 +243,9 @@ final class HomeFeedViewModel: ObservableObject { } struct HomeFeedView: View { +// @EnvironmentObject var authenticator: Authenticator +// @EnvironmentObject var dataService: DataService + @ObservedObject private var viewModel: HomeFeedViewModel @State private var selectedLinkItem: FeedItem? @State private var searchQuery = "" @@ -314,7 +312,7 @@ struct HomeFeedView: View { ForEach(viewModel.items) { item in let link = ZStack { NavigationLink( - destination: LinkItemDetailView(viewModel: viewModel.detailViewModelCreator(item)), + destination: LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item)), tag: item, selection: $selectedLinkItem ) { diff --git a/apple/OmnivoreKit/Sources/Binders/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/Binders/Views/LinkItemDetailView.swift index 68fe931bb..7753cd4b6 100644 --- a/apple/OmnivoreKit/Sources/Binders/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/Binders/Views/LinkItemDetailView.swift @@ -5,88 +5,6 @@ import SwiftUI import Utils import Views -// TODO: remove this view model -extension LinkItemDetailViewModel { - static func make(feedItem: FeedItem, services: Services) -> LinkItemDetailViewModel { - let viewModel = LinkItemDetailViewModel(item: feedItem) - viewModel.bind(services: services) - return viewModel - } - - func bind(services: Services) { - performActionSubject.sink { [weak self] action in - switch action { - case .load: - self?.loadWebAppWrapper(services: services) - case let .updateReadStatus(markAsRead: markAsRead): - self?.updateItemReadStatus(markAsRead: markAsRead, dataService: services.dataService) - } - } - .store(in: &subscriptions) - } - - private func updateItemReadStatus(markAsRead: Bool, dataService: DataService) { - dataService - .updateArticleReadingProgressPublisher( - itemID: item.id, - readingProgress: markAsRead ? 100 : 0, - anchorIndex: 0 - ) - .sink { completion in - guard case let .failure(error) = completion else { return } - print(error) - } receiveValue: { [weak self] feedItem in - self?.item.readingProgress = feedItem.readingProgress - } - .store(in: &subscriptions) - } - - private func loadWebAppWrapper(services: Services) { - // Attempt to get `Viewer` from DataService - if let currentViewer = services.dataService.currentViewer { - createWebAppWrapperViewModel(username: currentViewer.username, services: services) - return - } - - services.dataService.viewerPublisher().sink( - receiveCompletion: { completion in - guard case let .failure(error) = completion else { return } - print(error) - }, - receiveValue: { [weak self] viewer in - self?.createWebAppWrapperViewModel(username: viewer.username, services: services) - } - ) - .store(in: &subscriptions) - } - - private func createWebAppWrapperViewModel(username: String, services: Services) { - let baseURL = services.dataService.appEnvironment.webAppBaseURL - - let urlRequest = URLRequest.webRequest( - baseURL: services.dataService.appEnvironment.webAppBaseURL, - urlPath: "/app/\(username)/\(item.slug)", - queryParams: ["isAppEmbedView": "true", "highlightBarDisabled": isMacApp ? "false" : "true"] - ) - - let newWebAppWrapperViewModel = WebAppWrapperViewModel( - webViewURLRequest: urlRequest, - baseURL: baseURL, - rawAuthCookie: services.authenticator.omnivoreAuthCookieString - ) - - newWebAppWrapperViewModel.performActionSubject.sink { action in - switch action { - case let .shareHighlight(highlightID): - print("show share modal for highlight with id: \(highlightID)") - } - } - .store(in: &newWebAppWrapperViewModel.subscriptions) - - webAppWrapperViewModel = newWebAppWrapperViewModel - } -} - enum PDFProvider { static var pdfViewerProvider: ((URL, FeedItem) -> AnyView)? } @@ -101,14 +19,84 @@ final class LinkItemDetailViewModel: ObservableObject { } var subscriptions = Set() - let performActionSubject = PassthroughSubject() init(item: FeedItem) { self.item = item } + + func updateItemReadStatus(dataService: DataService) { + dataService + .updateArticleReadingProgressPublisher( + itemID: item.id, + readingProgress: item.isRead ? 0 : 100, + anchorIndex: 0 + ) + .sink { completion in + guard case let .failure(error) = completion else { return } + print(error) + } receiveValue: { [weak self] feedItem in + self?.item.readingProgress = feedItem.readingProgress + } + .store(in: &subscriptions) + } + + func loadWebAppWrapper(dataService: DataService, rawAuthCookie: String?) { + // Attempt to get `Viewer` from DataService + if let currentViewer = dataService.currentViewer { + createWebAppWrapperViewModel( + username: currentViewer.username, + dataService: dataService, + rawAuthCookie: rawAuthCookie + ) + return + } + + dataService.viewerPublisher().sink( + receiveCompletion: { completion in + guard case let .failure(error) = completion else { return } + print(error) + }, + receiveValue: { [weak self] viewer in + self?.createWebAppWrapperViewModel( + username: viewer.username, + dataService: dataService, + rawAuthCookie: rawAuthCookie + ) + } + ) + .store(in: &subscriptions) + } + + private func createWebAppWrapperViewModel(username: String, dataService: DataService, rawAuthCookie: String?) { + let baseURL = dataService.appEnvironment.webAppBaseURL + + let urlRequest = URLRequest.webRequest( + baseURL: dataService.appEnvironment.webAppBaseURL, + urlPath: "/app/\(username)/\(item.slug)", + queryParams: ["isAppEmbedView": "true", "highlightBarDisabled": isMacApp ? "false" : "true"] + ) + + let newWebAppWrapperViewModel = WebAppWrapperViewModel( + webViewURLRequest: urlRequest, + baseURL: baseURL, + rawAuthCookie: rawAuthCookie + ) + + newWebAppWrapperViewModel.performActionSubject.sink { action in + switch action { + case let .shareHighlight(highlightID): + print("show share modal for highlight with id: \(highlightID)") + } + } + .store(in: &newWebAppWrapperViewModel.subscriptions) + + webAppWrapperViewModel = newWebAppWrapperViewModel + } } struct LinkItemDetailView: View { + @EnvironmentObject var authenticator: Authenticator + @EnvironmentObject var dataService: DataService @Environment(\.presentationMode) var presentationMode: Binding static let navBarHeight = 50.0 @@ -122,7 +110,9 @@ struct LinkItemDetailView: View { var toggleReadStatusToolbarItem: some View { Button( - action: { viewModel.performActionSubject.send(.updateReadStatus(markAsRead: !viewModel.item.isRead)) }, + action: { + viewModel.updateItemReadStatus(dataService: dataService) + }, label: { Image(systemName: viewModel.item.isRead ? "line.horizontal.3.decrease.circle" : "checkmark.circle") } @@ -232,7 +222,10 @@ struct LinkItemDetailView: View { Spacer() } .onAppear { - viewModel.performActionSubject.send(.load) + viewModel.loadWebAppWrapper( + dataService: dataService, + rawAuthCookie: authenticator.omnivoreAuthCookieString + ) } .navigationBarHidden(true) } @@ -275,7 +268,10 @@ struct LinkItemDetailView: View { Spacer() } .onAppear { - viewModel.performActionSubject.send(.load) + viewModel.loadWebAppWrapper( + dataService: dataService, + rawAuthCookie: authenticator.omnivoreAuthCookieString + ) } } } diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift index 96ab85343..cfe31bc1a 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppViewCoordinator.swift @@ -57,21 +57,21 @@ extension WebAppViewCoordinator: WKNavigationDelegate { let yOffset = scrollView.contentOffset.y if yOffset == 0 { - scrollView.contentInset.top = navBarHeight + scrollView.contentInset.top = readerViewNavBarHeight navBarVisibilityRatio = 1 return } if yOffset < 0 { navBarVisibilityRatio = 1 - scrollView.contentInset.top = navBarHeight + scrollView.contentInset.top = readerViewNavBarHeight return } - if yOffset < navBarHeight { + if yOffset < readerViewNavBarHeight { let isScrollingUp = yOffsetAtStartOfDrag ?? 0 > yOffset - navBarVisibilityRatio = isScrollingUp || yOffset < 0 ? 1 : min(1, 1 - (yOffset / navBarHeight)) - scrollView.contentInset.top = navBarVisibilityRatio * navBarHeight + navBarVisibilityRatio = isScrollingUp || yOffset < 0 ? 1 : min(1, 1 - (yOffset / readerViewNavBarHeight)) + scrollView.contentInset.top = navBarVisibilityRatio * readerViewNavBarHeight return } @@ -79,21 +79,21 @@ extension WebAppViewCoordinator: WKNavigationDelegate { if yOffset > yOffsetAtStartOfDrag, !isNavBarHidden { let translation = yOffset - yOffsetAtStartOfDrag - let ratio = translation < navBarHeight ? 1 - (translation / navBarHeight) : 0 + let ratio = translation < readerViewNavBarHeight ? 1 - (translation / readerViewNavBarHeight) : 0 navBarVisibilityRatio = min(ratio, 1) - scrollView.contentInset.top = navBarVisibilityRatio * navBarHeight + scrollView.contentInset.top = navBarVisibilityRatio * readerViewNavBarHeight } } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if decelerate, scrollView.contentOffset.y + scrollView.contentInset.top < (yOffsetAtStartOfDrag ?? 0) { - scrollView.contentInset.top = navBarHeight + scrollView.contentInset.top = readerViewNavBarHeight navBarVisibilityRatio = 1 } } func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { - scrollView.contentInset.top = navBarHeight + scrollView.contentInset.top = readerViewNavBarHeight navBarVisibilityRatio = 1 return false }