show subscriptions in list view

This commit is contained in:
Satindar Dhillon
2022-05-25 22:51:41 -07:00
parent 4969968c53
commit e1aa68858f
4 changed files with 144 additions and 70 deletions

View File

@ -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")
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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<QueryResult, Unions.SubscriptionsResult> {
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
}
}
}