feat: Kisani rebrand, step-based onboarding flow, dark mode fixes

- Rename app to Kisani in all UI text and Face ID prompt
- Replace flat 2-state RootView with 5-step flow: ConnectView →
  LoginView → FolderSetupView → PhotoPermissionView → MainTabView
- Remove NavigationStack from LoginView (was causing nested-stack crash);
  drive navigation via ConnectionStore.isSessionActive
- Add FolderSetupView and PhotoPermissionView onboarding screens with
  step indicator (2/3, 3/3), spring entrance animations, and UGreen
  connection chip
- Fix dark mode button invisibility: PrimaryButtonStyle and PillButtonStyle
  now use AppTheme.inkInverse; GhostButtonStyle border uses ink.opacity(0.25)
- Add AppTheme.inkInverse adaptive token (dark bg ink on dark, white on light)
- Add ConnectionStore.isSessionActive (non-persisted) and
  onboardingPhotoShown (UserDefaults-persisted)
- Add os_log logging in LoginViewModel at auth start/success/failure
- Remove dead Help button from ConnectView
- UGreenCompatibilityBadge, KeychainStore, SavedConnections, extensions,
  PrivacyInfo.xcprivacy, unit test target and test stubs
- AppTheme: full UIColor dynamic provider tokens for dark/light adaptive color
- AppearanceMode enum + segmented picker in SettingsView
- BackupView: enlarged ring, removed options card and linear progress bar
- HistoryView: duration and bytes formatting, improved typography hierarchy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Robin Kutesa
2026-05-16 16:20:53 +03:00
parent 03d63e60de
commit ae38f6e417
38 changed files with 1649 additions and 451 deletions

View File

@@ -10,6 +10,8 @@ enum NASProtocol: String, Codable, CaseIterable {
case .sftp: return 22
}
}
var localizedDescription: String { rawValue }
}
struct NASConnection: Codable, Identifiable, Equatable {
@@ -18,10 +20,15 @@ struct NASConnection: Codable, Identifiable, Equatable {
var port: Int
var nasProtocol: NASProtocol
var username: String
var password: String
var password: String // Runtime only persisted in Keychain, not UserDefaults
var remotePath: String
var displayName: String
// Excludes password so it is never written to UserDefaults.
enum CodingKeys: String, CodingKey {
case id, host, port, nasProtocol, username, remotePath, displayName
}
init(
host: String,
port: Int? = nil,
@@ -39,4 +46,16 @@ struct NASConnection: Codable, Identifiable, Equatable {
self.remotePath = remotePath
self.displayName = displayName.isEmpty ? host : displayName
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
id = try c.decode(UUID.self, forKey: .id)
host = try c.decode(String.self, forKey: .host)
port = try c.decode(Int.self, forKey: .port)
nasProtocol = try c.decode(NASProtocol.self, forKey: .nasProtocol)
username = try c.decode(String.self, forKey: .username)
remotePath = try c.decode(String.self, forKey: .remotePath)
displayName = try c.decode(String.self, forKey: .displayName)
password = "" // filled from Keychain by SavedConnections.load()
}
}