summaryrefslogtreecommitdiff
path: root/Hutch/Views/Lists
diff options
context:
space:
mode:
Diffstat (limited to 'Hutch/Views/Lists')
-rw-r--r--Hutch/Views/Lists/MailingListListView.swift67
1 files changed, 67 insertions, 0 deletions
diff --git a/Hutch/Views/Lists/MailingListListView.swift b/Hutch/Views/Lists/MailingListListView.swift
index 2fcc640..09fa2a0 100644
--- a/Hutch/Views/Lists/MailingListListView.swift
+++ b/Hutch/Views/Lists/MailingListListView.swift
@@ -5,6 +5,7 @@ import SwiftUI
final class MailingListListViewModel {
private(set) var mailingLists: [InboxMailingListReference] = []
private(set) var isLoading = false
+ private(set) var isPerformingAction = false
var error: String?
var searchText = ""
@@ -28,10 +29,51 @@ final class MailingListListViewModel {
}
"""
+ private static let unsubscribeMutation = """
+ mutation mailingListUnsubscribe($listID: Int!) {
+ subscription: mailingListUnsubscribe(listID: $listID) { id }
+ }
+ """
+
init(client: SRHTClient) {
self.client = client
}
+ /// Unsubscribes from a list and drops it from the list on success. This view
+ /// is built from the subscriptions query, so a successful unsubscribe means
+ /// the row no longer belongs here.
+ func unsubscribe(from mailingList: InboxMailingListReference) async {
+ guard !isPerformingAction else { return }
+ isPerformingAction = true
+ error = nil
+ defer { isPerformingAction = false }
+
+ let previousLists = mailingLists
+ mailingLists.removeAll { $0.rid == mailingList.rid }
+
+ do {
+ struct Response: Decodable, Sendable {
+ // mailingListUnsubscribe is nullable: sr.ht returns null when there
+ // was no subscription to remove, which is still a success.
+ let subscription: SubscriptionPayload?
+ }
+
+ struct SubscriptionPayload: Decodable, Sendable {
+ let id: Int
+ }
+
+ _ = try await client.execute(
+ service: .lists,
+ query: Self.unsubscribeMutation,
+ variables: ["listID": mailingList.id],
+ responseType: Response.self
+ )
+ } catch {
+ mailingLists = previousLists
+ self.error = "Couldn't unsubscribe from \(mailingList.name). \(error.userFacingMessage)"
+ }
+ }
+
var filteredMailingLists: [InboxMailingListReference] {
let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
guard !q.isEmpty else { return mailingLists }
@@ -106,6 +148,7 @@ final class MailingListListViewModel {
struct MailingListListView: View {
@Environment(AppState.self) private var appState
@State private var viewModel: MailingListListViewModel?
+ @State private var pendingUnsubscribe: InboxMailingListReference?
var body: some View {
Group {
@@ -141,6 +184,14 @@ struct MailingListListView: View {
}
.padding(.vertical, 2)
}
+ .swipeActions(edge: .trailing) {
+ Button {
+ pendingUnsubscribe = mailingList
+ } label: {
+ SwiftUI.Label("Unsubscribe", systemImage: "bell.slash")
+ }
+ .tint(.orange)
+ }
}
.themedRow()
}
@@ -151,6 +202,22 @@ struct MailingListListView: View {
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Search lists"
)
+ .confirmationDialog(
+ pendingUnsubscribe.map { "Unsubscribe from \($0.name)?" } ?? "",
+ isPresented: .init(
+ get: { pendingUnsubscribe != nil },
+ set: { if !$0 { pendingUnsubscribe = nil } }
+ ),
+ titleVisibility: .visible,
+ presenting: pendingUnsubscribe
+ ) { mailingList in
+ Button("Unsubscribe", role: .destructive) {
+ Task { await viewModel.unsubscribe(from: mailingList) }
+ }
+ Button("Cancel", role: .cancel) { pendingUnsubscribe = nil }
+ } message: { _ in
+ Text("You will stop receiving email from this list. Hutch cannot resubscribe you — you would need to do that from the list's page on the web.")
+ }
.overlay {
if viewModel.isLoading, viewModel.mailingLists.isEmpty {
SRHTLoadingStateView(message: "Loading mailing lists…")