diff --git a/examples/catalog_gallery/.gitignore b/examples/catalog_gallery/.gitignore new file mode 100644 index 000000000..3820a95c6 --- /dev/null +++ b/examples/catalog_gallery/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/examples/catalog_gallery/.metadata b/examples/catalog_gallery/.metadata index 298107264..45dedd14f 100644 --- a/examples/catalog_gallery/.metadata +++ b/examples/catalog_gallery/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "b45fa18946ecc2d9b4009952c636ba7e2ffbb787" + revision: "90673a4eef275d1a6692c26ac80d6d746d41a73a" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 - base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 - - platform: macos - create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 - base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: ios + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a # User provided section diff --git a/examples/catalog_gallery/ios/Podfile.lock b/examples/catalog_gallery/ios/Podfile.lock new file mode 100644 index 000000000..473f8fb35 --- /dev/null +++ b/examples/catalog_gallery/ios/Podfile.lock @@ -0,0 +1,42 @@ +PODS: + - audioplayers_darwin (0.0.1): + - Flutter + - FlutterMacOS + - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`) + - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + +EXTERNAL SOURCES: + audioplayers_darwin: + :path: ".symlinks/plugins/audioplayers_darwin/darwin" + Flutter: + :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" + +SPEC CHECKSUMS: + audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj b/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj index f18019350..e7683a88d 100644 --- a/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj @@ -8,9 +8,12 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2A29654D940D21A30364E925 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7CECF6BD102067868D544CA /* Pods_RunnerTests.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 595DCE77AB86E242BD0C1C55 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30E4A80F5D94A1B617DDF1BC /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* 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 */; }; @@ -42,11 +45,16 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2DABEC56ABBFE6AE531647B3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 30E4A80F5D94A1B617DDF1BC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4A33480D83C28470082255E2 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 66A2CCD34B79417F9DE85218 /* 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 = ""; }; 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 = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -55,6 +63,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9EE70E42FE81EC8E5DBF9758 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + C7CECF6BD102067868D544CA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DAA6FE74A3EF743340BCAA64 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + E2DFF9D8D656CDA14EB8234D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,6 +74,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 595DCE77AB86E242BD0C1C55 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EBE77F0A2ED3457A6A1CB89B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A29654D940D21A30364E925 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +97,20 @@ path = RunnerTests; sourceTree = ""; }; + 6301AEA3C852D7E9642DF31B /* Pods */ = { + isa = PBXGroup; + children = ( + 66A2CCD34B79417F9DE85218 /* Pods-Runner.debug.xcconfig */, + 2DABEC56ABBFE6AE531647B3 /* Pods-Runner.release.xcconfig */, + 9EE70E42FE81EC8E5DBF9758 /* Pods-Runner.profile.xcconfig */, + E2DFF9D8D656CDA14EB8234D /* Pods-RunnerTests.debug.xcconfig */, + DAA6FE74A3EF743340BCAA64 /* Pods-RunnerTests.release.xcconfig */, + 4A33480D83C28470082255E2 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +129,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 6301AEA3C852D7E9642DF31B /* Pods */, + D5BDE282E1C5B077F972150C /* Frameworks */, ); sourceTree = ""; }; @@ -116,11 +153,21 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; + D5BDE282E1C5B077F972150C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 30E4A80F5D94A1B617DDF1BC /* Pods_Runner.framework */, + C7CECF6BD102067868D544CA /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +175,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 79D9C5F1152B821FE2BFE7AD /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + EBE77F0A2ED3457A6A1CB89B /* Frameworks */, ); buildRules = ( ); @@ -145,12 +194,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 9801627BB9BFB4FDFFDE61DB /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3761847A8501422C54C05A6E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +273,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 3761847A8501422C54C05A6E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +306,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 79D9C5F1152B821FE2BFE7AD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +343,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + 9801627BB9BFB4FDFFDE61DB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -270,6 +382,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -378,6 +491,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E2DFF9D8D656CDA14EB8234D /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,6 +509,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = DAA6FE74A3EF743340BCAA64 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -410,6 +525,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 4A33480D83C28470082255E2 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/examples/catalog_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/catalog_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/examples/catalog_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/catalog_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/catalog_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/examples/catalog_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/catalog_gallery/ios/Runner/AppDelegate.swift b/examples/catalog_gallery/ios/Runner/AppDelegate.swift index 4cb238206..95d7bd41a 100644 --- a/examples/catalog_gallery/ios/Runner/AppDelegate.swift +++ b/examples/catalog_gallery/ios/Runner/AppDelegate.swift @@ -6,12 +6,15 @@ 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) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/examples/catalog_gallery/ios/Runner/Info.plist b/examples/catalog_gallery/ios/Runner/Info.plist index 4c34f458c..e33458351 100644 --- a/examples/catalog_gallery/ios/Runner/Info.plist +++ b/examples/catalog_gallery/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +66,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h b/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..02588e01d --- /dev/null +++ b/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "GeneratedPluginRegistrant.h" diff --git a/examples/catalog_gallery/ios/Runner/SceneDelegate.swift b/examples/catalog_gallery/ios/Runner/SceneDelegate.swift new file mode 100644 index 000000000..e84dcfec2 --- /dev/null +++ b/examples/catalog_gallery/ios/Runner/SceneDelegate.swift @@ -0,0 +1,10 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/examples/catalog_gallery/linux/flutter/generated_plugin_registrant.cc b/examples/catalog_gallery/linux/flutter/generated_plugin_registrant.cc index f6f23bfe9..cc10c4daa 100644 --- a/examples/catalog_gallery/linux/flutter/generated_plugin_registrant.cc +++ b/examples/catalog_gallery/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/examples/catalog_gallery/linux/flutter/generated_plugins.cmake b/examples/catalog_gallery/linux/flutter/generated_plugins.cmake index f16b4c342..8e2a1900c 100644 --- a/examples/catalog_gallery/linux/flutter/generated_plugins.cmake +++ b/examples/catalog_gallery/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux url_launcher_linux ) diff --git a/examples/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift index 8236f5728..ad1073ab5 100644 --- a/examples/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/catalog_gallery/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import url_launcher_macos +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/examples/catalog_gallery/macos/Podfile.lock b/examples/catalog_gallery/macos/Podfile.lock index ed05ac66c..3e8b08332 100644 --- a/examples/catalog_gallery/macos/Podfile.lock +++ b/examples/catalog_gallery/macos/Podfile.lock @@ -1,21 +1,35 @@ PODS: + - audioplayers_darwin (0.0.1): + - Flutter + - FlutterMacOS - FlutterMacOS (1.0.0) - url_launcher_macos (0.0.1): - FlutterMacOS + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: + - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/darwin`) - FlutterMacOS (from `Flutter/ephemeral`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) EXTERNAL SOURCES: + audioplayers_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/darwin FlutterMacOS: :path: Flutter/ephemeral url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + video_player_avfoundation: + :path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin SPEC CHECKSUMS: + audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd + video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 diff --git a/examples/catalog_gallery/samples/audioPlayer.sample b/examples/catalog_gallery/samples/audioPlayer.sample new file mode 100644 index 000000000..c73d4d463 --- /dev/null +++ b/examples/catalog_gallery/samples/audioPlayer.sample @@ -0,0 +1,8 @@ +--- +description: An audio player playing a public domain piano clip (Beethoven — Minuet in G). +name: audioPlayer +prompt: | + Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for an AudioPlayer component that plays a short audio clip. +--- +{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/standard_catalog.json"}} +{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["audioPlayerComponent"]},{"id":"audioPlayerComponent","component":"AudioPlayer","url":"https://upload.wikimedia.org/wikipedia/commons/d/db/Minuet_in_G_%28Beethoven%29%2C_piano.ogg","description":"Beethoven — Minuet in G (public domain)"}]}} diff --git a/examples/catalog_gallery/samples/videoPlayer.sample b/examples/catalog_gallery/samples/videoPlayer.sample new file mode 100644 index 000000000..27ab29d93 --- /dev/null +++ b/examples/catalog_gallery/samples/videoPlayer.sample @@ -0,0 +1,8 @@ +--- +description: A video player playing a public domain clip. +name: videoPlayer +prompt: | + Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a Video component that plays a short video clip. +--- +{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/standard_catalog.json"}} +{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Column","children":["videoComponent"]},{"id":"videoComponent","component":"Video","url":"https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"}]}} diff --git a/examples/composer/lib/main.dart b/examples/composer/lib/main.dart index 6eb8ccea9..f7fd7be5c 100644 --- a/examples/composer/lib/main.dart +++ b/examples/composer/lib/main.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:window_manager/window_manager.dart'; @@ -14,9 +15,11 @@ import 'surface_editor.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await windowManager.ensureInitialized(); - await windowManager.setSize(const Size(1700, 1000)); - await windowManager.center(); + if (!kIsWeb) { + await windowManager.ensureInitialized(); + await windowManager.setSize(const Size(1700, 1000)); + await windowManager.center(); + } Logger.root.level = Level.INFO; Logger.root.onRecord.listen((record) { diff --git a/examples/composer/linux/flutter/generated_plugin_registrant.cc b/examples/composer/linux/flutter/generated_plugin_registrant.cc index faea87d4e..ed5411c18 100644 --- a/examples/composer/linux/flutter/generated_plugin_registrant.cc +++ b/examples/composer/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); diff --git a/examples/composer/linux/flutter/generated_plugins.cmake b/examples/composer/linux/flutter/generated_plugins.cmake index 4f427dd2a..c085ca836 100644 --- a/examples/composer/linux/flutter/generated_plugins.cmake +++ b/examples/composer/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux screen_retriever_linux url_launcher_linux window_manager diff --git a/examples/composer/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/composer/macos/Flutter/GeneratedPluginRegistrant.swift index c634c8302..802a33c8d 100644 --- a/examples/composer/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/composer/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,16 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import screen_retriever_macos import url_launcher_macos +import video_player_avfoundation import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/examples/composer/samples/audioPlayer.sample b/examples/composer/samples/audioPlayer.sample new file mode 100644 index 000000000..87691807d --- /dev/null +++ b/examples/composer/samples/audioPlayer.sample @@ -0,0 +1,8 @@ +--- +description: An audio player embedded in a card with descriptive text about the piece. +name: audioPlayer +prompt: | + Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a Card containing a heading, a short description, and an AudioPlayer component playing a public domain piano clip. +--- +{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/standard_catalog.json"}} +{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"contentColumn"},{"id":"contentColumn","component":"Column","children":["heading","description","player"]},{"id":"heading","component":"Text","text":"Minuet in G Major","variant":"h4"},{"id":"description","component":"Text","text":"A charming piano miniature composed by Ludwig van Beethoven, often mistakenly attributed to Bach. This short piece in G major showcases Beethoven's early classical style with its elegant, dance-like melody.","variant":"body"},{"id":"player","component":"AudioPlayer","url":"https://upload.wikimedia.org/wikipedia/commons/d/db/Minuet_in_G_%28Beethoven%29%2C_piano.ogg","description":"Beethoven — Minuet in G (public domain)"}]}} diff --git a/examples/composer/samples/manifest.txt b/examples/composer/samples/manifest.txt index bb1e78005..81725b307 100644 --- a/examples/composer/samples/manifest.txt +++ b/examples/composer/samples/manifest.txt @@ -1,4 +1,5 @@ animalKingdomExplorer.sample +audioPlayer.sample calendarEventCreator.sample chatRoom.sample checkoutPage.sample @@ -41,4 +42,5 @@ surveyForm.sample travelItinerary.sample triviaQuiz.sample videoCallInterface.sample +videoPlayer.sample weatherForecast.sample diff --git a/examples/composer/samples/videoPlayer.sample b/examples/composer/samples/videoPlayer.sample new file mode 100644 index 000000000..576d158e9 --- /dev/null +++ b/examples/composer/samples/videoPlayer.sample @@ -0,0 +1,8 @@ +--- +description: A video player embedded in a card with descriptive text about the film. +name: videoPlayer +prompt: | + Generate a 'createSurface' message and a 'updateComponents' message with surfaceId 'main' for a Card containing a heading, a short description, and a Video component playing a public domain animated short. +--- +{"version":"v0.9","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_9/standard_catalog.json"}} +{"version":"v0.9","updateComponents":{"surfaceId":"main","components":[{"id":"root","component":"Card","child":"contentColumn"},{"id":"contentColumn","component":"Column","children":["heading","description","player"]},{"id":"heading","component":"Text","text":"Big Buck Bunny","variant":"h4"},{"id":"description","component":"Text","text":"Big Buck Bunny is a short, open-source animated film created by the Blender Institute. It follows a giant rabbit who takes revenge on three bullying rodents. The film was made entirely with free software and is released under a Creative Commons license.","variant":"body"},{"id":"player","component":"Video","url":"https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"}]}} diff --git a/examples/composer/windows/flutter/generated_plugin_registrant.cc b/examples/composer/windows/flutter/generated_plugin_registrant.cc index 48096aeb7..ec517e2c6 100644 --- a/examples/composer/windows/flutter/generated_plugin_registrant.cc +++ b/examples/composer/windows/flutter/generated_plugin_registrant.cc @@ -6,15 +6,21 @@ #include "generated_plugin_registrant.h" +#include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + VideoPlayerWinPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VideoPlayerWinPluginCApi")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/examples/composer/windows/flutter/generated_plugins.cmake b/examples/composer/windows/flutter/generated_plugins.cmake index 2e1248d1c..f74fccabd 100644 --- a/examples/composer/windows/flutter/generated_plugins.cmake +++ b/examples/composer/windows/flutter/generated_plugins.cmake @@ -3,8 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows screen_retriever_windows url_launcher_windows + video_player_win window_manager ) diff --git a/examples/simple_chat/linux/flutter/generated_plugin_registrant.cc b/examples/simple_chat/linux/flutter/generated_plugin_registrant.cc index f6f23bfe9..cc10c4daa 100644 --- a/examples/simple_chat/linux/flutter/generated_plugin_registrant.cc +++ b/examples/simple_chat/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/examples/simple_chat/linux/flutter/generated_plugins.cmake b/examples/simple_chat/linux/flutter/generated_plugins.cmake index f16b4c342..8e2a1900c 100644 --- a/examples/simple_chat/linux/flutter/generated_plugins.cmake +++ b/examples/simple_chat/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux url_launcher_linux ) diff --git a/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift index 8236f5728..ad1073ab5 100644 --- a/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/simple_chat/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import url_launcher_macos +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/examples/simple_chat/windows/flutter/generated_plugin_registrant.cc b/examples/simple_chat/windows/flutter/generated_plugin_registrant.cc index 4f7884874..8d8a566fd 100644 --- a/examples/simple_chat/windows/flutter/generated_plugin_registrant.cc +++ b/examples/simple_chat/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + VideoPlayerWinPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VideoPlayerWinPluginCApi")); } diff --git a/examples/simple_chat/windows/flutter/generated_plugins.cmake b/examples/simple_chat/windows/flutter/generated_plugins.cmake index 88b22e5c7..97b61367a 100644 --- a/examples/simple_chat/windows/flutter/generated_plugins.cmake +++ b/examples/simple_chat/windows/flutter/generated_plugins.cmake @@ -3,7 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows url_launcher_windows + video_player_win ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/examples/travel_app/linux/flutter/generated_plugin_registrant.cc b/examples/travel_app/linux/flutter/generated_plugin_registrant.cc index f6f23bfe9..cc10c4daa 100644 --- a/examples/travel_app/linux/flutter/generated_plugin_registrant.cc +++ b/examples/travel_app/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/examples/travel_app/linux/flutter/generated_plugins.cmake b/examples/travel_app/linux/flutter/generated_plugins.cmake index f16b4c342..8e2a1900c 100644 --- a/examples/travel_app/linux/flutter/generated_plugins.cmake +++ b/examples/travel_app/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux url_launcher_linux ) diff --git a/examples/travel_app/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/travel_app/macos/Flutter/GeneratedPluginRegistrant.swift index 4dc2c0c72..b7df0db66 100644 --- a/examples/travel_app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/travel_app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,16 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import firebase_app_check import firebase_core import url_launcher_macos +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/examples/travel_app/windows/flutter/generated_plugin_registrant.cc b/examples/travel_app/windows/flutter/generated_plugin_registrant.cc index ec8e8d457..a90480746 100644 --- a/examples/travel_app/windows/flutter/generated_plugin_registrant.cc +++ b/examples/travel_app/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,18 @@ #include "generated_plugin_registrant.h" +#include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + VideoPlayerWinPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VideoPlayerWinPluginCApi")); } diff --git a/examples/travel_app/windows/flutter/generated_plugins.cmake b/examples/travel_app/windows/flutter/generated_plugins.cmake index 02d26c31b..8c5d36ec5 100644 --- a/examples/travel_app/windows/flutter/generated_plugins.cmake +++ b/examples/travel_app/windows/flutter/generated_plugins.cmake @@ -3,8 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows firebase_core url_launcher_windows + video_player_win ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/examples/verdure/client/linux/flutter/generated_plugin_registrant.cc b/examples/verdure/client/linux/flutter/generated_plugin_registrant.cc index 7299b5cf2..d8a40627d 100644 --- a/examples/verdure/client/linux/flutter/generated_plugin_registrant.cc +++ b/examples/verdure/client/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); diff --git a/examples/verdure/client/linux/flutter/generated_plugins.cmake b/examples/verdure/client/linux/flutter/generated_plugins.cmake index 786ff5c29..04f81f4b4 100644 --- a/examples/verdure/client/linux/flutter/generated_plugins.cmake +++ b/examples/verdure/client/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux file_selector_linux url_launcher_linux ) diff --git a/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift index 8caf5d583..074b04b4c 100644 --- a/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/examples/verdure/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,16 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import device_info_plus import file_selector_macos import url_launcher_macos +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/audio_player.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/audio_player.dart index 3ce6e8890..587a1542d 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/audio_player.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/audio_player.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + +import 'package:audioplayers/audioplayers.dart' as ap; import 'package:flutter/material.dart'; import 'package:json_schema_builder/json_schema_builder.dart'; @@ -9,6 +12,7 @@ import '../../model/a2ui_schemas.dart'; import '../../model/catalog_item.dart'; import '../../primitives/simple_items.dart'; import '../../widgets/widget_utilities.dart'; +import 'format_duration.dart'; final _schema = S.object( description: 'An audio player component that plays audio from a given URL.', @@ -23,24 +27,34 @@ final _schema = S.object( required: ['url'], ); -/// A catalog item for an audio player. -/// -/// This widget displays a placeholder for an audio player, used to represent -/// a component capable of playing audio from a given URL. +/// A simple audio player. /// /// ## Parameters: /// /// - `url`: The URL of the audio to play. +/// - `description`: An optional description of the audio. final audioPlayer = CatalogItem( name: 'AudioPlayer', dataSchema: _schema, widgetBuilder: (itemContext) { - final Object? description = (itemContext.data as JsonMap)['description']; + final data = itemContext.data as JsonMap; + final Object? url = data['url']; + final Object? description = data['description']; + return BoundString( dataContext: itemContext.dataContext, - value: description, - builder: (context, value) { - return Semantics(label: value, child: const Icon(Icons.audiotrack)); + value: url, + builder: (context, urlValue) { + return BoundString( + dataContext: itemContext.dataContext, + value: description, + builder: (context, descriptionValue) { + return _AudioPlayerWidget( + url: urlValue, + description: descriptionValue, + ); + }, + ); }, ); }, @@ -50,9 +64,178 @@ final audioPlayer = CatalogItem( { "id": "root", "component": "AudioPlayer", - "url": "https://example.com/audio.mp3" + "url": "https://upload.wikimedia.org/wikipedia/commons/d/db/Minuet_in_G_%28Beethoven%29%2C_piano.ogg", + "description": "Beethoven — Minuet in G (public domain)" } ] ''', ], ); + +class _AudioPlayerWidget extends StatefulWidget { + const _AudioPlayerWidget({required this.url, this.description}); + + final String? url; + final String? description; + + @override + State<_AudioPlayerWidget> createState() => _AudioPlayerWidgetState(); +} + +class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { + late final ap.AudioPlayer _player; + late final List> _subscriptions; + bool _isPlaying = false; + Duration _position = Duration.zero; + Duration _duration = Duration.zero; + double _volume = 0.5; + + @override + void initState() { + super.initState(); + _player = ap.AudioPlayer(); + _player.setVolume(_volume); + + _subscriptions = [ + _player.onPlayerStateChanged.listen((state) { + if (mounted) { + setState(() { + _isPlaying = state == ap.PlayerState.playing; + }); + } + }), + _player.onPositionChanged.listen((position) { + if (mounted) { + setState(() => _position = position); + } + }), + _player.onDurationChanged.listen((duration) { + if (mounted) { + setState(() => _duration = duration); + } + }), + ]; + + _setSource(); + } + + @override + void didUpdateWidget(_AudioPlayerWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.url != oldWidget.url) { + _player.stop(); + _position = Duration.zero; + _duration = Duration.zero; + _setSource(); + } + } + + void _setSource() { + final String? url = widget.url; + if (url != null && url.isNotEmpty) { + _player.setSource(ap.UrlSource(url)); + } + } + + @override + void dispose() { + for (final StreamSubscription sub in _subscriptions) { + sub.cancel(); + } + _player.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + const contentPadding = EdgeInsets.all(12); + + return Card( + child: Padding( + padding: contentPadding, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.description != null && widget.description!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + widget.description!, + style: theme.textTheme.titleSmall, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Row( + children: [ + IconButton( + icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow), + onPressed: widget.url != null && widget.url!.isNotEmpty + ? () { + if (_isPlaying) { + _player.pause(); + } else { + _player.resume(); + } + } + : null, + ), + Text( + formatDuration(_position), + style: theme.textTheme.bodySmall, + ), + Expanded( + child: Slider( + value: _duration.inMilliseconds > 0 + ? _position.inMilliseconds + .clamp(0, _duration.inMilliseconds) + .toDouble() + : 0, + max: _duration.inMilliseconds > 0 + ? _duration.inMilliseconds.toDouble() + : 1, + onChanged: (value) { + _player.seek(Duration(milliseconds: value.toInt())); + }, + ), + ), + Text( + formatDuration(_duration), + style: theme.textTheme.bodySmall, + ), + const SizedBox(width: 12), + Icon( + _volume == 0 + ? Icons.volume_off + : _volume < 0.5 + ? Icons.volume_down + : Icons.volume_up, + size: 20, + ), + SizedBox( + width: 100, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + overlayShape: SliderComponentShape.noOverlay, + padding: EdgeInsets.zero, + ), + child: Slider( + value: _volume, + onChanged: (value) { + setState(() => _volume = value); + _player.setVolume(value); + }, + ), + ), + ), + SizedBox(width: contentPadding.right), + ], + ), + ], + ), + ), + ); + } +} diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/format_duration.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/format_duration.dart new file mode 100644 index 000000000..831a6e6ed --- /dev/null +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/format_duration.dart @@ -0,0 +1,13 @@ +// Copyright 2025 The Flutter Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Formats a [Duration] as `mm:ss` or `h:mm:ss` if longer than an hour. +String formatDuration(Duration d) { + final String minutes = d.inMinutes.remainder(60).toString().padLeft(2, '0'); + final String seconds = d.inSeconds.remainder(60).toString().padLeft(2, '0'); + if (d.inHours > 0) { + return '${d.inHours}:$minutes:$seconds'; + } + return '$minutes:$seconds'; +} diff --git a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart index 1aee5487f..812847cb0 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -2,11 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:json_schema_builder/json_schema_builder.dart'; +import 'package:video_player/video_player.dart' as vp; import '../../model/a2ui_schemas.dart'; import '../../model/catalog_item.dart'; +import '../../primitives/logging.dart'; +import '../../primitives/simple_items.dart'; +import '../../widgets/widget_utilities.dart'; +import 'format_duration.dart'; final _schema = S.object( description: 'A video player.', @@ -18,11 +24,12 @@ final _schema = S.object( required: ['url'], ); +// Linux is the only platform without video_player support. +bool get _isVideoSupported => + defaultTargetPlatform != TargetPlatform.linux || kIsWeb; + /// A video player. /// -/// This widget currently displays a placeholder for a video player. It is -/// intended to play video content from the given `url`. -/// /// ## Parameters: /// /// - `url`: The URL of the video to play. @@ -30,9 +37,14 @@ final video = CatalogItem( name: 'Video', dataSchema: _schema, widgetBuilder: (itemContext) { - return ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), - child: const Placeholder(child: Center(child: Text('Video'))), + final Object? url = (itemContext.data as JsonMap)['url']; + + return BoundString( + dataContext: itemContext.dataContext, + value: url, + builder: (context, urlValue) { + return _VideoPlayerWidget(url: urlValue); + }, ); }, exampleData: [ @@ -41,9 +53,248 @@ final video = CatalogItem( { "id": "root", "component": "Video", - "url": "https://example.com/video.mp4" + "url": "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4" } ] ''', ], ); + +class _VideoPlayerWidget extends StatefulWidget { + const _VideoPlayerWidget({required this.url}); + + final String? url; + + @override + State<_VideoPlayerWidget> createState() => _VideoPlayerWidgetState(); +} + +class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { + vp.VideoPlayerController? _controller; + bool _hasError = false; + + @override + void initState() { + super.initState(); + if (!_isVideoSupported) { + genUiLogger.warning( + 'Video playback is not supported on ' + '${defaultTargetPlatform.name}.', + ); + } + _initController(); + } + + @override + void didUpdateWidget(_VideoPlayerWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.url != oldWidget.url) { + _disposeController(); + _initController(); + } + } + + void _initController() { + final String? url = widget.url; + if (url == null || url.isEmpty || !_isVideoSupported) return; + + _hasError = false; + + final Uri uri; + try { + uri = Uri.parse(url); + } on FormatException { + genUiLogger.warning('Invalid video URL: $url'); + _hasError = true; + return; + } + + _controller = vp.VideoPlayerController.networkUrl(uri) + ..initialize() + .then((_) { + _controller?.setVolume(0.5); + if (mounted) setState(() {}); + }) + .catchError((Object error) { + genUiLogger.warning('Failed to initialize video player', error); + if (mounted) setState(() => _hasError = true); + }); + } + + void _disposeController() { + _controller?.dispose(); + _controller = null; + } + + @override + void dispose() { + _disposeController(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (!_isVideoSupported) { + return const Card( + child: Padding( + padding: EdgeInsets.all(16), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.videocam_off), + SizedBox(width: 8), + Text('Video playback is not supported on this platform.'), + ], + ), + ), + ); + } + + final vp.VideoPlayerController? controller = _controller; + + if (_hasError) { + return const Card( + child: Padding( + padding: EdgeInsets.all(16), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error_outline), + SizedBox(width: 8), + Text('Failed to load video.'), + ], + ), + ), + ); + } + + if (controller == null || !controller.value.isInitialized) { + return const AspectRatio( + aspectRatio: 16 / 9, + child: Center(child: CircularProgressIndicator()), + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () => controller.value.isPlaying + ? controller.pause() + : controller.play(), + child: AspectRatio( + aspectRatio: controller.value.aspectRatio, + child: Stack( + alignment: Alignment.center, + children: [ + vp.VideoPlayer(controller), + _CenterPlayButton(controller: controller), + ], + ), + ), + ), + _BottomControlBar(controller: controller), + ], + ); + } +} + +class _CenterPlayButton extends StatelessWidget { + const _CenterPlayButton({required this.controller}); + + final vp.VideoPlayerController controller; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, child) { + if (value.isPlaying) return const SizedBox.shrink(); + return Container( + decoration: const BoxDecoration( + color: Colors.black54, + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(12), + child: const Icon(Icons.play_arrow, color: Colors.white, size: 48), + ); + }, + ); + } +} + +class _BottomControlBar extends StatelessWidget { + const _BottomControlBar({required this.controller}); + + final vp.VideoPlayerController controller; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, child) { + final Duration position = value.position; + final Duration duration = value.duration; + final double volume = value.volume; + + return Row( + children: [ + IconButton( + icon: Icon(value.isPlaying ? Icons.pause : Icons.play_arrow), + onPressed: () { + if (value.isPlaying) { + controller.pause(); + } else { + controller.play(); + } + }, + ), + Text( + formatDuration(position), + style: Theme.of(context).textTheme.bodySmall, + ), + Expanded( + child: Slider( + value: duration.inMilliseconds > 0 + ? position.inMilliseconds + .clamp(0, duration.inMilliseconds) + .toDouble() + : 0, + max: duration.inMilliseconds > 0 + ? duration.inMilliseconds.toDouble() + : 1, + onChanged: (v) { + controller.seekTo(Duration(milliseconds: v.toInt())); + }, + ), + ), + Text( + formatDuration(duration), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(width: 12), + Icon( + volume == 0 + ? Icons.volume_off + : volume < 0.5 + ? Icons.volume_down + : Icons.volume_up, + size: 20, + ), + SizedBox( + width: 100, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + overlayShape: SliderComponentShape.noOverlay, + padding: EdgeInsets.zero, + ), + child: Slider(value: volume, onChanged: controller.setVolume), + ), + ), + const SizedBox(width: 8), + ], + ); + }, + ); + } +} diff --git a/packages/genui/pubspec.yaml b/packages/genui/pubspec.yaml index 17141b3ea..5f48b3aa1 100644 --- a/packages/genui/pubspec.yaml +++ b/packages/genui/pubspec.yaml @@ -16,6 +16,7 @@ environment: resolution: workspace dependencies: + audioplayers: ^6.6.0 collection: ^1.19.1 flutter: sdk: flutter @@ -28,6 +29,8 @@ dependencies: rxdart: ^0.28.0 url_launcher: ^6.3.2 uuid: ^4.4.0 + video_player: ^2.11.1 + video_player_win: ^3.2.2 dev_dependencies: async: ^2.13.0 diff --git a/packages/genui/test/catalog/core_widgets/audio_player_test.dart b/packages/genui/test/catalog/core_widgets/audio_player_test.dart index 55432bc56..7d115e0d1 100644 --- a/packages/genui/test/catalog/core_widgets/audio_player_test.dart +++ b/packages/genui/test/catalog/core_widgets/audio_player_test.dart @@ -43,18 +43,8 @@ void main() { ), ); - expect(find.byIcon(Icons.audiotrack), findsOneWidget); + expect(find.byIcon(Icons.play_arrow), findsOneWidget); - // Check for Semantics widget properties directly if find.bySemanticsLabel - // fails - final Semantics semantics = tester.widget( - find - .ancestor( - of: find.byIcon(Icons.audiotrack), - matching: find.byType(Semantics), - ) - .first, - ); - expect(semantics.properties.label, 'Audio Description'); + expect(find.text('Audio Description'), findsOneWidget); }); }