mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
Merge pull request #113 from neoarz/THEMEING
Added More theming options, and flexibility
This commit is contained in:
commit
7c85bd6b16
11 changed files with 507 additions and 100 deletions
21
StikJIT/Assets.xcassets/DarkUI.imageset/Contents.json
vendored
Normal file
21
StikJIT/Assets.xcassets/DarkUI.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "DarkUI.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
StikJIT/Assets.xcassets/DarkUI.imageset/DarkUI.png
vendored
Normal file
BIN
StikJIT/Assets.xcassets/DarkUI.imageset/DarkUI.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
21
StikJIT/Assets.xcassets/LightUI.imageset/Contents.json
vendored
Normal file
21
StikJIT/Assets.xcassets/LightUI.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LightUI.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
StikJIT/Assets.xcassets/LightUI.imageset/LightUI.png
vendored
Normal file
BIN
StikJIT/Assets.xcassets/LightUI.imageset/LightUI.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
21
StikJIT/Assets.xcassets/System.imageset/Contents.json
vendored
Normal file
21
StikJIT/Assets.xcassets/System.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "System.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
StikJIT/Assets.xcassets/System.imageset/System.png
vendored
Normal file
BIN
StikJIT/Assets.xcassets/System.imageset/System.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
|
|
@ -91,7 +91,9 @@ struct HeartbeatApp: App {
|
|||
@State private var alert_string = ""
|
||||
@State private var alert_title = ""
|
||||
@StateObject private var mount = MountingProgress.shared
|
||||
@AppStorage("appTheme") private var appTheme: String = "system"
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
|
||||
let urls: [String] = [
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist",
|
||||
|
|
@ -112,6 +114,19 @@ struct HeartbeatApp: App {
|
|||
let fixMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))!
|
||||
let origMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))!
|
||||
method_exchangeImplementations(origMethod, fixMethod)
|
||||
|
||||
// Apply theme immediately when app launches
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first {
|
||||
switch appTheme {
|
||||
case "dark":
|
||||
window.overrideUserInterfaceStyle = .dark
|
||||
case "light":
|
||||
window.overrideUserInterfaceStyle = .light
|
||||
default:
|
||||
window.overrideUserInterfaceStyle = .unspecified
|
||||
}
|
||||
}
|
||||
}
|
||||
func newVerCheck() {
|
||||
let currentDate = Calendar.current.startOfDay(for: Date())
|
||||
|
|
@ -138,6 +153,19 @@ struct HeartbeatApp: App {
|
|||
UserDefaults.standard.set(currentDate, forKey: "VersionUpdateAlert")
|
||||
}
|
||||
}
|
||||
private func applyTheme() {
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first {
|
||||
switch appTheme {
|
||||
case "dark":
|
||||
window.overrideUserInterfaceStyle = .dark
|
||||
case "light":
|
||||
window.overrideUserInterfaceStyle = .light
|
||||
default:
|
||||
window.overrideUserInterfaceStyle = .unspecified
|
||||
}
|
||||
}
|
||||
}
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if isLoading2 {
|
||||
|
|
@ -214,6 +242,7 @@ struct HeartbeatApp: App {
|
|||
} else {
|
||||
MainTabView()
|
||||
.onAppear() {
|
||||
applyTheme()
|
||||
let fileManager = FileManager.default
|
||||
for (index, urlString) in urls.enumerated() {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(outputFiles[index])
|
||||
|
|
@ -462,6 +491,7 @@ struct LoadingView: View {
|
|||
@State private var animate = false
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
|
||||
@AppStorage("appTheme") private var appTheme: String = "system"
|
||||
|
||||
private var accentColor: Color {
|
||||
if customAccentColorHex.isEmpty {
|
||||
|
|
@ -471,10 +501,21 @@ struct LoadingView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private var isDarkMode: Bool {
|
||||
switch appTheme {
|
||||
case "dark":
|
||||
return true
|
||||
case "light":
|
||||
return false
|
||||
default:
|
||||
return colorScheme == .dark
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Use system background color instead of fixed black
|
||||
Color(colorScheme == .dark ? .black : .white)
|
||||
// Use theme-aware background color
|
||||
Color(isDarkMode ? .black : .white)
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack {
|
||||
|
|
@ -482,7 +523,7 @@ struct LoadingView: View {
|
|||
// Background circle - slightly visible in both themes
|
||||
Circle()
|
||||
.stroke(lineWidth: 8)
|
||||
.foregroundColor(colorScheme == .dark ?
|
||||
.foregroundColor(isDarkMode ?
|
||||
Color.white.opacity(0.3) :
|
||||
Color.black.opacity(0.1))
|
||||
.frame(width: 80, height: 80)
|
||||
|
|
@ -511,7 +552,7 @@ struct LoadingView: View {
|
|||
// Text adapts to theme
|
||||
Text("Loading...")
|
||||
.font(.system(size: 20, weight: .medium, design: .rounded))
|
||||
.foregroundColor(colorScheme == .dark ?
|
||||
.foregroundColor(isDarkMode ?
|
||||
.white.opacity(0.8) :
|
||||
.black.opacity(0.8))
|
||||
.padding(.top, 20)
|
||||
|
|
|
|||
53
StikJIT/Views/AppIconView.swift
Normal file
53
StikJIT/Views/AppIconView.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import SwiftUI
|
||||
|
||||
struct AppIconView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
|
||||
|
||||
private var accentColor: Color {
|
||||
if customAccentColorHex.isEmpty {
|
||||
return .blue
|
||||
} else {
|
||||
return Color(hex: customAccentColorHex) ?? .blue
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color(UIColor.systemBackground)
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 24) {
|
||||
Image(systemName: "hammer.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(accentColor)
|
||||
.padding(.bottom, 8)
|
||||
|
||||
Text("Work in Progress")
|
||||
.font(.system(size: 24, weight: .bold))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("App icon customization coming soon!")
|
||||
.font(.system(size: 17))
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("App Icon")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AppIconView()
|
||||
}
|
||||
263
StikJIT/Views/DisplayView.swift
Normal file
263
StikJIT/Views/DisplayView.swift
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
// DisplayView.swift
|
||||
// StikJIT
|
||||
//
|
||||
// Created by neoarz on 4/9/25.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AccentColorPicker: View {
|
||||
@Binding var selectedColor: Color
|
||||
|
||||
let colors: [Color] = [
|
||||
.blue, // Default system blue
|
||||
.init(hex: "#7FFFD4")!, // Aqua
|
||||
.init(hex: "#50C878")!, // Green
|
||||
.red, // Red
|
||||
.init(hex: "#6A5ACD")!, // Purple
|
||||
.init(hex: "#DA70D6")!, // Pink
|
||||
.white, // white
|
||||
.black // black
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Accent Color")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
LazyVGrid(columns: Array(repeating: .init(.flexible(), spacing: 12), count: 9), spacing: 12) {
|
||||
ForEach(colors, id: \.self) { color in
|
||||
Circle()
|
||||
.fill(color)
|
||||
.frame(width: 28, height: 28)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(Color.primary.opacity(0.1), lineWidth: 1)
|
||||
)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(selectedColor == color ? Color.primary : Color.clear, lineWidth: 2)
|
||||
)
|
||||
.onTapGesture {
|
||||
selectedColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// Direct Color Picker Circle
|
||||
ColorPicker("", selection: $selectedColor)
|
||||
.labelsHidden()
|
||||
.frame(width: 28, height: 28)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(Color.primary.opacity(0.1), lineWidth: 1)
|
||||
)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.top, 12)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayView: View {
|
||||
@AppStorage("username") private var username = "User"
|
||||
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
|
||||
@AppStorage("appTheme") private var appTheme: String = "system"
|
||||
@AppStorage("loadAppIconsOnJIT") private var loadAppIconsOnJIT = true
|
||||
@State private var selectedAccentColor: Color = .blue
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
private var accentColor: Color {
|
||||
if customAccentColorHex.isEmpty {
|
||||
return .blue
|
||||
} else {
|
||||
return Color(hex: customAccentColorHex) ?? .blue
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(colorScheme == .dark ? .black : UIColor.systemBackground)
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
// App Theme Section with Accent Colors
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Theme")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
// Theme selector with preview images
|
||||
HStack(spacing: 12) {
|
||||
// Automatic Theme
|
||||
ThemeOptionButton(
|
||||
title: "Automatic",
|
||||
imageName: "System",
|
||||
isSelected: appTheme == "system",
|
||||
accentColor: selectedAccentColor,
|
||||
action: { appTheme = "system" }
|
||||
)
|
||||
|
||||
// Light Theme
|
||||
ThemeOptionButton(
|
||||
title: "Light",
|
||||
imageName: "LightUI",
|
||||
isSelected: appTheme == "light",
|
||||
accentColor: selectedAccentColor,
|
||||
action: { appTheme = "light" }
|
||||
)
|
||||
|
||||
// Dark Theme
|
||||
ThemeOptionButton(
|
||||
title: "Dark",
|
||||
imageName: "DarkUI",
|
||||
isSelected: appTheme == "dark",
|
||||
accentColor: selectedAccentColor,
|
||||
action: { appTheme = "dark" }
|
||||
)
|
||||
}
|
||||
.onChange(of: appTheme) { newValue in
|
||||
applyTheme(newValue)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding(.vertical, 4)
|
||||
|
||||
// Accent Color Picker
|
||||
AccentColorPicker(selectedColor: Binding(
|
||||
get: { selectedAccentColor },
|
||||
set: { newColor in
|
||||
selectedAccentColor = newColor
|
||||
saveCustomAccentColor(newColor)
|
||||
}
|
||||
))
|
||||
}
|
||||
.padding(.vertical, 16)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color(UIColor.systemGray6))
|
||||
.cornerRadius(16)
|
||||
|
||||
// Username Section
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Username")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
HStack {
|
||||
TextField("Username", text: $username)
|
||||
.font(.body)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.vertical, 8)
|
||||
|
||||
if !username.isEmpty {
|
||||
Button(action: { username = "" }) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundColor(Color(UIColor.tertiaryLabel))
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.background(Color(UIColor.tertiarySystemFill))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.padding(.vertical, 16)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color(UIColor.systemGray6))
|
||||
.cornerRadius(16)
|
||||
|
||||
// JIT Options Section
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("App List")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Toggle("Load App Icons when Enabling JIT", isOn: $loadAppIconsOnJIT)
|
||||
.foregroundColor(.primary)
|
||||
.tint(.green)
|
||||
|
||||
Text("Disabling this will hide app icons in the app list and may improve performance, while also giving it a more aesthetic look.")
|
||||
.font(.footnote)
|
||||
.foregroundColor(Color(UIColor.secondaryLabel))
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 16)
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color(UIColor.systemGray6))
|
||||
.cornerRadius(16)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Display")
|
||||
.onAppear {
|
||||
loadCustomAccentColor()
|
||||
applyTheme(appTheme)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadCustomAccentColor() {
|
||||
if customAccentColorHex.isEmpty {
|
||||
selectedAccentColor = .blue
|
||||
} else {
|
||||
selectedAccentColor = Color(hex: customAccentColorHex) ?? .blue
|
||||
}
|
||||
}
|
||||
|
||||
private func saveCustomAccentColor(_ color: Color) {
|
||||
// Always save the custom color if we got here (since auto theme mode is disabled)
|
||||
customAccentColorHex = color.toHex() ?? ""
|
||||
}
|
||||
|
||||
private func applyTheme(_ theme: String) {
|
||||
// Set the app's theme (will apply when app is restarted)
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first {
|
||||
switch theme {
|
||||
case "dark":
|
||||
window.overrideUserInterfaceStyle = .dark
|
||||
case "light":
|
||||
window.overrideUserInterfaceStyle = .light
|
||||
default:
|
||||
window.overrideUserInterfaceStyle = .unspecified
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ThemeOptionButton: View {
|
||||
let title: String
|
||||
let imageName: String
|
||||
let isSelected: Bool
|
||||
let accentColor: Color
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
VStack(spacing: 8) {
|
||||
Image(imageName)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 160)
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(isSelected ? accentColor : Color.clear, lineWidth: 2)
|
||||
)
|
||||
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
.foregroundColor(isSelected ? accentColor : .primary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +64,7 @@ struct AppButton: View {
|
|||
@State var appName: String
|
||||
@Binding var recentApps: [String]
|
||||
@Binding var appIcons: [String: UIImage]
|
||||
@AppStorage("loadAppIconsOnJIT") private var loadAppIconsOnJIT = true
|
||||
var onSelectApp: (String) -> Void
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
|
@ -77,28 +78,31 @@ struct AppButton: View {
|
|||
}
|
||||
onSelectApp(bundleID)
|
||||
}) {
|
||||
HStack(spacing: 16) {
|
||||
if let image = appIcons[bundleID] {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(12)
|
||||
.shadow(color: colorScheme == .dark ? Color.black.opacity(0.2) : Color.gray.opacity(0.2), radius: 3, x: 0, y: 1)
|
||||
} else {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(UIColor.systemGray5))
|
||||
.frame(width: 60, height: 60)
|
||||
.overlay(
|
||||
Image(systemName: "app")
|
||||
.font(.system(size: 26))
|
||||
.foregroundColor(.gray)
|
||||
)
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 2, x: 0, y: 1)
|
||||
.onAppear {
|
||||
loadAppIcon(for: bundleID)
|
||||
}
|
||||
HStack(spacing: loadAppIconsOnJIT ? 16 : 12) {
|
||||
if loadAppIconsOnJIT {
|
||||
if let image = appIcons[bundleID] {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(12)
|
||||
.shadow(color: colorScheme == .dark ? Color.black.opacity(0.2) : Color.gray.opacity(0.2), radius: 3, x: 0, y: 1)
|
||||
} else {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(UIColor.systemGray5))
|
||||
.frame(width: 60, height: 60)
|
||||
.overlay(
|
||||
Image(systemName: "app")
|
||||
.font(.system(size: 26))
|
||||
.foregroundColor(.gray)
|
||||
)
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 2, x: 0, y: 1)
|
||||
.onAppear {
|
||||
loadAppIcon(for: bundleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(appName)
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
|
|
@ -110,15 +114,18 @@ struct AppButton: View {
|
|||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, loadAppIconsOnJIT ? 0 : 8)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAppIcon(for bundleID: String) {
|
||||
AppStoreIconFetcher.getIcon(for: bundleID) { image in
|
||||
if let image = image {
|
||||
DispatchQueue.main.async {
|
||||
withAnimation(.easeIn(duration: 0.2)) {
|
||||
self.appIcons[bundleID] = image
|
||||
if loadAppIconsOnJIT {
|
||||
AppStoreIconFetcher.getIcon(for: bundleID) { image in
|
||||
if let image = image {
|
||||
DispatchQueue.main.async {
|
||||
withAnimation(.easeIn(duration: 0.2)) {
|
||||
self.appIcons[bundleID] = image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ struct SettingsView: View {
|
|||
@State private var importProgress: Float = 0.0
|
||||
@State private var is_lc = false
|
||||
@State private var showColorPickerPopup = false
|
||||
@State private var showingAppIconSheet = false
|
||||
|
||||
@StateObject private var mountProg = MountingProgress.shared
|
||||
|
||||
@State private var mounted = false
|
||||
|
||||
@State private var showingConsoleLogsView = false
|
||||
@State private var showingDisplayView = false
|
||||
|
||||
private var appVersion: String {
|
||||
let marketingVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0"
|
||||
|
|
@ -81,25 +83,6 @@ struct SettingsView: View {
|
|||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
// Username Field
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Username")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
TextField("Username", text: $username)
|
||||
.padding(14)
|
||||
.background(Color(UIColor.tertiarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
.shadow(color: Color.black.opacity(0.03), radius: 1, x: 0, y: 1)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
|
@ -114,68 +97,62 @@ struct SettingsView: View {
|
|||
.foregroundColor(.primary)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
// Theme selector with segmented control style
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("Accent Color")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
// Clean segmented style picker
|
||||
Picker("Theme Mode", selection: Binding(
|
||||
get: { customAccentColorHex.isEmpty },
|
||||
set: { newValue in
|
||||
if newValue {
|
||||
// System accent color selected
|
||||
customAccentColorHex = ""
|
||||
selectedAccentColor = .blue
|
||||
} else if customAccentColorHex.isEmpty {
|
||||
// Custom color selected - initialize with current theme color
|
||||
selectedAccentColor = .blue
|
||||
customAccentColorHex = selectedAccentColor.toHex() ?? "#0000FF"
|
||||
}
|
||||
}
|
||||
)) {
|
||||
Text("Default").tag(true)
|
||||
Text("Custom").tag(false)
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.padding(.vertical, 4)
|
||||
|
||||
// Show selection button and color picker inline when in custom mode
|
||||
if !customAccentColorHex.isEmpty {
|
||||
// Display and App Icon buttons
|
||||
VStack(spacing: 12) {
|
||||
Button(action: {
|
||||
showingDisplayView = true
|
||||
}) {
|
||||
HStack {
|
||||
// Left side - just text, no color preview
|
||||
Text("Choose color")
|
||||
.font(.subheadline)
|
||||
Image(systemName: "paintbrush")
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(accentColor)
|
||||
Text("Display")
|
||||
.fontWeight(.medium)
|
||||
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
|
||||
// Right side - embedded color picker
|
||||
ColorPicker("", selection: $selectedAccentColor, supportsOpacity: false)
|
||||
.labelsHidden()
|
||||
.scaleEffect(0.8)
|
||||
.onChange(of: selectedAccentColor) { newColor in
|
||||
saveCustomAccentColor(newColor)
|
||||
}
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.transition(.opacity)
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(UIColor.tertiarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// Help text - only show for system mode
|
||||
if customAccentColorHex.isEmpty {
|
||||
Text("Uses default blue accent color")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top, 4)
|
||||
Button(action: {
|
||||
showingAppIconSheet = true
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "app")
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(accentColor)
|
||||
Text("App Icon")
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(UIColor.tertiarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
.padding(.horizontal, 16)
|
||||
.animation(.easeInOut(duration: 0.2), value: customAccentColorHex.isEmpty)
|
||||
}
|
||||
.sheet(isPresented: $showingAppIconSheet) {
|
||||
AppIconView()
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
|
|
@ -509,6 +486,9 @@ struct SettingsView: View {
|
|||
.sheet(isPresented: $showingConsoleLogsView) {
|
||||
ConsoleLogsView()
|
||||
}
|
||||
.sheet(isPresented: $showingDisplayView) {
|
||||
DisplayView()
|
||||
}
|
||||
|
||||
// Version info should now come after System Logs
|
||||
HStack {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue