krz/rune
an ios client for njalla
clone: git clone https://gitbay.org/krz/rune.git
v1.0.0: Rune/Views/Domains/DomainListView.swift · raw
1import SwiftUI
2
3struct DomainListView: View {
4 @ObservedObject var viewModel: DomainViewModel
5 let client: NjallaClient?
6
7 var body: some View {
8 NavigationStack {
9 Group {
10 if let client {
11 content(client: client)
12 } else {
13 ContentUnavailableView("Sign in required", systemImage: "key.fill", description: Text("Add a valid Njalla API token to load domains."))
14 }
15 }
16 .navigationTitle("Domains")
17 }
18 .alert("API Error", isPresented: errorBinding) {
19 Button("OK", role: .cancel) {}
20 } message: {
21 Text(viewModel.errorMessage ?? "")
22 }
23 }
24
25 @ViewBuilder
26 private func content(client: NjallaClient) -> some View {
27 if viewModel.isLoadingDomains && viewModel.domains.isEmpty {
28 ProgressView()
29 } else if viewModel.domains.isEmpty {
30 ContentUnavailableView("No Domains", systemImage: "globe", description: Text("No domains found on this account."))
31 } else {
32 List(viewModel.domains) { domain in
33 NavigationLink {
34 DomainDetailView(domainName: domain.name, viewModel: viewModel, client: client)
35 } label: {
36 DomainRow(domain: domain)
37 }
38 }
39 .listStyle(.insetGrouped)
40 .refreshable {
41 await viewModel.loadDomains(client: client)
42 }
43 }
44 }
45
46 private var errorBinding: Binding<Bool> {
47 Binding(
48 get: { viewModel.errorMessage != nil },
49 set: { newValue in
50 if !newValue {
51 viewModel.errorMessage = nil
52 }
53 }
54 )
55 }
56}
57
58private struct DomainRow: View {
59 let domain: Domain
60
61 var body: some View {
62 VStack(alignment: .leading, spacing: 6) {
63 Text(domain.name)
64 .font(.headline)
65
66 HStack {
67 if let status = domain.status {
68 Text(status)
69 }
70
71 if let expiry = domain.expiry {
72 Text("Expiry: \(expiry.formattedExpiry())")
73 }
74 }
75 .font(.subheadline)
76 .foregroundStyle(.secondary)
77
78 if let autorenew = domain.autorenew {
79 Text(autorenew ? "Autorenew On" : "Autorenew Off")
80 .font(.subheadline)
81 .foregroundStyle(.secondary)
82 }
83 }
84 .padding(.vertical, 4)
85 }
86}