mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 12:30:29 +00:00
Pairing file button
This commit is contained in:
parent
d4fb2512b3
commit
4190b39fc7
3 changed files with 354 additions and 35 deletions
|
|
@ -425,7 +425,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"StikJIT/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = SZ977XLF24;
|
||||
DEVELOPMENT_TEAM = 9WY43R4CSV;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
|
@ -439,7 +439,7 @@
|
|||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
|
|
@ -448,7 +448,7 @@
|
|||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.StikJIT;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.StikJITnn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
|
||||
|
|
@ -471,7 +471,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"StikJIT/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = SZ977XLF24;
|
||||
DEVELOPMENT_TEAM = 9WY43R4CSV;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
|
@ -485,7 +485,7 @@
|
|||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
|
|
@ -494,7 +494,7 @@
|
|||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.StikJIT;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.StikJITnn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ struct HomeView: View {
|
|||
@State private var isProcessing = false
|
||||
@State private var isShowingInstalledApps = false
|
||||
@State private var isShowingPairingFilePicker = false
|
||||
@State private var pairingFileExists: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
|
@ -28,42 +29,73 @@ struct HomeView: View {
|
|||
.font(.system(.largeTitle, design: .rounded))
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("Click enable jit to get started")
|
||||
Text("Follow the steps below to enable JIT")
|
||||
.font(.system(.subheadline, design: .rounded))
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(.top, 40)
|
||||
|
||||
// Pairing File Button
|
||||
Button(action: {
|
||||
if !FileManager.default.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path) {
|
||||
isShowingPairingFilePicker = true
|
||||
} else {
|
||||
isShowingInstalledApps = true
|
||||
}
|
||||
isShowingPairingFilePicker = true
|
||||
}) {
|
||||
Label("Enable JIT", systemImage: "list.bullet")
|
||||
.font(.system(.title3, design: .rounded))
|
||||
.fontWeight(.semibold)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(16)
|
||||
.shadow(color: Color.blue.opacity(0.3), radius: 8, x: 0, y: 4)
|
||||
HStack {
|
||||
Image(systemName: "doc.badge.plus")
|
||||
.font(.system(size: 20))
|
||||
Text("Import Pairing File")
|
||||
.font(.system(.title3, design: .rounded))
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.purple)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(16)
|
||||
.shadow(color: Color.purple.opacity(0.3), radius: 8, x: 0, y: 4)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
// Enable JIT Button
|
||||
Button(action: {
|
||||
isShowingInstalledApps = true
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.system(size: 20))
|
||||
Text("Enable JIT")
|
||||
.font(.system(.title3, design: .rounded))
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(pairingFileExists ? Color.blue : Color.gray)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(16)
|
||||
.shadow(color: pairingFileExists ? Color.blue.opacity(0.3) : Color.gray.opacity(0.3), radius: 8, x: 0, y: 4)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.disabled(!pairingFileExists)
|
||||
|
||||
if !pairingFileExists {
|
||||
Text("Please import a pairing file first")
|
||||
.font(.system(.caption, design: .rounded))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.onAppear {
|
||||
checkPairingFileExists()
|
||||
}
|
||||
.onReceive(timer) { _ in
|
||||
refreshBackground()
|
||||
checkPairingFileExists()
|
||||
}
|
||||
.fileImporter(isPresented: $isShowingPairingFilePicker, allowedContentTypes: [.item]) {result in
|
||||
.fileImporter(isPresented: $isShowingPairingFilePicker, allowedContentTypes: [.item]) { result in
|
||||
switch result {
|
||||
|
||||
case .success(let url):
|
||||
let fileManager = FileManager.default
|
||||
let accessing = url.startAccessingSecurityScopedResource()
|
||||
|
|
@ -78,6 +110,9 @@ struct HomeView: View {
|
|||
print("File copied successfully!")
|
||||
startHeartbeatInBackground()
|
||||
|
||||
// Update the file exists state
|
||||
pairingFileExists = true
|
||||
|
||||
Thread.sleep(forTimeInterval: 5)
|
||||
} catch {
|
||||
print("Error copying file: \(error)")
|
||||
|
|
@ -103,6 +138,10 @@ struct HomeView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func checkPairingFileExists() {
|
||||
pairingFileExists = FileManager.default.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path)
|
||||
}
|
||||
|
||||
private func refreshBackground() {
|
||||
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? Color.primaryBackground
|
||||
}
|
||||
|
|
@ -125,8 +164,84 @@ struct HomeView: View {
|
|||
}
|
||||
}
|
||||
|
||||
// Replace the AppInfo struct and related code with this simpler version
|
||||
struct AppInfo: Identifiable, Hashable {
|
||||
let id = UUID()
|
||||
let bundleID: String
|
||||
let name: String
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(bundleID)
|
||||
}
|
||||
|
||||
static func == (lhs: AppInfo, rhs: AppInfo) -> Bool {
|
||||
return lhs.bundleID == rhs.bundleID
|
||||
}
|
||||
|
||||
// Generate a color based on the bundle ID
|
||||
var iconColor: Color {
|
||||
var hash = 0
|
||||
for char in bundleID {
|
||||
hash = ((hash << 5) &- hash) &+ Int(char.asciiValue ?? 0)
|
||||
}
|
||||
let hue = Double(abs(hash) % 360) / 360.0
|
||||
return Color(hue: hue, saturation: 0.8, brightness: 0.9)
|
||||
}
|
||||
|
||||
// Get an appropriate SF Symbol based on bundle ID
|
||||
var iconSymbol: String {
|
||||
let appTypes: [(pattern: String, icon: String)] = [
|
||||
("game", "gamecontroller"),
|
||||
("play", "gamecontroller"),
|
||||
("photo", "camera"),
|
||||
("camera", "camera.viewfinder"),
|
||||
("spotify", "music.note"),
|
||||
("music", "music.note"),
|
||||
("audio", "headphones"),
|
||||
("netflix", "play.tv"),
|
||||
("video", "play.rectangle"),
|
||||
("tv", "tv"),
|
||||
("player", "play.circle"),
|
||||
("facebook", "message"),
|
||||
("messenger", "message.fill"),
|
||||
("chat", "bubble.left.and.bubble.right"),
|
||||
("message", "bubble.left"),
|
||||
("mail", "envelope"),
|
||||
("safari", "globe"),
|
||||
("web", "network"),
|
||||
("browser", "safari"),
|
||||
("note", "note.text"),
|
||||
("docs", "doc.text"),
|
||||
("document", "doc"),
|
||||
("word", "doc.richtext"),
|
||||
("file", "folder"),
|
||||
("sheets", "tablecells"),
|
||||
("excel", "tablecells.fill"),
|
||||
("calc", "function"),
|
||||
("shop", "cart"),
|
||||
("store", "bag"),
|
||||
("pay", "creditcard"),
|
||||
("wallet", "wallet.pass"),
|
||||
("health", "heart"),
|
||||
("fitness", "figure.walk"),
|
||||
("exercise", "figure.run"),
|
||||
]
|
||||
|
||||
let lowerBundleID = bundleID.lowercased()
|
||||
let lowerName = name.lowercased()
|
||||
|
||||
for appType in appTypes {
|
||||
if lowerBundleID.contains(appType.pattern) || lowerName.contains(appType.pattern) {
|
||||
return appType.icon
|
||||
}
|
||||
}
|
||||
|
||||
return "app.fill"
|
||||
}
|
||||
}
|
||||
|
||||
class InstalledAppsViewModel: ObservableObject {
|
||||
@Published var apps: [String] = []
|
||||
@Published var apps: [AppInfo] = []
|
||||
|
||||
init() {
|
||||
loadApps()
|
||||
|
|
@ -144,7 +259,18 @@ class InstalledAppsViewModel: ObservableObject {
|
|||
if output.hasPrefix("Error:") {
|
||||
self.apps = []
|
||||
} else {
|
||||
self.apps = output.components(separatedBy: "\n").filter { !$0.isEmpty }
|
||||
// Parse the output into AppInfo objects
|
||||
self.apps = output.components(separatedBy: "\n")
|
||||
.filter { !$0.isEmpty }
|
||||
.compactMap { line -> AppInfo? in
|
||||
let components = line.components(separatedBy: "|")
|
||||
if components.count >= 2 {
|
||||
return AppInfo(bundleID: components[0], name: components[1])
|
||||
} else if components.count == 1 {
|
||||
return AppInfo(bundleID: components[0], name: components[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,33 +283,48 @@ struct InstalledAppsListView: View {
|
|||
@StateObject var viewModel = InstalledAppsViewModel()
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State private var searchText: String = ""
|
||||
@State private var appIcons: [String: UIImage] = [:]
|
||||
|
||||
var onSelect: (String) -> Void
|
||||
|
||||
var filteredApps: [String] {
|
||||
var filteredApps: [AppInfo] {
|
||||
if searchText.isEmpty {
|
||||
return viewModel.apps
|
||||
} else {
|
||||
return viewModel.apps.filter { $0.localizedCaseInsensitiveContains(searchText) }
|
||||
return viewModel.apps.filter {
|
||||
$0.bundleID.localizedCaseInsensitiveContains(searchText) ||
|
||||
$0.name.localizedCaseInsensitiveContains(searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
selectedBackgroundColor .edgesIgnoringSafeArea(.all)
|
||||
selectedBackgroundColor.edgesIgnoringSafeArea(.all)
|
||||
|
||||
List {
|
||||
ForEach(filteredApps, id: \.self) { app in
|
||||
ForEach(filteredApps) { app in
|
||||
Button(action: {
|
||||
onSelect(app)
|
||||
onSelect(app.bundleID)
|
||||
}) {
|
||||
Text(app)
|
||||
.font(.system(.body, design: .rounded))
|
||||
.padding(.vertical, 8)
|
||||
HStack {
|
||||
// Simplified app icon display
|
||||
appIconView(for: app)
|
||||
.padding(.trailing, 8)
|
||||
|
||||
// App details
|
||||
appDetailsView(for: app)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundColor(.gray.opacity(0.7))
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.listRowBackground(Color(.secondarySystemGroupedBackground))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
.listStyle(InsetGroupedListStyle())
|
||||
|
|
@ -202,11 +343,159 @@ struct InstalledAppsListView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper view for app icon
|
||||
private func appIconView(for app: AppInfo) -> some View {
|
||||
Group {
|
||||
if let icon = appIcons[app.bundleID] {
|
||||
// Show cached icon
|
||||
Image(uiImage: icon)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 48, height: 48)
|
||||
.cornerRadius(12)
|
||||
} else {
|
||||
// Show placeholder and try to load icon
|
||||
placeholderIconView(for: app)
|
||||
.onAppear {
|
||||
loadAppIcon(for: app)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper view for placeholder icon
|
||||
private func placeholderIconView(for app: AppInfo) -> some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(app.iconColor)
|
||||
.frame(width: 48, height: 48)
|
||||
|
||||
Image(systemName: app.iconSymbol)
|
||||
.font(.system(size: 24))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper view for app details
|
||||
private func appDetailsView(for app: AppInfo) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(app.name)
|
||||
.font(.system(.body, design: .rounded))
|
||||
.fontWeight(.medium)
|
||||
|
||||
Text(app.bundleID)
|
||||
.font(.system(.caption, design: .rounded))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to load app icon
|
||||
private func loadAppIcon(for app: AppInfo) {
|
||||
AppStoreIconFetcher.getIcon(for: app.bundleID) { image in
|
||||
if let image = image {
|
||||
self.appIcons[app.bundleID] = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshBackground() {
|
||||
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? Color.primaryBackground
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the Icon Fetcher with a simpler implementation
|
||||
class AppStoreIconFetcher {
|
||||
static private var iconCache: [String: UIImage] = [:]
|
||||
|
||||
static func getIcon(for bundleID: String, completion: @escaping (UIImage?) -> Void) {
|
||||
// Check cache first
|
||||
if let cachedIcon = iconCache[bundleID] {
|
||||
completion(cachedIcon)
|
||||
return
|
||||
}
|
||||
|
||||
// Simplified URL creation
|
||||
let baseURLString = "https://itunes.apple.com/lookup?bundleId="
|
||||
let urlString = baseURLString + bundleID
|
||||
guard let url = URL(string: urlString) else {
|
||||
DispatchQueue.main.async { completion(nil) }
|
||||
return
|
||||
}
|
||||
|
||||
// Step 1: Fetch app data
|
||||
fetchAppData(from: url) { appData in
|
||||
if let appData = appData {
|
||||
// Step 2: Extract icon URL from app data
|
||||
extractIconURL(from: appData) { iconURL in
|
||||
if let iconURL = iconURL {
|
||||
// Step 3: Download icon
|
||||
downloadIcon(from: iconURL) { image in
|
||||
if let image = image {
|
||||
// Store in cache
|
||||
iconCache[bundleID] = image
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion(image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to fetch app data
|
||||
private static func fetchAppData(from url: URL, completion: @escaping ([String: Any]?) -> Void) {
|
||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard let data = data, error == nil else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let results = json["results"] as? [[String: Any]],
|
||||
!results.isEmpty {
|
||||
completion(results.first)
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
} catch {
|
||||
completion(nil)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
// Helper method to extract icon URL
|
||||
private static func extractIconURL(from appData: [String: Any], completion: @escaping (URL?) -> Void) {
|
||||
if let iconURLString = appData["artworkUrl100"] as? String,
|
||||
let iconURL = URL(string: iconURLString) {
|
||||
completion(iconURL)
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to download icon
|
||||
private static func downloadIcon(from url: URL, completion: @escaping (UIImage?) -> Void) {
|
||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
completion(image)
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HomeView()
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ char *list_installed_apps() {
|
|||
}
|
||||
|
||||
plist_t *app_list = (plist_t *)apps;
|
||||
char *result = malloc(8192); // Allocate memory for output
|
||||
char *result = malloc(16384); // Increased buffer size for additional data
|
||||
result[0] = '\0';
|
||||
|
||||
for (size_t i = 0; i < apps_len; i++) {
|
||||
|
|
@ -83,8 +83,30 @@ char *list_installed_apps() {
|
|||
if (bundle_id_node) {
|
||||
char *bundle_id = NULL;
|
||||
plist_get_string_val(bundle_id_node, &bundle_id);
|
||||
|
||||
// Get app name
|
||||
char *app_name = NULL;
|
||||
plist_t name_node = plist_dict_get_item(app, "CFBundleName");
|
||||
if (name_node) {
|
||||
plist_get_string_val(name_node, &app_name);
|
||||
} else {
|
||||
name_node = plist_dict_get_item(app, "CFBundleDisplayName");
|
||||
if (name_node) {
|
||||
plist_get_string_val(name_node, &app_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Add bundle ID and app name to result
|
||||
strcat(result, bundle_id);
|
||||
strcat(result, "|"); // Use pipe as separator
|
||||
if (app_name) {
|
||||
strcat(result, app_name);
|
||||
free(app_name);
|
||||
} else {
|
||||
strcat(result, bundle_id); // Use bundle ID as fallback name
|
||||
}
|
||||
strcat(result, "\n");
|
||||
|
||||
free(bundle_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -97,3 +119,11 @@ char *list_installed_apps() {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *fetch_app_icon(const char *bundle_path) {
|
||||
// This would require implementing AFC service to access files on the device
|
||||
// and extract the icon file from the app bundle
|
||||
|
||||
// For now, return NULL as this requires significant additional code
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue