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:
@@ -1,55 +1,107 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// MARK: — Appearance preference
|
||||
|
||||
enum AppearanceMode: String, CaseIterable {
|
||||
case system, light, dark
|
||||
|
||||
var resolvedScheme: ColorScheme? {
|
||||
switch self {
|
||||
case .system: return nil
|
||||
case .light: return .light
|
||||
case .dark: return .dark
|
||||
}
|
||||
}
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .system: return "System"
|
||||
case .light: return "Light"
|
||||
case .dark: return "Dark"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .system: return "circle.lefthalf.filled"
|
||||
case .light: return "sun.max"
|
||||
case .dark: return "moon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AppTheme {
|
||||
// MARK: — Surfaces
|
||||
static let background = Color.white
|
||||
static let surfaceRaised = Color.white
|
||||
static let surfaceSunken = Color(hex: "#F5F5F5")
|
||||
static let background = Color(UIColor.systemBackground)
|
||||
static let surfaceRaised = Color(UIColor.systemBackground)
|
||||
static let surfaceSunken = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor(white: 0.11, alpha: 1)
|
||||
: UIColor(white: 0.961, alpha: 1)
|
||||
})
|
||||
|
||||
// MARK: — Ink
|
||||
static let ink = Color(hex: "#0F0F0F")
|
||||
static let inkSecondary = Color(hex: "#6B6B6B")
|
||||
static let inkTertiary = Color(hex: "#ABABAB")
|
||||
static let inkQuaternary = Color(hex: "#D4D4D4")
|
||||
static let ink = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor(white: 0.93, alpha: 1)
|
||||
: UIColor(red: 0.059, green: 0.059, blue: 0.059, alpha: 1)
|
||||
})
|
||||
static let inkSecondary = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor(white: 0.58, alpha: 1)
|
||||
: UIColor(white: 0.42, alpha: 1)
|
||||
})
|
||||
static let inkTertiary = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor(white: 0.40, alpha: 1)
|
||||
: UIColor(white: 0.67, alpha: 1)
|
||||
})
|
||||
static let inkQuaternary = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor(white: 0.22, alpha: 1)
|
||||
: UIColor(white: 0.83, alpha: 1)
|
||||
})
|
||||
|
||||
// Readable on AppTheme.ink background in both color schemes
|
||||
static let inkInverse = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor(red: 0.059, green: 0.059, blue: 0.059, alpha: 1)
|
||||
: UIColor(white: 1.0, alpha: 1)
|
||||
})
|
||||
|
||||
// MARK: — Semantic
|
||||
static let positive = Color(hex: "#16A34A")
|
||||
static let destructive = Color(hex: "#DC2626")
|
||||
static let interactive = Color(hex: "#2563EB")
|
||||
static let positive = Color(hex: "#16A34A")
|
||||
static let destructive = Color(hex: "#DC2626")
|
||||
static let interactive = Color(hex: "#2563EB")
|
||||
|
||||
// MARK: — Chrome
|
||||
static let separator = Color.black.opacity(0.07)
|
||||
static let cardShadow = Color.black.opacity(0.05)
|
||||
static let cardBorderColor = Color.black.opacity(0.07)
|
||||
static let separator = Color(UIColor.separator)
|
||||
static let cardShadow = Color(UIColor { t in
|
||||
t.userInterfaceStyle == .dark
|
||||
? UIColor.clear
|
||||
: UIColor.black.withAlphaComponent(0.048)
|
||||
})
|
||||
static let cardBorderColor = Color(UIColor.separator)
|
||||
|
||||
// MARK: — Metrics
|
||||
static let radius: CGFloat = 14
|
||||
static let radiusLarge: CGFloat = 18
|
||||
static let hPad: CGFloat = 20
|
||||
static let cardPad: CGFloat = 16
|
||||
static let sectionGap: CGFloat = 28
|
||||
static let radius: CGFloat = 14
|
||||
static let radiusLarge: CGFloat = 18
|
||||
static let hPad: CGFloat = 20
|
||||
static let cardPad: CGFloat = 16
|
||||
static let sectionGap: CGFloat = 28
|
||||
|
||||
// MARK: — Typography
|
||||
static func display(_ size: CGFloat = 32) -> Font {
|
||||
.system(size: size, weight: .semibold, design: .default)
|
||||
}
|
||||
static func title(_ size: CGFloat = 20) -> Font {
|
||||
.system(size: size, weight: .semibold)
|
||||
}
|
||||
static func headline(_ size: CGFloat = 15) -> Font {
|
||||
.system(size: size, weight: .medium)
|
||||
}
|
||||
static func body(_ size: CGFloat = 15) -> Font {
|
||||
.system(size: size, weight: .regular)
|
||||
}
|
||||
static func caption(_ size: CGFloat = 13) -> Font {
|
||||
.system(size: size, weight: .regular)
|
||||
}
|
||||
static func micro(_ size: CGFloat = 11) -> Font {
|
||||
.system(size: size, weight: .medium)
|
||||
}
|
||||
static func title(_ size: CGFloat = 20) -> Font { .system(size: size, weight: .semibold) }
|
||||
static func headline(_ size: CGFloat = 15) -> Font { .system(size: size, weight: .medium) }
|
||||
static func body(_ size: CGFloat = 15) -> Font { .system(size: size, weight: .regular) }
|
||||
static func caption(_ size: CGFloat = 13) -> Font { .system(size: size, weight: .regular) }
|
||||
static func micro(_ size: CGFloat = 11) -> Font { .system(size: size, weight: .medium) }
|
||||
|
||||
// MARK: — Back-compat aliases used by existing call sites
|
||||
// MARK: — Back-compat aliases
|
||||
static let blue = interactive
|
||||
static let green = positive
|
||||
static let red = destructive
|
||||
@@ -82,9 +134,7 @@ struct CardStyle: ViewModifier {
|
||||
}
|
||||
|
||||
extension View {
|
||||
func card(padded: Bool = true) -> some View {
|
||||
modifier(CardStyle(padded: padded))
|
||||
}
|
||||
func card(padded: Bool = true) -> some View { modifier(CardStyle(padded: padded)) }
|
||||
|
||||
@ViewBuilder func `if`<T: View>(_ condition: Bool, transform: (Self) -> T) -> some View {
|
||||
if condition { transform(self) } else { self }
|
||||
|
||||
Reference in New Issue
Block a user