diff options
| author | Christian Cleberg <[email protected]> | 2026-04-11 11:48:40 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-11 11:48:40 -0500 |
| commit | fcf864a15b70e4ecb5bd789b1db3116221c34394 (patch) | |
| tree | f15315324b9f95286b6bf7392dd0ccaee210c2de /Rune/Views/Domains/ForwardListView.swift | |
| parent | cf587aa0573ac4f34e1effe70108a9eca82093ac (diff) | |
| download | rune-fcf864a15b70e4ecb5bd789b1db3116221c34394.tar.gz rune-fcf864a15b70e4ecb5bd789b1db3116221c34394.tar.bz2 rune-fcf864a15b70e4ecb5bd789b1db3116221c34394.zip | |
add FUNDING.yml
Diffstat (limited to 'Rune/Views/Domains/ForwardListView.swift')
| -rw-r--r-- | Rune/Views/Domains/ForwardListView.swift | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/Rune/Views/Domains/ForwardListView.swift b/Rune/Views/Domains/ForwardListView.swift new file mode 100644 index 0000000..4e20ab2 --- /dev/null +++ b/Rune/Views/Domains/ForwardListView.swift @@ -0,0 +1,171 @@ +import SwiftUI + +struct ForwardListView: View { + let domainName: String + @ObservedObject var viewModel: DomainViewModel + let client: NjallaClient + + @State private var showingAddForward = false + @State private var forwardPendingDeletion: EmailForward? + + var body: some View { + List { + if let errorMessage = viewModel.forwardsErrorMessage { + Section { + InlineErrorView(message: errorMessage, retryTitle: "Retry Forwards") { + Task { + await viewModel.loadForwards(for: domainName, client: client) + } + } + .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) + } + } + + if viewModel.isLoadingForwards && viewModel.forwards.isEmpty { + Section { + HStack { + Spacer() + ProgressView("Loading Forwards") + Spacer() + } + } + } else if viewModel.forwards.isEmpty { + Section { + ContentUnavailableView( + "No Forwards", + systemImage: "envelope", + description: Text("No email forwards are configured for this domain.") + ) + } + } else { + ForEach(viewModel.forwards) { forward in + VStack(alignment: .leading, spacing: 4) { + Text("@\(forward.from)") + .font(.headline) + Text(forward.to) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .padding(.vertical, 4) + .swipeActions { + Button("Delete", role: .destructive) { + guard !viewModel.isSaving else { return } + forwardPendingDeletion = forward + } + } + .contextMenu { + Button("Delete Forward", role: .destructive) { + forwardPendingDeletion = forward + } + } + .disabled(viewModel.isSaving) + } + } + } + .listStyle(.insetGrouped) + .navigationTitle("Forwards") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button { + showingAddForward = true + } label: { + Label("Add Forward", systemImage: "plus") + } + .disabled(viewModel.isSaving) + } + .sheet(isPresented: $showingAddForward) { + NavigationStack { + ForwardAddView(domainName: domainName, viewModel: viewModel, client: client) + } + } + .task { + debugLog("Loading forwards for \(domainName)") + await viewModel.loadForwards(for: domainName, client: client) + debugLog("Loaded \(viewModel.forwards.count) forwards for \(domainName)") + } + .refreshable { + debugLog("Refreshing forwards for \(domainName)") + await viewModel.loadForwards(for: domainName, client: client) + debugLog("Refresh complete with \(viewModel.forwards.count) forwards for \(domainName)") + } + .overlay(alignment: .top) { + if viewModel.isLoadingForwards && !viewModel.forwards.isEmpty { + ProgressView() + .padding(.top, 8) + } + } + .alert(deleteAlertTitle, isPresented: deleteBinding) { + Button("Delete Forward", role: .destructive) { + guard let forwardPendingDeletion else { return } + Task { + await delete(forwardPendingDeletion) + } + } + Button("Cancel", role: .cancel) { + forwardPendingDeletion = nil + } + } message: { + Text("Delete the forward from \(forwardPendingDeletion?.from ?? "") to \(forwardPendingDeletion?.to ?? "")?") + } + .alert("Request Failed", isPresented: mutationErrorBinding) { + Button("OK", role: .cancel) {} + } message: { + Text(viewModel.mutationErrorMessage ?? "") + } + } + + private func delete(_ forward: EmailForward) async { + guard !viewModel.isSaving else { + return + } + + debugLog("Deleting forward \(forward.from)@\(forward.domain) -> \(forward.to)") + do { + try await viewModel.removeForward(forward, client: client) + debugLog("Deleted forward \(forward.from)@\(forward.domain) -> \(forward.to)") + forwardPendingDeletion = nil + } catch is CancellationError { + debugLog("Delete cancelled for \(forward.from)@\(forward.domain) -> \(forward.to)") + return + } catch { + debugLog("Delete failed for \(forward.from)@\(forward.domain) -> \(forward.to): \(error.localizedDescription)") + return + } + } + + private var deleteAlertTitle: String { + guard let forwardPendingDeletion else { + return "" + } + + return "Delete forward \(forwardPendingDeletion.from)@\(domainName)?" + } + + private var deleteBinding: Binding<Bool> { + Binding( + get: { forwardPendingDeletion != nil }, + set: { newValue in + if !newValue { + forwardPendingDeletion = nil + } + } + ) + } + + private var mutationErrorBinding: Binding<Bool> { + Binding( + get: { viewModel.mutationErrorMessage != nil }, + set: { newValue in + if !newValue { + viewModel.dismissMutationError() + } + } + ) + } + + private func debugLog(_ message: String) { + #if DEBUG + debugPrint("[ForwardListView]", message) + #endif + } +} |
