From a58e557092fa7ff1c1bb6ea727af3aade5feb202 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 20 Sep 2024 16:33:26 +0800 Subject: [PATCH] bugfix & code style improvement --- LCSharedUtils.m | 5 +- LiveContainerSwiftUI/LCAppBanner.swift | 210 ++++-------------- LiveContainerSwiftUI/LCAppListView.swift | 166 +++++++------- LiveContainerSwiftUI/LCAppModel.swift | 136 ++++++++++++ LiveContainerSwiftUI/LCAppSettingsView.swift | 127 +++-------- LiveContainerSwiftUI/LCSettingsView.swift | 65 ++---- LiveContainerSwiftUI/LCTabView.swift | 16 +- LiveContainerSwiftUI/LCTweaksView.swift | 54 ++--- LiveContainerSwiftUI/LCWebView.swift | 57 ++--- .../project.pbxproj | 4 + LiveContainerSwiftUI/ObjcBridge.swift | 31 ++- LiveContainerSwiftUI/Shared.swift | 37 ++- LiveContainerUI/LCUtils.m | 8 +- 13 files changed, 427 insertions(+), 489 deletions(-) create mode 100644 LiveContainerSwiftUI/LCAppModel.swift diff --git a/LCSharedUtils.m b/LCSharedUtils.m index 28c3691..f670d0f 100644 --- a/LCSharedUtils.m +++ b/LCSharedUtils.m @@ -24,11 +24,11 @@ + (NSString *)appGroupID { } + (NSString *)certificatePassword { - NSString* ans = [lcUserDefaults objectForKey:@"LCCertificatePassword"]; + NSString* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; if(ans) { return ans; } else { - return [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; + return [lcUserDefaults objectForKey:@"LCCertificatePassword"]; } } @@ -65,6 +65,7 @@ + (BOOL)askForJIT { NSURL *launchURL = [NSURL URLWithString:[NSString stringWithFormat:urlScheme, NSBundle.mainBundle.bundleIdentifier]]; if ([UIApplication.sharedApplication canOpenURL:launchURL]) { [UIApplication.sharedApplication openURL:launchURL options:@{} completionHandler:nil]; + [LCSharedUtils launchToGuestApp]; return YES; } } else { diff --git a/LiveContainerSwiftUI/LCAppBanner.swift b/LiveContainerSwiftUI/LCAppBanner.swift index 3705a43..17445d9 100644 --- a/LiveContainerSwiftUI/LCAppBanner.swift +++ b/LiveContainerSwiftUI/LCAppBanner.swift @@ -10,34 +10,23 @@ import SwiftUI import UniformTypeIdentifiers protocol LCAppBannerDelegate { - func removeApp(app: LCAppInfo) - func changeAppVisibility(app: LCAppInfo) + func removeApp(app: LCAppModel) func installMdm(data: Data) func openNavigationView(view: AnyView) - func closeNavigationView() } -struct LCAppBanner : View, LCAppSettingDelegate { +struct LCAppBanner : View { @State var appInfo: LCAppInfo var delegate: LCAppBannerDelegate - @StateObject var model : LCAppModel + @ObservedObject var model : LCAppModel @Binding var appDataFolders: [String] @Binding var tweakFolders: [String] - - @State private var confirmAppRemovalShow = false - @State private var confirmAppFolderRemovalShow = false - - @State private var confirmAppRemoval = false - @State private var confirmAppFolderRemoval = false - @State private var appRemovalContinuation : CheckedContinuation? = nil - @State private var appFolderRemovalContinuation : CheckedContinuation? = nil - - @State private var enablingJITShow = false - @State private var confirmEnablingJIT = false - @State private var confirmEnablingJITContinuation : CheckedContinuation? = nil + @StateObject private var appRemovalAlert = YesNoHelper() + @StateObject private var appFolderRemovalAlert = YesNoHelper() + @StateObject private var jitAlert = YesNoHelper() @State private var saveIconExporterShow = false @State private var saveIconFile : ImageDocument? @@ -45,19 +34,15 @@ struct LCAppBanner : View, LCAppSettingDelegate { @State private var errorShow = false @State private var errorInfo = "" - @State private var isSingingInProgress = false - @State private var signProgress = 0.0 - - @State private var observer : NSKeyValueObservation? @EnvironmentObject private var sharedModel : SharedModel - init(appInfo: LCAppInfo, delegate: LCAppBannerDelegate, appDataFolders: Binding<[String]>, tweakFolders: Binding<[String]>) { - _appInfo = State(initialValue: appInfo) + init(appModel: LCAppModel, delegate: LCAppBannerDelegate, appDataFolders: Binding<[String]>, tweakFolders: Binding<[String]>) { + _appInfo = State(initialValue: appModel.appInfo) _appDataFolders = appDataFolders _tweakFolders = tweakFolders self.delegate = delegate - _model = StateObject(wrappedValue: LCAppModel(appInfo: appInfo)) + _model = ObservedObject(wrappedValue: appModel) } var body: some View { @@ -96,7 +81,7 @@ struct LCAppBanner : View, LCAppSettingDelegate { Button { Task{ await runApp() } } label: { - if !isSingingInProgress { + if !model.isSigningInProgress { Text("lc.appBanner.run".loc).bold().foregroundColor(.white) } else { ProgressView().progressViewStyle(.circular) @@ -108,7 +93,7 @@ struct LCAppBanner : View, LCAppSettingDelegate { .frame(height: 32) .fixedSize() .background(GeometryReader { g in - if !isSingingInProgress { + if !model.isSigningInProgress { Capsule().fill(Color("FontColor")) } else { let w = g.size.width @@ -118,7 +103,7 @@ struct LCAppBanner : View, LCAppSettingDelegate { Circle() .fill(Color("FontColor")) .frame(width: w * 2, height: w * 2) - .offset(x: (signProgress - 2) * w, y: h/2-w) + .offset(x: (model.signProgress - 2) * w, y: h/2-w) } }) @@ -129,6 +114,9 @@ struct LCAppBanner : View, LCAppSettingDelegate { .padding() .frame(height: 88) .background(RoundedRectangle(cornerSize: CGSize(width:22, height: 22)).fill(Color("AppBannerBG"))) + .onAppear() { + handleOnAppear() + } .fileExporter( isPresented: $saveIconExporterShow, @@ -198,54 +186,40 @@ struct LCAppBanner : View, LCAppSettingDelegate { - } - - .onChange(of: sharedModel.bundleIdToLaunch, perform: { newValue in - Task { await handleURLSchemeLaunch() } - }) - - .onAppear() { - Task { await handleURLSchemeLaunch() } } - .alert("lc.appBanner.confirmUninstallTitle".loc, isPresented: $confirmAppRemovalShow) { + .alert("lc.appBanner.confirmUninstallTitle".loc, isPresented: $appRemovalAlert.show) { Button(role: .destructive) { - self.confirmAppRemoval = true - self.appRemovalContinuation?.resume() + appRemovalAlert.close(result: true) } label: { Text("lc.appBanner.uninstall".loc) } Button("lc.common.cancel".loc, role: .cancel) { - self.confirmAppRemoval = false - self.appRemovalContinuation?.resume() + appRemovalAlert.close(result: false) } } message: { Text("lc.appBanner.confirmUninstallMsg %@".localizeWithFormat(appInfo.displayName()!)) } - .alert("lc.appBanner.deleteDataTitle".loc, isPresented: $confirmAppFolderRemovalShow) { + .alert("lc.appBanner.deleteDataTitle".loc, isPresented: $appFolderRemovalAlert.show) { Button(role: .destructive) { - self.confirmAppFolderRemoval = true - self.appFolderRemovalContinuation?.resume() + appFolderRemovalAlert.close(result: true) } label: { Text("lc.common.delete".loc) } Button("lc.common.cancel".loc, role: .cancel) { - self.confirmAppFolderRemoval = false - self.appFolderRemovalContinuation?.resume() + appFolderRemovalAlert.close(result: false) } } message: { Text("lc.appBanner.deleteDataMsg \(appInfo.displayName()!)") } - .alert("lc.appBanner.waitForJitTitle".loc, isPresented: $enablingJITShow) { + .alert("lc.appBanner.waitForJitTitle".loc, isPresented: $jitAlert.show) { Button { - self.confirmEnablingJIT = true - self.confirmEnablingJITContinuation?.resume() + jitAlert.close(result: true) } label: { Text("lc.appBanner.jitLaunchNow".loc) } Button("lc.common.cancel", role: .cancel) { - self.confirmEnablingJIT = false - self.confirmEnablingJITContinuation?.resume() + jitAlert.close(result: false) } } message: { Text("lc.appBanner.waitForJitMsg".loc) @@ -260,96 +234,24 @@ struct LCAppBanner : View, LCAppSettingDelegate { } - func handleURLSchemeLaunch() async { - if self.appInfo.relativeBundlePath == sharedModel.bundleIdToLaunch { - await runApp() - } + func handleOnAppear() { + model.jitAlert = jitAlert } func runApp() async { - if let runningLC = LCUtils.getAppRunningLCScheme(bundleId: self.appInfo.relativeBundlePath) { - let openURL = URL(string: "\(runningLC)://livecontainer-launch?bundle-name=\(self.appInfo.relativeBundlePath!)")! - if UIApplication.shared.canOpenURL(openURL) { - await UIApplication.shared.open(openURL) - return - } - } - model.isAppRunning = true - - var signError : String? = nil - await withCheckedContinuation({ c in - appInfo.patchExecAndSignIfNeed(completionHandler: { error in - signError = error; - c.resume() - }, progressHandler: { signProgress in - guard let signProgress else { - return - } - self.isSingingInProgress = true - self.observer = signProgress.observe(\.fractionCompleted) { p, v in - DispatchQueue.main.async { - self.signProgress = signProgress.fractionCompleted - } - } - }, forceSign: false) - }) - self.isSingingInProgress = false - if let signError { - errorInfo = signError + do { + try await model.runApp() + } catch { + errorInfo = errorInfo errorShow = true - model.isAppRunning = false - return } - - UserDefaults.standard.set(self.appInfo.relativeBundlePath, forKey: "selected") - if appInfo.isJITNeeded { - await self.jitLaunch() - } else { - LCUtils.launchToGuestApp() - } - - model.isAppRunning = false - } + func openSettings() { - delegate.openNavigationView(view: AnyView(LCAppSettingsView(model: model, appDataFolders: $appDataFolders, tweakFolders: $tweakFolders, delegate: self))) + delegate.openNavigationView(view: AnyView(LCAppSettingsView(model: model, appDataFolders: $appDataFolders, tweakFolders: $tweakFolders))) } - func forceResign() async { - if model.isAppRunning { - return - } - - model.isAppRunning = true - var signError : String? = nil - await withCheckedContinuation({ c in - appInfo.patchExecAndSignIfNeed(completionHandler: { error in - signError = error; - c.resume() - }, progressHandler: { signProgress in - guard let signProgress else { - return - } - self.isSingingInProgress = true - self.observer = signProgress.observe(\.fractionCompleted) { p, v in - DispatchQueue.main.async { - self.signProgress = signProgress.fractionCompleted - } - } - }, forceSign: true) - }) - self.isSingingInProgress = false - if let signError { - errorInfo = signError - errorShow = true - model.isAppRunning = false - return - } - model.isAppRunning = false - } - - func openDataFolder() { let url = URL(string:"shareddocuments://\(LCPath.docPath.path)/Data/Application/\(appInfo.dataUUID()!)") @@ -360,29 +262,22 @@ struct LCAppBanner : View, LCAppSettingDelegate { func uninstall() async { do { - await withCheckedContinuation { c in - self.appRemovalContinuation = c - self.confirmAppRemovalShow = true; - } - - if !self.confirmAppRemoval { + if let result = await appRemovalAlert.open(), !result { return } + + var doRemoveAppFolder = false if self.appInfo.getDataUUIDNoAssign() != nil { - self.confirmAppFolderRemovalShow = true; - await withCheckedContinuation { c in - self.appFolderRemovalContinuation = c - self.confirmAppFolderRemovalShow = true; + if let result = await appFolderRemovalAlert.open() { + doRemoveAppFolder = result } - } else { - self.confirmAppFolderRemoval = false; + } - let fm = FileManager() try fm.removeItem(atPath: self.appInfo.bundlePath()!) - self.delegate.removeApp(app: self.appInfo) - if self.confirmAppFolderRemoval { + self.delegate.removeApp(app: self.model) + if doRemoveAppFolder { let dataUUID = appInfo.dataUUID()! let dataFolderPath = LCPath.dataPath.appendingPathComponent(dataUUID) try fm.removeItem(at: dataFolderPath) @@ -399,20 +294,7 @@ struct LCAppBanner : View, LCAppSettingDelegate { errorShow = true } } - - func jitLaunch() async { - LCUtils.askForJIT() - await withCheckedContinuation { c in - self.confirmEnablingJITContinuation = c - enablingJITShow = true - } - if confirmEnablingJIT { - LCUtils.launchToGuestApp() - } else { - UserDefaults.standard.removeObject(forKey: "selected") - } - } func copyLaunchUrl() { UIPasteboard.general.string = "livecontainer://livecontainer-launch?bundle-name=\(appInfo.relativeBundlePath!)" @@ -429,18 +311,6 @@ struct LCAppBanner : View, LCAppSettingDelegate { } - func toggleHidden() async { - delegate.closeNavigationView() - if appInfo.isHidden { - appInfo.isHidden = false - model.uiIsHidden = false - } else { - appInfo.isHidden = true - model.uiIsHidden = true - } - delegate.changeAppVisibility(app: appInfo) - } - func saveIcon() { let img = appInfo.icon()! self.saveIconFile = ImageDocument(uiImage: img) diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 8d9955c..17ee1e5 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -11,12 +11,14 @@ import UniformTypeIdentifiers struct AppReplaceOption : Hashable { var isReplace: Bool var nameOfFolderToInstall: String - var appToReplace: LCAppInfo? + var appToReplace: LCAppModel? } -struct LCAppListView : View, LCAppBannerDelegate { - @Binding var apps: [LCAppInfo] - @Binding var hiddenApps: [LCAppInfo] +struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { + + @Binding var apps: [LCAppModel] + @Binding var hiddenApps: [LCAppModel] + @Binding var appDataFolderNames: [String] @Binding var tweakFolderNames: [String] @@ -32,16 +34,12 @@ struct LCAppListView : View, LCAppBannerDelegate { @State var uiInstallProgressPercentage = 0.0 @State var installObserver : NSKeyValueObservation? - @State var installReplaceComfirmVisible = false @State var installOptions: [AppReplaceOption] - @State var installOptionChosen: AppReplaceOption? - @State var installOptionContinuation : CheckedContinuation? = nil + @StateObject var installReplaceAlert = AlertHelper() @State var webViewOpened = false @State var webViewURL : URL = URL(string: "about:blank")! - @State private var webViewUrlInputOpened = false - @State private var webViewUrlInputContent = "" - @State private var webViewUrlInputContinuation : CheckedContinuation? = nil + @StateObject private var webViewUrlInput = InputHelper() @State var safariViewOpened = false @State var safariViewURL = URL(string: "https://google.com")! @@ -51,13 +49,13 @@ struct LCAppListView : View, LCAppBannerDelegate { @EnvironmentObject private var sharedModel : SharedModel - init(apps: Binding<[LCAppInfo]>, hiddenApps: Binding<[LCAppInfo]>, appDataFolderNames: Binding<[String]>, tweakFolderNames: Binding<[String]>) { + init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>, tweakFolderNames: Binding<[String]>) { _installOptions = State(initialValue: []) - _installOptionChosen = State(initialValue: nil) _apps = apps _hiddenApps = hiddenApps _appDataFolderNames = appDataFolderNames _tweakFolderNames = tweakFolderNames + } var body: some View { @@ -90,7 +88,7 @@ struct LCAppListView : View, LCAppBannerDelegate { .zIndex(.infinity) LazyVStack { ForEach(apps, id: \.self) { app in - LCAppBanner(appInfo: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) + LCAppBanner(appModel: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) } .transition(.scale) @@ -115,7 +113,7 @@ struct LCAppListView : View, LCAppBannerDelegate { Spacer() } ForEach(hiddenApps, id: \.self) { app in - LCAppBanner(appInfo: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) + LCAppBanner(appModel: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) } .transition(.scale) } @@ -137,14 +135,9 @@ struct LCAppListView : View, LCAppBannerDelegate { .coordinateSpace(name: "scroll") .onAppear { if !didAppear { - didAppear = true - Task { await checkIfAppDelegateNeedOpenWebPage() } - onLaunchBundleIdChange() + onAppear() } } - .onChange(of: sharedModel.bundleIdToLaunch) { newValue in - onLaunchBundleIdChange() - } .navigationTitle("lc.appList.myApps".loc) .toolbar { @@ -184,19 +177,17 @@ struct LCAppListView : View, LCAppBannerDelegate { .fileImporter(isPresented: $choosingIPA, allowedContentTypes: [.ipa]) { result in Task { await startInstallApp(result) } } - .alert("lc.appList.installation".loc, isPresented: $installReplaceComfirmVisible) { + .alert("lc.appList.installation".loc, isPresented: $installReplaceAlert.show) { ForEach(installOptions, id: \.self) { installOption in Button(role: installOption.isReplace ? .destructive : nil, action: { - self.installOptionChosen = installOption - self.installOptionContinuation?.resume() + installReplaceAlert.close(result: installOption) }, label: { Text(installOption.isReplace ? installOption.nameOfFolderToInstall : "lc.appList.installAsNew".loc) }) } Button(role: .cancel, action: { - self.installOptionChosen = nil - self.installOptionContinuation?.resume() + installReplaceAlert.close(result: nil) }, label: { Text("lc.appList.abortInstallation".loc) }) @@ -204,17 +195,15 @@ struct LCAppListView : View, LCAppBannerDelegate { Text("lc.appList.installReplaceTip".loc) } .textFieldAlert( - isPresented: $webViewUrlInputOpened, + isPresented: $webViewUrlInput.show, title: "lc.appList.enterUrlTip".loc, - text: $webViewUrlInputContent, + text: $webViewUrlInput.initVal, placeholder: "scheme://", action: { newText in - self.webViewUrlInputContent = newText! - webViewUrlInputContinuation?.resume() + webViewUrlInput.close(result: newText) }, actionCancel: {_ in - self.webViewUrlInputContent = "" - webViewUrlInputContinuation?.resume() + webViewUrlInput.close(result: nil) } ) .fullScreenCover(isPresented: $webViewOpened) { @@ -227,42 +216,37 @@ struct LCAppListView : View, LCAppBannerDelegate { } func onOpenWebViewTapped() async { - await withCheckedContinuation { c in - webViewUrlInputOpened = true - webViewUrlInputContinuation = c + guard let urlToOpen = await webViewUrlInput.open(), urlToOpen != "" else { + return } - if webViewUrlInputContent == "" { - return - } - await openWebView(urlString: webViewUrlInputContent) - webViewUrlInputContent = "" + await openWebView(urlString: urlToOpen) } - func checkIfAppDelegateNeedOpenWebPage() async { - LCObjcBridge.openUrlStrFunc = openWebView; - if LCObjcBridge.urlStrToOpen != nil { - await self.openWebView(urlString: LCObjcBridge.urlStrToOpen!) - LCObjcBridge.urlStrToOpen = nil - } else if let urlStr = UserDefaults.standard.string(forKey: "webPageToOpen") { - UserDefaults.standard.removeObject(forKey: "webPageToOpen") - await self.openWebView(urlString: urlStr) + func onAppear() { + for app in apps { + app.delegate = self } + for app in hiddenApps { + app.delegate = self + } + + LCObjcBridge.setLaunchAppFunc(handler: launchAppWithBundleId) + LCObjcBridge.setOpenUrlStrFunc(handler: openWebView) } + func openWebView(urlString: String) async { guard var urlToOpen = URLComponents(string: urlString), urlToOpen.url != nil else { errorInfo = "lc.appList.urlInvalidError".loc errorShow = true - webViewUrlInputContent = "" return } - webViewUrlInputContent = "" if urlToOpen.scheme == nil || urlToOpen.scheme! == "" { urlToOpen.scheme = "https" } if urlToOpen.scheme != "https" && urlToOpen.scheme != "http" { - var appToLaunch : LCAppInfo? = nil + var appToLaunch : LCAppModel? = nil var appListsToConsider = [apps] if sharedModel.isHiddenAppUnlocked || !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { appListsToConsider.append(hiddenApps) @@ -270,7 +254,7 @@ struct LCAppListView : View, LCAppBannerDelegate { appLoop: for appList in appListsToConsider { for app in appList { - if let schemes = app.urlSchemes() { + if let schemes = app.appInfo.urlSchemes() { for scheme in schemes { if let scheme = scheme as? String, scheme == urlToOpen.scheme { appToLaunch = app @@ -288,7 +272,7 @@ struct LCAppListView : View, LCAppBannerDelegate { return } - if appToLaunch.isHidden && !sharedModel.isHiddenAppUnlocked { + if appToLaunch.appInfo.isHidden && !sharedModel.isHiddenAppUnlocked { do { if !(try await LCUtils.authenticateUser()) { return @@ -300,7 +284,7 @@ struct LCAppListView : View, LCAppBannerDelegate { } } - UserDefaults.standard.setValue(appToLaunch.relativeBundlePath!, forKey: "selected") + UserDefaults.standard.setValue(appToLaunch.appInfo.relativeBundlePath!, forKey: "selected") UserDefaults.standard.setValue(urlToOpen.url!.absoluteString, forKey: "launchAppUrlScheme") LCUtils.launchToGuestApp() @@ -379,10 +363,10 @@ struct LCAppListView : View, LCAppBannerDelegate { var appRelativePath = "\(newAppInfo.bundleIdentifier()!).app" var outputFolder = LCPath.bundlePath.appendingPathComponent(appRelativePath) - var appToReplace : LCAppInfo? = nil + var appToReplace : LCAppModel? = nil // Folder exist! show alert for user to choose which bundle to replace let sameBundleIdApp = self.apps.filter { app in - return app.bundleIdentifier()! == newAppInfo.bundleIdentifier() + return app.appInfo.bundleIdentifier()! == newAppInfo.bundleIdentifier() } if fm.fileExists(atPath: outputFolder.path) || sameBundleIdApp.count > 0 { appRelativePath = "\(newAppInfo.bundleIdentifier()!)_\(Int(CFAbsoluteTimeGetCurrent())).app" @@ -390,17 +374,11 @@ struct LCAppListView : View, LCAppBannerDelegate { self.installOptions = [AppReplaceOption(isReplace: false, nameOfFolderToInstall: appRelativePath)] for app in sameBundleIdApp { - self.installOptions.append(AppReplaceOption(isReplace: true, nameOfFolderToInstall: app.relativeBundlePath, appToReplace: app)) + self.installOptions.append(AppReplaceOption(isReplace: true, nameOfFolderToInstall: app.appInfo.relativeBundlePath, appToReplace: app)) } - await withCheckedContinuation { c in - self.installOptionContinuation = c - self.installReplaceComfirmVisible = true - } - - - // user cancelled - guard let installOptionChosen = self.installOptionChosen else { + guard let installOptionChosen = await installReplaceAlert.open() else { + // user cancelled self.installprogressVisible = false try fm.removeItem(at: payloadPath) return @@ -411,7 +389,7 @@ struct LCAppListView : View, LCAppBannerDelegate { if installOptionChosen.isReplace { try fm.removeItem(at: outputFolder) self.apps.removeAll { appNow in - return appNow.relativeBundlePath == installOptionChosen.nameOfFolderToInstall + return appNow.appInfo.relativeBundlePath == installOptionChosen.nameOfFolderToInstall } } } @@ -441,25 +419,28 @@ struct LCAppListView : View, LCAppBannerDelegate { } // set data folder to the folder of the chosen app if let appToReplace = appToReplace { - finalNewApp.setDataUUID(appToReplace.getDataUUIDNoAssign()) + finalNewApp.setDataUUID(appToReplace.appInfo.getDataUUIDNoAssign()) } DispatchQueue.main.async { - self.apps.append(finalNewApp) + self.apps.append(LCAppModel(appInfo: finalNewApp)) self.installprogressVisible = false } } - func removeApp(app: LCAppInfo) { + func removeApp(app: LCAppModel) { DispatchQueue.main.async { self.apps.removeAll { now in return app == now } + self.hiddenApps.removeAll { now in + return app == now + } } } - func changeAppVisibility(app: LCAppInfo) { + func changeAppVisibility(app: LCAppModel) { DispatchQueue.main.async { - if app.isHidden { + if app.appInfo.isHidden { self.apps.removeAll { now in return app == now } @@ -474,22 +455,22 @@ struct LCAppListView : View, LCAppBannerDelegate { } - func onLaunchBundleIdChange() { - if sharedModel.bundleIdToLaunch == "" { + func launchAppWithBundleId(bundleId : String) async { + if bundleId == "" { return } - var appFound = false + var appFound : LCAppModel? = nil var isFoundAppHidden = false for app in apps { - if app.relativeBundlePath == sharedModel.bundleIdToLaunch { - appFound = true + if app.appInfo.relativeBundlePath == bundleId { + appFound = app break } } - if !appFound && !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { + if appFound == nil && !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { for app in hiddenApps { - if app.relativeBundlePath == sharedModel.bundleIdToLaunch { - appFound = true + if app.appInfo.relativeBundlePath == bundleId { + appFound = app isFoundAppHidden = true break } @@ -497,25 +478,30 @@ struct LCAppListView : View, LCAppBannerDelegate { } if isFoundAppHidden && !sharedModel.isHiddenAppUnlocked { - Task { - do { - let result = try await LCUtils.authenticateUser() - if !result { - sharedModel.bundleIdToLaunch = "" - } - } catch { - sharedModel.bundleIdToLaunch = "" - errorInfo = error.localizedDescription - errorShow = true + do { + let result = try await LCUtils.authenticateUser() + if !result { + return } - + } catch { + errorInfo = error.localizedDescription + errorShow = true } } - if !appFound { + guard let appFound else { errorInfo = "lc.appList.appNotFoundError".loc errorShow = true + return + } + + do { + try await appFound.runApp() + } catch { + errorInfo = error.localizedDescription + errorShow = true } + } func authenticateUser() async { diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift new file mode 100644 index 0000000..f24f815 --- /dev/null +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -0,0 +1,136 @@ +import Foundation + +protocol LCAppModelDelegate { + func closeNavigationView() + func changeAppVisibility(app : LCAppModel) +} + +class LCAppModel: ObservableObject, Hashable { + + @Published var appInfo : LCAppInfo + + @Published var isAppRunning = false + @Published var isSigningInProgress = false + @Published var signProgress = 0.0 + private var observer : NSKeyValueObservation? + + @Published var uiIsJITNeeded : Bool + @Published var uiIsHidden : Bool + @Published var uiIsShared : Bool + @Published var uiDataFolder : String? + @Published var uiTweakFolder : String? + @Published var uiDoSymlinkInbox : Bool + @Published var uiBypassAssertBarrierOnQueue : Bool + + var jitAlert : YesNoHelper? = nil + + var delegate : LCAppModelDelegate? + + init(appInfo : LCAppInfo, delegate: LCAppModelDelegate? = nil) { + self.appInfo = appInfo + self.delegate = delegate + + self.uiIsJITNeeded = appInfo.isJITNeeded + self.uiIsHidden = appInfo.isHidden + self.uiIsShared = appInfo.isShared + self.uiDataFolder = appInfo.getDataUUIDNoAssign() + self.uiTweakFolder = appInfo.tweakFolder() + self.uiDoSymlinkInbox = appInfo.doSymlinkInbox + self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue + } + + static func == (lhs: LCAppModel, rhs: LCAppModel) -> Bool { + return lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + + func runApp() async throws{ + if isAppRunning { + return + } + + if let runningLC = LCUtils.getAppRunningLCScheme(bundleId: self.appInfo.relativeBundlePath) { + let openURL = URL(string: "\(runningLC)://livecontainer-launch?bundle-name=\(self.appInfo.relativeBundlePath!)")! + if await UIApplication.shared.canOpenURL(openURL) { + await UIApplication.shared.open(openURL) + return + } + } + isAppRunning = true + defer { + isAppRunning = false + } + try await signApp(force: false) + + UserDefaults.standard.set(self.appInfo.relativeBundlePath, forKey: "selected") + if appInfo.isJITNeeded { + await self.jitLaunch() + } else { + LCUtils.launchToGuestApp() + } + + isAppRunning = false + + } + + func forceResign() async throws { + if isAppRunning { + return + } + isAppRunning = true + defer { + isAppRunning = false + } + try await signApp(force: true) + } + + func signApp(force: Bool = false) async throws { + var signError : String? = nil + await withCheckedContinuation({ c in + appInfo.patchExecAndSignIfNeed(completionHandler: { error in + signError = error; + c.resume() + }, progressHandler: { signProgress in + guard let signProgress else { + return + } + self.isSigningInProgress = true + self.observer = signProgress.observe(\.fractionCompleted) { p, v in + DispatchQueue.main.async { + self.signProgress = signProgress.fractionCompleted + } + } + }, forceSign: force) + }) + self.isSigningInProgress = false + if let signError { + throw signError + } + } + + func jitLaunch() async { + LCUtils.askForJIT() + + guard let result = await jitAlert?.open(), result else { + UserDefaults.standard.removeObject(forKey: "selected") + return + } + LCUtils.launchToGuestApp() + + } + + func toggleHidden() async { + delegate?.closeNavigationView() + if appInfo.isHidden { + appInfo.isHidden = false + uiIsHidden = false + } else { + appInfo.isHidden = true + uiIsHidden = true + } + delegate?.changeAppVisibility(app: self) + } +} diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index a103d84..13d2f53 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -8,38 +8,6 @@ import Foundation import SwiftUI -protocol LCAppSettingDelegate { - func forceResign() async - func toggleHidden() async -} - -class LCAppModel: ObservableObject { - @Published var appInfo : LCAppInfo - - @Published var isAppRunning = false - - @Published var uiIsJITNeeded : Bool - @Published var uiIsHidden : Bool - @Published var uiIsShared : Bool - @Published var uiDataFolder : String? - @Published var uiTweakFolder : String? - @Published var uiDoSymlinkInbox : Bool - @Published var uiBypassAssertBarrierOnQueue : Bool - - init(appInfo : LCAppInfo) { - self.appInfo = appInfo - - self.uiIsJITNeeded = appInfo.isJITNeeded - self.uiIsHidden = appInfo.isHidden - self.uiIsShared = appInfo.isShared - self.uiDataFolder = appInfo.getDataUUIDNoAssign() - self.uiTweakFolder = appInfo.tweakFolder() - self.uiDoSymlinkInbox = appInfo.doSymlinkInbox - self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue - } -} - - struct LCAppSettingsView : View{ private var appInfo : LCAppInfo @@ -53,30 +21,20 @@ struct LCAppSettingsView : View{ @State private var uiPickerDataFolder : String? @State private var uiPickerTweakFolder : String? - @State private var renameFolderShow = false - @State private var renameFolderContent = "" - @State private var renameFolerContinuation : CheckedContinuation? = nil - - @State private var confirmMoveToAppGroupShow = false - @State private var confirmMoveToAppGroup = false - @State private var confirmMoveToAppGroupContinuation : CheckedContinuation? = nil - - @State private var confirmMoveToPrivateDocShow = false - @State private var confirmMoveToPrivateDoc = false - @State private var confirmMoveToPrivateDocContinuation : CheckedContinuation? = nil + @StateObject private var renameFolderInput = InputHelper() + @StateObject private var moveToAppGroupAlert = YesNoHelper() + @StateObject private var moveToPrivateDocAlert = YesNoHelper() @State private var errorShow = false @State private var errorInfo = "" - private let delegate : LCAppSettingDelegate @EnvironmentObject private var sharedModel : SharedModel - init(model: LCAppModel, appDataFolders: Binding<[String]>, tweakFolders: Binding<[String]>, delegate: LCAppSettingDelegate) { + init(model: LCAppModel, appDataFolders: Binding<[String]>, tweakFolders: Binding<[String]>) { self.appInfo = model.appInfo self._model = ObservedObject(wrappedValue: model) _appDataFolders = appDataFolders _tweakFolders = tweakFolders - self.delegate = delegate self._uiPickerDataFolder = State(initialValue: model.uiDataFolder) self._uiPickerTweakFolder = State(initialValue: model.uiTweakFolder) } @@ -256,43 +214,37 @@ struct LCAppSettingsView : View{ } .textFieldAlert( - isPresented: $renameFolderShow, + isPresented: $renameFolderInput.show, title: "lc.common.enterNewFolderName".loc, - text: $renameFolderContent, + text: $renameFolderInput.initVal, placeholder: "", action: { newText in - self.renameFolderContent = newText! - renameFolerContinuation?.resume() + renameFolderInput.close(result: newText!) }, actionCancel: {_ in - self.renameFolderContent = "" - renameFolerContinuation?.resume() + renameFolderInput.close(result: "") } ) - .alert("lc.appSettings.toSharedApp".loc, isPresented: $confirmMoveToAppGroupShow) { + .alert("lc.appSettings.toSharedApp".loc, isPresented: $moveToAppGroupAlert.show) { Button { - self.confirmMoveToAppGroup = true - self.confirmMoveToAppGroupContinuation?.resume() + self.moveToAppGroupAlert.close(result: true) } label: { Text("lc.common.move".loc) } Button("lc.common.cancel".loc, role: .cancel) { - self.confirmMoveToAppGroup = false - self.confirmMoveToAppGroupContinuation?.resume() + self.moveToAppGroupAlert.close(result: false) } } message: { Text("lc.appSettings.toSharedAppDesc".loc) } - .alert("lc.appSettings.toPrivateApp".loc, isPresented: $confirmMoveToPrivateDocShow) { + .alert("lc.appSettings.toPrivateApp".loc, isPresented: $moveToPrivateDocAlert.show) { Button { - self.confirmMoveToPrivateDoc = true - self.confirmMoveToPrivateDocContinuation?.resume() + self.moveToPrivateDocAlert.close(result: true) } label: { Text("lc.common.move".loc) } Button("lc.common.cancel".loc, role: .cancel) { - self.confirmMoveToPrivateDoc = false - self.confirmMoveToPrivateDocContinuation?.resume() + self.moveToPrivateDocAlert.close(result: false) } } message: { Text("lc.appSettings.toPrivateAppDesc".loc) @@ -306,19 +258,11 @@ struct LCAppSettingsView : View{ } func createFolder() async { - - self.renameFolderContent = NSUUID().uuidString - - await withCheckedContinuation { c in - self.renameFolerContinuation = c - self.renameFolderShow = true - } - - if self.renameFolderContent == "" { + guard let newName = await renameFolderInput.open(initVal: NSUUID().uuidString), newName != "" else { return } let fm = FileManager() - let dest = LCPath.dataPath.appendingPathComponent(self.renameFolderContent) + let dest = LCPath.dataPath.appendingPathComponent(newName) do { try fm.createDirectory(at: dest, withIntermediateDirectories: false) } catch { @@ -327,8 +271,8 @@ struct LCAppSettingsView : View{ return } - self.appDataFolders.append(self.renameFolderContent) - self.setDataFolder(folderName: self.renameFolderContent) + self.appDataFolders.append(newName) + self.setDataFolder(folderName: newName) } @@ -337,17 +281,13 @@ struct LCAppSettingsView : View{ return } - self.renameFolderContent = self.model.uiDataFolder == nil ? "" : self.model.uiDataFolder! - await withCheckedContinuation { c in - self.renameFolerContinuation = c - self.renameFolderShow = true - } - if self.renameFolderContent == "" { + let initVal = self.model.uiDataFolder == nil ? "" : self.model.uiDataFolder! + guard let newName = await renameFolderInput.open(initVal: initVal), newName != "" else { return } let fm = FileManager() let orig = LCPath.dataPath.appendingPathComponent(appInfo.getDataUUIDNoAssign()) - let dest = LCPath.dataPath.appendingPathComponent(self.renameFolderContent) + let dest = LCPath.dataPath.appendingPathComponent(newName) do { try fm.moveItem(at: orig, to: dest) } catch { @@ -361,8 +301,8 @@ struct LCAppSettingsView : View{ return } - self.appDataFolders[i] = self.renameFolderContent - self.setDataFolder(folderName: self.renameFolderContent) + self.appDataFolders[i] = newName + self.setDataFolder(folderName: newName) } @@ -373,11 +313,7 @@ struct LCAppSettingsView : View{ } func moveToAppGroup() async { - await withCheckedContinuation { c in - confirmMoveToAppGroupContinuation = c - confirmMoveToAppGroupShow = true - } - if !confirmMoveToAppGroup { + guard let result = await moveToAppGroupAlert.open(), result else { return } @@ -417,11 +353,7 @@ struct LCAppSettingsView : View{ return } - await withCheckedContinuation { c in - confirmMoveToPrivateDocContinuation = c - confirmMoveToPrivateDocShow = true - } - if !confirmMoveToPrivateDoc { + guard let result = await moveToPrivateDocAlert.open(), result else { return } @@ -467,10 +399,15 @@ struct LCAppSettingsView : View{ model.uiBypassAssertBarrierOnQueue = enabled } func toggleHidden() async { - await delegate.toggleHidden() + await model.toggleHidden() } func forceResign() async { - await delegate.forceResign() + do { + try await model.forceResign() + } catch { + errorInfo = error.localizedDescription + errorShow = true + } } } diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index 94a8feb..29c96e8 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -14,18 +14,14 @@ struct LCSettingsView: View { @State var successShow = false @State var successInfo = "" - @Binding var apps: [LCAppInfo] - @Binding var hiddenApps: [LCAppInfo] + @Binding var apps: [LCAppModel] + @Binding var hiddenApps: [LCAppModel] @Binding var appDataFolderNames: [String] - @State private var confirmAppFolderRemovalShow = false - @State private var confirmAppFolderRemoval = false - @State private var appFolderRemovalContinuation : CheckedContinuation? = nil + @StateObject private var appFolderRemovalAlert = YesNoHelper() @State private var folderRemoveCount = 0 - @State private var confirmKeyChainRemovalShow = false - @State private var confirmKeyChainRemoval = false - @State private var confirmKeyChainContinuation : CheckedContinuation? = nil + @StateObject private var keyChainRemovalAlert = YesNoHelper() @State var isJitLessEnabled = false @@ -40,7 +36,7 @@ struct LCSettingsView: View { @EnvironmentObject private var sharedModel : SharedModel - init(apps: Binding<[LCAppInfo]>, hiddenApps: Binding<[LCAppInfo]>, appDataFolderNames: Binding<[String]>) { + init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>) { _isJitLessEnabled = State(initialValue: LCUtils.certificatePassword() != nil) _isAltCertIgnored = State(initialValue: UserDefaults.standard.bool(forKey: "LCIgnoreALTCertificate")) _frameShortIcon = State(initialValue: UserDefaults.standard.bool(forKey: "LCFrameShortcutIcons")) @@ -239,19 +235,17 @@ struct LCSettingsView: View { } message: { Text(successInfo) } - .alert("lc.settings.cleanDataFolder".loc, isPresented: $confirmAppFolderRemovalShow) { + .alert("lc.settings.cleanDataFolder".loc, isPresented: $appFolderRemovalAlert.show) { if folderRemoveCount > 0 { Button(role: .destructive) { - self.confirmAppFolderRemoval = true - self.appFolderRemovalContinuation?.resume() + appFolderRemovalAlert.close(result: true) } label: { Text("lc.common.delete".loc) } } Button("lc.common.cancel".loc, role: .cancel) { - self.confirmAppFolderRemoval = false - self.appFolderRemovalContinuation?.resume() + appFolderRemovalAlert.close(result: false) } } message: { if folderRemoveCount > 0 { @@ -261,17 +255,15 @@ struct LCSettingsView: View { } } - .alert("lc.settings.cleanKeychain".loc, isPresented: $confirmKeyChainRemovalShow) { + .alert("lc.settings.cleanKeychain".loc, isPresented: $keyChainRemovalAlert.show) { Button(role: .destructive) { - self.confirmKeyChainRemoval = true - self.confirmKeyChainContinuation?.resume() + keyChainRemovalAlert.close(result: true) } label: { Text("lc.common.delete".loc) } Button("lc.common.cancel".loc, role: .cancel) { - self.confirmKeyChainRemoval = false - self.confirmKeyChainContinuation?.resume() + keyChainRemovalAlert.close(result: false) } } message: { Text("lc.settings.cleanKeychainDesc".loc) @@ -350,15 +342,15 @@ struct LCSettingsView: View { func cleanUpUnusedFolders() async { - var folderNameToAppDict : [String:LCAppInfo] = [:] + var folderNameToAppDict : [String:LCAppModel] = [:] for app in apps { - guard let folderName = app.getDataUUIDNoAssign() else { + guard let folderName = app.appInfo.getDataUUIDNoAssign() else { continue } folderNameToAppDict[folderName] = app } for app in hiddenApps { - guard let folderName = app.getDataUUIDNoAssign() else { + guard let folderName = app.appInfo.getDataUUIDNoAssign() else { continue } folderNameToAppDict[folderName] = app @@ -371,13 +363,8 @@ struct LCSettingsView: View { } } folderRemoveCount = foldersToDelete.count - await withCheckedContinuation { c in - self.appFolderRemovalContinuation = c - DispatchQueue.main.async { - confirmAppFolderRemovalShow = true - } - } - if !confirmAppFolderRemoval { + + guard let result = await appFolderRemovalAlert.open(), result else { return } do { @@ -396,13 +383,7 @@ struct LCSettingsView: View { } func removeKeyChain() async { - await withCheckedContinuation { c in - self.confirmKeyChainContinuation = c - DispatchQueue.main.async { - confirmKeyChainRemovalShow = true - } - } - if !confirmKeyChainRemoval { + guard let result = await keyChainRemovalAlert.open(), result else { return } @@ -425,26 +406,26 @@ struct LCSettingsView: View { var appDataFoldersInUse : Set = Set(); var tweakFoldersInUse : Set = Set(); for app in apps { - if !app.isShared { + if !app.appInfo.isShared { continue } - if let folder = app.getDataUUIDNoAssign() { + if let folder = app.appInfo.getDataUUIDNoAssign() { appDataFoldersInUse.update(with: folder); } - if let folder = app.tweakFolder() { + if let folder = app.appInfo.tweakFolder() { tweakFoldersInUse.update(with: folder); } } for app in hiddenApps { - if !app.isShared { + if !app.appInfo.isShared { continue } - if let folder = app.getDataUUIDNoAssign() { + if let folder = app.appInfo.getDataUUIDNoAssign() { appDataFoldersInUse.update(with: folder); } - if let folder = app.tweakFolder() { + if let folder = app.appInfo.tweakFolder() { tweakFoldersInUse.update(with: folder); } diff --git a/LiveContainerSwiftUI/LCTabView.swift b/LiveContainerSwiftUI/LCTabView.swift index 274f578..ee95df5 100644 --- a/LiveContainerSwiftUI/LCTabView.swift +++ b/LiveContainerSwiftUI/LCTabView.swift @@ -9,8 +9,8 @@ import Foundation import SwiftUI struct LCTabView: View { - @State var apps: [LCAppInfo] - @State var hiddenApps: [LCAppInfo] + @State var apps: [LCAppModel] + @State var hiddenApps: [LCAppModel] @State var appDataFolderNames: [String] @State var tweakFolderNames: [String] @@ -22,8 +22,8 @@ struct LCTabView: View { var tempAppDataFolderNames : [String] = [] var tempTweakFolderNames : [String] = [] - var tempApps: [LCAppInfo] = [] - var tempHiddenApps: [LCAppInfo] = [] + var tempApps: [LCAppModel] = [] + var tempHiddenApps: [LCAppModel] = [] do { // load apps @@ -37,9 +37,9 @@ struct LCTabView: View { newApp.relativeBundlePath = appDir newApp.isShared = false if newApp.isHidden { - tempHiddenApps.append(newApp) + tempHiddenApps.append(LCAppModel(appInfo: newApp)) } else { - tempApps.append(newApp) + tempApps.append(LCAppModel(appInfo: newApp)) } } @@ -53,9 +53,9 @@ struct LCTabView: View { newApp.relativeBundlePath = appDir newApp.isShared = true if newApp.isHidden { - tempHiddenApps.append(newApp) + tempHiddenApps.append(LCAppModel(appInfo: newApp)) } else { - tempApps.append(newApp) + tempApps.append(LCAppModel(appInfo: newApp)) } } // load document folders diff --git a/LiveContainerSwiftUI/LCTweaksView.swift b/LiveContainerSwiftUI/LCTweaksView.swift index 8f16af6..4c79c81 100644 --- a/LiveContainerSwiftUI/LCTweaksView.swift +++ b/LiveContainerSwiftUI/LCTweaksView.swift @@ -25,13 +25,9 @@ struct LCTweakFolderView : View { @State private var errorShow = false @State private var errorInfo = "" - @State private var newFolderShow = false - @State private var newFolderContent = "" - @State private var newFolerContinuation : CheckedContinuation? = nil + @StateObject private var newFolderInput = InputHelper() - @State private var renameFileShow = false - @State private var renameFileContent = "" - @State private var renameFileContinuation : CheckedContinuation? = nil + @StateObject private var renameFileInput = InputHelper() @State private var choosingTweak = false @@ -169,31 +165,27 @@ struct LCTweakFolderView : View { Text(errorInfo) } .textFieldAlert( - isPresented: $newFolderShow, + isPresented: $newFolderInput.show, title: "lc.common.enterNewFolderName".loc, - text: $newFolderContent, + text: $newFolderInput.initVal, placeholder: "", action: { newText in - self.newFolderContent = newText! - newFolerContinuation?.resume() + newFolderInput.close(result: newText) }, actionCancel: {_ in - self.newFolderContent = "" - newFolerContinuation?.resume() + newFolderInput.close(result: "") } ) .textFieldAlert( - isPresented: $renameFileShow, + isPresented: $renameFileInput.show, title: "lc.common.enterNewName".loc, - text: $renameFileContent, + text: $renameFileInput.initVal, placeholder: "", action: { newText in - self.renameFileContent = newText! - renameFileContinuation?.resume() + renameFileInput.close(result: newText) }, actionCancel: {_ in - self.renameFileContent = "" - renameFileContinuation?.resume() + renameFileInput.close(result: "") } ) .fileImporter(isPresented: $choosingTweak, allowedContentTypes: [.dylib, .lcFramework, .deb], allowsMultipleSelection: true) { result in @@ -253,14 +245,7 @@ struct LCTweakFolderView : View { } func renameTweakItem(tweakItem: LCTweakItem) async { - self.renameFileContent = tweakItem.fileUrl.lastPathComponent - - await withCheckedContinuation { c in - self.renameFileContinuation = c - self.renameFileShow = true - } - - if self.renameFileContent == "" { + guard let newName = await renameFileInput.open(initVal: tweakItem.fileUrl.lastPathComponent), newName != "" else { return } @@ -270,7 +255,7 @@ struct LCTweakFolderView : View { guard let indexToRename = indexToRename else { return } - let newUrl = self.baseUrl.appendingPathComponent(self.renameFileContent) + let newUrl = self.baseUrl.appendingPathComponent(newName) let fm = FileManager() do { @@ -290,7 +275,7 @@ struct LCTweakFolderView : View { return } tweakFolders.remove(at: indexToRename2) - tweakFolders.insert(self.renameFileContent, at: indexToRename2) + tweakFolders.insert(newName, at: indexToRename2) } } @@ -341,18 +326,11 @@ struct LCTweakFolderView : View { } func createNewFolder() async { - self.newFolderContent = "" - - await withCheckedContinuation { c in - self.newFolerContinuation = c - self.newFolderShow = true - } - - if self.newFolderContent == "" { + guard let newName = await renameFileInput.open(), newName != "" else { return } let fm = FileManager() - let dest = baseUrl.appendingPathComponent(self.newFolderContent) + let dest = baseUrl.appendingPathComponent(newName) do { try fm.createDirectory(at: dest, withIntermediateDirectories: false) } catch { @@ -362,7 +340,7 @@ struct LCTweakFolderView : View { } tweakItems.append(LCTweakItem(fileUrl: dest, isFolder: true, isFramework: false, isTweak: false)) if isRoot { - tweakFolders.append(self.newFolderContent) + tweakFolders.append(newName) } } diff --git a/LiveContainerSwiftUI/LCWebView.swift b/LiveContainerSwiftUI/LCWebView.swift index ae89465..3c65239 100644 --- a/LiveContainerSwiftUI/LCWebView.swift +++ b/LiveContainerSwiftUI/LCWebView.swift @@ -17,21 +17,18 @@ struct LCWebView: View { @State private var uiLoadStatus = 0.0 @State private var pageTitle = "" - @Binding var apps : [LCAppInfo] - @Binding var hiddenApps : [LCAppInfo] + @Binding var apps : [LCAppModel] + @Binding var hiddenApps : [LCAppModel] - @State private var runAppAlertShow = false + @State private var runAppAlert = YesNoHelper() @State private var runAppAlertMsg = "" - @State private var doRunApp = false - @State private var renameFolderContent = "" - @State private var doRunAppContinuation : CheckedContinuation? = nil @State private var errorShow = false @State private var errorInfo = "" @EnvironmentObject private var sharedModel : SharedModel - init(url: Binding, apps: Binding<[LCAppInfo]>, hiddenApps: Binding<[LCAppInfo]>, isPresent: Binding) { + init(url: Binding, apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, isPresent: Binding) { self.webView = WebView() self._url = url self._apps = apps @@ -101,14 +98,12 @@ struct LCWebView: View { } } - .alert("lc.webView.runApp".loc, isPresented: $runAppAlertShow) { + .alert("lc.webView.runApp".loc, isPresented: $runAppAlert.show) { Button("lc.appBanner.run".loc, action: { - self.doRunApp = true - self.doRunAppContinuation?.resume() + runAppAlert.close(result: true) }) Button("lc.common.cancel".loc, role: .cancel, action: { - self.doRunApp = false - self.doRunAppContinuation?.resume() + runAppAlert.close(result: false) }) } message: { Text(runAppAlertMsg) @@ -148,7 +143,7 @@ struct LCWebView: View { } public func onURLSchemeDetected(url: URL) async { - var appToLaunch : LCAppInfo? = nil + var appToLaunch : LCAppModel? = nil var appListsToConsider = [apps] if sharedModel.isHiddenAppUnlocked || !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { appListsToConsider.append(hiddenApps) @@ -156,7 +151,7 @@ struct LCWebView: View { appLoop: for appList in appListsToConsider { for app in appList { - if let schemes = app.urlSchemes() { + if let schemes = app.appInfo.urlSchemes() { for scheme in schemes { if let scheme = scheme as? String, scheme == url.scheme { appToLaunch = app @@ -174,7 +169,7 @@ struct LCWebView: View { return } - if appToLaunch.isHidden && !sharedModel.isHiddenAppUnlocked { + if appToLaunch.appInfo.isHidden && !sharedModel.isHiddenAppUnlocked { do { if !(try await LCUtils.authenticateUser()) { @@ -187,33 +182,28 @@ struct LCWebView: View { } } - runAppAlertMsg = "lc.webView.pageLaunch %@".localizeWithFormat(appToLaunch.displayName()!) + runAppAlertMsg = "lc.webView.pageLaunch %@".localizeWithFormat(appToLaunch.appInfo.displayName()!) - await withCheckedContinuation { c in - self.doRunAppContinuation = c - runAppAlertShow = true - } - - if !doRunApp { + if let doRunApp = await runAppAlert.open(), !doRunApp { return } - launchToApp(bundleId: appToLaunch.relativeBundlePath!, url: url) + launchToApp(bundleId: appToLaunch.appInfo.relativeBundlePath!, url: url) } public func onUniversalLinkDetected(url: URL, bundleIDs: [String]) async { - var bundleIDToAppDict: [String: LCAppInfo] = [:] + var bundleIDToAppDict: [String: LCAppModel] = [:] for app in apps { - bundleIDToAppDict[app.bundleIdentifier()!] = app + bundleIDToAppDict[app.appInfo.bundleIdentifier()!] = app } if !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") || sharedModel.isHiddenAppUnlocked { for app in hiddenApps { - bundleIDToAppDict[app.bundleIdentifier()!] = app + bundleIDToAppDict[app.appInfo.bundleIdentifier()!] = app } } - var appToLaunch: LCAppInfo? = nil + var appToLaunch: LCAppModel? = nil for bundleID in bundleIDs { if let app = bundleIDToAppDict[bundleID] { appToLaunch = app @@ -224,7 +214,7 @@ struct LCWebView: View { return } - if appToLaunch.isHidden && !sharedModel.isHiddenAppUnlocked { + if appToLaunch.appInfo.isHidden && !sharedModel.isHiddenAppUnlocked { do { if !(try await LCUtils.authenticateUser()) { return @@ -236,16 +226,11 @@ struct LCWebView: View { } } - runAppAlertMsg = "lc.webView.pageCanBeOpenIn %@".localizeWithFormat(appToLaunch.displayName()!) - runAppAlertShow = true - await withCheckedContinuation { c in - self.doRunAppContinuation = c - runAppAlertShow = true - } - if !doRunApp { + runAppAlertMsg = "lc.webView.pageCanBeOpenIn %@".localizeWithFormat(appToLaunch.appInfo.displayName()!) + if let doRunApp = await runAppAlert.open(), !doRunApp { return } - launchToApp(bundleId: appToLaunch.relativeBundlePath!, url: url) + launchToApp(bundleId: appToLaunch.appInfo.relativeBundlePath!, url: url) } } diff --git a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj index c05bbad..55c7be9 100644 --- a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj +++ b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 173564D32C76FE3500C6C918 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 173564C82C76FE3500C6C918 /* Assets.xcassets */; }; 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173F183F2C7B7B74002953AA /* LCWebView.swift */; }; 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178B4C3D2C77654400DD1F74 /* Shared.swift */; }; + 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */; }; 17C536F42C98529D006C2C75 /* LCAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */; }; /* End PBXBuildFile section */ @@ -37,6 +38,7 @@ 173F183F2C7B7B74002953AA /* LCWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCWebView.swift; sourceTree = ""; }; 178B4C3D2C77654400DD1F74 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; 178B4C3F2C7766A300DD1F74 /* LiveContainerSwiftUI-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LiveContainerSwiftUI-Bridging-Header.h"; sourceTree = ""; }; + 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppModel.swift; sourceTree = ""; }; 17B9B88D2C760678009D079E /* LiveContainerSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveContainerSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppSettingsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -70,6 +72,7 @@ 173564C22C76FE3500C6C918 /* LCSwiftBridge.h */, 173564C42C76FE3500C6C918 /* LCSwiftBridge.m */, 170C3DF82C99A489007F86FB /* Localizable.xcstrings */, + 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */, ); name = LiveContainerSwiftUI; sourceTree = ""; @@ -166,6 +169,7 @@ 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */, 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */, 173564CE2C76FE3500C6C918 /* Makefile in Sources */, + 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */, 17C536F42C98529D006C2C75 /* LCAppSettingsView.swift in Sources */, 173564CF2C76FE3500C6C918 /* LCSwiftBridge.m in Sources */, 173564C92C76FE3500C6C918 /* LCAppListView.swift in Sources */, diff --git a/LiveContainerSwiftUI/ObjcBridge.swift b/LiveContainerSwiftUI/ObjcBridge.swift index dee7dd0..5a76e13 100644 --- a/LiveContainerSwiftUI/ObjcBridge.swift +++ b/LiveContainerSwiftUI/ObjcBridge.swift @@ -10,8 +10,29 @@ import SwiftUI @objc public class LCObjcBridge: NSObject { - public static var urlStrToOpen: String? = nil - public static var openUrlStrFunc: ((String) async -> Void)? + private static var urlStrToOpen: String? = nil + private static var openUrlStrFunc: ((String) async -> Void)? + private static var bundleToLaunch: String? = nil + private static var launchAppFunc: ((String) async -> Void)? + + public static func setOpenUrlStrFunc(handler: @escaping ((String) async -> Void)){ + self.openUrlStrFunc = handler + if let urlStrToOpen = self.urlStrToOpen { + Task { await handler(urlStrToOpen) } + self.urlStrToOpen = nil + } else if let urlStr = UserDefaults.standard.string(forKey: "webPageToOpen") { + UserDefaults.standard.removeObject(forKey: "webPageToOpen") + Task { await handler(urlStr) } + } + } + + public static func setLaunchAppFunc(handler: @escaping ((String) async -> Void)){ + self.launchAppFunc = handler + if let bundleToLaunch = self.bundleToLaunch { + Task { await handler(bundleToLaunch) } + self.bundleToLaunch = nil + } + } @objc public static func openWebPage(urlStr: String) { if openUrlStrFunc == nil { @@ -22,7 +43,11 @@ import SwiftUI } @objc public static func launchApp(bundleId: String) { - DataManager.shared.model.bundleIdToLaunch = bundleId + if launchAppFunc == nil { + bundleToLaunch = bundleId + } else { + Task { await launchAppFunc!(bundleId) } + } } @objc public static func getRootVC() -> UIViewController { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 1575202..80e109d 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -52,7 +52,6 @@ struct LCPath { } class SharedModel: ObservableObject { - @Published var bundleIdToLaunch: String = "" @Published var isHiddenAppUnlocked = false } @@ -61,6 +60,42 @@ class DataManager { let model = SharedModel() } +class AlertHelper : ObservableObject { + @Published var show = false + private var result : T? + private var c : CheckedContinuation? = nil + + func open() async -> T? { + await withCheckedContinuation { c in + self.c = c + DispatchQueue.main.async { + self.show = true + } + } + return self.result + } + + func close(result: T?) { + self.result = result + c?.resume() + } +} + +typealias YesNoHelper = AlertHelper + +class InputHelper : AlertHelper { + @Published var initVal = "" + + func open(initVal: String) async -> String? { + self.initVal = initVal + return await super.open() + } + + override func open() async -> String? { + self.initVal = "" + return await super.open() + } +} extension String: LocalizedError { public var errorDescription: String? { return self } diff --git a/LiveContainerUI/LCUtils.m b/LiveContainerUI/LCUtils.m index e52dce0..bcb13f8 100644 --- a/LiveContainerUI/LCUtils.m +++ b/LiveContainerUI/LCUtils.m @@ -56,11 +56,11 @@ + (NSData *)certificateDataFile { } + (NSData *)certificateDataProperty { - NSData* ans = [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateData"]; + NSData* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificateData"]; if(ans) { return ans; } else { - return [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificateData"]; + return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateData"]; } } @@ -72,11 +72,11 @@ + (NSData *)certificateData { + (NSString *)certificatePassword { if (self.certificateDataFile) { - NSString* ans = [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificatePassword"]; + NSString* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; if(ans) { return ans; } - return [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; + return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificatePassword"]; } else if (self.certificateDataProperty) { return @""; } else {