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

@@ -9,30 +9,33 @@ struct ConnectView: View {
var body: some View {
NavigationStack {
ZStack {
AppTheme.surfaceSunken.ignoresSafeArea()
AppTheme.background.ignoresSafeArea()
ScrollView {
VStack(spacing: 0) {
// Logo + title
VStack(spacing: 10) {
// Header
VStack(spacing: 20) {
// App icon
Image("nas_logo_clean")
.resizable()
.scaledToFit()
.frame(width: 44, height: 44)
.clipShape(RoundedRectangle(cornerRadius: 11, style: .continuous))
.frame(width: 52, height: 52)
.clipShape(RoundedRectangle(cornerRadius: 13, style: .continuous))
.shadow(color: .black.opacity(0.08), radius: 14, x: 0, y: 4)
.padding(.top, 44)
Text("NAS Connect")
.font(.system(size: 26, weight: .semibold))
.foregroundStyle(AppTheme.ink)
// Title
VStack(spacing: 6) {
Text("Kisani")
.font(.system(size: 26, weight: .semibold))
.foregroundStyle(AppTheme.ink)
Text("Connect to your UGreen or any SMB / SFTP NAS")
.font(AppTheme.caption())
.foregroundStyle(AppTheme.inkTertiary)
.multilineTextAlignment(.center)
// UGreen NAS compatibility badge
UGreenCompatibilityBadge()
}
}
.padding(.bottom, 32)
.padding(.bottom, 28)
.padding(.horizontal, AppTheme.hPad)
// Protocol picker
@@ -50,7 +53,7 @@ struct ConnectView: View {
.padding(.vertical, 7)
.background(
RoundedRectangle(cornerRadius: 7, style: .continuous)
.fill(vm.selectedProtocol == proto ? Color.white : Color.clear)
.fill(vm.selectedProtocol == proto ? AppTheme.surfaceRaised : Color.clear)
.shadow(
color: vm.selectedProtocol == proto ? .black.opacity(0.06) : .clear,
radius: 3, x: 0, y: 1
@@ -61,7 +64,7 @@ struct ConnectView: View {
}
}
.padding(3)
.background(Color.black.opacity(0.06))
.background(AppTheme.surfaceSunken)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.padding(.horizontal, AppTheme.hPad)
.padding(.bottom, 20)
@@ -90,19 +93,14 @@ struct ConnectView: View {
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(Color.white)
.background(AppTheme.surfaceRaised)
.clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous))
.shadow(color: AppTheme.cardShadow, radius: 12, x: 0, y: 2)
HStack(spacing: 20) {
Text("IP Address, ex: 192.168.1.10")
.font(AppTheme.micro(11))
.foregroundStyle(AppTheme.inkTertiary)
Text("NAS Name, ex: MY_NAS")
.font(AppTheme.micro(11))
.foregroundStyle(AppTheme.inkTertiary)
}
.padding(.leading, 4)
Text("e.g. 192.168.1.10 or MY_NAS")
.font(AppTheme.micro(11))
.foregroundStyle(AppTheme.inkTertiary)
.padding(.leading, 4)
}
.padding(.horizontal, AppTheme.hPad)
.padding(.bottom, 12)
@@ -163,7 +161,7 @@ struct ConnectView: View {
.padding(.horizontal, 16)
.padding(.vertical, 14)
}
.background(Color.white)
.background(AppTheme.surfaceRaised)
.clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)
@@ -199,7 +197,7 @@ struct ConnectView: View {
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(Color.white)
.background(AppTheme.surfaceRaised)
.clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous))
.shadow(color: AppTheme.cardShadow, radius: 12, x: 0, y: 2)
}
@@ -255,25 +253,11 @@ struct ConnectView: View {
.disabled(!vm.canConnect)
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: vm.isConnected)
// Help
Button {
// future: show help sheet
} label: {
HStack(spacing: 6) {
Image(systemName: "questionmark.circle")
.font(.system(size: 13))
Text("Help")
.font(AppTheme.caption())
}
.foregroundStyle(AppTheme.inkTertiary)
}
.padding(.top, 20)
.padding(.bottom, 48)
Spacer().frame(height: 48)
}
}
}
.navigationTitle("NAS Connect")
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(true)
.navigationDestination(isPresented: $navigateToDashboard) { MainTabView() }
.sheet(isPresented: $vm.showFolderBrowser) {
if let conn = ConnectionStore.shared.savedConnection {