Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/quick_actions/quick_actions_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.2.4

* Adds support for UIScene lifecycle.
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.

## 1.2.3

* Updates to Pigeon 26.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
super.application(application, didFinishLaunchingWithOptions: launchOptions)
// For UI integration tests. See https://github.com/flutter/plugins/pull/3811.
return false
}

func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,26 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,110 @@ struct QuickActionsPluginTests {
plugin.applicationDidBecomeActive(UIApplication.shared)
}
}

// MARK: - Scene lifecycle tests

@Test func windowScenePerformActionForShortcutItem() async {
let flutterApi: MockFlutterApi = MockFlutterApi()
let mockShortcutItemProvider = MockShortcutItemProvider()

let plugin = QuickActionsPlugin(
flutterApi: flutterApi,
shortcutItemProvider: mockShortcutItemProvider)

let item = UIApplicationShortcutItem(
type: "SearchTheThing",
localizedTitle: "Search the thing",
localizedSubtitle: nil,
icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
userInfo: nil)

await confirmation("shortcut should be handled via windowScene") { confirmed in
flutterApi.launchActionCallback = { aString in
#expect(aString == item.type)
confirmed()
}

let windowScene = UIApplication.shared.connectedScenes.first as! UIWindowScene
let actionResult = plugin.windowScene(
windowScene,
performActionFor: item
) { success in
}

#expect(actionResult, "windowScene performActionFor must return true.")
}
}

@Test func sceneWillConnectToWithoutShortcut() {
let flutterApi: MockFlutterApi = MockFlutterApi()
let mockShortcutItemProvider = MockShortcutItemProvider()

let plugin = QuickActionsPlugin(
flutterApi: flutterApi,
shortcutItemProvider: mockShortcutItemProvider)

let connectResult = plugin.scene(
UIApplication.shared.connectedScenes.first!,
willConnectTo: UIApplication.shared.connectedScenes.first!.session,
options: nil)
#expect(
!connectResult,
"scene willConnectTo must return false if not launched from shortcut.")
}

@Test func sceneDidBecomeActiveLaunchWithoutShortcut() async {
let flutterApi: MockFlutterApi = MockFlutterApi()
let mockShortcutItemProvider = MockShortcutItemProvider()

let plugin = QuickActionsPlugin(
flutterApi: flutterApi,
shortcutItemProvider: mockShortcutItemProvider)

let connectResult = plugin.scene(
UIApplication.shared.connectedScenes.first!,
willConnectTo: UIApplication.shared.connectedScenes.first!.session,
options: nil)
#expect(!connectResult)

await confirmation("launchAction should not be called", expectedCount: 0) { confirmed in
flutterApi.launchActionCallback = { _ in
confirmed()
}
plugin.sceneDidBecomeActive(UIApplication.shared.connectedScenes.first!)
}
}

@Test func sceneDidBecomeActiveLaunchWithShortcut() async {
let item = UIApplicationShortcutItem(
type: "SearchTheThing",
localizedTitle: "Search the thing",
localizedSubtitle: nil,
icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
userInfo: nil)

let flutterApi: MockFlutterApi = MockFlutterApi()
let mockShortcutItemProvider = MockShortcutItemProvider()

let plugin = QuickActionsPlugin(
flutterApi: flutterApi,
shortcutItemProvider: mockShortcutItemProvider)

await confirmation("shortcut should be handled when scene becomes active") { confirmed in
flutterApi.launchActionCallback = { aString in
#expect(aString == item.type)
confirmed()
}

// Simulate cold start: scene connects with a shortcut item via connectionOptions.
// We can't construct UIScene.ConnectionOptions directly, so we simulate the effect
// by calling the AppDelegate-style method that sets launchingShortcutType.
let launchResult = plugin.application(
UIApplication.shared,
didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
#expect(!launchResult)
Comment on lines +324 to +327
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use scene:willConnectTo:options:, not the application launch. When migrated to UIScene, the application:didFinishLaunchingWithOptions: is expected to always have nil options.


plugin.sceneDidBecomeActive(UIApplication.shared.connectedScenes.first!)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
// found in the LICENSE file.

import Flutter
import UIKit

public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsApi {
public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsApi,
FlutterSceneLifeCycleDelegate
{

public static func register(with registrar: FlutterPluginRegistrar) {
let messenger = registrar.messenger()
let flutterApi = IOSQuickActionsFlutterApi(binaryMessenger: messenger)
let instance = QuickActionsPlugin(flutterApi: flutterApi)
IOSQuickActionsApiSetup.setUp(binaryMessenger: messenger, api: instance)
registrar.addApplicationDelegate(instance)
registrar.addSceneDelegate(instance)
}

private let shortcutItemProvider: ShortcutItemProviding
Expand Down Expand Up @@ -72,6 +76,44 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsA
}
}

// MARK: - FlutterSceneLifeCycleDelegate

public func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions?
) -> Bool {
// Handle the case where app is launched via a shortcut item in scene-based lifecycle.
if let shortcutItem = connectionOptions?.shortcutItem {
// Keep hold of the shortcut type and handle it in the
// `sceneDidBecomeActive:` method once the Dart MethodChannel
// is initialized.
launchingShortcutType = shortcutItem.type
// Return true to indicate we handled the connection.
return true
}
return false
}

public func sceneDidBecomeActive(_ scene: UIScene) {
if let shortcutType = launchingShortcutType {
handleShortcut(shortcutType)
launchingShortcutType = nil
}
}

public func windowScene(
_ windowScene: UIWindowScene,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) -> Bool {
handleShortcut(shortcutItem.type)
completionHandler(true)
return true
}

// MARK: - Shortcut handling

func handleShortcut(_ shortcut: String) {
flutterApi.launchAction(action: shortcut) { _ in
// noop
Expand Down
6 changes: 3 additions & 3 deletions packages/quick_actions/quick_actions_ios/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: quick_actions_ios
description: An implementation for the iOS platform of the Flutter `quick_actions` plugin.
repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
version: 1.2.3
version: 1.2.4

environment:
sdk: ^3.9.0
flutter: ">=3.35.0"
sdk: ^3.10.0
flutter: ">=3.38.0"

flutter:
plugin:
Expand Down
Loading