Merge pull request #113 from neoarz/THEMEING

Added More theming options, and flexibility
This commit is contained in:
Stephen 2025-04-11 20:31:20 -04:00 • committed by GitHub
commit 7c85bd6b16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 507 additions and 100 deletions

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

View file

@ -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)

View 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()
}

View 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)
}
}

View file

@ -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
}
}
}
}

View file

@ -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 {