From cbcdd98f71bad663c0a465298a25c918bb29de0f Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Tue, 17 Mar 2026 06:42:08 -0700 Subject: [PATCH 01/24] init --- examples/catalog_gallery/.gitignore | 45 ++++ examples/catalog_gallery/.metadata | 12 +- .../catalog_gallery/analysis_options.yaml | 28 ++ examples/catalog_gallery/ios/Podfile.lock | 42 +++ .../ios/Runner.xcodeproj/project.pbxproj | 122 ++++++++- .../contents.xcworkspacedata | 7 + .../contents.xcworkspacedata | 10 + .../ios/Runner/AppDelegate.swift | 11 +- .../catalog_gallery/ios/Runner/Info.plist | 29 +- .../ios/Runner/Runner-Bridging-Header.h | 1 + .../ios/Runner/SceneDelegate.swift | 6 + .../ios/RunnerTests/RunnerTests.swift | 4 - .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + examples/catalog_gallery/macos/Podfile.lock | 14 + .../samples/audioPlayer.sample | 8 + .../samples/videoPlayer.sample | 8 + .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + .../flutter/generated_plugin_registrant.cc | 6 + .../windows/flutter/generated_plugins.cmake | 2 + .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + .../flutter/generated_plugin_registrant.cc | 6 + .../windows/flutter/generated_plugins.cmake | 2 + .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + .../basic_catalog_widgets/audio_player.dart | 195 +++++++++++++- .../catalog/basic_catalog_widgets/video.dart | 253 +++++++++++++++++- packages/genui/pubspec.yaml | 3 + 34 files changed, 811 insertions(+), 39 deletions(-) create mode 100644 examples/catalog_gallery/.gitignore create mode 100644 examples/catalog_gallery/analysis_options.yaml create mode 100644 examples/catalog_gallery/ios/Podfile.lock create mode 100644 examples/catalog_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/catalog_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h create mode 100644 examples/catalog_gallery/ios/Runner/SceneDelegate.swift create mode 100644 examples/catalog_gallery/samples/audioPlayer.sample create mode 100644 examples/catalog_gallery/samples/videoPlayer.sample 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/analysis_options.yaml b/examples/catalog_gallery/analysis_options.yaml new file mode 100644 index 000000000..0d2902135 --- /dev/null +++ b/examples/catalog_gallery/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options 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..10ec8e674 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; }; @@ -346,7 +459,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -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; @@ -472,7 +588,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -523,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; 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..c30b367ec 100644 --- a/examples/catalog_gallery/ios/Runner/AppDelegate.swift +++ b/examples/catalog_gallery/ios/Runner/AppDelegate.swift @@ -1,17 +1,16 @@ -// 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 @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..308a2a560 --- /dev/null +++ b/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#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..b9ce8ea2b --- /dev/null +++ b/examples/catalog_gallery/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift b/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift index 7d077cd5a..86a7c3b1b 100644 --- a/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift +++ b/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift @@ -1,7 +1,3 @@ -// 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 import XCTest 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..4bf8fb52f --- /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.10","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_10/standard_catalog.json"}} +{"version":"v0.10","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..712d15ce7 --- /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.10","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_10/standard_catalog.json"}} +{"version":"v0.10","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/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..62b1c5f60 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,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:audioplayers/audioplayers.dart' as ap; import 'package:flutter/material.dart'; import 'package:json_schema_builder/json_schema_builder.dart'; @@ -23,24 +24,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 +61,175 @@ 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" } ] ''', ], ); + +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; + bool _isPlaying = false; + Duration _position = Duration.zero; + Duration _duration = Duration.zero; + double _volume = 1.0; + + @override + void initState() { + super.initState(); + _player = ap.AudioPlayer(); + + _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(); + _setSource(); + } + } + + void _setSource() { + final String? url = widget.url; + if (url != null && url.isNotEmpty) { + _player.setSource(ap.UrlSource(url)); + } + } + + @override + void dispose() { + _player.dispose(); + super.dispose(); + } + + 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'; + } + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + + return Card( + child: Padding( + padding: const EdgeInsets.all(12), + 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: () { + if (_isPlaying) { + _player.pause(); + } else { + _player.resume(); + } + }, + ), + 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); + }, + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} 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..bae39d4e2 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,16 @@ // 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'; final _schema = S.object( description: 'A video player.', @@ -18,11 +23,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 +36,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 +52,237 @@ 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(); + _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; + _controller = vp.VideoPlayerController.networkUrl(Uri.parse(url)) + ..initialize().then((_) { + 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) { + genUiLogger.warning( + 'Video playback is not supported on ' + '${defaultTargetPlatform.name}.', + ); + 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; + + 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, + ), + IconButton( + icon: Icon( + value.volume > 0 ? Icons.volume_up : Icons.volume_off, + ), + onPressed: () { + controller.setVolume(value.volume > 0 ? 0.0 : 1.0); + }, + ), + ], + ); + }, + ); + } + + 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/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 From d273690e441bcb745efa04355cf3a3ee2008a59a Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 13:55:14 +0000 Subject: [PATCH 02/24] fix(audio_player): cancel stream subscriptions on dispose --- .../basic_catalog_widgets/audio_player.dart | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) 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 62b1c5f60..d574f417e 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,8 @@ // 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'; @@ -80,6 +82,7 @@ class _AudioPlayerWidget extends StatefulWidget { 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; @@ -90,25 +93,25 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { super.initState(); _player = ap.AudioPlayer(); - _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); - } - }); + _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(); } @@ -131,6 +134,9 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { @override void dispose() { + for (final StreamSubscription sub in _subscriptions) { + sub.cancel(); + } _player.dispose(); super.dispose(); } From 41a05f26db0e8da03ac279c61ae03b3c0b1ed6f7 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 15:00:40 +0000 Subject: [PATCH 03/24] fix(audio_player): use 'play' when starting a new stream --- .../catalog/basic_catalog_widgets/audio_player.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 d574f417e..3993dd2d7 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 @@ -84,6 +84,7 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { late final ap.AudioPlayer _player; late final List> _subscriptions; bool _isPlaying = false; + bool _hasStarted = false; Duration _position = Duration.zero; Duration _duration = Duration.zero; double _volume = 1.0; @@ -121,6 +122,9 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { super.didUpdateWidget(oldWidget); if (widget.url != oldWidget.url) { _player.stop(); + _position = Duration.zero; + _duration = Duration.zero; + _hasStarted = false; _setSource(); } } @@ -178,6 +182,12 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { onPressed: () { if (_isPlaying) { _player.pause(); + } else if (!_hasStarted) { + final String? url = widget.url; + if (url != null && url.isNotEmpty) { + _hasStarted = true; + _player.play(ap.UrlSource(url)); + } } else { _player.resume(); } From 831e6ac2f2b170eaca8cf58b8fcb687bcba4dde6 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 15:05:03 +0000 Subject: [PATCH 04/24] fix(chore): copyrights --- examples/catalog_gallery/analysis_options.yaml | 4 ++++ examples/catalog_gallery/ios/Runner/AppDelegate.swift | 4 ++++ examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h | 4 ++++ examples/catalog_gallery/ios/Runner/SceneDelegate.swift | 4 ++++ examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/examples/catalog_gallery/analysis_options.yaml b/examples/catalog_gallery/analysis_options.yaml index 0d2902135..e3372be68 100644 --- a/examples/catalog_gallery/analysis_options.yaml +++ b/examples/catalog_gallery/analysis_options.yaml @@ -1,3 +1,7 @@ +# 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. + # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # diff --git a/examples/catalog_gallery/ios/Runner/AppDelegate.swift b/examples/catalog_gallery/ios/Runner/AppDelegate.swift index c30b367ec..95d7bd41a 100644 --- a/examples/catalog_gallery/ios/Runner/AppDelegate.swift +++ b/examples/catalog_gallery/ios/Runner/AppDelegate.swift @@ -1,3 +1,7 @@ +// 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 diff --git a/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h b/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h index 308a2a560..02588e01d 100644 --- a/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h +++ b/examples/catalog_gallery/ios/Runner/Runner-Bridging-Header.h @@ -1 +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 index b9ce8ea2b..e84dcfec2 100644 --- a/examples/catalog_gallery/ios/Runner/SceneDelegate.swift +++ b/examples/catalog_gallery/ios/Runner/SceneDelegate.swift @@ -1,3 +1,7 @@ +// 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 diff --git a/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift b/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift index 86a7c3b1b..7d077cd5a 100644 --- a/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift +++ b/examples/catalog_gallery/ios/RunnerTests/RunnerTests.swift @@ -1,3 +1,7 @@ +// 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 import XCTest From 99ff7fc3e922af1d5345deb8fd654a81027c74bf Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 15:11:38 +0000 Subject: [PATCH 05/24] fix(chore): undo experimental change to ios deployment target --- .../catalog_gallery/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj b/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj index 10ec8e674..e7683a88d 100644 --- a/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/catalog_gallery/ios/Runner.xcodeproj/project.pbxproj @@ -459,7 +459,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -588,7 +588,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -639,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; From 3753ea8660ba92d1f60a8cef26909c6d197eb8c6 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 15:24:30 +0000 Subject: [PATCH 06/24] fix(video): move warning call to initState --- .../catalog/basic_catalog_widgets/video.dart | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) 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 bae39d4e2..58d625b00 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -75,6 +75,12 @@ class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { @override void initState() { super.initState(); + if (!_isVideoSupported) { + genUiLogger.warning( + 'Video playback is not supported on ' + '${defaultTargetPlatform.name}.', + ); + } _initController(); } @@ -93,12 +99,14 @@ class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { _hasError = false; _controller = vp.VideoPlayerController.networkUrl(Uri.parse(url)) - ..initialize().then((_) { - if (mounted) setState(() {}); - }).catchError((Object error) { - genUiLogger.warning('Failed to initialize video player', error); - if (mounted) setState(() => _hasError = true); - }); + ..initialize() + .then((_) { + if (mounted) setState(() {}); + }) + .catchError((Object error) { + genUiLogger.warning('Failed to initialize video player', error); + if (mounted) setState(() => _hasError = true); + }); } void _disposeController() { @@ -115,10 +123,6 @@ class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { @override Widget build(BuildContext context) { if (!_isVideoSupported) { - genUiLogger.warning( - 'Video playback is not supported on ' - '${defaultTargetPlatform.name}.', - ); return const Card( child: Padding( padding: EdgeInsets.all(16), @@ -200,11 +204,7 @@ class _CenterPlayButton extends StatelessWidget { shape: BoxShape.circle, ), padding: const EdgeInsets.all(12), - child: const Icon( - Icons.play_arrow, - color: Colors.white, - size: 48, - ), + child: const Icon(Icons.play_arrow, color: Colors.white, size: 48), ); }, ); @@ -227,9 +227,7 @@ class _BottomControlBar extends StatelessWidget { return Row( children: [ IconButton( - icon: Icon( - value.isPlaying ? Icons.pause : Icons.play_arrow, - ), + icon: Icon(value.isPlaying ? Icons.pause : Icons.play_arrow), onPressed: () { if (value.isPlaying) { controller.pause(); @@ -246,8 +244,8 @@ class _BottomControlBar extends StatelessWidget { child: Slider( value: duration.inMilliseconds > 0 ? position.inMilliseconds - .clamp(0, duration.inMilliseconds) - .toDouble() + .clamp(0, duration.inMilliseconds) + .toDouble() : 0, max: duration.inMilliseconds > 0 ? duration.inMilliseconds.toDouble() @@ -262,9 +260,7 @@ class _BottomControlBar extends StatelessWidget { style: Theme.of(context).textTheme.bodySmall, ), IconButton( - icon: Icon( - value.volume > 0 ? Icons.volume_up : Icons.volume_off, - ), + icon: Icon(value.volume > 0 ? Icons.volume_up : Icons.volume_off), onPressed: () { controller.setVolume(value.volume > 0 ? 0.0 : 1.0); }, @@ -276,10 +272,8 @@ class _BottomControlBar extends StatelessWidget { } 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'); + 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'; } From f0043ccfb01ee61bfcb5ade0ce5a56ffa6f4ae8c Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 15:28:52 +0000 Subject: [PATCH 07/24] fix(chore): missing copyright notice --- examples/eval/test/test_infra/ai_client.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/eval/test/test_infra/ai_client.dart b/examples/eval/test/test_infra/ai_client.dart index ab75a506f..f8316f48d 100644 --- a/examples/eval/test/test_infra/ai_client.dart +++ b/examples/eval/test/test_infra/ai_client.dart @@ -1,3 +1,7 @@ +// 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 'dart:async'; import 'package:dartantic_ai/dartantic_ai.dart' as dartantic; From 16248991fc413fd70a8d539f72c46f07ec0cc8a9 Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Tue, 17 Mar 2026 10:48:18 -0700 Subject: [PATCH 08/24] format --- .../lib/src/catalog/basic_catalog_widgets/audio_player.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3993dd2d7..21ad3c2ee 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 @@ -221,8 +221,8 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { _volume == 0 ? Icons.volume_off : _volume < 0.5 - ? Icons.volume_down - : Icons.volume_up, + ? Icons.volume_down + : Icons.volume_up, size: 20, ), SizedBox( From b85c2abbd6f81b48c3f57528e401934b90db6461 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 17:50:08 +0000 Subject: [PATCH 09/24] remove accidentally introduced analysis_options.yaml --- .../catalog_gallery/analysis_options.yaml | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 examples/catalog_gallery/analysis_options.yaml diff --git a/examples/catalog_gallery/analysis_options.yaml b/examples/catalog_gallery/analysis_options.yaml deleted file mode 100644 index e3372be68..000000000 --- a/examples/catalog_gallery/analysis_options.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# 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. - -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options From 99df17c5b91b34b28e2c9a5b8cbdaf392727c75a Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 18:11:50 +0000 Subject: [PATCH 10/24] fix audio/video catalog version IDs --- examples/catalog_gallery/samples/audioPlayer.sample | 4 ++-- examples/catalog_gallery/samples/videoPlayer.sample | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/catalog_gallery/samples/audioPlayer.sample b/examples/catalog_gallery/samples/audioPlayer.sample index 4bf8fb52f..c73d4d463 100644 --- a/examples/catalog_gallery/samples/audioPlayer.sample +++ b/examples/catalog_gallery/samples/audioPlayer.sample @@ -4,5 +4,5 @@ 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.10","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_10/standard_catalog.json"}} -{"version":"v0.10","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)"}]}} +{"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 index 712d15ce7..27ab29d93 100644 --- a/examples/catalog_gallery/samples/videoPlayer.sample +++ b/examples/catalog_gallery/samples/videoPlayer.sample @@ -4,5 +4,5 @@ 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.10","createSurface":{"surfaceId":"main","catalogId":"https://a2ui.org/specification/v0_10/standard_catalog.json"}} -{"version":"v0.10","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"}]}} +{"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"}]}} From 4554d327eea8ec504214800990a2e8c0415b1f77 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 18:14:38 +0000 Subject: [PATCH 11/24] fix test --- .../catalog/core_widgets/audio_player_test.dart | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) 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); }); } From b4489e10f18f6f4fe47232025d112c1827187561 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 19:30:38 +0000 Subject: [PATCH 12/24] fix(audioPlayer): add padding after volume slider --- .../lib/src/catalog/basic_catalog_widgets/audio_player.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 21ad3c2ee..7ebcc084b 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 @@ -157,10 +157,11 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); + const contentPadding = EdgeInsets.all(12); return Card( child: Padding( - padding: const EdgeInsets.all(12), + padding: contentPadding, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -241,6 +242,7 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { ), ), ), + SizedBox(width: contentPadding.right), ], ), ], From 241cb2e8fe235d1d719a4b4974267468ea503262 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 20:20:14 +0000 Subject: [PATCH 13/24] decrease default volume to 0.5 from 1 --- .../lib/src/catalog/basic_catalog_widgets/audio_player.dart | 3 ++- .../genui/lib/src/catalog/basic_catalog_widgets/video.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) 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 7ebcc084b..e778995df 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 @@ -87,12 +87,13 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { bool _hasStarted = false; Duration _position = Duration.zero; Duration _duration = Duration.zero; - double _volume = 1.0; + double _volume = 0.5; @override void initState() { super.initState(); _player = ap.AudioPlayer(); + _player.setVolume(_volume); _subscriptions = [ _player.onPlayerStateChanged.listen((state) { 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 58d625b00..2b340a5dc 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -101,6 +101,7 @@ class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { _controller = vp.VideoPlayerController.networkUrl(Uri.parse(url)) ..initialize() .then((_) { + _controller?.setVolume(0.5); if (mounted) setState(() {}); }) .catchError((Object error) { From a773a39508cc66f1eef5290a49cfedaee6d3eb94 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Tue, 17 Mar 2026 20:51:08 +0000 Subject: [PATCH 14/24] feat: give video a volume slider, add description to audio player example data --- .../basic_catalog_widgets/audio_player.dart | 3 +- .../catalog/basic_catalog_widgets/video.dart | 47 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) 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 e778995df..d8e9f7ad2 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 @@ -63,7 +63,8 @@ final audioPlayer = CatalogItem( { "id": "root", "component": "AudioPlayer", - "url": "https://upload.wikimedia.org/wikipedia/commons/d/db/Minuet_in_G_%28Beethoven%29%2C_piano.ogg" + "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/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart index 2b340a5dc..ab53e93c6 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -212,15 +212,22 @@ class _CenterPlayButton extends StatelessWidget { } } -class _BottomControlBar extends StatelessWidget { +class _BottomControlBar extends StatefulWidget { const _BottomControlBar({required this.controller}); final vp.VideoPlayerController controller; + @override + State<_BottomControlBar> createState() => _BottomControlBarState(); +} + +class _BottomControlBarState extends State<_BottomControlBar> { + double _volume = 0.5; + @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: controller, + valueListenable: widget.controller, builder: (context, value, child) { final Duration position = value.position; final Duration duration = value.duration; @@ -231,9 +238,9 @@ class _BottomControlBar extends StatelessWidget { icon: Icon(value.isPlaying ? Icons.pause : Icons.play_arrow), onPressed: () { if (value.isPlaying) { - controller.pause(); + widget.controller.pause(); } else { - controller.play(); + widget.controller.play(); } }, ), @@ -252,7 +259,7 @@ class _BottomControlBar extends StatelessWidget { ? duration.inMilliseconds.toDouble() : 1, onChanged: (v) { - controller.seekTo(Duration(milliseconds: v.toInt())); + widget.controller.seekTo(Duration(milliseconds: v.toInt())); }, ), ), @@ -260,12 +267,32 @@ class _BottomControlBar extends StatelessWidget { _formatDuration(duration), style: Theme.of(context).textTheme.bodySmall, ), - IconButton( - icon: Icon(value.volume > 0 ? Icons.volume_up : Icons.volume_off), - onPressed: () { - controller.setVolume(value.volume > 0 ? 0.0 : 1.0); - }, + 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); + widget.controller.setVolume(value); + }, + ), + ), ), + const SizedBox(width: 8), ], ); }, From ed73ac14b3c8a5d2588792ed58299524b24dc5ad Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 21:13:26 +0000 Subject: [PATCH 15/24] feat(composer): add video and audioPlayer examples to composer app --- examples/composer/lib/main.dart | 9 ++++++--- .../linux/flutter/generated_plugin_registrant.cc | 4 ++++ examples/composer/linux/flutter/generated_plugins.cmake | 1 + .../macos/Flutter/GeneratedPluginRegistrant.swift | 4 ++++ examples/composer/samples/audioPlayer.sample | 8 ++++++++ examples/composer/samples/manifest.txt | 2 ++ examples/composer/samples/videoPlayer.sample | 8 ++++++++ .../windows/flutter/generated_plugin_registrant.cc | 6 ++++++ .../composer/windows/flutter/generated_plugins.cmake | 2 ++ 9 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 examples/composer/samples/audioPlayer.sample create mode 100644 examples/composer/samples/videoPlayer.sample 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 ) From 7785ec08c796be8fa2e8dfd6c9a97ac91ee04761 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 21:19:44 +0000 Subject: [PATCH 16/24] fixes --- .../basic_catalog_widgets/audio_player.dart | 24 ++++------- .../catalog/basic_catalog_widgets/video.dart | 43 ++++++++++--------- 2 files changed, 31 insertions(+), 36 deletions(-) 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 d8e9f7ad2..d00485f44 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 @@ -85,7 +85,6 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { late final ap.AudioPlayer _player; late final List> _subscriptions; bool _isPlaying = false; - bool _hasStarted = false; Duration _position = Duration.zero; Duration _duration = Duration.zero; double _volume = 0.5; @@ -126,7 +125,6 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { _player.stop(); _position = Duration.zero; _duration = Duration.zero; - _hasStarted = false; _setSource(); } } @@ -182,19 +180,15 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { children: [ IconButton( icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow), - onPressed: () { - if (_isPlaying) { - _player.pause(); - } else if (!_hasStarted) { - final String? url = widget.url; - if (url != null && url.isNotEmpty) { - _hasStarted = true; - _player.play(ap.UrlSource(url)); - } - } else { - _player.resume(); - } - }, + onPressed: widget.url != null && widget.url!.isNotEmpty + ? () { + if (_isPlaying) { + _player.pause(); + } else { + _player.resume(); + } + } + : null, ), Text( _formatDuration(_position), 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 ab53e93c6..1cd7c6a94 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -98,7 +98,17 @@ class _VideoPlayerWidgetState extends State<_VideoPlayerWidget> { if (url == null || url.isEmpty || !_isVideoSupported) return; _hasError = false; - _controller = vp.VideoPlayerController.networkUrl(Uri.parse(url)) + + 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); @@ -212,25 +222,19 @@ class _CenterPlayButton extends StatelessWidget { } } -class _BottomControlBar extends StatefulWidget { +class _BottomControlBar extends StatelessWidget { const _BottomControlBar({required this.controller}); final vp.VideoPlayerController controller; - @override - State<_BottomControlBar> createState() => _BottomControlBarState(); -} - -class _BottomControlBarState extends State<_BottomControlBar> { - double _volume = 0.5; - @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: widget.controller, + valueListenable: controller, builder: (context, value, child) { final Duration position = value.position; final Duration duration = value.duration; + final double volume = value.volume; return Row( children: [ @@ -238,9 +242,9 @@ class _BottomControlBarState extends State<_BottomControlBar> { icon: Icon(value.isPlaying ? Icons.pause : Icons.play_arrow), onPressed: () { if (value.isPlaying) { - widget.controller.pause(); + controller.pause(); } else { - widget.controller.play(); + controller.play(); } }, ), @@ -259,7 +263,7 @@ class _BottomControlBarState extends State<_BottomControlBar> { ? duration.inMilliseconds.toDouble() : 1, onChanged: (v) { - widget.controller.seekTo(Duration(milliseconds: v.toInt())); + controller.seekTo(Duration(milliseconds: v.toInt())); }, ), ), @@ -269,9 +273,9 @@ class _BottomControlBarState extends State<_BottomControlBar> { ), const SizedBox(width: 12), Icon( - _volume == 0 + volume == 0 ? Icons.volume_off - : _volume < 0.5 + : volume < 0.5 ? Icons.volume_down : Icons.volume_up, size: 20, @@ -284,11 +288,8 @@ class _BottomControlBarState extends State<_BottomControlBar> { padding: EdgeInsets.zero, ), child: Slider( - value: _volume, - onChanged: (value) { - setState(() => _volume = value); - widget.controller.setVolume(value); - }, + value: volume, + onChanged: controller.setVolume, ), ), ), @@ -299,7 +300,7 @@ class _BottomControlBarState extends State<_BottomControlBar> { ); } - String _formatDuration(Duration d) { + static 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) { From 0dc1d59fe8d26517692361953381d84770aa67b1 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 21:34:00 +0000 Subject: [PATCH 17/24] refactor(audio-video): move formatDuration to its own neighboring file --- .../basic_catalog_widgets/audio_player.dart | 14 +++----------- .../src/catalog/basic_catalog_widgets/video.dart | 13 +++---------- 2 files changed, 6 insertions(+), 21 deletions(-) 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 d00485f44..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 @@ -12,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.', @@ -145,15 +146,6 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { super.dispose(); } - 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'; - } - @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); @@ -191,7 +183,7 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { : null, ), Text( - _formatDuration(_position), + formatDuration(_position), style: theme.textTheme.bodySmall, ), Expanded( @@ -210,7 +202,7 @@ class _AudioPlayerWidgetState extends State<_AudioPlayerWidget> { ), ), Text( - _formatDuration(_duration), + formatDuration(_duration), style: theme.textTheme.bodySmall, ), const SizedBox(width: 12), 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 1cd7c6a94..60f3ad31b 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -12,6 +12,7 @@ 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.', @@ -249,7 +250,7 @@ class _BottomControlBar extends StatelessWidget { }, ), Text( - _formatDuration(position), + formatDuration(position), style: Theme.of(context).textTheme.bodySmall, ), Expanded( @@ -268,7 +269,7 @@ class _BottomControlBar extends StatelessWidget { ), ), Text( - _formatDuration(duration), + formatDuration(duration), style: Theme.of(context).textTheme.bodySmall, ), const SizedBox(width: 12), @@ -300,12 +301,4 @@ class _BottomControlBar extends StatelessWidget { ); } - static 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'; - } } From b9cbc7a5b28c3daa8a73a1d459b1f4a91c2d0481 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 21:46:02 +0000 Subject: [PATCH 18/24] experiment: try fixing CI --- .github/workflows/eval.yaml | 2 +- .github/workflows/flutter_packages.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/eval.yaml b/.github/workflows/eval.yaml index 044a5e7c3..0dddd870e 100644 --- a/.github/workflows/eval.yaml +++ b/.github/workflows/eval.yaml @@ -27,7 +27,7 @@ jobs: fi echo "GEMINI_API_KEY has ${#GEMINI_API_KEY} characters" - name: Install Flutter - uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e + uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 with: channel: beta cache: true diff --git a/.github/workflows/flutter_packages.yaml b/.github/workflows/flutter_packages.yaml index 1ef5fe4ab..503d3c5e0 100644 --- a/.github/workflows/flutter_packages.yaml +++ b/.github/workflows/flutter_packages.yaml @@ -131,7 +131,7 @@ jobs: distribution: "zulu" java-version: "17" cache: "gradle" - - uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e + - uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 with: channel: ${{ matrix.flutter_version }} cache: true From 2e302a4bf3de39f09fe7f4fa42841c2bd88e5a2d Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 21:48:57 +0000 Subject: [PATCH 19/24] oop --- .github/workflows/eval.yaml | 2 +- .github/workflows/flutter_packages.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/eval.yaml b/.github/workflows/eval.yaml index 0dddd870e..044a5e7c3 100644 --- a/.github/workflows/eval.yaml +++ b/.github/workflows/eval.yaml @@ -27,7 +27,7 @@ jobs: fi echo "GEMINI_API_KEY has ${#GEMINI_API_KEY} characters" - name: Install Flutter - uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 + uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e with: channel: beta cache: true diff --git a/.github/workflows/flutter_packages.yaml b/.github/workflows/flutter_packages.yaml index 503d3c5e0..1ef5fe4ab 100644 --- a/.github/workflows/flutter_packages.yaml +++ b/.github/workflows/flutter_packages.yaml @@ -131,7 +131,7 @@ jobs: distribution: "zulu" java-version: "17" cache: "gradle" - - uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 + - uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e with: channel: ${{ matrix.flutter_version }} cache: true From 77b9d351abb712ea5e7fcee21a4892562af86db3 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 22:42:20 +0000 Subject: [PATCH 20/24] poke CI From f49b0289a65673afef25b0684544c0f5f9b9b880 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 23:22:15 +0000 Subject: [PATCH 21/24] CI experiment --- .github/workflows/eval.yaml | 14 +++---------- .github/workflows/flutter_packages.yaml | 6 ++---- .../internals/install_flutter/action.yml | 21 +++++++++++++++++++ .../format_duration.dart | 13 ++++++++++++ 4 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/internals/install_flutter/action.yml create mode 100644 packages/genui/lib/src/catalog/basic_catalog_widgets/format_duration.dart diff --git a/.github/workflows/eval.yaml b/.github/workflows/eval.yaml index c76488c2c..de91305e9 100644 --- a/.github/workflows/eval.yaml +++ b/.github/workflows/eval.yaml @@ -15,6 +15,8 @@ jobs: runs-on: ubuntu-latest environment: eval steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Check that REPO_GEMINI_API_KEY is set and available env: # REPO_GEMINI_API_KEY is expected to be set in the repository secrets @@ -27,14 +29,11 @@ jobs: fi echo "GEMINI_API_KEY has ${#GEMINI_API_KEY} characters" - name: Install Flutter - uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 + uses: ./.github/workflows/internals/install_flutter with: channel: beta - cache: true - name: Print Flutter version run: flutter --version - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install dependencies working-directory: examples/eval run: dart pub get @@ -49,10 +48,3 @@ jobs: path: ${{ env.PUB_CACHE }} key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} restore-keys: ${{ runner.os }}-pub- - - - - - - - diff --git a/.github/workflows/flutter_packages.yaml b/.github/workflows/flutter_packages.yaml index 80695f26f..0c8632e97 100644 --- a/.github/workflows/flutter_packages.yaml +++ b/.github/workflows/flutter_packages.yaml @@ -103,10 +103,9 @@ jobs: if: github.repository == 'flutter/genui' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 + - uses: ./.github/workflows/internals/install_flutter with: channel: stable - cache: true - name: Install dependencies run: flutter pub get > /dev/null - name: Check copyrights @@ -131,10 +130,9 @@ jobs: distribution: "zulu" java-version: "17" cache: "gradle" - - uses: subosito/flutter-action@0ca7a949e71ae44c8e688a51c5e7e93b2c87e295 + - uses: ./.github/workflows/internals/install_flutter with: channel: ${{ matrix.flutter_version }} - cache: true - name: Print Flutter version run: flutter --version - name: Cache Pub dependencies diff --git a/.github/workflows/internals/install_flutter/action.yml b/.github/workflows/internals/install_flutter/action.yml new file mode 100644 index 000000000..568021b05 --- /dev/null +++ b/.github/workflows/internals/install_flutter/action.yml @@ -0,0 +1,21 @@ +# 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. + +name: "Install Flutter" +description: "Installs Flutter by cloning the repository at a given channel." +inputs: + channel: + description: "The Flutter channel (branch) to install, e.g. 'stable' or 'beta'." + required: false + default: "stable" +runs: + using: "composite" + steps: + - name: Install Flutter + shell: bash + run: | + cd $HOME + git clone https://github.com/flutter/flutter.git --depth 1 -b ${{ inputs.channel }} _flutter + echo "$HOME/_flutter/bin" >> $GITHUB_PATH + cd $GITHUB_WORKSPACE 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'; +} From a41db5aa98b9edaee21f83e2caf14b886854fdb1 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 23:28:08 +0000 Subject: [PATCH 22/24] fix CI experiment --- .github/workflows/internals/install_flutter/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/internals/install_flutter/action.yml b/.github/workflows/internals/install_flutter/action.yml index 568021b05..77dca59a4 100644 --- a/.github/workflows/internals/install_flutter/action.yml +++ b/.github/workflows/internals/install_flutter/action.yml @@ -18,4 +18,5 @@ runs: cd $HOME git clone https://github.com/flutter/flutter.git --depth 1 -b ${{ inputs.channel }} _flutter echo "$HOME/_flutter/bin" >> $GITHUB_PATH + echo "PUB_CACHE=$HOME/.pub-cache" >> $GITHUB_ENV cd $GITHUB_WORKSPACE From c9f25c7d8d4ca4a3268d428dca22067ee9137e83 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Thu, 19 Mar 2026 23:38:39 +0000 Subject: [PATCH 23/24] more CI experiment --- .github/workflows/eval.yaml | 9 ++++---- .github/workflows/flutter_packages.yaml | 6 +++-- .../internals/install_flutter/action.yml | 22 ------------------- 3 files changed, 9 insertions(+), 28 deletions(-) delete mode 100644 .github/workflows/internals/install_flutter/action.yml diff --git a/.github/workflows/eval.yaml b/.github/workflows/eval.yaml index de91305e9..7c7b4f50a 100644 --- a/.github/workflows/eval.yaml +++ b/.github/workflows/eval.yaml @@ -5,7 +5,7 @@ name: eval-workflow on: - push: + push: # Workflow runs on push to any branch. schedule: - cron: "0 * * * *" # hourly @@ -15,8 +15,6 @@ jobs: runs-on: ubuntu-latest environment: eval steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Check that REPO_GEMINI_API_KEY is set and available env: # REPO_GEMINI_API_KEY is expected to be set in the repository secrets @@ -29,11 +27,14 @@ jobs: fi echo "GEMINI_API_KEY has ${#GEMINI_API_KEY} characters" - name: Install Flutter - uses: ./.github/workflows/internals/install_flutter + uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e with: channel: beta + cache: true - name: Print Flutter version run: flutter --version + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install dependencies working-directory: examples/eval run: dart pub get diff --git a/.github/workflows/flutter_packages.yaml b/.github/workflows/flutter_packages.yaml index 0c8632e97..e53b1d1e4 100644 --- a/.github/workflows/flutter_packages.yaml +++ b/.github/workflows/flutter_packages.yaml @@ -103,9 +103,10 @@ jobs: if: github.repository == 'flutter/genui' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: ./.github/workflows/internals/install_flutter + - uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e with: channel: stable + cache: true - name: Install dependencies run: flutter pub get > /dev/null - name: Check copyrights @@ -130,9 +131,10 @@ jobs: distribution: "zulu" java-version: "17" cache: "gradle" - - uses: ./.github/workflows/internals/install_flutter + - uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e with: channel: ${{ matrix.flutter_version }} + cache: true - name: Print Flutter version run: flutter --version - name: Cache Pub dependencies diff --git a/.github/workflows/internals/install_flutter/action.yml b/.github/workflows/internals/install_flutter/action.yml deleted file mode 100644 index 77dca59a4..000000000 --- a/.github/workflows/internals/install_flutter/action.yml +++ /dev/null @@ -1,22 +0,0 @@ -# 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. - -name: "Install Flutter" -description: "Installs Flutter by cloning the repository at a given channel." -inputs: - channel: - description: "The Flutter channel (branch) to install, e.g. 'stable' or 'beta'." - required: false - default: "stable" -runs: - using: "composite" - steps: - - name: Install Flutter - shell: bash - run: | - cd $HOME - git clone https://github.com/flutter/flutter.git --depth 1 -b ${{ inputs.channel }} _flutter - echo "$HOME/_flutter/bin" >> $GITHUB_PATH - echo "PUB_CACHE=$HOME/.pub-cache" >> $GITHUB_ENV - cd $GITHUB_WORKSPACE From 62978930e2f4d64472e4dfd7db60a67ed654ba01 Mon Sep 17 00:00:00 2001 From: andrewkolos Date: Fri, 20 Mar 2026 00:21:50 +0000 Subject: [PATCH 24/24] format --- .../genui/lib/src/catalog/basic_catalog_widgets/video.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 60f3ad31b..812847cb0 100644 --- a/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart +++ b/packages/genui/lib/src/catalog/basic_catalog_widgets/video.dart @@ -288,10 +288,7 @@ class _BottomControlBar extends StatelessWidget { overlayShape: SliderComponentShape.noOverlay, padding: EdgeInsets.zero, ), - child: Slider( - value: volume, - onChanged: controller.setVolume, - ), + child: Slider(value: volume, onChanged: controller.setVolume), ), ), const SizedBox(width: 8), @@ -300,5 +297,4 @@ class _BottomControlBar extends StatelessWidget { }, ); } - }