Skip to content

Commit 892055f

Browse files
authored
Merge branch 'StephenDev0:main' into main
2 parents 61b6d66 + 3b1e779 commit 892055f

3 files changed

Lines changed: 159 additions & 26 deletions

File tree

StikJIT/Utilities/Extensions.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import Foundation
88
import UniformTypeIdentifiers
99
import UIKit
1010

11+
enum AccessibilityAnnouncer {
12+
static func announce(_ message: String) {
13+
DispatchQueue.main.async {
14+
UIAccessibility.post(notification: .announcement, argument: message)
15+
}
16+
}
17+
}
18+
1119
enum PairingFileStore {
1220
static let fileName = "rp_pairing_file.plist"
1321
private static let legacyFileName = "pairingFile.plist"

StikJIT/Views/HomeView.swift

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ struct HomeView: View {
8080
@State private var viewDidAppeared = false
8181
@State private var pendingJITEnableConfiguration : JITEnableConfiguration? = nil
8282
@State private var isShowingPairingFilePicker = false
83+
@State private var debugFeedback: DebugFeedback?
8384

8485
@State var scriptViewShow = false
8586
@State private var isShowingConsole = false
@@ -89,12 +90,26 @@ struct HomeView: View {
8990

9091
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
9192

93+
private struct DebugFeedback: Identifiable {
94+
let id = UUID()
95+
let message: String
96+
let isError: Bool
97+
let isWorking: Bool
98+
}
99+
92100
var body: some View {
93-
InstalledAppsListView(onSelectApp: { selectedBundle in
101+
InstalledAppsListView(onSelectApp: { selectedBundle, selectedName in
94102
bundleID = selectedBundle
95103
HapticFeedbackHelper.trigger()
96-
startJITInBackground(bundleID: selectedBundle)
104+
startJITInBackground(bundleID: selectedBundle, displayName: selectedName)
97105
}, showDoneButton: false, onImportPairingFile: { isShowingPairingFilePicker = true })
106+
.overlay(alignment: .bottom) {
107+
if let debugFeedback {
108+
debugFeedbackView(debugFeedback)
109+
.padding(.bottom, 40)
110+
.transition(.move(edge: .bottom).combined(with: .opacity))
111+
}
112+
}
98113
.onAppear {
99114
startTunnelInBackground()
100115
MountingProgress.shared.checkforMounted()
@@ -227,6 +242,28 @@ struct HomeView: View {
227242
}
228243
}
229244
}
245+
246+
private func debugFeedbackView(_ feedback: DebugFeedback) -> some View {
247+
HStack(spacing: 10) {
248+
if feedback.isWorking {
249+
ProgressView()
250+
.controlSize(.small)
251+
}
252+
253+
Text(feedback.message)
254+
.font(.subheadline.weight(.semibold))
255+
.multilineTextAlignment(.center)
256+
}
257+
.padding(.horizontal, 16)
258+
.padding(.vertical, 10)
259+
.background(Capsule().fill(.ultraThinMaterial))
260+
.foregroundStyle(feedback.isError ? .red : .primary)
261+
.shadow(radius: 4)
262+
.padding(.horizontal, 20)
263+
.accessibilityElement(children: .ignore)
264+
.accessibilityLabel(feedback.message)
265+
}
266+
230267
private func getJsCallback(_ script: Data, name: String? = nil) -> DebugAppCallback {
231268
return { pid, debugProxyHandle, remoteServerHandle, semaphore in
232269
let model = RunJSViewModel(pid: Int(pid),
@@ -250,9 +287,15 @@ struct HomeView: View {
250287
}
251288
}
252289

253-
private func startJITInBackground(bundleID: String? = nil, pid: Int? = nil, scriptData: Data? = nil, scriptName: String? = nil, triggeredByURLScheme: Bool = false) {
290+
private func startJITInBackground(bundleID: String? = nil, pid: Int? = nil, scriptData: Data? = nil, scriptName: String? = nil, triggeredByURLScheme: Bool = false, displayName: String? = nil) {
254291
isProcessing = true
292+
let targetName = displayName ?? bundleID ?? pid.map { String(format: "process %d".localized, $0) } ?? "app".localized
293+
let startingMessage = String(format: "Starting JIT for %@".localized, targetName)
255294
LogManager.shared.addInfoLog("Starting Debug for \(bundleID ?? String(pid ?? 0))")
295+
withAnimation {
296+
debugFeedback = DebugFeedback(message: startingMessage, isError: false, isWorking: true)
297+
}
298+
AccessibilityAnnouncer.announce(startingMessage)
256299

257300
if triggeredByURLScheme {
258301
pubTunnelConnected = false
@@ -266,9 +309,31 @@ struct HomeView: View {
266309
if triggeredByURLScheme {
267310
sleep(1)
268311
}
269-
let finishProcessing = {
312+
313+
let finishProcessing: (Bool, String?) -> Void = { success, detail in
270314
DispatchQueue.main.async {
271315
isProcessing = false
316+
let message = success
317+
? String(format: "JIT request completed for %@".localized, targetName)
318+
: String(format: "JIT failed for %@".localized, targetName)
319+
let feedback = DebugFeedback(message: message, isError: !success, isWorking: false)
320+
withAnimation {
321+
debugFeedback = feedback
322+
}
323+
AccessibilityAnnouncer.announce(message)
324+
325+
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
326+
if debugFeedback?.id == feedback.id {
327+
withAnimation {
328+
debugFeedback = nil
329+
}
330+
}
331+
}
332+
333+
if !success {
334+
let failureMessage = detail ?? "StikDebug could not launch or attach to the selected app. Check that the VPN is enabled, the pairing file is current, and the app is still installed.".localized
335+
showAlert(title: "Failed to Enable JIT".localized, message: failureMessage, showOk: true)
336+
}
272337
}
273338
}
274339

@@ -286,16 +351,20 @@ struct HomeView: View {
286351
callback = getJsCallback(sd, name: scriptName ?? bundleID ?? "Script")
287352
}
288353

289-
let logger: LogFunc = { message in if let message { LogManager.shared.addInfoLog(message) } }
354+
var lastDebugMessage: String?
355+
let logger: LogFunc = { message in
356+
if let message {
357+
lastDebugMessage = message
358+
LogManager.shared.addInfoLog(message)
359+
}
360+
}
290361
var success: Bool
291362
if let pid {
292363
success = JITEnableContext.shared.debugApp(withPID: Int32(pid), logger: logger, jsCallback: callback)
293364
} else if let bundleID {
294365
success = JITEnableContext.shared.debugApp(withBundleID: bundleID, logger: logger, jsCallback: callback)
295366
} else {
296-
DispatchQueue.main.async {
297-
showAlert(title: "Failed to Debug App".localized, message: "Either bundle ID or PID should be specified.".localized, showOk: true)
298-
}
367+
lastDebugMessage = "Either bundle ID or PID should be specified.".localized
299368
success = false
300369
}
301370

@@ -308,7 +377,7 @@ struct HomeView: View {
308377
}
309378
}
310379
}
311-
finishProcessing()
380+
finishProcessing(success, success ? nil : lastDebugMessage)
312381
}
313382
}
314383

StikJIT/Views/InstalledAppsListView.swift

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct InstalledAppsListView: View {
3939
@AppStorage("pinnedSystemAppNames") private var pinnedSystemAppNames: [String: String] = [:]
4040

4141
@Environment(\.dismiss) private var dismiss
42-
var onSelectApp: (String) -> Void
42+
var onSelectApp: (String, String) -> Void
4343
var showDoneButton: Bool = true
4444
var onImportPairingFile: (() -> Void)? = nil
4545

@@ -372,12 +372,13 @@ private enum AppListTab: Int, CaseIterable, Identifiable {
372372
bundleID: bundleID, appName: appName,
373373
isLaunching: launchingBundles.contains(bundleID),
374374
performanceMode: performanceMode
375-
) { startLaunching(bundleID: bundleID) }
375+
) { startLaunching(bundleID: bundleID, appName: appName) }
376376
.overlay(alignment: .topTrailing) {
377377
if isPinned {
378378
Image(systemName: "star.fill")
379379
.font(.system(size: 12, weight: .semibold))
380380
.foregroundStyle(.yellow).padding(6)
381+
.accessibilityHidden(true)
381382
}
382383
}
383384
.contextMenu {
@@ -448,20 +449,24 @@ private enum AppListTab: Int, CaseIterable, Identifiable {
448449
if touched { WidgetCenter.shared.reloadAllTimelines() }
449450
}
450451

451-
private func startLaunching(bundleID: String) {
452+
private func startLaunching(bundleID: String, appName: String) {
452453
guard !launchingBundles.contains(bundleID) else { return }
453454
launchingBundles.insert(bundleID)
454455
Haptics.selection()
456+
AccessibilityAnnouncer.announce(String(format: "Launching %@".localized, appName))
455457

456458
viewModel.launchWithoutDebug(bundleID: bundleID) { success in
457459
launchingBundles.remove(bundleID)
458460

459-
let message = success ? "Launch request sent".localized : "Launch failed".localized
461+
let message = success
462+
? String(format: "Launch request sent for %@".localized, appName)
463+
: String(format: "Launch failed for %@".localized, appName)
460464
let feedback = LaunchFeedback(message: message, success: success)
461465

462466
if success {
463467
Haptics.light()
464468
}
469+
AccessibilityAnnouncer.announce(message)
465470

466471
withAnimation {
467472
launchFeedback = feedback
@@ -521,7 +526,7 @@ struct AppButton: View {
521526
@AppStorage("loadAppIconsOnJIT") private var loadAppIconsOnJIT = true
522527
@AppStorage("enableAdvancedOptions") private var enableAdvancedOptions = false
523528

524-
var onSelectApp: (String) -> Void
529+
var onSelectApp: (String, String) -> Void
525530
let sharedDefaults: UserDefaults
526531
let performanceMode: Bool
527532

@@ -534,7 +539,7 @@ struct AppButton: View {
534539
appName: String,
535540
recentApps: Binding<[String]>,
536541
favoriteApps: Binding<[String]>,
537-
onSelectApp: @escaping (String) -> Void,
542+
onSelectApp: @escaping (String, String) -> Void,
538543
sharedDefaults: UserDefaults,
539544
performanceMode: Bool
540545
) {
@@ -577,6 +582,7 @@ struct AppButton: View {
577582
}
578583
}
579584
.padding(.vertical, loadAppIconsOnJIT ? 4 : 8)
585+
.contentShape(Rectangle())
580586
}
581587
.buttonStyle(.plain)
582588
.contextMenu {
@@ -588,8 +594,7 @@ struct AppButton: View {
588594
.disabled(!favoriteApps.contains(bundleID) && favoriteApps.count >= 4)
589595
}
590596
Button {
591-
UIPasteboard.general.string = bundleID
592-
Haptics.light()
597+
copyBundleID()
593598
} label: {
594599
Label("Copy Bundle ID", systemImage: "doc.on.doc")
595600
}
@@ -615,8 +620,7 @@ struct AppButton: View {
615620
.tint(.yellow)
616621

617622
Button {
618-
UIPasteboard.general.string = bundleID
619-
Haptics.light()
623+
copyBundleID()
620624
} label: {
621625
Label("Copy ID", systemImage: "doc.on.doc")
622626
}
@@ -638,8 +642,34 @@ struct AppButton: View {
638642
}
639643
}
640644
.accessibilityElement(children: .ignore)
641-
.accessibilityLabel("\(appName)")
642-
.accessibilityHint("Double-tap to select. Swipe for actions, long-press for options.")
645+
.accessibilityLabel(String(format: "Enable JIT for %@".localized, appName))
646+
.accessibilityValue(accessibilityValue)
647+
.accessibilityHint("Double-tap to open the app and enable JIT. Use the actions rotor for favorites or bundle ID.".localized)
648+
.accessibilityAddTraits(.isButton)
649+
.accessibilityRemoveTraits(.isStaticText)
650+
.accessibilityAction(named: Text(favoriteAccessibilityActionLabel)) {
651+
toggleFavorite()
652+
}
653+
.accessibilityAction(named: Text("Copy Bundle ID".localized)) {
654+
copyBundleID()
655+
}
656+
}
657+
658+
private var accessibilityValue: String {
659+
var parts = [String(format: "Bundle ID %@".localized, bundleID)]
660+
if favoriteApps.contains(bundleID) {
661+
parts.append("Favorite".localized)
662+
}
663+
if let assignedScriptName {
664+
parts.append(String(format: "Assigned script %@".localized, assignedScriptName))
665+
}
666+
return parts.joined(separator: ", ")
667+
}
668+
669+
private var favoriteAccessibilityActionLabel: String {
670+
favoriteApps.contains(bundleID)
671+
? "Remove from Favorites".localized
672+
: "Add to Favorites".localized
643673
}
644674

645675
// MARK: Icon
@@ -678,18 +708,29 @@ struct AppButton: View {
678708
recentApps = Array(recentApps.prefix(3))
679709
}
680710
persistIfChanged()
681-
onSelectApp(bundleID)
711+
onSelectApp(bundleID, appName)
682712
}
683713

684714
private func toggleFavorite() {
685715
Haptics.light()
686-
if favoriteApps.contains(bundleID) {
716+
let wasFavorite = favoriteApps.contains(bundleID)
717+
if wasFavorite {
687718
favoriteApps.removeAll { $0 == bundleID }
688719
} else if favoriteApps.count < 4 {
689720
favoriteApps.insert(bundleID, at: 0)
690721
recentApps.removeAll { $0 == bundleID }
722+
} else {
723+
AccessibilityAnnouncer.announce("Favorites are full".localized)
724+
return
691725
}
692726
persistIfChanged()
727+
AccessibilityAnnouncer.announce(wasFavorite ? "Removed from Favorites".localized : "Added to Favorites".localized)
728+
}
729+
730+
private func copyBundleID() {
731+
UIPasteboard.general.string = bundleID
732+
Haptics.light()
733+
AccessibilityAnnouncer.announce("Bundle ID copied".localized)
693734
}
694735

695736
private func assignScript(_ url: URL?) {
@@ -792,6 +833,7 @@ struct LaunchAppRow: View {
792833
}
793834
}
794835
.padding(.vertical, loadAppIconsOnJIT ? 4 : 8)
836+
.contentShape(Rectangle())
795837
}
796838
.buttonStyle(.plain)
797839
.disabled(isLaunching)
@@ -806,8 +848,22 @@ struct LaunchAppRow: View {
806848
}
807849
}
808850
.accessibilityElement(children: .ignore)
809-
.accessibilityLabel("\(appName)")
810-
.accessibilityHint("Double-tap to launch via CoreDevice.")
851+
.accessibilityLabel(String(format: "Launch %@".localized, appName))
852+
.accessibilityValue(accessibilityValue)
853+
.accessibilityHint(isLaunching
854+
? "Launch request in progress.".localized
855+
: "Double-tap to launch this app without enabling JIT.".localized)
856+
.accessibilityAddTraits(.isButton)
857+
.accessibilityRemoveTraits(.isStaticText)
858+
.accessibilityAction(named: Text("Launch App".localized)) {
859+
guard !isLaunching else { return }
860+
launchAction()
861+
}
862+
}
863+
864+
private var accessibilityValue: String {
865+
let state = isLaunching ? "Launching".localized : "Ready".localized
866+
return "\(state), \(String(format: "Bundle ID %@".localized, bundleID))"
811867
}
812868

813869
private var iconView: some View {
@@ -1112,7 +1168,7 @@ extension Dictionary: @retroactive RawRepresentable where Key: Codable, Value: C
11121168

11131169
struct InstalledAppsListView_Previews: PreviewProvider {
11141170
static var previews: some View {
1115-
InstalledAppsListView { _ in }
1171+
InstalledAppsListView { _, _ in }
11161172
.environment(\.colorScheme, .dark)
11171173
}
11181174
}

0 commit comments

Comments
 (0)