diff --git a/StikJIT.xcodeproj/project.pbxproj b/StikJIT.xcodeproj/project.pbxproj index b72196a..0cddf18 100644 --- a/StikJIT.xcodeproj/project.pbxproj +++ b/StikJIT.xcodeproj/project.pbxproj @@ -634,4 +634,4 @@ /* End XCConfigurationList section */ }; rootObject = DC6F1D2F2D94EADD0071B2B6 /* Project object */; -} \ No newline at end of file +} diff --git a/StikJIT/Extensions & Stuff/Color.swift b/StikJIT/Extensions & Stuff/Color.swift index 0da0839..6393435 100644 --- a/StikJIT/Extensions & Stuff/Color.swift +++ b/StikJIT/Extensions & Stuff/Color.swift @@ -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 } } diff --git a/StikJIT/StikJITApp.swift b/StikJIT/StikJITApp.swift index 1e1298b..4ca5f0c 100644 --- a/StikJIT/StikJITApp.swift +++ b/StikJIT/StikJITApp.swift @@ -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) diff --git a/StikJIT/Utilities/AppStoreIconFetcher.swift b/StikJIT/Utilities/AppStoreIconFetcher.swift index 6542d51..a4e9829 100644 --- a/StikJIT/Utilities/AppStoreIconFetcher.swift +++ b/StikJIT/Utilities/AppStoreIconFetcher.swift @@ -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) diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index d9299a8..bc07008 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -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() diff --git a/StikJIT/Views/HomeView.swift b/StikJIT/Views/HomeView.swift index 39bedbf..08c0003 100644 --- a/StikJIT/Views/HomeView.swift +++ b/StikJIT/Views/HomeView.swift @@ -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) { diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index 888772f..860d44e 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -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) {