Update the url scheme to support iOS 26 JIT

This commit is contained in:
Stephen B 2025-09-23 16:47:01 -04:00
parent f10f56e5e5
commit 0bd73257c2
4 changed files with 454 additions and 69 deletions

View file

@ -9,47 +9,45 @@ import WidgetKit
import SwiftUI
import UIKit
// MARK: - Timeline Entry
struct AppsEntry: TimelineEntry {
// MARK: - Favorites Widget ----------------------------------------------------
struct FavoritesEntry: TimelineEntry {
let date: Date
let bundleIDs: [String]
}
// MARK: - Provider
struct AppsProvider: TimelineProvider {
struct FavoritesProvider: TimelineProvider {
private let sharedDefaults = UserDefaults(suiteName: "group.com.stik.sj")
func placeholder(in context: Context) -> AppsEntry {
AppsEntry(date: .now, bundleIDs: [])
func placeholder(in context: Context) -> FavoritesEntry {
FavoritesEntry(date: .now, bundleIDs: [])
}
func getSnapshot(in context: Context, completion: @escaping (AppsEntry) -> Void) {
func getSnapshot(in context: Context, completion: @escaping (FavoritesEntry) -> Void) {
completion(makeEntry())
}
func getTimeline(in context: Context, completion: @escaping (Timeline<AppsEntry>) -> Void) {
func getTimeline(in context: Context, completion: @escaping (Timeline<FavoritesEntry>) -> Void) {
let entry = makeEntry()
completion(Timeline(entries: [entry], policy: .never))
}
private func makeEntry() -> AppsEntry {
let favs = sharedDefaults?.stringArray(forKey: "favoriteApps") ?? []
let bundleIDs = Array(favs.prefix(4))
return AppsEntry(date: .now, bundleIDs: bundleIDs)
private func makeEntry() -> FavoritesEntry {
let favorites = sharedDefaults?.stringArray(forKey: "favoriteApps") ?? []
return FavoritesEntry(date: .now, bundleIDs: Array(favorites.prefix(4)))
}
}
// MARK: - Widget View
struct AppsWidgetEntryView: View {
let entry: AppsEntry
struct FavoritesWidgetEntryView: View {
let entry: FavoritesEntry
var body: some View {
HStack(spacing: 8) {
ForEach(0..<4, id: \.self) { idx in
if idx < entry.bundleIDs.count {
IconCell(bundleID: entry.bundleIDs[idx])
iconCell(bundleID: entry.bundleIDs[idx])
} else {
PlaceholderCell()
placeholderCell()
}
}
}
@ -58,7 +56,7 @@ struct AppsWidgetEntryView: View {
}
@ViewBuilder
private func IconCell(bundleID: String) -> some View {
private func iconCell(bundleID: String) -> some View {
if let img = loadIcon(for: bundleID) {
Link(destination: URL(string: "stikjit://enable-jit?bundle-id=\(bundleID)")!) {
Image(uiImage: img)
@ -67,12 +65,12 @@ struct AppsWidgetEntryView: View {
.cornerRadius(12)
}
} else {
PlaceholderCell()
placeholderCell()
}
}
@ViewBuilder
private func PlaceholderCell() -> some View {
private func placeholderCell() -> some View {
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color(UIColor.systemGray5))
@ -82,14 +80,153 @@ struct AppsWidgetEntryView: View {
}
.aspectRatio(1, contentMode: .fit)
}
}
private func loadIcon(for bundleID: String) -> UIImage? {
guard let container = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.stik.sj")
else { return nil }
let url = container
.appendingPathComponent("icons", isDirectory: true)
.appendingPathComponent("\(bundleID).png")
return UIImage(contentsOfFile: url.path)
struct FavoritesWidget: Widget {
let kind: String = "FavoritesWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: FavoritesProvider()) { entry in
FavoritesWidgetEntryView(entry: entry)
}
.configurationDisplayName("StikDebug Favorites")
.description("Quick-launch your top 4 favorite debug targets.")
.supportedFamilies([.systemMedium])
}
}
// MARK: - System Apps Widget -------------------------------------------------
struct SystemAppSnapshot: Identifiable {
let bundleID: String
let displayName: String
var id: String { bundleID }
}
struct SystemAppsEntry: TimelineEntry {
let date: Date
let items: [SystemAppSnapshot]
}
struct SystemAppsProvider: TimelineProvider {
private let sharedDefaults = UserDefaults(suiteName: "group.com.stik.sj")
func placeholder(in context: Context) -> SystemAppsEntry {
SystemAppsEntry(date: .now, items: [])
}
func getSnapshot(in context: Context, completion: @escaping (SystemAppsEntry) -> Void) {
completion(makeEntry())
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SystemAppsEntry>) -> Void) {
let entry = makeEntry()
completion(Timeline(entries: [entry], policy: .never))
}
private func makeEntry() -> SystemAppsEntry {
let pinned = sharedDefaults?.stringArray(forKey: "pinnedSystemApps") ?? []
let names = sharedDefaults?.dictionary(forKey: "pinnedSystemAppNames") as? [String: String] ?? [:]
let snapshots = pinned.prefix(4).map { bundleID -> SystemAppSnapshot in
let displayName = names[bundleID] ?? friendlyName(bundleID: bundleID)
return SystemAppSnapshot(bundleID: bundleID, displayName: displayName)
}
return SystemAppsEntry(date: .now, items: snapshots)
}
private func friendlyName(bundleID: String) -> String {
let components = bundleID.split(separator: ".")
if let last = components.last {
let cleaned = last.replacingOccurrences(of: "_", with: " ")
let trimmed = cleaned.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty { return trimmed.capitalized }
}
return bundleID
}
}
struct SystemAppsWidgetEntryView: View {
let entry: SystemAppsEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(entry.items) { item in
Link(destination: URL(string: "stikjit://enable-jit?bundle-id=\(item.bundleID)")!) {
HStack(spacing: 10) {
icon(for: item.bundleID)
.frame(width: 44, height: 44)
VStack(alignment: .leading, spacing: 2) {
Text(item.displayName)
.font(.headline)
.foregroundStyle(.primary)
.lineLimit(1)
Text(item.bundleID)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer()
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(UIColor.secondarySystemBackground).opacity(0.35))
)
}
}
if entry.items.isEmpty {
Text("Pin system apps from StikDebug to see them here.")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.top, 4)
}
}
.padding(12)
.containerBackground(Color(UIColor.systemBackground), for: .widget)
}
@ViewBuilder
private func icon(for bundleID: String) -> some View {
if let image = loadIcon(for: bundleID) {
Image(uiImage: image)
.resizable()
.aspectRatio(1, contentMode: .fill)
.cornerRadius(10)
} else {
RoundedRectangle(cornerRadius: 10)
.fill(Color(UIColor.systemGray5))
.overlay(
Image(systemName: "app")
.font(.system(size: 20, weight: .regular))
.foregroundColor(.gray)
)
}
}
}
struct SystemAppsWidget: Widget {
let kind: String = "SystemAppsWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: SystemAppsProvider()) { entry in
SystemAppsWidgetEntryView(entry: entry)
}
.configurationDisplayName("Hidden System Apps")
.description("Launch your pinned hidden system apps directly from the widget.")
.supportedFamilies([.systemMedium])
}
}
// MARK: - Shared Helpers -----------------------------------------------------
private func loadIcon(for bundleID: String) -> UIImage? {
guard let container = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.stik.sj")
else { return nil }
let url = container
.appendingPathComponent("icons", isDirectory: true)
.appendingPathComponent("\(bundleID).png")
return UIImage(contentsOfFile: url.path)
}

View file

@ -9,15 +9,9 @@ import WidgetKit
import SwiftUI
@main
struct AppsWidget: Widget {
let kind: String = "AppsWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: AppsProvider()) { entry in
AppsWidgetEntryView(entry: entry)
}
.configurationDisplayName("StikDebug Favorites")
.description("Quick-launch your top 4 favorite debug targets.")
.supportedFamilies([.systemMedium])
struct StikDebugWidgetBundle: WidgetBundle {
var body: some Widget {
FavoritesWidget()
// SystemAppsWidget() // Temporarily disabled
}
}