From 83bd55bb64f122fb026ab580dd5869ecab68a8b2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 23 Sep 2026 16:25:21 +0000 Subject: [PATCH 1/3] chore: update AltStore source to v5.0.0 --- apps.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index b89446ed..a5fae4a4 100644 --- a/apps.json +++ b/apps.json @@ -7,12 +7,12 @@ "name": "SpotiFLAC Mobile", "bundleIdentifier": "com.zarz.spotiflac", "developerName": "zarzet", - "version": "4.9.6", - "versionDate": "2026-09-07", - "downloadURL": "https://github.com/spotiflacapp/SpotiFLAC-Mobile/releases/download/v4.9.6/SpotiFLAC-v4.9.6-ios-unsigned.ipa", + "version": "5.0.0", + "versionDate": "2026-09-23", + "downloadURL": "https://github.com/spotiflacapp/SpotiFLAC-Mobile/releases/download/v5.0.0/SpotiFLAC-v5.0.0-ios-unsigned.ipa", "localizedDescription": "SpotiFLAC Mobile is written in Flutter and uses installable extensions for metadata and audio downloads.", "iconURL": "https://raw.githubusercontent.com/spotiflacapp/SpotiFLAC-Mobile/main/assets/images/logo.png", - "size": 34171700 + "size": 34325236 } ] } From d957dde639b9530b0be57cab17b3df93328de9a5 Mon Sep 17 00:00:00 2001 From: Bartolo <162994065+BartolomeoRusso9@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:12:50 +0200 Subject: [PATCH 2/3] fix(ios): adopt the UIScene lifecycle required on iOS 27 On iOS 27 an app without a scene manifest launches to a blank launch screen (and trips UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption under the debugger). Add the manifest with a FlutterSceneDelegate subclass. Under scenes the app delegate has no window at launch, so the backend method/event channels and plugins are now registered in didInitializeImplicitFlutterEngine. SceneDelegate forwards what UIKit now sends to the scene instead: extension OAuth/session-grant redirect URLs and the background/foreground transitions that keep downloads alive. The folder picker and ASWebAuthenticationSession anchor use the active scene's window. --- ios/Runner/AppDelegate.swift | 95 ++++++++++++++++++++++++++++++------ ios/Runner/Info.plist | 21 ++++++++ 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 1c4dc381..2edd11c3 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -4,7 +4,7 @@ import UIKit import UniformTypeIdentifiers @main -@objc class AppDelegate: FlutterAppDelegate { +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { private let CHANNEL = "com.zarz.spotiflac/backend" private let DOWNLOAD_PROGRESS_STREAM_CHANNEL = "com.zarz.spotiflac/download_progress_stream" private let LIBRARY_SCAN_PROGRESS_STREAM_CHANNEL = "com.zarz.spotiflac/library_scan_progress_stream" @@ -43,11 +43,21 @@ import UniformTypeIdentifiers _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + // With the UIScene lifecycle (required from iOS 27) there is no root + // view controller yet: channels and plugins are registered once the + // storyboard's implicit engine exists, in + // didInitializeImplicitFlutterEngine below. + if let url = launchOptions?[.url] as? URL { + _ = handleExtensionOAuthRedirect(url: url) + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } - let controller = window?.rootViewController as! FlutterViewController + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + let messenger = engineBridge.applicationRegistrar.messenger() let channel = FlutterMethodChannel( name: CHANNEL, - binaryMessenger: controller.binaryMessenger + binaryMessenger: messenger ) backendChannel = channel if !pendingSessionGrantEvents.isEmpty { @@ -59,11 +69,11 @@ import UniformTypeIdentifiers } let downloadProgressEvents = FlutterEventChannel( name: DOWNLOAD_PROGRESS_STREAM_CHANNEL, - binaryMessenger: controller.binaryMessenger + binaryMessenger: messenger ) let libraryScanProgressEvents = FlutterEventChannel( name: LIBRARY_SCAN_PROGRESS_STREAM_CHANNEL, - binaryMessenger: controller.binaryMessenger + binaryMessenger: messenger ) channel.setMethodCallHandler { [weak self] call, result in @@ -93,19 +103,23 @@ import UniformTypeIdentifiers } ) ) + + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } - GeneratedPluginRegistrant.register(with: self) - if let url = launchOptions?[.url] as? URL { - _ = handleExtensionOAuthRedirect(url: url) - } - return super.application(application, didFinishLaunchingWithOptions: launchOptions) + /// The window of the foreground scene. Under the UIScene lifecycle the + /// app delegate's own `window` is never set. + var activeWindow: UIWindow? { + let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } + let active = scenes.first { $0.activationState == .foregroundActive } ?? scenes.first + return active?.windows.first { $0.isKeyWindow } ?? active?.windows.first ?? window } /// Extension return URLs: /// - OAuth: spotiflac://callback?code=...&state= /// - Signed session: spotiflac://session-grant?grant=...&state= @discardableResult - private func handleExtensionOAuthRedirect(url: URL) -> Bool { + func handleExtensionOAuthRedirect(url: URL) -> Bool { guard let route = ExtensionCallbackParser.parse(url) else { return false } streamQueue.async { var extensionId = "" @@ -336,13 +350,23 @@ import UniformTypeIdentifiers override func applicationDidEnterBackground(_ application: UIApplication) { super.applicationDidEnterBackground(application) + didEnterBackground() + } + + override func applicationWillEnterForeground(_ application: UIApplication) { + super.applicationWillEnterForeground(application) + willEnterForeground() + } + + /// Called by the application callbacks above and, under the UIScene + /// lifecycle (where UIKit no longer sends those), by SceneDelegate. + func didEnterBackground() { if downloadsActive { beginBackgroundDownloadTask() } } - override func applicationWillEnterForeground(_ application: UIApplication) { - super.applicationWillEnterForeground(application) + func willEnterForeground() { endBackgroundDownloadTask() } @@ -588,7 +612,7 @@ import UniformTypeIdentifiers )) return } - guard var topController = window?.rootViewController else { + guard var topController = activeWindow?.rootViewController else { result(FlutterError( code: "NO_VIEW_CONTROLLER", message: "No view controller available to present the folder picker", @@ -745,7 +769,7 @@ import UniformTypeIdentifiers @available(iOS 13.0, *) extension AppDelegate: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { - return window ?? ASPresentationAnchor() + return activeWindow ?? ASPresentationAnchor() } } @@ -816,3 +840,44 @@ private final class ClosureStreamHandler: NSObject, FlutterStreamHandler { onCancelHandler(arguments) } } + +/// UIScene lifecycle entry point (required from iOS 27). FlutterSceneDelegate +/// hosts the storyboard's FlutterViewController; this subclass forwards what +/// UIKit now delivers to the scene instead of the app delegate: OAuth +/// redirect URLs and the background/foreground transitions that keep +/// downloads alive. +class SceneDelegate: FlutterSceneDelegate { + private var appDelegate: AppDelegate? { + UIApplication.shared.delegate as? AppDelegate + } + + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + for context in connectionOptions.urlContexts { + _ = appDelegate?.handleExtensionOAuthRedirect(url: context.url) + } + } + + override func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + let unhandled = URLContexts.filter { + appDelegate?.handleExtensionOAuthRedirect(url: $0.url) != true + } + if !unhandled.isEmpty { + super.scene(scene, openURLContexts: unhandled) + } + } + + override func sceneDidEnterBackground(_ scene: UIScene) { + super.sceneDidEnterBackground(scene) + appDelegate?.didEnterBackground() + } + + override func sceneWillEnterForeground(_ scene: UIScene) { + super.sceneWillEnterForeground(scene) + appDelegate?.willEnterForeground() + } +} diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 5e04753a..4c1dc88f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,6 +45,27 @@ LaunchScreen UIMainStoryboardFile Main + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait From fb8a9d98eac552f00c54fb7431cc6a020d27665e Mon Sep 17 00:00:00 2001 From: Bartolo <162994065+BartolomeoRusso9@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:13:07 +0200 Subject: [PATCH 3/3] Update Podfile.lock --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7e853beb..c5d12707 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -19,7 +19,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: 71a624a5bc0c04062bf19101d501e466baf2fb47 - open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 + open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1 SpotiFLACBackend: e0c81656e8e4c31e86b9f14bbb8e245fb3a14011 PODFILE CHECKSUM: 6ca58d78915f3e4fcb64938b9167b26e33d44a8a