From ff17b813ac6ba9a28e4b8eb417faee03f41218f1 Mon Sep 17 00:00:00 2001 From: misha Date: Fri, 16 Oct 2026 08:26:26 +0300 Subject: [PATCH] feat(ios): env migration for ios 27 --- ios/Runner.xcodeproj/project.pbxproj | 4 + ios/Runner/AppDelegate.swift | 118 +++++++++++++++++---------- ios/Runner/Info.plist | 21 +++++ ios/Runner/SceneDelegate.swift | 86 +++++++++++++++++++ pubspec.yaml | 2 +- 5 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 ios/Runner/SceneDelegate.swift diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9a9c45c0..15e4ffe4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 46B43B96F809CEC6058EFDD3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15ADDB2A35F334AF0AE50011 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + C4A8E2022E9A4B1000123456 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A8E2012E9A4B1000123456 /* SceneDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -37,6 +38,7 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + C4A8E2012E9A4B1000123456 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 938EE8FB5BAA359AAEDEFFF8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -115,6 +117,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + C4A8E2012E9A4B1000123456 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, FEE92A0E2D772EA300E6E989 /* Runner.entitlements */, ); @@ -300,6 +303,7 @@ buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + C4A8E2022E9A4B1000123456 /* SceneDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 93039753..5355c97b 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -44,55 +44,75 @@ import flutter_foreground_task // WebRTC must not activate the session itself. CallKit and the in-app // call both own AVAudioSession, and a second activation drops the mic. - RTCAudioSession.sharedInstance().useManualAudio = true + let rtcSession = RTCAudioSession.sharedInstance() + rtcSession.useManualAudio = true + rtcSession.isAudioEnabled = false - if let controller = window?.rootViewController as? FlutterViewController { - let channel = FlutterMethodChannel( - name: voipDiagnosticsChannelName, - binaryMessenger: controller.binaryMessenger - ) - voipDiagnosticsChannel = channel - channel.setMethodCallHandler { [weak self] call, result in - guard let self else { - result(nil) - return - } - switch call.method { - case "drainVoipDiagnostics": - result(self.drainVoipDiagnostics()) - case "drainPendingAcceptedVoipCall": - result(self.drainPendingAcceptedVoipCall()) - default: - result(FlutterMethodNotImplemented) - } + registerCallChannels(from: window?.rootViewController as? FlutterViewController) + + return launched + } + + func registerCallChannels(from controller: FlutterViewController?) { + guard voipDiagnosticsChannel == nil else { return } + let flutterController = controller ?? flutterViewControllerFromScenes() + guard let flutterController else { return } + + let channel = FlutterMethodChannel( + name: voipDiagnosticsChannelName, + binaryMessenger: flutterController.binaryMessenger + ) + voipDiagnosticsChannel = channel + channel.setMethodCallHandler { [weak self] call, result in + guard let self else { + result(nil) + return } - - let callAudioChannel = FlutterMethodChannel( - name: callAudioSessionChannelName, - binaryMessenger: controller.binaryMessenger - ) - callAudioChannel.setMethodCallHandler { [weak self] call, result in - guard let self else { - result(nil) - return - } - switch call.method { - case "activate": - self.activateInAppCallAudioSession() - result(nil) - case "restart": - self.restartInAppCallAudioUnit() - result(nil) - case "deactivate": - self.deactivateInAppCallAudioSession() - result(nil) - default: - result(FlutterMethodNotImplemented) - } + switch call.method { + case "drainVoipDiagnostics": + result(self.drainVoipDiagnostics()) + case "drainPendingAcceptedVoipCall": + result(self.drainPendingAcceptedVoipCall()) + default: + result(FlutterMethodNotImplemented) } } - return launched + let callAudioChannel = FlutterMethodChannel( + name: callAudioSessionChannelName, + binaryMessenger: flutterController.binaryMessenger + ) + callAudioChannel.setMethodCallHandler { [weak self] call, result in + guard let self else { + result(nil) + return + } + switch call.method { + case "activate": + self.activateInAppCallAudioSession() + result(nil) + case "restart": + self.restartInAppCallAudioUnit() + result(nil) + case "deactivate": + self.deactivateInAppCallAudioSession() + result(nil) + default: + result(FlutterMethodNotImplemented) + } + } + } + + private func flutterViewControllerFromScenes() -> FlutterViewController? { + for scene in UIApplication.shared.connectedScenes { + guard let windowScene = scene as? UIWindowScene else { continue } + for sceneWindow in windowScene.windows { + if let controller = sceneWindow.rootViewController as? FlutterViewController { + return controller + } + } + } + return nil } @available(iOS 8.0, *) @@ -116,6 +136,14 @@ import flutter_foreground_task open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { + handleOpenURL( + url, + sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String + ) + } + + @discardableResult + func handleOpenURL(_ url: URL, sourceApplication: String?) -> Bool { if url.scheme == appUrlScheme { let components = URLComponents(url: url, resolvingAgainstBaseURL: false) let queryItems = components?.queryItems ?? [] @@ -149,7 +177,7 @@ import flutter_foreground_task return YXLSdk.shared.handleOpen( url, - sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String + sourceApplication: sourceApplication ) } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 71e74b60..10a72b1c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,6 +41,27 @@ Может использоваться для стабильного поддержания связи во время звонка. NSPhotoLibraryUsageDescription Предоставление доступа позволит Вам добавлять фото к записям + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..f27a5751 --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,86 @@ +import Flutter +import UIKit +import YandexLoginSDK + +/// iOS 27 requires a UIScene lifecycle. Flutter 3.38 does not yet have +/// `FlutterImplicitEngineDelegate`, so channels and URL handling are wired +/// here after the scene creates the Flutter view. +class SceneDelegate: FlutterSceneDelegate { + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + registerCallChannels() + handleConnectionOptions(connectionOptions) + } + + override func sceneDidBecomeActive(_ scene: UIScene) { + super.sceneDidBecomeActive(scene) + registerCallChannels() + } + + override func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + var handledCustomURL = false + if let appDelegate = UIApplication.shared.delegate as? AppDelegate { + for context in URLContexts { + if appDelegate.handleOpenURL( + context.url, + sourceApplication: context.options.sourceApplication + ) { + handledCustomURL = true + } + } + } + if !handledCustomURL { + super.scene(scene, openURLContexts: URLContexts) + } + } + + override func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if YXLSdk.shared.processUserActivity(userActivity) { + return + } + super.scene(scene, continue: userActivity) + } + + private func registerCallChannels() { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + appDelegate.registerCallChannels(from: flutterViewController) + } + + private var flutterViewController: FlutterViewController? { + if let controller = window?.rootViewController as? FlutterViewController { + return controller + } + for scene in UIApplication.shared.connectedScenes { + guard let windowScene = scene as? UIWindowScene else { continue } + for sceneWindow in windowScene.windows { + if let controller = sceneWindow.rootViewController as? FlutterViewController { + return controller + } + } + } + return nil + } + + private func handleConnectionOptions(_ options: UIScene.ConnectionOptions) { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + for context in options.urlContexts { + _ = appDelegate.handleOpenURL( + context.url, + sourceApplication: context.options.sourceApplication + ) + } + for activity in options.userActivities { + if YXLSdk.shared.processUserActivity(activity) { + continue + } + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4b8ae04d..99132ffa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.3.52+141 +version: 1.3.53+142 publish_to: 'none' environment: