diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift index aa2f9ef7e..902eae6aa 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/RecommendToView.swift @@ -1,8 +1,115 @@ -// -// File.swift -// -// -// Created by Jackson Harper on 12/5/22. -// +import Models +import Services +import SwiftUI +import Views -import Foundation +@MainActor final class RecommendToViewModel: ObservableObject { + @Published var isLoading = false + @Published var networkError = true + @Published var recommendationGroups = [InternalRecommendationGroup]() + @Published var selectedGroups = [String]() + @Published var isRunning = false + @Published var showError = false + + let pageID: String + + init(pageID: String) { + self.pageID = pageID + } + + func loadGroups(dataService: DataService) async { + isLoading = true + + do { + recommendationGroups = try await dataService.recommendationGroups() + } catch { + networkError = true + } + + isLoading = false + } + + func recommend(dataService: DataService) async { + isRunning = true + + do { + try await dataService.recommendPage(pageID: pageID, groupIDs: selectedGroups) + } catch { + showError = true + } + + isRunning = false + } +} + +struct RecommendToView: View { + var dataService: DataService + @StateObject var viewModel: RecommendToViewModel + @Environment(\.dismiss) private var dismiss + + var nextButton: some View { + if viewModel.isRunning { + return AnyView(ProgressView()) + } else { + return AnyView(Button(action: { + Task { + await viewModel.recommend(dataService: dataService) + Snackbar.show(message: "Recommendation sent") + dismiss() + } + }, label: { + Text("Next") + }) + .disabled(viewModel.selectedGroups.isEmpty) + ) + } + } + + var body: some View { + VStack { + List { + ForEach(viewModel.recommendationGroups) { group in + HStack { + Text(group.name) + + Spacer() + + if viewModel.selectedGroups.contains(group.id) { + Image(systemName: "checkmark") + } + } + .contentShape(Rectangle()) + .onTapGesture { + let idx = viewModel.selectedGroups.firstIndex(of: group.id) + if let idx = idx { + viewModel.selectedGroups.remove(at: idx) + } else { + viewModel.selectedGroups.append(group.id) + } + } + } + } + .listStyle(.grouped) + + Spacer() + } + .alert(isPresented: $viewModel.showError) { + Alert( + title: Text("Error recommending this page"), + dismissButton: .cancel(Text("Ok")) { + viewModel.showError = false + } + ) + } + .navigationBarTitle("Recommend To") + .navigationBarTitleDisplayMode(.inline) + .navigationViewStyle(.stack) + .navigationBarItems(leading: Button(action: { + dismiss() + }, label: { Text("Cancel") }), + trailing: nextButton) + .task { + await viewModel.loadGroups(dataService: dataService) + } + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 748246a6a..9da188077 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -29,6 +29,7 @@ struct WebReaderContainerView: View { @State private var bottomBarOpacity = 0.0 @State private var errorAlertMessage: String? @State private var showErrorAlertMessage = false + @State private var displayRecommendSheet = false @EnvironmentObject var dataService: DataService @EnvironmentObject var audioController: AudioController @@ -219,6 +220,13 @@ struct WebReaderContainerView: View { action: delete, label: { Label("Delete", systemImage: "trash") } ) + Button( + action: { + // dataService.updateLinkReadingProgress(itemID: item.unwrappedID, readingProgress: 0, anchorIndex: 0) + displayRecommendSheet = true + }, + label: { Label("Recommend", systemImage: "sparkles") } + ) } } @@ -351,6 +359,14 @@ struct WebReaderContainerView: View { showErrorAlertMessage = false }) } + .formSheet(isPresented: $displayRecommendSheet) { + NavigationView { + RecommendToView( + dataService: dataService, + viewModel: RecommendToViewModel(pageID: item.unwrappedID) + ) + } + } .sheet(isPresented: $showHighlightAnnotationModal) { HighlightAnnotationSheet( annotation: $annotation, diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift index aa2f9ef7e..6716460db 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RecommendPage.swift @@ -1,8 +1,48 @@ -// -// File.swift -// -// -// Created by Jackson Harper on 12/5/22. -// - +import CoreData import Foundation +import Models +import SwiftGraphQL + +public extension DataService { + func recommendPage(pageID: String, groupIDs: [String]) async throws { + enum MutationResult { + case saved(taskNames: [String]) + case error(errorMessage: String) + } + + let selection = Selection { + try $0.on( + recommendError: .init { .error(errorMessage: try $0.errorCodes().first.toString()) }, + recommendSuccess: .init { + .saved(taskNames: try $0.taskNames()) + } + ) + } + + let mutation = Selection.Mutation { + try $0.recommend( + input: .init(groupIds: groupIDs, pageId: pageID), + selection: selection + ) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + + return try await withCheckedThrowingContinuation { continuation in + send(mutation, to: path, headers: headers) { queryResult in + guard let payload = try? queryResult.get() else { + continuation.resume(throwing: BasicError.message(messageText: "network error")) + return + } + + switch payload.data { + case .saved: + continuation.resume() + case let .error(errorMessage: errorMessage): + continuation.resume(throwing: BasicError.message(messageText: errorMessage)) + } + } + } + } +} diff --git a/packages/db/migrate.ts b/packages/db/migrate.ts index 8a4386940..01b6ef065 100755 --- a/packages/db/migrate.ts +++ b/packages/db/migrate.ts @@ -70,7 +70,7 @@ log(`Migrating to ${targetMigrationLabel}.\n`) const logAppliedMigrations = ( appliedMigrations: Postgrator.Migration[] ): void => { - if (appliedMigrations.length > 0) { + if (appliedMigrations && appliedMigrations.length > 0) { log( `Applied ${chalk.green( appliedMigrations.length.toString()