commit 9af86307f7de9b4914660fab768be1b9a048a401
parent 7754b85b01a45adc0160e6b084713cc9c53b5385
Author: triesap <tyson@radroots.org>
Date: Sun, 14 Jun 2026 17:18:13 -0700
field-ios: stabilize app shell startup actions
- open local identity setup without blocking on relay connect
- keep relay status polling in the background
- preserve location check-in result state across view refreshes
- make location check-in use the full-row capture action affordance
Diffstat:
2 files changed, 43 insertions(+), 15 deletions(-)
diff --git a/Radroots/App/AppState.swift b/Radroots/App/AppState.swift
@@ -125,8 +125,7 @@ public final class AppState: ObservableObject {
self.captureIntake = captureIntake
await refreshRuntimeState(using: service)
if runtimeIdentityReady && !isLocked {
- try await connect(using: service)
- startPollingStatus()
+ startConnectingAndPollingStatus(using: service)
}
await refreshNostrProfileExternalActionCapability()
try refreshFileAccessProbe(
@@ -164,20 +163,18 @@ public final class AppState: ObservableObject {
let service = try requireRuntimeService()
try await restoreStoredIdentity(using: service)
setLocked(false)
- try await connect(using: service)
await refreshRuntimeState(using: service)
await refreshNostrProfileExternalActionCapability()
- startPollingStatus()
+ startConnectingAndPollingStatus(using: service)
}
public func createLocalIdentity() async throws {
let service = try requireRuntimeService()
try await createHostCustodyIdentity(using: service)
setLocked(false)
- try await connect(using: service)
await refreshRuntimeState(using: service)
await refreshNostrProfileExternalActionCapability()
- startPollingStatus()
+ startConnectingAndPollingStatus(using: service)
}
public func importNostrSecret(_ secretKey: String) async throws {
@@ -191,10 +188,9 @@ public final class AppState: ObservableObject {
)
try persistIdentity(record)
setLocked(false)
- try await connect(using: service)
await refreshRuntimeState(using: service)
await refreshNostrProfileExternalActionCapability()
- startPollingStatus()
+ startConnectingAndPollingStatus(using: service)
}
public func signOut() {
@@ -240,7 +236,19 @@ public final class AppState: ObservableObject {
}
public func refreshLocationCheckInStatus() async {
- locationCheckInState = await locationCheckIn.status()
+ switch locationCheckInState {
+ case .idle:
+ break
+ case .checking, .checkedIn, .failed:
+ return
+ }
+ let refreshedState = await locationCheckIn.status()
+ switch locationCheckInState {
+ case .idle:
+ locationCheckInState = refreshedState
+ case .checking, .checkedIn, .failed:
+ return
+ }
}
public func performLocationCheckIn() async {
@@ -650,11 +658,17 @@ public final class AppState: ObservableObject {
UserDefaults.standard.set(value, forKey: lockKey)
}
- private func startPollingStatus() {
+ private func startConnectingAndPollingStatus(using service: FieldRuntimeService) {
statusTask?.cancel()
statusTask = Task { [weak self] in
+ do {
+ try await self?.connect(using: service)
+ } catch {
+ self?.relayLastError = error.localizedDescription
+ self?.relayLight = .red
+ }
while !Task.isCancelled {
- await self?.refreshRuntimeState()
+ await self?.refreshRuntimeState(using: service)
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
}
diff --git a/Radroots/Views/HomeView.swift b/Radroots/Views/HomeView.swift
@@ -414,11 +414,25 @@ private struct LocationCheckInRow: View {
await app.performLocationCheckIn()
}
} label: {
- Label(actionTitle, systemImage: "location.fill")
- .font(.subheadline.weight(.semibold))
- .frame(maxWidth: .infinity)
+ HStack(spacing: 14) {
+ Image(systemName: "location.fill")
+ .font(.title3.weight(.semibold))
+ .frame(width: 34, height: 34)
+ .accessibilityHidden(true)
+ Text(actionTitle)
+ .font(.headline)
+ Spacer()
+ if isChecking {
+ ProgressView()
+ } else {
+ Image(systemName: "chevron.right")
+ .font(.footnote.weight(.semibold))
+ .foregroundStyle(.tertiary)
+ .accessibilityHidden(true)
+ }
+ }
+ .padding(.vertical, 4)
}
- .buttonStyle(.borderedProminent)
.disabled(isChecking)
.accessibilityIdentifier("\(accessibilityIDPrefix).action")
}