feat: in-app dark mode toggle (System / Light / Dark)
- AppearanceManager with UserDefaults persistence - Three modes: System (follows iOS), Light, Dark - Toggle in Home screen profile menu under "Appearance" - Applied via .preferredColorScheme at app root - Persists across app launches Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
A10003 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10003 /* Config.swift */; };
|
A10003 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10003 /* Config.swift */; };
|
||||||
A10004 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10004 /* APIClient.swift */; };
|
A10004 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10004 /* APIClient.swift */; };
|
||||||
A10005 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10005 /* AuthManager.swift */; };
|
A10005 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10005 /* AuthManager.swift */; };
|
||||||
|
A10050 /* AppearanceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10050 /* AppearanceManager.swift */; };
|
||||||
A10006 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10006 /* LoginView.swift */; };
|
A10006 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10006 /* LoginView.swift */; };
|
||||||
A10007 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10007 /* HomeView.swift */; };
|
A10007 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10007 /* HomeView.swift */; };
|
||||||
A10008 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10008 /* HomeViewModel.swift */; };
|
A10008 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10008 /* HomeViewModel.swift */; };
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
B10003 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
|
B10003 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
|
||||||
B10004 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
|
B10004 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
|
||||||
B10005 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = "<group>"; };
|
B10005 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = "<group>"; };
|
||||||
|
B10050 /* AppearanceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceManager.swift; sourceTree = "<group>"; };
|
||||||
B10006 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
B10006 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||||
B10007 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
B10007 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||||
B10008 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
|
B10008 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -137,6 +139,7 @@
|
|||||||
children = (
|
children = (
|
||||||
B10004 /* APIClient.swift */,
|
B10004 /* APIClient.swift */,
|
||||||
B10005 /* AuthManager.swift */,
|
B10005 /* AuthManager.swift */,
|
||||||
|
B10050 /* AppearanceManager.swift */,
|
||||||
);
|
);
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -411,6 +414,7 @@
|
|||||||
A10003 /* Config.swift in Sources */,
|
A10003 /* Config.swift in Sources */,
|
||||||
A10004 /* APIClient.swift in Sources */,
|
A10004 /* APIClient.swift in Sources */,
|
||||||
A10005 /* AuthManager.swift in Sources */,
|
A10005 /* AuthManager.swift in Sources */,
|
||||||
|
A10050 /* AppearanceManager.swift in Sources */,
|
||||||
A10006 /* LoginView.swift in Sources */,
|
A10006 /* LoginView.swift in Sources */,
|
||||||
A10007 /* HomeView.swift in Sources */,
|
A10007 /* HomeView.swift in Sources */,
|
||||||
A10008 /* HomeViewModel.swift in Sources */,
|
A10008 /* HomeViewModel.swift in Sources */,
|
||||||
|
|||||||
43
ios/Platform/Platform/Core/AppearanceManager.swift
Normal file
43
ios/Platform/Platform/Core/AppearanceManager.swift
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@Observable
|
||||||
|
final class AppearanceManager {
|
||||||
|
enum Mode: String, CaseIterable {
|
||||||
|
case system, light, 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.fill"
|
||||||
|
case .dark: return "moon.fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colorScheme: ColorScheme? {
|
||||||
|
switch self {
|
||||||
|
case .system: return nil
|
||||||
|
case .light: return .light
|
||||||
|
case .dark: return .dark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode: Mode {
|
||||||
|
didSet {
|
||||||
|
UserDefaults.standard.set(mode.rawValue, forKey: "appearance_mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let saved = UserDefaults.standard.string(forKey: "appearance_mode") ?? "system"
|
||||||
|
mode = Mode(rawValue: saved) ?? .system
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import PhotosUI
|
|||||||
|
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
@Environment(AuthManager.self) private var auth
|
@Environment(AuthManager.self) private var auth
|
||||||
|
@Environment(AppearanceManager.self) private var appearance
|
||||||
@State private var vm = HomeViewModel()
|
@State private var vm = HomeViewModel()
|
||||||
@State private var ringAnimated = false
|
@State private var ringAnimated = false
|
||||||
@Binding var selectedTab: Int
|
@Binding var selectedTab: Int
|
||||||
@@ -50,6 +51,18 @@ struct HomeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
|
Menu {
|
||||||
|
ForEach(AppearanceManager.Mode.allCases, id: \.self) { mode in
|
||||||
|
Button {
|
||||||
|
appearance.mode = mode
|
||||||
|
} label: {
|
||||||
|
Label(mode.label, systemImage: mode.icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Appearance", systemImage: appearance.mode.icon)
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
Task { await auth.logout() }
|
Task { await auth.logout() }
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct PlatformApp: App {
|
struct PlatformApp: App {
|
||||||
@State private var authManager = AuthManager()
|
@State private var authManager = AuthManager()
|
||||||
|
@State private var appearance = AppearanceManager()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
.environment(authManager)
|
.environment(authManager)
|
||||||
|
.environment(appearance)
|
||||||
|
.preferredColorScheme(appearance.mode.colorScheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user