Skip to content

Commit 7b23e9e

Browse files
committed
new file picker & notification fix, impelement LiveContainer#234
1 parent 1b7cc26 commit 7b23e9e

File tree

11 files changed

+180
-41
lines changed

11 files changed

+180
-41
lines changed

LiveContainerSwiftUI/LCAppInfo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
@property bool bypassAssertBarrierOnQueue;
1616
@property UIColor* cachedColor;
1717
@property Signer signer;
18+
@property bool doUseLCBundleId;
1819
@property NSString* selectedLanguage;
1920

2021
- (void)setBundlePath:(NSString*)newBundlePath;

LiveContainerSwiftUI/LCAppInfo.m

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ - (NSString*)version {
6565
}
6666

6767
- (NSString*)bundleIdentifier {
68-
return _info[@"CFBundleIdentifier"];
68+
if([self doUseLCBundleId]) {
69+
return _info[@"LCOrignalBundleIdentifier"];
70+
} else {
71+
return _info[@"CFBundleIdentifier"];
72+
}
6973
}
7074

7175
- (NSString*)dataUUID {
@@ -380,6 +384,25 @@ - (void)setDoSymlinkInbox:(bool)doSymlinkInbox {
380384

381385
}
382386

387+
- (bool)doUseLCBundleId {
388+
if(_info[@"doUseLCBundleId"] != nil) {
389+
return [_info[@"doUseLCBundleId"] boolValue];
390+
} else {
391+
return NO;
392+
}
393+
}
394+
- (void)setDoUseLCBundleId:(bool)doUseLCBundleId {
395+
_info[@"doUseLCBundleId"] = [NSNumber numberWithBool:doUseLCBundleId];
396+
if(doUseLCBundleId) {
397+
_info[@"LCOrignalBundleIdentifier"] = _info[@"CFBundleIdentifier"];
398+
_info[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier;
399+
} else if (_info[@"LCOrignalBundleIdentifier"]) {
400+
_info[@"CFBundleIdentifier"] = _info[@"LCOrignalBundleIdentifier"];
401+
[_info removeObjectForKey:@"LCOrignalBundleIdentifier"];
402+
}
403+
[self save];
404+
}
405+
383406
- (bool)bypassAssertBarrierOnQueue {
384407
if(_info[@"bypassAssertBarrierOnQueue"] != nil) {
385408
return [_info[@"bypassAssertBarrierOnQueue"] boolValue];

LiveContainerSwiftUI/LCAppListView.swift

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate {
168168
if sharedModel.multiLCStatus != 2 {
169169
if !installprogressVisible {
170170
Button("Add".loc, systemImage: "plus", action: {
171-
if choosingIPA {
172-
choosingIPA = false
173-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
174-
choosingIPA = true
175-
})
176-
} else {
177-
choosingIPA = true
178-
}
179-
180-
171+
choosingIPA = true
181172
})
182173
} else {
183174
ProgressView().progressViewStyle(.circular)
@@ -203,9 +194,11 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate {
203194
} message: {
204195
Text(errorInfo)
205196
}
206-
.fileImporter(isPresented: $choosingIPA, allowedContentTypes: [.ipa]) { result in
207-
Task { await startInstallApp(result) }
208-
}
197+
.betterFileImporter(isPresented: $choosingIPA, types: [.ipa], multiple: false, callback: { fileUrls in
198+
Task { await startInstallApp(fileUrls[0]) }
199+
}, onDismiss: {
200+
choosingIPA = false
201+
})
209202
.alert("lc.appList.installation".loc, isPresented: $installReplaceAlert.show) {
210203
ForEach(installOptions, id: \.self) { installOption in
211204
Button(role: installOption.isReplace ? .destructive : nil, action: {
@@ -332,9 +325,8 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate {
332325

333326

334327

335-
func startInstallApp(_ result:Result<URL, any Error>) async {
328+
func startInstallApp(_ fileUrl:URL) async {
336329
do {
337-
let fileUrl = try result.get()
338330
self.installprogressVisible = true
339331
try await installIpaFile(fileUrl)
340332
} catch {
@@ -349,9 +341,6 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate {
349341
}
350342

351343
func installIpaFile(_ url:URL) async throws {
352-
if(!url.startAccessingSecurityScopedResource()) {
353-
throw "lc.appList.ipaAccessError".loc;
354-
}
355344
let fm = FileManager()
356345

357346
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
@@ -370,7 +359,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate {
370359

371360
// decompress
372361
await decompress(url.path, fm.temporaryDirectory.path, decompressProgress)
373-
url.stopAccessingSecurityScopedResource()
362+
try fm.removeItem(at: url)
374363

375364
let payloadContents = try fm.contentsOfDirectory(atPath: payloadPath.path)
376365
var appBundleName : String? = nil

LiveContainerSwiftUI/LCAppModel.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class LCAppModel: ObservableObject, Hashable {
2121
@Published var uiDataFolder : String?
2222
@Published var uiTweakFolder : String?
2323
@Published var uiDoSymlinkInbox : Bool
24+
@Published var uiUseLCBundleId : Bool
2425
@Published var uiBypassAssertBarrierOnQueue : Bool
2526
@Published var uiSigner : Signer
2627
@Published var uiSelectedLanguage : String
@@ -48,6 +49,7 @@ class LCAppModel: ObservableObject, Hashable {
4849
self.uiDoSymlinkInbox = appInfo.doSymlinkInbox
4950
self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue
5051
self.uiSigner = appInfo.signer
52+
self.uiUseLCBundleId = appInfo.doUseLCBundleId
5153
}
5254

5355
static func == (lhs: LCAppModel, rhs: LCAppModel) -> Bool {

LiveContainerSwiftUI/LCAppSettingsView.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,26 @@ struct LCAppSettingsView : View{
240240

241241

242242

243+
Section {
244+
Toggle(isOn: $model.uiUseLCBundleId) {
245+
Text("lc.appSettings.useLCBundleId".loc)
246+
}
247+
.onChange(of: model.uiUseLCBundleId, perform: { newValue in
248+
Task { await setDoUseLCBundleId(newValue) }
249+
})
250+
} header: {
251+
Text("lc.appSettings.fixes".loc)
252+
} footer: {
253+
Text("lc.appSettings.useLCBundleIdDesc".loc)
254+
}
255+
243256
Section {
244257
Toggle(isOn: $model.uiDoSymlinkInbox) {
245258
Text("lc.appSettings.fixFilePicker".loc)
246259
}
247260
.onChange(of: model.uiDoSymlinkInbox, perform: { newValue in
248261
Task { await setSimlinkInbox(newValue) }
249262
})
250-
} header: {
251-
Text("lc.appSettings.fixes".loc)
252263
} footer: {
253264
Text("lc.appSettings.fixFilePickerDesc".loc)
254265
}
@@ -473,7 +484,11 @@ struct LCAppSettingsView : View{
473484
func setSimlinkInbox(_ simlinkInbox : Bool) async {
474485
appInfo.doSymlinkInbox = simlinkInbox
475486
model.uiDoSymlinkInbox = simlinkInbox
476-
487+
}
488+
489+
func setDoUseLCBundleId(_ doUseLCBundleId : Bool) async {
490+
appInfo.doUseLCBundleId = doUseLCBundleId
491+
model.uiUseLCBundleId = doUseLCBundleId
477492
}
478493

479494
func loadSupportedLanguages() {

LiveContainerSwiftUI/LCSettingsView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ struct LCSettingsView: View {
102102

103103
if isAltStorePatched {
104104
Button {
105-
testJITLessMode()
105+
testJITLessMode()
106106
} label: {
107107
Text("lc.settings.testJitLess".loc)
108108
}

LiveContainerSwiftUI/LCTweaksView.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,11 @@ struct LCTweakFolderView : View {
191191
renameFileInput.close(result: "")
192192
}
193193
)
194-
.fileImporter(isPresented: $choosingTweak, allowedContentTypes: [.dylib, .lcFramework, /*.deb*/], allowsMultipleSelection: true) { result in
195-
Task { await startInstallTweak(result) }
196-
}
194+
.betterFileImporter(isPresented: $choosingTweak, types: [.dylib, .lcFramework, /*.deb*/], multiple: true, callback: { fileUrls in
195+
Task { await startInstallTweak(fileUrls) }
196+
}, onDismiss: {
197+
choosingTweak = false
198+
})
197199
}
198200

199201
func deleteTweakItem(indexSet: IndexSet) {
@@ -319,27 +321,21 @@ struct LCTweakFolderView : View {
319321
}
320322
}
321323

322-
func startInstallTweak(_ result: Result<[URL], any Error>) async {
324+
func startInstallTweak(_ urls: [URL]) async {
323325
do {
324326
let fm = FileManager()
325-
let urls = try result.get()
326327
// we will sign later before app launch
327328

328329
for fileUrl in urls {
329330
// handle deb file
330-
if(!fileUrl.startAccessingSecurityScopedResource()) {
331-
throw "lc.tweakView.permissionDenied %@".localizeWithFormat(fileUrl.lastPathComponent)
332-
}
333331
if(!fileUrl.isFileURL) {
334332
throw "lc.tweakView.notFileError %@".localizeWithFormat(fileUrl.lastPathComponent)
335333
}
336334
let toPath = self.baseUrl.appendingPathComponent(fileUrl.lastPathComponent)
337-
try fm.copyItem(at: fileUrl, to: toPath)
335+
try fm.moveItem(at: fileUrl, to: toPath)
338336
LCParseMachO((toPath.path as NSString).utf8String) { path, header in
339337
LCPatchAddRPath(path, header);
340338
}
341-
fileUrl.stopAccessingSecurityScopedResource()
342-
343339

344340
let isFramework = toPath.lastPathComponent.hasSuffix(".framework")
345341
let isTweak = toPath.lastPathComponent.hasSuffix(".dylib")

LiveContainerSwiftUI/Localizable.xcstrings

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,13 +745,13 @@
745745
"en" : {
746746
"stringUnit" : {
747747
"state" : "translated",
748-
"value" : "Fix File Picker"
748+
"value" : "(Legacy) Fix File Picker"
749749
}
750750
},
751751
"zh_CN" : {
752752
"stringUnit" : {
753753
"state" : "translated",
754-
"value" : "修复文件导入"
754+
"value" : "(旧方法)修复文件导入"
755755
}
756756
}
757757
}
@@ -1062,6 +1062,40 @@
10621062
}
10631063
}
10641064
},
1065+
"lc.appSettings.useLCBundleId" : {
1066+
"extractionState" : "manual",
1067+
"localizations" : {
1068+
"en" : {
1069+
"stringUnit" : {
1070+
"state" : "translated",
1071+
"value" : "Fix File Picker & Local Notification"
1072+
}
1073+
},
1074+
"zh_CN" : {
1075+
"stringUnit" : {
1076+
"state" : "translated",
1077+
"value" : "修复文件导入和本地通知"
1078+
}
1079+
}
1080+
}
1081+
},
1082+
"lc.appSettings.useLCBundleIdDesc" : {
1083+
"extractionState" : "manual",
1084+
"localizations" : {
1085+
"en" : {
1086+
"stringUnit" : {
1087+
"state" : "translated",
1088+
"value" : "Fixes these issues by using LiveContainer’s bundle identifier when overwriting main bundle."
1089+
}
1090+
},
1091+
"zh_CN" : {
1092+
"stringUnit" : {
1093+
"state" : "translated",
1094+
"value" : "通过使用LiveContainer的包名解决上述问题。"
1095+
}
1096+
}
1097+
}
1098+
},
10651099
"lc.common.auto" : {
10661100
"extractionState" : "manual",
10671101
"localizations" : {

LiveContainerSwiftUI/Shared.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ extension View {
176176
self.modifier(TextFieldAlertModifier(isPresented: isPresented, title: title, text: text, placeholder: placeholder, action: action, actionCancel: actionCancel))
177177
}
178178

179+
public func betterFileImporter(
180+
isPresented: Binding<Bool>,
181+
types : [UTType],
182+
multiple : Bool = false,
183+
callback: @escaping ([URL]) -> (),
184+
onDismiss: @escaping () -> Void
185+
) -> some View {
186+
self.modifier(DocModifier(isPresented: isPresented, types: types, multiple: multiple, callback: callback, onDismiss: onDismiss))
187+
}
188+
179189
func onBackground(_ f: @escaping () -> Void) -> some View {
180190
self.onReceive(
181191
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification),
@@ -192,6 +202,70 @@ extension View {
192202

193203
}
194204

205+
public struct DocModifier: ViewModifier {
206+
207+
@State private var docController: UIDocumentPickerViewController?
208+
@State private var delegate : UIDocumentPickerDelegate
209+
210+
@Binding var isPresented: Bool
211+
212+
var callback: ([URL]) -> ()
213+
private let onDismiss: () -> Void
214+
private let types : [UTType]
215+
private let multiple : Bool
216+
217+
init(isPresented : Binding<Bool>, types : [UTType], multiple : Bool, callback: @escaping ([URL]) -> (), onDismiss: @escaping () -> Void) {
218+
self.callback = callback
219+
self.onDismiss = onDismiss
220+
self.types = types
221+
self.multiple = multiple
222+
self.delegate = Coordinator(callback: callback, onDismiss: onDismiss)
223+
self._isPresented = isPresented
224+
}
225+
226+
public func body(content: Content) -> some View {
227+
content.onChange(of: isPresented) { isPresented in
228+
if isPresented, docController == nil {
229+
let controller = UIDocumentPickerViewController(forOpeningContentTypes: types, asCopy: true)
230+
controller.allowsMultipleSelection = multiple
231+
controller.delegate = delegate
232+
self.docController = controller
233+
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
234+
return
235+
}
236+
scene.windows.first?.rootViewController?.present(controller, animated: true)
237+
} else if !isPresented, let docController = docController {
238+
docController.dismiss(animated: true)
239+
self.docController = nil
240+
}
241+
}
242+
}
243+
244+
private func shutdown() {
245+
isPresented = false
246+
docController = nil
247+
}
248+
249+
class Coordinator: NSObject, UIDocumentPickerDelegate {
250+
var callback: ([URL]) -> ()
251+
private let onDismiss: () -> Void
252+
253+
init(callback: @escaping ([URL]) -> Void, onDismiss: @escaping () -> Void) {
254+
self.callback = callback
255+
self.onDismiss = onDismiss
256+
}
257+
258+
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
259+
callback(urls)
260+
onDismiss()
261+
}
262+
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
263+
onDismiss()
264+
}
265+
}
266+
267+
}
268+
195269
public struct TextFieldAlertModifier: ViewModifier {
196270

197271
@State private var alertController: UIAlertController?

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Without JIT, guest apps need to be codesigned, which requires retrieving the cer
2121
- Install your app via the "Apps" tab.
2222
- Tap the run icon, it will attempt to restart LiveContainer with guest app loaded.
2323

24-
Note: If you update or reinstall SideStore/AltStore, you'll need to reapply the patch.
24+
Note: If you update or reinstall SideStore/AltStore, you'll need to reapply the patch. Re-patch is not needed when you refresh your store.
2525

2626
### With JIT (requires SideStore)
2727
- Tap the play icon, it will jump to SideStore and exit.
@@ -41,8 +41,8 @@ The first LiveContainer (blue icon) always launches by default.
4141
If an app is already running in the first container, you'll be prompted to either open it in the second LiveContainer (gray icon) or terminate the current app and relaunch it in the first. If the app is already running in the second container, it will switch automatically.
4242
To use an app in the second container, you must convert this app to a shared app. You can do that by opening the first LiveContainer (blue), long press on your app, open the settings of your app and then "Convert to Shared App". After that, you can launch your app using LiveContainer2 (grey).
4343

44-
### Fix File Picker
45-
Some apps may experience issues with their file pickers in LiveContainer. To resolve this, enable "Fix File Picker" in the app-specific settings.
44+
### Fix File Picker & Local Notification
45+
Some apps may experience issues with their file pickers or not be able to apply for notification permission in LiveContainer. To resolve this, enable "Fix File Picker & Local Notification" in the app-specific settings.
4646

4747
### "Open In App" Support
4848
- Tap the link icon in the top-right corner of the "Apps" tab and input the URL. LiveContainer will detect the appropriate app and ask if you want to launch it.

0 commit comments

Comments
 (0)