krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v5.0.2: DomainDig/ScheduledReportsView.swift · raw
1import SwiftUI
2
3struct ScheduledReportsView: View {
4 @Environment(\.appDensity) private var appDensity
5 @State private var settings = ScheduledReportStorage.loadSettings()
6 @State private var logs = ScheduledReportStorage.loadLogs()
7 @State private var isGenerating = false
8 @State private var statusMessage: String?
9
10 var body: some View {
11 List {
12 Section("Overview") {
13 if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
14 Text("Scheduled reports require Pro.")
15 .font(appDensity.font(.caption))
16 .foregroundStyle(Color(.appTextSecondary))
17 }
18
19 Toggle("Scheduled Reports", isOn: Binding(
20 get: { settings.isEnabled },
21 set: { newValue in
22 settings.isEnabled = newValue
23 saveSettings()
24 }
25 ))
26 .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
27
28 Picker("Cadence", selection: Binding(
29 get: { settings.cadence },
30 set: { newValue in
31 settings.cadence = newValue
32 saveSettings()
33 }
34 )) {
35 ForEach(ScheduledReportCadence.allCases) { cadence in
36 Text(cadence.title).tag(cadence)
37 }
38 }
39 .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
40
41 Picker("Format", selection: Binding(
42 get: { settings.format },
43 set: { newValue in
44 settings.format = newValue
45 saveSettings()
46 }
47 )) {
48 ForEach([DomainExportFormat.markdown, .pdf, .json]) { format in
49 Text(format.title).tag(format)
50 }
51 }
52 .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
53
54 LabeledContent(
55 "Last Generated",
56 value: settings.lastGeneratedAt?.formatted(date: .abbreviated, time: .shortened) ?? "Never"
57 )
58
59 if let statusMessage {
60 Text(statusMessage)
61 .font(appDensity.font(.caption))
62 .foregroundStyle(Color(.appTextSecondary))
63 }
64
65 Button(isGenerating ? "Generating…" : "Generate Now") {
66 Task { await generateNow() }
67 }
68 .disabled(isGenerating || !FeatureAccessService.hasAccess(to: .automatedMonitoring))
69 }
70 .listRowBackground(Color(.appSurface))
71
72 if logs.isEmpty {
73 Section {
74 EmptyStateCardView(
75 title: "No Reports Yet",
76 message: "Generated reports for your watchlist appear here after a manual or scheduled run.",
77 suggestion: "Tap Generate Now, or enable Scheduled Reports above.",
78 systemImage: "doc.text",
79 showsCardBackground: false
80 )
81 }
82 .listRowBackground(Color(.appSurface))
83 } else {
84 Section("Recent Reports") {
85 ForEach(logs) { log in
86 Button {
87 share(log)
88 } label: {
89 VStack(alignment: .leading, spacing: 2) {
90 Text("\(log.domainCount) domains • \(log.format.title)")
91 .foregroundStyle(.primary)
92 Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened))
93 .font(.caption)
94 .foregroundStyle(Color(.appTextSecondary))
95 }
96 }
97 }
98 }
99 .listRowBackground(Color(.appSurface))
100 }
101 }
102 .scrollContentBackground(.hidden)
103 .background(Color(.appBackground))
104 .navigationTitle("Scheduled Reports")
105 }
106
107 private func saveSettings() {
108 ScheduledReportStorage.saveSettings(settings)
109 ScheduledReportScheduler.shared.syncSchedule()
110 }
111
112 private func generateNow() async {
113 isGenerating = true
114 let outcome = await ScheduledReportService.shared.generateReport(trigger: .manual, requireEnabledSetting: false)
115 statusMessage = outcome.message
116 settings = ScheduledReportStorage.loadSettings()
117 logs = ScheduledReportStorage.loadLogs()
118 isGenerating = false
119 }
120
121 private func share(_ log: ScheduledReportLog) {
122 let url = ScheduledReportStorage.reportsDirectory.appendingPathComponent(log.fileName)
123 guard let data = try? Data(contentsOf: url) else {
124 statusMessage = "That report is no longer available on disk."
125 return
126 }
127 ExportPresenter.share(filename: log.fileName, data: data)
128 }
129}