field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraKeyRemovalView.swift (4611B)


      1 import SwiftUI
      2 
      3 enum TeraKeyRemovalConsent {
      4   static let local = "This removes only this device’s signing key. Drafts, photos, settings, signed requests and receipts remain on this device. This does not erase local data."
      5   static let remote = "Published copies may remain with relays and other people. Key removal does not retract them. To request retraction, use Retract on each post first, then select its saved request below. Linked revisions must finish in Drafts & outbox."
      6   static let recovery = "Selected requests are signed and saved before removal; delivery is not guaranteed. The app stops before removing the key. To open this account’s retained data or resume delivery later, you must import the same key from your own backup. Unselected unsigned work cannot be signed without that key."
      7 }
      8 
      9 struct TeraKeyRemovalView: View {
     10   let author: String
     11   @EnvironmentObject private var appModel: TeraAppModel
     12   @Environment(\.dismiss) private var dismiss
     13   @State private var requests: [TeraLegacyDraftSummary] = []
     14   @State private var selected: Set<String> = []
     15   @State private var cursor: String?
     16   @State private var loaded = false
     17   @State private var loading = false
     18   @State private var consent = false
     19   @State private var message: String?
     20   @State private var removal: Task<Void, Never>?
     21 
     22   var body: some View {
     23     NavigationStack {
     24       List {
     25         Section("Local signing key") {
     26           Text(TeraKeyRemovalConsent.local)
     27           Text(author).font(.caption.monospaced())
     28         }
     29         Section("Remote deletion requests") {
     30           Text(TeraKeyRemovalConsent.remote)
     31           ForEach(requests) { request in
     32             Toggle(isOn: Binding(get: { selected.contains(request.id) }, set: { enabled in
     33               if enabled {
     34                 selected.insert(request.id)
     35               } else {
     36                 selected.remove(request.id)
     37               }
     38             })) {
     39               VStack(alignment: .leading) {
     40                 Text("Saved retraction \(request.id.prefix(8))").font(.caption.monospaced())
     41                 Text(request.honestSummary).font(.caption)
     42               }
     43             }
     44           }
     45           if loaded, requests.isEmpty {
     46             Text("No independent saved retraction requests are shown.")
     47           }
     48           if cursor != nil {
     49             Button("Next page of requests") { Task { await load() } }
     50               .disabled(!selected.isEmpty || loading)
     51             Text("Selections apply to this page only. Prepare other requests in Drafts & outbox before removal.").font(.caption)
     52           }
     53           if loading {
     54             ProgressView()
     55           }
     56           if let message {
     57             Text(message)
     58           }
     59         }
     60         Section("Before removal") {
     61           Text(TeraKeyRemovalConsent.recovery)
     62           Toggle("I understand the retained data and loss of signing access", isOn: $consent)
     63             .accessibilityIdentifier("tera.keyRemoval.consent")
     64           Button("Remove this signing key", role: .destructive) {
     65             let chosen = requests.filter { selected.contains($0.id) }.map {
     66               TeraKeyRemovalRequest(id: $0.id, revision: $0.revision)
     67             }
     68             removal = Task {
     69               await appModel.removeSigningKey(author: author, requests: chosen)
     70               removal = nil
     71               dismiss()
     72             }
     73           }
     74           .disabled(!consent || !loaded || loading || removal != nil)
     75           .accessibilityIdentifier("tera.keyRemoval.confirm")
     76         }
     77       }
     78       .disabled(removal != nil)
     79       .navigationTitle("Remove signing key")
     80       .toolbar {
     81         ToolbarItem(placement: .cancellationAction) {
     82           Button("Cancel") { removal?.cancel(); dismiss() }
     83         }
     84       }
     85       .task {
     86         if !loaded {
     87           await load()
     88         }
     89       }
     90       .onDisappear { removal?.cancel() }
     91       .interactiveDismissDisabled(removal != nil)
     92     }
     93   }
     94 
     95   private func load() async {
     96     guard !loading else { return }
     97     loading = true
     98     defer { loading = false }
     99     do {
    100       let page = try await appModel.keyRemovalPage(author: author, cursor: cursor)
    101       try Task.checkCancellation()
    102       requests = page.entries.compactMap { entry in
    103         guard case let .draft(draft) = entry, draft.kind == .retraction,
    104               draft.revisionParentID == nil else { return nil }
    105         return draft
    106       }
    107       selected.removeAll()
    108       cursor = page.nextCursor
    109       loaded = true
    110       message = nil
    111     } catch {
    112       loaded = false
    113       message = "Saved requests could not be checked. Cancel and retry before removing the key."
    114     }
    115   }
    116 }