Skip to content

Commit 0bd7325

Browse files
committed
Update the url scheme to support iOS 26 JIT
1 parent f10f56e commit 0bd7325

4 files changed

Lines changed: 454 additions & 69 deletions

File tree

DebugWidget/DebugWidget.swift

Lines changed: 165 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,45 @@ import WidgetKit
99
import SwiftUI
1010
import UIKit
1111

12-
// MARK: - Timeline Entry
13-
struct AppsEntry: TimelineEntry {
12+
// MARK: - Favorites Widget ----------------------------------------------------
13+
14+
struct FavoritesEntry: TimelineEntry {
1415
let date: Date
1516
let bundleIDs: [String]
1617
}
1718

18-
// MARK: - Provider
19-
struct AppsProvider: TimelineProvider {
19+
struct FavoritesProvider: TimelineProvider {
2020
private let sharedDefaults = UserDefaults(suiteName: "group.com.stik.sj")
2121

22-
func placeholder(in context: Context) -> AppsEntry {
23-
AppsEntry(date: .now, bundleIDs: [])
22+
func placeholder(in context: Context) -> FavoritesEntry {
23+
FavoritesEntry(date: .now, bundleIDs: [])
2424
}
2525

26-
func getSnapshot(in context: Context, completion: @escaping (AppsEntry) -> Void) {
26+
func getSnapshot(in context: Context, completion: @escaping (FavoritesEntry) -> Void) {
2727
completion(makeEntry())
2828
}
2929

30-
func getTimeline(in context: Context, completion: @escaping (Timeline<AppsEntry>) -> Void) {
30+
func getTimeline(in context: Context, completion: @escaping (Timeline<FavoritesEntry>) -> Void) {
3131
let entry = makeEntry()
3232
completion(Timeline(entries: [entry], policy: .never))
3333
}
3434

35-
private func makeEntry() -> AppsEntry {
36-
let favs = sharedDefaults?.stringArray(forKey: "favoriteApps") ?? []
37-
let bundleIDs = Array(favs.prefix(4))
38-
return AppsEntry(date: .now, bundleIDs: bundleIDs)
35+
private func makeEntry() -> FavoritesEntry {
36+
let favorites = sharedDefaults?.stringArray(forKey: "favoriteApps") ?? []
37+
return FavoritesEntry(date: .now, bundleIDs: Array(favorites.prefix(4)))
3938
}
4039
}
4140

42-
// MARK: - Widget View
43-
struct AppsWidgetEntryView: View {
44-
let entry: AppsEntry
41+
struct FavoritesWidgetEntryView: View {
42+
let entry: FavoritesEntry
4543

4644
var body: some View {
4745
HStack(spacing: 8) {
4846
ForEach(0..<4, id: \.self) { idx in
4947
if idx < entry.bundleIDs.count {
50-
IconCell(bundleID: entry.bundleIDs[idx])
48+
iconCell(bundleID: entry.bundleIDs[idx])
5149
} else {
52-
PlaceholderCell()
50+
placeholderCell()
5351
}
5452
}
5553
}
@@ -58,7 +56,7 @@ struct AppsWidgetEntryView: View {
5856
}
5957

6058
@ViewBuilder
61-
private func IconCell(bundleID: String) -> some View {
59+
private func iconCell(bundleID: String) -> some View {
6260
if let img = loadIcon(for: bundleID) {
6361
Link(destination: URL(string: "stikjit://enable-jit?bundle-id=\(bundleID)")!) {
6462
Image(uiImage: img)
@@ -67,12 +65,12 @@ struct AppsWidgetEntryView: View {
6765
.cornerRadius(12)
6866
}
6967
} else {
70-
PlaceholderCell()
68+
placeholderCell()
7169
}
7270
}
7371

7472
@ViewBuilder
75-
private func PlaceholderCell() -> some View {
73+
private func placeholderCell() -> some View {
7674
ZStack {
7775
RoundedRectangle(cornerRadius: 12)
7876
.fill(Color(UIColor.systemGray5))
@@ -82,14 +80,153 @@ struct AppsWidgetEntryView: View {
8280
}
8381
.aspectRatio(1, contentMode: .fit)
8482
}
83+
}
84+
85+
struct FavoritesWidget: Widget {
86+
let kind: String = "FavoritesWidget"
87+
88+
var body: some WidgetConfiguration {
89+
StaticConfiguration(kind: kind, provider: FavoritesProvider()) { entry in
90+
FavoritesWidgetEntryView(entry: entry)
91+
}
92+
.configurationDisplayName("StikDebug Favorites")
93+
.description("Quick-launch your top 4 favorite debug targets.")
94+
.supportedFamilies([.systemMedium])
95+
}
96+
}
97+
98+
// MARK: - System Apps Widget -------------------------------------------------
99+
100+
struct SystemAppSnapshot: Identifiable {
101+
let bundleID: String
102+
let displayName: String
103+
var id: String { bundleID }
104+
}
105+
106+
struct SystemAppsEntry: TimelineEntry {
107+
let date: Date
108+
let items: [SystemAppSnapshot]
109+
}
110+
111+
struct SystemAppsProvider: TimelineProvider {
112+
private let sharedDefaults = UserDefaults(suiteName: "group.com.stik.sj")
113+
114+
func placeholder(in context: Context) -> SystemAppsEntry {
115+
SystemAppsEntry(date: .now, items: [])
116+
}
117+
118+
func getSnapshot(in context: Context, completion: @escaping (SystemAppsEntry) -> Void) {
119+
completion(makeEntry())
120+
}
121+
122+
func getTimeline(in context: Context, completion: @escaping (Timeline<SystemAppsEntry>) -> Void) {
123+
let entry = makeEntry()
124+
completion(Timeline(entries: [entry], policy: .never))
125+
}
126+
127+
private func makeEntry() -> SystemAppsEntry {
128+
let pinned = sharedDefaults?.stringArray(forKey: "pinnedSystemApps") ?? []
129+
let names = sharedDefaults?.dictionary(forKey: "pinnedSystemAppNames") as? [String: String] ?? [:]
130+
let snapshots = pinned.prefix(4).map { bundleID -> SystemAppSnapshot in
131+
let displayName = names[bundleID] ?? friendlyName(bundleID: bundleID)
132+
return SystemAppSnapshot(bundleID: bundleID, displayName: displayName)
133+
}
134+
return SystemAppsEntry(date: .now, items: snapshots)
135+
}
85136

86-
private func loadIcon(for bundleID: String) -> UIImage? {
87-
guard let container = FileManager.default.containerURL(
88-
forSecurityApplicationGroupIdentifier: "group.com.stik.sj")
89-
else { return nil }
90-
let url = container
91-
.appendingPathComponent("icons", isDirectory: true)
92-
.appendingPathComponent("\(bundleID).png")
93-
return UIImage(contentsOfFile: url.path)
137+
private func friendlyName(bundleID: String) -> String {
138+
let components = bundleID.split(separator: ".")
139+
if let last = components.last {
140+
let cleaned = last.replacingOccurrences(of: "_", with: " ")
141+
let trimmed = cleaned.trimmingCharacters(in: .whitespacesAndNewlines)
142+
if !trimmed.isEmpty { return trimmed.capitalized }
143+
}
144+
return bundleID
145+
}
146+
}
147+
148+
struct SystemAppsWidgetEntryView: View {
149+
let entry: SystemAppsEntry
150+
151+
var body: some View {
152+
VStack(alignment: .leading, spacing: 8) {
153+
ForEach(entry.items) { item in
154+
Link(destination: URL(string: "stikjit://enable-jit?bundle-id=\(item.bundleID)")!) {
155+
HStack(spacing: 10) {
156+
icon(for: item.bundleID)
157+
.frame(width: 44, height: 44)
158+
159+
VStack(alignment: .leading, spacing: 2) {
160+
Text(item.displayName)
161+
.font(.headline)
162+
.foregroundStyle(.primary)
163+
.lineLimit(1)
164+
Text(item.bundleID)
165+
.font(.caption)
166+
.foregroundStyle(.secondary)
167+
.lineLimit(1)
168+
}
169+
Spacer()
170+
}
171+
.padding(8)
172+
.background(
173+
RoundedRectangle(cornerRadius: 12, style: .continuous)
174+
.fill(Color(UIColor.secondarySystemBackground).opacity(0.35))
175+
)
176+
}
177+
}
178+
179+
if entry.items.isEmpty {
180+
Text("Pin system apps from StikDebug to see them here.")
181+
.font(.caption)
182+
.foregroundStyle(.secondary)
183+
.padding(.top, 4)
184+
}
185+
}
186+
.padding(12)
187+
.containerBackground(Color(UIColor.systemBackground), for: .widget)
94188
}
189+
190+
@ViewBuilder
191+
private func icon(for bundleID: String) -> some View {
192+
if let image = loadIcon(for: bundleID) {
193+
Image(uiImage: image)
194+
.resizable()
195+
.aspectRatio(1, contentMode: .fill)
196+
.cornerRadius(10)
197+
} else {
198+
RoundedRectangle(cornerRadius: 10)
199+
.fill(Color(UIColor.systemGray5))
200+
.overlay(
201+
Image(systemName: "app")
202+
.font(.system(size: 20, weight: .regular))
203+
.foregroundColor(.gray)
204+
)
205+
}
206+
}
207+
}
208+
209+
struct SystemAppsWidget: Widget {
210+
let kind: String = "SystemAppsWidget"
211+
212+
var body: some WidgetConfiguration {
213+
StaticConfiguration(kind: kind, provider: SystemAppsProvider()) { entry in
214+
SystemAppsWidgetEntryView(entry: entry)
215+
}
216+
.configurationDisplayName("Hidden System Apps")
217+
.description("Launch your pinned hidden system apps directly from the widget.")
218+
.supportedFamilies([.systemMedium])
219+
}
220+
}
221+
222+
// MARK: - Shared Helpers -----------------------------------------------------
223+
224+
private func loadIcon(for bundleID: String) -> UIImage? {
225+
guard let container = FileManager.default.containerURL(
226+
forSecurityApplicationGroupIdentifier: "group.com.stik.sj")
227+
else { return nil }
228+
let url = container
229+
.appendingPathComponent("icons", isDirectory: true)
230+
.appendingPathComponent("\(bundleID).png")
231+
return UIImage(contentsOfFile: url.path)
95232
}

DebugWidget/DebugWidgetBundle.swift

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,9 @@ import WidgetKit
99
import SwiftUI
1010

1111
@main
12-
struct AppsWidget: Widget {
13-
let kind: String = "AppsWidget"
14-
15-
var body: some WidgetConfiguration {
16-
StaticConfiguration(kind: kind, provider: AppsProvider()) { entry in
17-
AppsWidgetEntryView(entry: entry)
18-
}
19-
.configurationDisplayName("StikDebug Favorites")
20-
.description("Quick-launch your top 4 favorite debug targets.")
21-
.supportedFamilies([.systemMedium])
12+
struct StikDebugWidgetBundle: WidgetBundle {
13+
var body: some Widget {
14+
FavoritesWidget()
15+
// SystemAppsWidget() // Temporarily disabled
2216
}
2317
}

0 commit comments

Comments
 (0)