krz/rune
an ios client for njalla
clone: git clone https://gitbay.org/krz/rune.git
main: Rune/Views/Domains/GlueListView.swift · raw
1import SwiftUI
2
3struct GlueListView: View {
4 let domainName: String
5 @ObservedObject var viewModel: DomainViewModel
6 let client: NjallaClient
7
8 @State private var showingAddGlue = false
9 @State private var recordPendingDeletion: GlueRecord?
10
11 var body: some View {
12 List {
13 if let errorMessage = viewModel.glueErrorMessage {
14 Section {
15 InlineErrorView(message: errorMessage, retryTitle: "Retry Glue Records") {
16 Task {
17 await viewModel.loadGlue(for: domainName, client: client)
18 }
19 }
20 .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
21 }
22 }
23
24 if viewModel.isLoadingGlue && viewModel.glueRecords.isEmpty {
25 Section {
26 HStack {
27 Spacer()
28 ProgressView("Loading Glue")
29 Spacer()
30 }
31 }
32 } else if viewModel.glueRecords.isEmpty {
33 Section {
34 ContentUnavailableView(
35 "No Glue Records",
36 systemImage: "list.bullet.rectangle",
37 description: Text("No glue records are configured for this domain.")
38 )
39 }
40 } else {
41 ForEach(viewModel.glueRecords) { record in
42 NavigationLink {
43 GlueEditView(domainName: domainName, existingRecord: record, viewModel: viewModel, client: client)
44 } label: {
45 VStack(alignment: .leading, spacing: 4) {
46 Text(record.name)
47 .font(.headline)
48 Text("IPv4: \(record.address4 ?? "n/a")")
49 .font(.subheadline)
50 .foregroundStyle(.secondary)
51 Text("IPv6: \(record.address6 ?? "n/a")")
52 .font(.subheadline)
53 .foregroundStyle(.secondary)
54 }
55 .padding(.vertical, 4)
56 }
57 .swipeActions {
58 Button("Delete", role: .destructive) {
59 recordPendingDeletion = record
60 }
61 }
62 .disabled(viewModel.isSaving)
63 }
64 }
65 }
66 .listStyle(.insetGrouped)
67 .navigationTitle("Glue Records")
68 .navigationBarTitleDisplayMode(.inline)
69 .toolbar {
70 Button {
71 showingAddGlue = true
72 } label: {
73 Label("Add Glue", systemImage: "plus")
74 }
75 .disabled(viewModel.isSaving)
76 }
77 .sheet(isPresented: $showingAddGlue) {
78 NavigationStack {
79 GlueEditView(domainName: domainName, existingRecord: nil, viewModel: viewModel, client: client)
80 }
81 }
82 .task {
83 await viewModel.loadGlue(for: domainName, client: client)
84 }
85 .refreshable {
86 await viewModel.loadGlue(for: domainName, client: client)
87 }
88 .alert(deleteAlertTitle, isPresented: deleteBinding) {
89 Button("Delete Glue", role: .destructive) {
90 guard let recordPendingDeletion else { return }
91 Task {
92 do {
93 try await viewModel.removeGlue(recordPendingDeletion, client: client)
94 self.recordPendingDeletion = nil
95 } catch {
96 return
97 }
98 }
99 }
100 Button("Cancel", role: .cancel) {
101 recordPendingDeletion = nil
102 }
103 } message: {
104 Text("Delete glue record \(recordPendingDeletion?.name ?? "")?")
105 }
106 .alert("Request Failed", isPresented: mutationErrorBinding) {
107 Button("OK", role: .cancel) {}
108 } message: {
109 Text(viewModel.mutationErrorMessage ?? "")
110 }
111 }
112
113 private var deleteAlertTitle: String {
114 guard let recordPendingDeletion else { return "" }
115 return "Delete glue record \(recordPendingDeletion.name)?"
116 }
117
118 private var deleteBinding: Binding<Bool> {
119 Binding(
120 get: { recordPendingDeletion != nil },
121 set: { newValue in
122 if !newValue {
123 recordPendingDeletion = nil
124 }
125 }
126 )
127 }
128
129 private var mutationErrorBinding: Binding<Bool> {
130 Binding(
131 get: { viewModel.mutationErrorMessage != nil },
132 set: { newValue in
133 if !newValue {
134 viewModel.dismissMutationError()
135 }
136 }
137 )
138 }
139}