diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/Subscriptions.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/Subscriptions.swift index 675b91c7c..fb0de0b62 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/Subscriptions.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/Subscriptions.swift @@ -5,41 +5,17 @@ import Views @MainActor final class SubscriptionsViewModel: ObservableObject { @Published var isLoading = false - @Published var emails = [NewsletterEmail]() + @Published var subscriptions = [Subscription]() + @Published var popularSubscriptions = [Subscription]() + @Published var hasNetworkError = false - func loadSubscriptions(dataService _: DataService) async { + func loadSubscriptions(dataService: DataService) async { isLoading = true -// if let subscriptions = try? await dataService.subscriptions() { -// await dataService.viewContext.perform { [weak self] in -// self?.emails = objectIDs.compactMap { dataService.viewContext.object(with: $0) as? NewsletterEmail } -// } -// } - - isLoading = false - } - - func loadEmails(dataService: DataService) async { - isLoading = true - - if let objectIDs = try? await dataService.newsletterEmails() { - await dataService.viewContext.perform { [weak self] in - self?.emails = objectIDs.compactMap { dataService.viewContext.object(with: $0) as? NewsletterEmail } - } - } - - isLoading = false - } - - func createEmail(dataService: DataService) async { - isLoading = true - - if let objectID = try? await dataService.createNewsletter() { - await dataService.viewContext.perform { [weak self] in - if let item = dataService.viewContext.object(with: objectID) as? NewsletterEmail { - self?.emails.insert(item, at: 0) - } - } + do { + subscriptions = try await dataService.subscriptions() + } catch { + hasNetworkError = true } isLoading = false @@ -49,7 +25,7 @@ import Views struct SubscriptionsView: View { @EnvironmentObject var dataService: DataService @StateObject var viewModel = SubscriptionsViewModel() - let footerText = "Add PDFs to your library, or subscribe to emails using an Omnivore email address." + let footerText = "Describe subscriptions here." var body: some View { Group { @@ -64,50 +40,30 @@ struct SubscriptionsView: View { .listStyle(InsetListStyle()) #endif } - .task { await viewModel.loadEmails(dataService: dataService) } + .task { await viewModel.loadSubscriptions(dataService: dataService) } } private var innerBody: some View { Group { - Section(footer: Text(footerText)) { + ForEach(viewModel.subscriptions, id: \.subscriptionID) { subscription in Button( - action: { - Task { await viewModel.createEmail(dataService: dataService) } - }, - label: { - HStack { - Image(systemName: "plus.circle.fill").foregroundColor(.green) - Text("Create a new email address") - Spacer() - } - } + action: {}, + label: { Text(subscription.name) } ) - .disabled(viewModel.isLoading) - } - - if !viewModel.emails.isEmpty { - Section(header: Text("Existing Emails (Tap to copy)")) { - ForEach(viewModel.emails) { newsletterEmail in - Button( - action: { - #if os(iOS) - UIPasteboard.general.string = newsletterEmail.email - #endif - - #if os(macOS) - let pasteBoard = NSPasteboard.general - pasteBoard.clearContents() - pasteBoard.writeObjects([newsletterEmail.unwrappedEmail as NSString]) - #endif - - Snackbar.show(message: "Email copied") - }, - label: { Text(newsletterEmail.unwrappedEmail) } - ) - } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button( + role: .destructive, + action: { +// itemToRemove = item +// confirmationShown = true + }, + label: { + Image(systemName: "trash") + } + ) } } } - .navigationTitle("Emails") + .navigationTitle("Subscriptions") } } diff --git a/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift b/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift index e49cb03f9..e6929645f 100644 --- a/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift +++ b/apple/OmnivoreKit/Sources/Models/AppEnvironment.swift @@ -11,7 +11,7 @@ public enum AppEnvironment: String { public static let initialAppEnvironment: AppEnvironment = { #if DEBUG #if targetEnvironment(simulator) - return .demo // could also return .local here + return .prod // could also return .local here #else return .demo #endif diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/Subscription.swift b/apple/OmnivoreKit/Sources/Models/DataModels/Subscription.swift new file mode 100644 index 000000000..10d6208a9 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Models/DataModels/Subscription.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct Subscription { + public let createdAt: Date? + public let description: String? + public let subscriptionID: String + public let name: String + public let newsletterEmailAddress: String + public let status: SubscriptionStatus + public let unsubscribeHttpUrl: String? + public let unsubscribeMailTo: String? + public let updatedAt: Date? + public let url: String? + + public init( + createdAt: Date?, + description: String?, + subscriptionID: String, + name: String, + newsletterEmailAddress: String, + status: SubscriptionStatus, + unsubscribeHttpUrl: String?, + unsubscribeMailTo: String?, + updatedAt: Date?, + url: String? + ) { + self.createdAt = createdAt + self.description = description + self.subscriptionID = subscriptionID + self.name = name + self.newsletterEmailAddress = newsletterEmailAddress + self.status = status + self.unsubscribeHttpUrl = unsubscribeHttpUrl + self.unsubscribeMailTo = unsubscribeMailTo + self.updatedAt = updatedAt + self.url = url + } +} + +public enum SubscriptionStatus { + case active + case deleted + case unsubscribed +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/SubscriptionsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/SubscriptionsQuery.swift new file mode 100644 index 000000000..2c2976126 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/SubscriptionsQuery.swift @@ -0,0 +1,74 @@ +import Foundation +import Models +import SwiftGraphQL + +public extension DataService { + func subscriptions() async throws -> [Subscription] { + enum QueryResult { + case success(result: [Subscription]) + case error(error: String) + } + + let subsciptionSelection = Selection.Subscription { + Subscription( + createdAt: try $0.createdAt().value, + description: try $0.description(), + subscriptionID: try $0.id(), + name: try $0.name(), + newsletterEmailAddress: try $0.newsletterEmail(), + status: try SubscriptionStatus.make(from: $0.status()), + unsubscribeHttpUrl: try $0.unsubscribeHttpUrl(), + unsubscribeMailTo: try $0.unsubscribeMailTo(), + updatedAt: try $0.updatedAt().value, + url: try $0.url() + ) + } + + let selection = Selection { + try $0.on( + subscriptionsError: .init { + QueryResult.error(error: try $0.errorCodes().description) + }, + subscriptionsSuccess: .init { + QueryResult.success(result: try $0.subscriptions(selection: subsciptionSelection.list)) + } + ) + } + + let query = Selection.Query { + try $0.subscriptions(selection: selection) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + + return try await withCheckedThrowingContinuation { continuation in + send(query, to: path, headers: headers) { queryResult in + guard let payload = try? queryResult.get() else { + continuation.resume(throwing: BasicError.message(messageText: "network request failed")) + return + } + + switch payload.data { + case let .success(result: result): + continuation.resume(returning: result) + case .error: + continuation.resume(throwing: BasicError.message(messageText: "Subscriptions fetch error")) + } + } + } + } +} + +extension SubscriptionStatus { + static func make(from status: Enums.SubscriptionStatus) -> SubscriptionStatus { + switch status { + case .active: + return .active + case .deleted: + return .deleted + case .unsubscribed: + return .unsubscribed + } + } +}