Implement App Names

This commit is contained in:
Stossy11 2025-03-28 06:38:34 +11:00
parent d4fb2512b3
commit 8f3180f69a
3 changed files with 149 additions and 71 deletions

View file

@ -126,7 +126,7 @@ struct HomeView: View {
}
class InstalledAppsViewModel: ObservableObject {
@Published var apps: [String] = []
@Published var apps: [String: String] = [:]
init() {
loadApps()
@ -134,78 +134,40 @@ class InstalledAppsViewModel: ObservableObject {
func loadApps() {
guard let rawPointer = list_installed_apps() else {
self.apps = []
self.apps = [:]
return
}
let output = String(cString: rawPointer)
free(rawPointer)
if output.hasPrefix("Error:") {
self.apps = []
} else {
self.apps = output.components(separatedBy: "\n").filter { !$0.isEmpty }
guard let jsonData = output.data(using: .utf8) else {
print("Error: Failed to convert string to data")
self.apps = [:]
return
}
if output.hasPrefix("{\"error\":") {
self.apps = [:]
}
print(output)
// Decode the JSON into a Swift dictionary
do {
let decoder = JSONDecoder()
let apps = try decoder.decode([String: String].self, from: jsonData)
self.apps = apps
return
} catch {
print("Error: Failed to decode JSON - \(error)")
self.apps = [:]
return
}
}
}
struct InstalledAppsListView: View {
@AppStorage("username") private var username = "User"
@AppStorage("customBackgroundColor") private var customBackgroundColorHex: String = Color.primaryBackground.toHex() ?? "#000000"
@State private var selectedBackgroundColor: Color = Color(hex: UserDefaults.standard.string(forKey: "customBackgroundColor") ?? "#000000") ?? Color.primaryBackground
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@StateObject var viewModel = InstalledAppsViewModel()
@Environment(\.dismiss) var dismiss
@State private var searchText: String = ""
var onSelect: (String) -> Void
var filteredApps: [String] {
if searchText.isEmpty {
return viewModel.apps
} else {
return viewModel.apps.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
}
var body: some View {
NavigationView {
ZStack {
selectedBackgroundColor .edgesIgnoringSafeArea(.all)
List {
ForEach(filteredApps, id: \.self) { app in
Button(action: {
onSelect(app)
}) {
Text(app)
.font(.system(.body, design: .rounded))
.padding(.vertical, 8)
}
.listRowBackground(Color(.secondarySystemGroupedBackground))
.cornerRadius(8)
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Installed Apps")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") {
dismiss()
}
}
}
.searchable(text: $searchText, prompt: "Search Apps")
}
.onReceive(timer) { _ in
refreshBackground()
}
}
}
private func refreshBackground() {
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? Color.primaryBackground
}
}
#Preview {

View file

@ -0,0 +1,70 @@
//
// InstalledAppsListView.swift
// StikJIT
//
// Created by Stossy11 on 28/03/2025.
//
import SwiftUI
struct InstalledAppsListView: View {
@AppStorage("username") private var username = "User"
@AppStorage("customBackgroundColor") private var customBackgroundColorHex: String = Color.primaryBackground.toHex() ?? "#000000"
@State private var selectedBackgroundColor: Color = Color(hex: UserDefaults.standard.string(forKey: "customBackgroundColor") ?? "#000000") ?? Color.primaryBackground
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@StateObject var viewModel = InstalledAppsViewModel()
@Environment(\.dismiss) var dismiss
@State private var searchText: String = ""
var onSelect: (String) -> Void
var filteredApps: [String] {
if searchText.isEmpty {
return Array(viewModel.apps.keys) // Use the keys (app names)
} else {
return viewModel.apps.keys.filter { $0.localizedCaseInsensitiveContains(searchText) }
}
}
var body: some View {
NavigationView {
ZStack {
selectedBackgroundColor.edgesIgnoringSafeArea(.all)
List {
ForEach(filteredApps, id: \.self) { app in
Button(action: {
onSelect(viewModel.apps[app] ?? "") // Select using the app's bundle ID
}) {
Text(app)
.font(.system(.body, design: .rounded))
.padding(.vertical, 8)
}
.listRowBackground(Color(.secondarySystemGroupedBackground))
.cornerRadius(8)
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Installed Apps")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") {
dismiss()
}
}
}
.searchable(text: $searchText, prompt: "Search Apps")
}
.onReceive(timer) { _ in
refreshBackground()
}
.onAppear {
print(filteredApps)
}
}
}
private func refreshBackground() {
selectedBackgroundColor = Color(hex: customBackgroundColorHex) ?? Color.primaryBackground
}
}

View file

@ -19,7 +19,7 @@ char *list_installed_apps() {
addr.sin_family = AF_INET;
addr.sin_port = htons(LOCKDOWN_PORT);
if (inet_pton(AF_INET, "10.7.0.1", &addr.sin_addr) <= 0) {
return strdup("Error: Invalid IP address");
return strdup("{\"error\": \"Invalid IP address\"}");
}
char pairingFilePath[1024];
@ -37,21 +37,21 @@ char *list_installed_apps() {
IdevicePairingFile *pairing_file = NULL;
IdeviceErrorCode err = idevice_pairing_file_read(pairingFilePath, &pairing_file);
if (err != IdeviceSuccess) {
return strdup("Error: Failed to read pairing file");
return strdup("{\"error\": \"Failed to read pairing file\"}");
}
TcpProviderHandle *provider = NULL;
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ExampleProvider", &provider);
if (err != IdeviceSuccess) {
idevice_pairing_file_free(pairing_file);
return strdup("Error: Failed to create TCP provider");
return strdup("{\"error\": \"Failed to create TCP provider\"}");
}
InstallationProxyClientHandle *client = NULL;
err = installation_proxy_connect_tcp(provider, &client);
if (err != IdeviceSuccess) {
tcp_provider_free(provider);
return strdup("Error: Failed to connect to installation proxy");
return strdup("{\"error\": \"Failed to connect to installation proxy\"}");
}
void *apps = NULL;
@ -60,13 +60,15 @@ char *list_installed_apps() {
if (err != IdeviceSuccess) {
installation_proxy_client_free(client);
tcp_provider_free(provider);
return strdup("Error: Failed to get apps");
return strdup("{\"error\": \"Failed to get apps\"}");
}
plist_t *app_list = (plist_t *)apps;
char *result = malloc(8192); // Allocate memory for output
result[0] = '\0';
strcat(result, "{\n");
int first_entry = 1;
for (size_t i = 0; i < apps_len; i++) {
plist_t app = app_list[i];
// Check if the app has an "Entitlements" dictionary.
@ -83,15 +85,59 @@ char *list_installed_apps() {
if (bundle_id_node) {
char *bundle_id = NULL;
plist_get_string_val(bundle_id_node, &bundle_id);
strcat(result, bundle_id);
strcat(result, "\n");
// Skip if bundle ID is empty
if (bundle_id == NULL || strlen(bundle_id) == 0) {
free(bundle_id);
continue;
}
// Retrieve the app name
plist_t app_name_node = plist_dict_get_item(app, "CFBundleName");
char *app_name = NULL;
if (app_name_node) {
plist_get_string_val(app_name_node, &app_name);
} else {
app_name = strdup("Unknown");
}
// Escape special characters in app name and bundle ID
char escaped_app_name[1024] = {0};
char escaped_bundle_id[1024] = {0};
for (int j = 0, k = 0; app_name[j] != '\0'; j++, k++) {
if (app_name[j] == '"' || app_name[j] == '\\') {
escaped_app_name[k++] = '\\';
}
escaped_app_name[k] = app_name[j];
}
for (int j = 0, k = 0; bundle_id[j] != '\0'; j++, k++) {
if (bundle_id[j] == '"' || bundle_id[j] == '\\') {
escaped_bundle_id[k++] = '\\';
}
escaped_bundle_id[k] = bundle_id[j];
}
// Add the app name and bundle ID to the result in JSON format
if (!first_entry) {
strcat(result, ",\n");
}
strcat(result, " \"");
strcat(result, escaped_app_name);
strcat(result, "\": \"");
strcat(result, escaped_bundle_id);
strcat(result, "\"");
first_entry = 0;
free(bundle_id);
free(app_name);
}
}
}
}
}
strcat(result, "\n}\n");
installation_proxy_client_free(client);
tcp_provider_free(provider);