Merge pull request #70 from neoarz/THEMEING

Themeing
This commit is contained in:
Stephen 2025-04-04 12:23:54 -04:00 • committed by GitHub
commit a8451e4df0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 213 additions and 103 deletions

View file

@ -634,4 +634,4 @@
/* End XCConfigurationList section */
};
rootObject = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
}
}

View file

@ -19,18 +19,25 @@ extension Color {
}
init?(hex: String) {
let r, g, b: CGFloat
let start = hex.index(hex.startIndex, offsetBy: 1)
let hexColor = String(hex[start...])
if hexColor.count == 6, let hexNumber = Int(hexColor, radix: 16) {
r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
b = CGFloat(hexNumber & 0x0000ff) / 255
self.init(red: r, green: g, blue: b)
return
if hex.isEmpty || hex.count < 2 {
return nil
}
let r, g, b: CGFloat
if hex.hasPrefix("#") && hex.count >= 7 {
let start = hex.index(hex.startIndex, offsetBy: 1)
let hexColor = String(hex[start...])
if hexColor.count == 6, let hexNumber = Int(hexColor, radix: 16) {
r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
b = CGFloat(hexNumber & 0x0000ff) / 255
self.init(red: r, green: g, blue: b)
return
}
}
return nil
}
}

View file

@ -441,39 +441,53 @@ func startHeartbeatInBackground() {
struct LoadingView: View {
@State private var animate = false
@Environment(\.colorScheme) private var colorScheme
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.black, Color.black]),
startPoint: .topLeading,
endPoint: .bottomTrailing)
// Use system background color instead of fixed black
Color(colorScheme == .dark ? .black : .white)
.ignoresSafeArea()
VStack {
ZStack {
// Background circle - slightly visible in both themes
Circle()
.stroke(lineWidth: 8)
.foregroundColor(Color.white.opacity(0.3))
.foregroundColor(colorScheme == .dark ?
Color.white.opacity(0.3) :
Color.black.opacity(0.1))
.frame(width: 80, height: 80)
// Animated circle - adapts to theme
Circle()
.trim(from: 0, to: 0.7)
.stroke(AngularGradient(
gradient: Gradient(colors: [Color.white.opacity(0.8), Color.white.opacity(0.3)]),
gradient: Gradient(colors: [
colorScheme == .dark ? Color.white.opacity(0.8) : Color.blue.opacity(0.8),
colorScheme == .dark ? Color.white.opacity(0.3) : Color.blue.opacity(0.3)
]),
center: .center
), style: StrokeStyle(lineWidth: 6, lineCap: .round))
.rotationEffect(.degrees(animate ? 360 : 0))
.frame(width: 80, height: 80)
.animation(Animation.linear(duration: 1.2).repeatForever(autoreverses: false), value: animate)
}
.shadow(color: .white.opacity(0.5), radius: 10, x: 0, y: 0)
// Shadow adapts to theme
.shadow(color: colorScheme == .dark ?
.white.opacity(0.5) :
.blue.opacity(0.3),
radius: 10, x: 0, y: 0)
.onAppear {
animate = true
}
// Text adapts to theme
Text("Loading...")
.font(.system(size: 20, weight: .medium, design: .rounded))
.foregroundColor(.white.opacity(0.8))
.foregroundColor(colorScheme == .dark ?
.white.opacity(0.8) :
.black.opacity(0.8))
.padding(.top, 20)
.opacity(animate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: true), value: animate)

View file

@ -1,5 +1,6 @@
//
// AppStoreIconFetcher.swift
// IconsFetcher.swift
// Dont change the name of the actual file (it will break stuff)
// StikJIT
//
// Created by neoarz on 3/28/25.
@ -8,6 +9,7 @@
import UIKit
// Not AppStore
// Uses idevice
class AppStoreIconFetcher {
static private var iconCache: [String: UIImage] = [:]
static private let iconFetchDispatchQueue = DispatchQueue(label: "com.stik.StikJIT.iconFetchQueue", attributes: .concurrent)

View file

@ -10,6 +10,7 @@ import UIKit
struct ConsoleLogsView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.colorScheme) private var colorScheme
@StateObject private var logManager = LogManager.shared
@State private var autoScroll = true
@State private var scrollView: ScrollViewProxy? = nil
@ -23,22 +24,23 @@ struct ConsoleLogsView: View {
var body: some View {
NavigationView {
ZStack {
Color.black
// Use system background color instead of fixed black
Color(colorScheme == .dark ? .black : .white)
.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
// Terminal logs area (made it look somewhat like feathers implementation)
// Terminal logs area with theme support
ScrollViewReader { proxy in
ScrollView {
VStack(spacing: 0) {
// Device Information (all thw way on top)
// Device Information
ForEach(["Version: \(UIDevice.current.systemVersion)",
"Name: \(UIDevice.current.name)",
"Model: \(UIDevice.current.model)",
"StikJIT Version: App Version: 1.0"], id: \.self) { info in
Text("[\(timeString())] ℹ️ \(info)")
.font(.system(size: 11, design: .monospaced))
.foregroundColor(.white)
.foregroundColor(colorScheme == .dark ? .white : .black)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 2)
.padding(.horizontal, 4)
@ -72,7 +74,7 @@ struct ConsoleLogsView: View {
Spacer()
// Action buttons section - update to be theme aware
VStack(spacing: 16) {
// Error count with red theme
HStack {
@ -86,7 +88,7 @@ struct ConsoleLogsView: View {
}
.padding(.horizontal)
// Action buttons with dark background
// Action buttons with theme-appropriate background
VStack(spacing: 1) {
// Export button
Button(action: {
@ -138,14 +140,18 @@ struct ConsoleLogsView: View {
.padding(.horizontal, 20)
.contentShape(Rectangle())
}
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.background(colorScheme == .dark ?
Color(red: 0.1, green: 0.1, blue: 0.1) :
Color(UIColor.secondarySystemBackground))
Divider()
.background(Color(red: 0.15, green: 0.15, blue: 0.15))
.background(colorScheme == .dark ?
Color(red: 0.15, green: 0.15, blue: 0.15) :
Color(UIColor.separator))
// Copy button
Button(action: {
// Create logs content with device information
// Existing code for copying logs
var logsContent = "=== DEVICE INFORMATION ===\n"
logsContent += "Version: \(UIDevice.current.systemVersion)\n"
logsContent += "Name: \(UIDevice.current.name)\n"
@ -153,15 +159,12 @@ struct ConsoleLogsView: View {
logsContent += "StikJIT Version: App Version: 1.0\n\n"
logsContent += "=== LOG ENTRIES ===\n"
// Add all log entries with proper formatting
logsContent += logManager.logs.map {
"[\(formatTime(date: $0.timestamp))] [\($0.type.rawValue)] \($0.message)"
}.joined(separator: "\n")
// Copy to clipboard
UIPasteboard.general.string = logsContent
// Show success alert using SwiftUI alert
alertTitle = "Logs Copied"
alertMessage = "Logs have been copied to clipboard."
isError = false
@ -178,67 +181,72 @@ struct ConsoleLogsView: View {
.padding(.horizontal, 20)
.contentShape(Rectangle())
}
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.background(colorScheme == .dark ?
Color(red: 0.1, green: 0.1, blue: 0.1) :
Color(UIColor.secondarySystemBackground))
}
.cornerRadius(10)
.padding(.horizontal)
.padding(.bottom, 20)
.padding(.bottom, 16)
}
.padding(.bottom, 8)
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Console Logs")
.font(.headline)
.foregroundColor(.white)
}
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
dismiss()
}) {
HStack(spacing: 2) {
Image(systemName: "chevron.left")
.font(.system(size: 16, weight: .semibold))
Text("Settings")
.fontWeight(.regular)
}
.foregroundColor(.blue)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Console Logs")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
logManager.clearLogs()
}) {
Text("Clear")
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
dismiss()
}) {
HStack(spacing: 2) {
Image(systemName: "chevron.left")
.font(.system(size: 16, weight: .semibold))
Text("Settings")
.fontWeight(.regular)
}
.foregroundColor(.blue)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
logManager.clearLogs()
}) {
Text("Clear")
.foregroundColor(.blue)
}
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
.overlay(
ZStack {
if showingCustomAlert {
CustomErrorView(
title: alertTitle,
message: alertMessage,
onDismiss: {
showingCustomAlert = false
},
showButton: true,
primaryButtonText: "OK",
messageType: isError ? .error : .success
)
.overlay(
Group {
if showingCustomAlert {
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.all)
.overlay(
CustomErrorView(
title: alertTitle,
message: alertMessage,
onDismiss: {
showingCustomAlert = false
},
showButton: true,
primaryButtonText: "OK",
messageType: isError ? .error : .success
)
)
}
}
}
)
)
}
}
// Creates an NSAttributedString that combines timestamp, type, and message
// with proper styling for each component
// Update to use theme-aware colors
private func createLogAttributedString(_ logEntry: LogManager.LogEntry) -> NSAttributedString {
let fullString = NSMutableAttributedString()
@ -246,7 +254,7 @@ struct ConsoleLogsView: View {
let timestampString = "[\(formatTime(date: logEntry.timestamp))]"
let timestampAttr = NSAttributedString(
string: timestampString,
attributes: [.foregroundColor: UIColor.gray]
attributes: [.foregroundColor: colorScheme == .dark ? UIColor.gray : UIColor.darkGray]
)
fullString.append(timestampAttr)
fullString.append(NSAttributedString(string: " "))
@ -264,28 +272,25 @@ struct ConsoleLogsView: View {
// Message part
let messageAttr = NSAttributedString(
string: logEntry.message,
attributes: [.foregroundColor: UIColor.white]
attributes: [.foregroundColor: colorScheme == .dark ? UIColor.white : UIColor.black]
)
fullString.append(messageAttr)
return fullString
}
// Helper to display current time
private func timeString() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter.string(from: Date())
}
// Helper to format Date objects to time strings
private func formatTime(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter.string(from: date)
}
// Return color based on log type
private func colorForLogType(_ type: LogManager.LogEntry.LogType) -> Color {
switch type {
case .info:
@ -300,7 +305,6 @@ struct ConsoleLogsView: View {
}
}
struct ConsoleLogsView_Previews: PreviewProvider {
static var previews: some View {
ConsoleLogsView()

View file

@ -17,9 +17,10 @@ extension UIDocumentPickerViewController {
struct HomeView: View {
@AppStorage("username") private var username = "User"
@AppStorage("customBackgroundColor") private var customBackgroundColorHex: String = Color.primaryBackground.toHex() ?? "#000000"
@AppStorage("customBackgroundColor") private var customBackgroundColorHex: String = ""
@AppStorage("autoQuitAfterEnablingJIT") private var doAutoQuitAfterEnablingJIT = false
@State private var selectedBackgroundColor: Color = Color(hex: UserDefaults.standard.string(forKey: "customBackgroundColor") ?? "#000000") ?? Color.primaryBackground
@State private var selectedBackgroundColor: Color = Color(hex: UserDefaults.standard.string(forKey: "customBackgroundColor") ?? "") ?? .clear
@Environment(\.colorScheme) private var colorScheme
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@AppStorage("bundleID") private var bundleID: String = ""
@State private var isProcessing = false
@ -38,7 +39,11 @@ struct HomeView: View {
var body: some View {
ZStack {
selectedBackgroundColor.edgesIgnoringSafeArea(.all)
// Use the background color or default to appearance-based color if not set
(selectedBackgroundColor == .clear ?
(colorScheme == .dark ? Color.black : Color.white) :
selectedBackgroundColor)
.edgesIgnoringSafeArea(.all)
VStack(spacing: 25) {
Spacer()
@ -145,6 +150,11 @@ struct HomeView: View {
}
.onAppear {
checkPairingFileExists()
// Don't initialize specific color value when empty - empty means "use system theme"
// This was causing the toggle to turn off when returning to settings
// Initialize background color
refreshBackground()
}
.onReceive(timer) { _ in
refreshBackground()
@ -258,7 +268,11 @@ struct HomeView: View {
}
private func refreshBackground() {
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? Color.primaryBackground
if customBackgroundColorHex.isEmpty {
selectedBackgroundColor = colorScheme == .dark ? Color.black : Color.white
} else {
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? (colorScheme == .dark ? Color.black : Color.white)
}
}
private func startJITInBackground(with bundleID: String) {

View file

@ -8,18 +8,20 @@ import UniformTypeIdentifiers
struct SettingsView: View {
@AppStorage("username") private var username = "User"
@AppStorage("customBackgroundColor") private var customBackgroundColorHex: String = Color.primaryBackground.toHex() ?? "#000000"
@AppStorage("customBackgroundColor") private var customBackgroundColorHex: String = ""
@AppStorage("selectedAppIcon") private var selectedAppIcon: String = "AppIcon"
@AppStorage("autoQuitAfterEnablingJIT") private var doAutoQuitAfterEnablingJIT = false
@State private var isShowingPairingFilePicker = false
@Environment(\.colorScheme) private var colorScheme
@State private var selectedBackgroundColor: Color = Color.primaryBackground
@State private var selectedBackgroundColor: Color = .clear
@State private var showIconPopover = false
@State private var showPairingFileMessage = false
@State private var pairingFileIsValid = false
@State private var isImportingFile = false
@State private var importProgress: Float = 0.0
@State private var is_lc = false
@State private var showColorPickerPopup = false
@StateObject private var mountProg = MountingProgress.shared
@ -81,6 +83,11 @@ struct SettingsView: View {
.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)
@ -98,15 +105,72 @@ struct SettingsView: View {
.foregroundColor(.primary)
.padding(.bottom, 4)
ColorPicker("Background Color", selection: $selectedBackgroundColor)
.onChange(of: selectedBackgroundColor) { newColor in
saveCustomBackgroundColor(newColor)
// Theme selector with segmented control style
VStack(alignment: .leading, spacing: 10) {
Text("Background Color")
.font(.subheadline)
.foregroundColor(.secondary)
// Clean segmented style picker
Picker("Theme Mode", selection: Binding(
get: { customBackgroundColorHex.isEmpty },
set: { newValue in
if newValue {
// System theme selected
customBackgroundColorHex = ""
selectedBackgroundColor = colorScheme == .dark ? Color.black : Color.white
} else if customBackgroundColorHex.isEmpty {
// Custom color selected - initialize with current theme color
let initialColor = colorScheme == .dark ? Color.black : Color.white
customBackgroundColorHex = initialColor.toHex() ?? "#000000"
selectedBackgroundColor = initialColor
// Don't open color picker popup automatically anymore
// User will need to press the "Change Color" button
}
}
)) {
Text("System").tag(true)
Text("Custom").tag(false)
}
.foregroundColor(.primary)
.padding(.vertical, 6)
.pickerStyle(SegmentedPickerStyle())
.padding(.vertical, 4)
// Show selection button and color picker inline when in custom mode
if !customBackgroundColorHex.isEmpty {
HStack {
// Left side - just text, no color preview
Text("Choose color")
.font(.subheadline)
.fontWeight(.medium)
Spacer()
// Right side - embedded color picker
ColorPicker("", selection: $selectedBackgroundColor, supportsOpacity: false)
.labelsHidden()
.scaleEffect(0.8)
.onChange(of: selectedBackgroundColor) { newColor in
saveCustomBackgroundColor(newColor)
}
}
.padding(.vertical, 8)
.transition(.opacity)
}
// Help text - only show for system mode
if customBackgroundColorHex.isEmpty {
Text("Uses light/dark mode system colors")
.font(.caption)
.foregroundColor(.secondary)
.padding(.top, 4)
}
}
.padding(.vertical, 6)
}
.padding(.vertical, 20)
.padding(.horizontal, 16)
.animation(.easeInOut(duration: 0.2), value: customBackgroundColorHex.isEmpty)
}
SettingsCard {
@ -534,11 +598,16 @@ struct SettingsView: View {
}
private func loadCustomBackgroundColor() {
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? Color.primaryBackground
if customBackgroundColorHex.isEmpty {
selectedBackgroundColor = colorScheme == .dark ? Color.black : Color.white
} else {
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? (colorScheme == .dark ? Color.black : Color.white)
}
}
private func saveCustomBackgroundColor(_ color: Color) {
customBackgroundColorHex = color.toHex() ?? "#000000"
// Always save the custom color if we got here (since auto theme mode is disabled)
customBackgroundColorHex = color.toHex() ?? ""
}
private func changeAppIcon(to iconName: String) {