Pairing file button

This commit is contained in:
neoarz 2025-03-27 16:07:22 -04:00
parent d4fb2512b3
commit 4190b39fc7
3 changed files with 354 additions and 35 deletions

View file

@ -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";

View file

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

View file

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