feat: added color settings 0b06b19d
Steve · 2025-12-24 20:12 6 file(s) · +73 −5
Titan/Settings/ThemeSettings.swift +56 −0
26 26
27 27
    /// The home page URL that the browser navigates to on launch and when pressing Home
28 28
    @AppStorage("homePage") var homePage: String = "gemini://geminiprotocol.net/"
29 +
30 +
    /// Key for persisting accent color hex value
31 +
    private static let accentColorKey = "accentColorHex"
32 +
33 +
    init() {
34 +
        if let hex = UserDefaults.standard.string(forKey: Self.accentColorKey),
35 +
           let color = Color(hex: hex) {
36 +
            setAllColors(color)
37 +
        }
38 +
    }
39 +
40 +
    /// Sets all accent colors to the given color and persists the choice
41 +
    func setAllColors(_ color: Color) {
42 +
        accentColor = color
43 +
        progressBarColor = color
44 +
        linkColor = color
45 +
        mediaAccentColor = color
46 +
        toolbarButtonColor = color
47 +
48 +
        if let hex = color.toHex() {
49 +
            UserDefaults.standard.set(hex, forKey: Self.accentColorKey)
50 +
        }
51 +
    }
52 +
}
53 +
54 +
// MARK: - Color Hex Conversion
55 +
56 +
extension Color {
57 +
    init?(hex: String) {
58 +
        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
59 +
        hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
60 +
61 +
        guard hexSanitized.count == 6 else { return nil }
62 +
63 +
        var rgb: UInt64 = 0
64 +
        guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
65 +
66 +
        let red = Double((rgb & 0xFF0000) >> 16) / 255.0
67 +
        let green = Double((rgb & 0x00FF00) >> 8) / 255.0
68 +
        let blue = Double(rgb & 0x0000FF) / 255.0
69 +
70 +
        self.init(red: red, green: green, blue: blue)
71 +
    }
72 +
73 +
    func toHex() -> String? {
74 +
        guard let components = UIColor(self).cgColor.components else { return nil }
75 +
76 +
        let r = components.count > 0 ? components[0] : 0
77 +
        let g = components.count > 1 ? components[1] : 0
78 +
        let b = components.count > 2 ? components[2] : 0
79 +
80 +
        return String(format: "#%02X%02X%02X",
81 +
                      Int(r * 255),
82 +
                      Int(g * 255),
83 +
                      Int(b * 255))
84 +
    }
29 85
}
30 86
31 87
// MARK: - Environment Key
Titan/TitanApp.swift +1 −0
15 15
        WindowGroup {
16 16
            ContentView()
17 17
                .environment(\.themeSettings, themeSettings)
18 +
                .environmentObject(themeSettings)
18 19
        }
19 20
    }
20 21
}
Titan/Views/ContentView.swift +1 −1
28 28
}
29 29
30 30
struct ContentView: View {
31 -
    @Environment(\.themeSettings) private var themeSettings
31 +
    @EnvironmentObject private var themeSettings: ThemeSettings
32 32
    @State private var urlText = ""
33 33
    @State private var responseText = ""
34 34
    @State private var isLoading = false
Titan/Views/MediaPreviewView.swift +2 −2
12 12
    let media: MediaContent
13 13
    let onDismiss: () -> Void
14 14
15 -
    @Environment(\.themeSettings) private var themeSettings
15 +
    @EnvironmentObject private var themeSettings: ThemeSettings
16 16
    @State private var showingSaveOptions = false
17 17
    @State private var saveMessage: String?
18 18
    @State private var showingSaveAlert = false
175 175
    let data: Data
176 176
    let filename: String
177 177
178 -
    @Environment(\.themeSettings) private var themeSettings
178 +
    @EnvironmentObject private var themeSettings: ThemeSettings
179 179
    @StateObject private var audioPlayer = AudioPlayerViewModel()
180 180
181 181
    var body: some View {
Titan/Views/SettingsView.swift +12 −1
6 6
import SwiftUI
7 7
8 8
struct SettingsView: View {
9 -
    @Environment(\.themeSettings) private var themeSettings
9 +
    @EnvironmentObject private var themeSettings: ThemeSettings
10 10
    @Environment(\.dismiss) private var dismiss
11 11
12 12
    @State private var homePageText: String = ""
13 +
    @State private var selectedAccentColor: Color = .blue
13 14
14 15
    var body: some View {
15 16
        NavigationStack {
24 25
                } footer: {
25 26
                    Text("The page that loads when you open the app or tap the Home button.")
26 27
                }
28 +
29 +
                Section {
30 +
                    ColorPicker("Accent Color", selection: $selectedAccentColor, supportsOpacity: false)
31 +
                } header: {
32 +
                    Text("Appearance")
33 +
                } footer: {
34 +
                    Text("Changes the color of links, buttons, and other interactive elements.")
35 +
                }
27 36
            }
28 37
            .navigationTitle("Settings")
29 38
            .navigationBarTitleDisplayMode(.inline)
36 45
                ToolbarItem(placement: .confirmationAction) {
37 46
                    Button("Save") {
38 47
                        themeSettings.homePage = homePageText
48 +
                        themeSettings.setAllColors(selectedAccentColor)
39 49
                        dismiss()
40 50
                    }
41 51
                }
42 52
            }
43 53
            .onAppear {
44 54
                homePageText = themeSettings.homePage
55 +
                selectedAccentColor = themeSettings.accentColor
45 56
            }
46 57
        }
47 58
    }
Titan/Views/TitanContentView.swift +1 −1
52 52
    let baseURL: String
53 53
    let onLinkTap: (String) -> Void
54 54
55 -
    @Environment(\.themeSettings) private var themeSettings
55 +
    @EnvironmentObject private var themeSettings: ThemeSettings
56 56
57 57
    init(content: String, baseURL: String = "", onLinkTap: @escaping (String) -> Void) {
58 58
        self.content = content