├── assets └── idk.none ├── plugins ├── models.dart ├── assets │ ├── metadata.json │ ├── workplace.jpg │ └── main.dart ├── functions │ ├── metadata.json │ └── main.dart ├── static │ ├── metadata.json │ └── main.dart ├── widgets │ ├── metadata.json │ └── main.dart ├── pubspec.yaml ├── package_plugins.dart ├── plugin_base.json └── pubspec.lock ├── benchmark ├── README.md ├── pubspec.yaml ├── benchmark │ ├── main_benchmark.dart │ └── counter.dart └── pubspec.lock ├── android ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── ilya │ │ │ │ │ └── zverev │ │ │ │ │ └── info │ │ │ │ │ └── extension_test │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── tool ├── test_plugin.sh ├── generate_bindings.dart └── test_plugin.dart ├── LICENSE ├── README.md ├── pubspec.yaml ├── .gitignore ├── .metadata ├── lib ├── models.dart ├── plugins.dart ├── main.dart ├── plugin_provider.dart └── bridges.dart ├── analysis_options.yaml └── pubspec.lock /assets/idk.none: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/models.dart: -------------------------------------------------------------------------------- 1 | ../lib/models.dart -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Dart Eval Quick Benchmark 2 | 3 | Run it as `dart run benchmark`. 4 | -------------------------------------------------------------------------------- /plugins/assets/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "assets", 3 | "name": "Nice photo", 4 | "version": 1 5 | } 6 | -------------------------------------------------------------------------------- /plugins/assets/workplace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/plugins/assets/workplace.jpg -------------------------------------------------------------------------------- /plugins/functions/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "functions", 3 | "name": "Add Minus Button", 4 | "version": 1 5 | } 6 | -------------------------------------------------------------------------------- /plugins/static/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "static", 3 | "name": "Start from 9, increment by 2", 4 | "version": 1 5 | } 6 | -------------------------------------------------------------------------------- /plugins/widgets/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "widgets", 3 | "name": "Big number and persistent counter", 4 | "version": 1 5 | } 6 | -------------------------------------------------------------------------------- /plugins/static/main.dart: -------------------------------------------------------------------------------- 1 | import '../models.dart'; 2 | 3 | PluginBase setup(PluginContext context) => PluginBase(context, initial: 9, step: 2); 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/flutter-plugins-test/main/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/ilya/zverev/info/extension_test/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package ilya.zverev.info.extension_test 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /benchmark/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_eval_bench 2 | description: 'Dart Eval Benchmark' 3 | publish_to: 'none' 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ^3.6.0 9 | 10 | dependencies: 11 | dart_eval: ^0.7.10 12 | benchmark: ^0.3.0 13 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /tool/test_plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e -u 3 | if [ $# -lt 1 ]; then 4 | echo Usage: $0 [c] plugin_path 5 | exit 1 6 | fi 7 | 8 | PROJ_DIR="$(cd "$(dirname "$0")/.." > /dev/null; pwd -P)" 9 | 10 | if [ "$1" == "c" ]; then 11 | echo Compiling... 12 | cd "$PROJ_DIR/plugins" 13 | dart package_plugins.dart 14 | shift 15 | fi 16 | 17 | cd "$PROJ_DIR" 18 | flutter test tool/test_plugin.dart --dart-define "PLUGIN=$1" 19 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /plugins/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: package_plugins 2 | description: 'Plugin Packager' 3 | publish_to: 'none' 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ^3.6.0 9 | 10 | dependencies: 11 | dart_eval: ^0.7.10 12 | flutter_eval: ^0.7.6 13 | archive: ^4.0.2 14 | path: ^1.9.1 15 | 16 | dependency_overrides: 17 | flutter_eval: 18 | git: 19 | url: https://github.com/Zverik/flutter_eval.git 20 | ref: flutter_3_27 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Plugin Test 2 | 3 | This is an experiment in creating a plugin infrastructure around the 4 | Flutter demo app. 5 | 6 | A plugin is a zip archive, which must contain at least a `metadata.json` 7 | file with `id` and `name` fields, and a code compiled to `plugin.evc`. 8 | See the `plugins` directory for examples and for a build script. 9 | Open a plugin with the app to install it. 10 | 11 | To compile and test plugins, run `tool/test_plugin.sh c plugins/` 12 | 13 | ## Author and License 14 | 15 | Written by Ilya Zverev, published under the WTFPL. 16 | -------------------------------------------------------------------------------- /tool/generate_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dart_eval/dart_eval_bridge.dart'; 5 | import 'package:extension_test/bridges.dart'; 6 | 7 | void main() { 8 | // To properly generate the bindings, dart_eval needs Flutter context which is 9 | // delivered when running Dart programs via `flutter test`, 10 | // but isn't for Dart code ran via `dart run`. 11 | final serializer = BridgeSerializer(); 12 | serializer.addPlugin(const PluginBasePlugin()); 13 | final output = serializer.serialize(); 14 | File('plugins/plugin_base.json').writeAsStringSync(json.encode(output)); 15 | } 16 | -------------------------------------------------------------------------------- /plugins/assets/main.dart: -------------------------------------------------------------------------------- 1 | import '../models.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AssetPlugin extends PluginBase { 5 | AssetPlugin(PluginContext context) : super(context); 6 | 7 | @override 8 | Widget? numberWidget(BuildContext context, AppState state) { 9 | return Stack(children: [ 10 | Image.file( 11 | this.context.getFile('workplace.jpg'), 12 | fit: BoxFit.fill, 13 | ), 14 | Padding( 15 | padding: const EdgeInsets.only(left: 20), 16 | child: Text( 17 | state.counter.toString(), 18 | style: TextStyle(color: Colors.blue, fontSize: 100), 19 | ), 20 | ), 21 | ]); 22 | } 23 | } 24 | 25 | PluginBase setup(PluginContext context) => AssetPlugin(context); 26 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.3.2" apply false 22 | id "org.jetbrains.kotlin.android" version "2.0.20" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /plugins/functions/main.dart: -------------------------------------------------------------------------------- 1 | import '../models.dart'; 2 | import 'dart:math'; 3 | 4 | class FunctionPlugin extends PluginBase { 5 | final String _kMinusButton = '-1'; 6 | final String _kResetButton = 'X'; 7 | final Random _random = Random(); 8 | 9 | FunctionPlugin(PluginContext context) : super(context) { 10 | // buttons.add(_kMinusButton); 11 | } 12 | 13 | @override 14 | List get buttons => [_kResetButton, _kMinusButton]; 15 | 16 | @override 17 | int get step => _random.nextInt(5); 18 | 19 | @override 20 | void onButtonTapped(String button, AppState state) { 21 | if (button == _kMinusButton) { 22 | state.counter -= 1; 23 | } else if (button == _kResetButton) { 24 | state.counter = state.initial; 25 | } 26 | } 27 | } 28 | 29 | PluginBase setup(PluginContext context) => FunctionPlugin(context); 30 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: extension_test 2 | description: "Testing eval and Lua for plugins" 3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ^3.6.0 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | receive_sharing_intent: 14 | git: 15 | url: https://github.com/KasemJaffer/receive_sharing_intent.git 16 | flutter_archive: ^6.0.3 17 | path_provider: ^2.1.5 18 | shared_preferences: ^2.3.4 19 | flutter_eval: ^0.7.6 20 | dart_eval: ^0.7.10 21 | 22 | dependency_overrides: 23 | flutter_eval: 24 | git: 25 | url: https://github.com/Zverik/flutter_eval.git 26 | ref: flutter_3_27 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | flutter_lints: ^5.0.0 32 | 33 | flutter: 34 | uses-material-design: true 35 | assets: 36 | - assets/ 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | plugins/*/*.evc 3 | plugins/test_eval/ 4 | 5 | # Miscellaneous 6 | *.class 7 | *.log 8 | *.pyc 9 | *.swp 10 | .DS_Store 11 | .atom/ 12 | .build/ 13 | .buildlog/ 14 | .history 15 | .svn/ 16 | .swiftpm/ 17 | migrate_working_dir/ 18 | 19 | # IntelliJ related 20 | *.iml 21 | *.ipr 22 | *.iws 23 | .idea/ 24 | 25 | # The .vscode folder contains launch configuration and tasks you configure in 26 | # VS Code which you may wish to be included in version control, so this line 27 | # is commented out by default. 28 | #.vscode/ 29 | 30 | # Flutter/Dart/Pub related 31 | **/doc/api/ 32 | **/ios/Flutter/.last_build_id 33 | .dart_tool/ 34 | .flutter-plugins 35 | .flutter-plugins-dependencies 36 | .pub-cache/ 37 | .pub/ 38 | /build/ 39 | 40 | # Symbolication related 41 | app.*.symbols 42 | 43 | # Obfuscation related 44 | app.*.map.json 45 | 46 | # Android Studio will place build artifacts here 47 | /android/app/debug 48 | /android/app/profile 49 | /android/app/release 50 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 17 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 18 | - platform: android 19 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 20 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 21 | - platform: ios 22 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 23 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /lib/models.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// This class has all the values for the state. 6 | /// The main one is [counter], which replaces the old 7 | /// state variable from the demo app. 8 | class AppState extends ChangeNotifier { 9 | int initial = 0; 10 | int step = 1; 11 | int _counter = 0; 12 | 13 | int get counter => _counter; 14 | 15 | set counter(int value) { 16 | _counter = value; 17 | notifyListeners(); 18 | } 19 | 20 | @override 21 | String toString() => 'AppState($_counter, ($initial, $step))'; 22 | } 23 | 24 | abstract class PluginContext implements Listenable { 25 | final AppState state; 26 | PluginContext(this.state); 27 | 28 | Future readPreference(String key); 29 | Future savePreference(String key, int value); 30 | void repaint(); 31 | File getFile(String name); 32 | } 33 | 34 | class PluginBase { 35 | int initial; 36 | int step; 37 | final PluginContext context; 38 | List buttons = []; 39 | 40 | PluginBase(this.context, {this.initial = 0, this.step = 1}); 41 | 42 | dynamic init() {} 43 | 44 | void onButtonTapped(String button, AppState state) {} 45 | 46 | void onCounterChanged(AppState state) {} 47 | 48 | Widget? numberWidget(BuildContext context, AppState state) { 49 | return null; 50 | } 51 | 52 | Widget? settingsWidget(BuildContext context) { 53 | return null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | android { 9 | namespace = "ilya.zverev.info.extension_test" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "ilya.zverev.info.extension_test" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /plugins/widgets/main.dart: -------------------------------------------------------------------------------- 1 | import '../models.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class WidgetPlugin extends PluginBase { 5 | bool needSave = true; 6 | bool readValue = false; 7 | 8 | WidgetPlugin(PluginContext context) : super(context); 9 | 10 | @override 11 | Future init() async { 12 | final needSaveInt = await context.readPreference('widget-save'); 13 | needSave = needSaveInt == 1; 14 | if (needSave) { 15 | final int? value = await context.readPreference('widget-counter'); 16 | if (value != null) context.state.counter = value; 17 | } 18 | readValue = true; 19 | } 20 | 21 | @override 22 | void onCounterChanged(AppState state) { 23 | if (needSave && readValue) { 24 | context.savePreference('widget-counter', state.counter); 25 | } 26 | } 27 | 28 | @override 29 | Widget? numberWidget(BuildContext context, AppState state) { 30 | return Text( 31 | state.counter.toString(), 32 | style: TextStyle( 33 | color: Colors.red, 34 | fontSize: 300, 35 | fontWeight: FontWeight.bold, 36 | ), 37 | ); 38 | } 39 | 40 | @override 41 | Widget? settingsWidget(BuildContext context) { 42 | return Column( 43 | children: [ 44 | SwitchListTile( 45 | title: Text('Persist counter'), 46 | value: needSave, 47 | onChanged: (value) { 48 | needSave = value; 49 | this.context.savePreference('widget-save', needSave ? 1 : 0); 50 | if (needSave) { 51 | onCounterChanged(this.context.state); 52 | } 53 | this.context.repaint(); 54 | }, 55 | ), 56 | ], 57 | ); 58 | } 59 | } 60 | 61 | PluginBase setup(PluginContext context) => WidgetPlugin(context); 62 | -------------------------------------------------------------------------------- /benchmark/benchmark/main_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_eval/dart_eval.dart'; 2 | import 'package:benchmark/benchmark.dart'; 3 | import 'counter.dart'; 4 | 5 | class IncrementerSub extends Incrementer { 6 | IncrementerSub(super.c); 7 | 8 | @override 9 | doJobOverride() { 10 | counter.increment(); 11 | } 12 | } 13 | 14 | void main() { 15 | final code = ''' 16 | import 'package:test/counter.dart'; 17 | 18 | void increment(Counter c) { 19 | c.increment(); 20 | } 21 | 22 | class IncrementerSub extends Incrementer { 23 | IncrementerSub(Counter c): super(c); 24 | 25 | @override 26 | doJobSuper() { 27 | super.doJobSuper(); 28 | } 29 | 30 | @override 31 | doJobOverride() { 32 | counter.increment(); 33 | } 34 | } 35 | 36 | Incrementer construct(Counter c) => IncrementerSub(c); 37 | '''; 38 | final compiler = Compiler(); 39 | compiler.addPlugin(CounterPlugin()); 40 | final program = compiler.compile({ 41 | kPackageName: {'main.dart': code}, 42 | }); 43 | final runtime = Runtime.ofProgram(program); 44 | runtime.addPlugin(CounterPlugin()); 45 | 46 | const kIterations = 100000; 47 | final counter = Counter(); 48 | final incrementer = runtime.executeLib( 49 | 'package:$kPackageName/main.dart', 'construct', [$Counter.wrap(counter)]); 50 | final rawIncrementer = IncrementerSub(counter); 51 | 52 | group('Counter tests', () { 53 | setUp(() { 54 | counter.reset(); 55 | }); 56 | 57 | tearDown(() { 58 | assert(counter.count == kIterations); 59 | }); 60 | 61 | benchmark('Raw increment', () { 62 | counter.increment(); 63 | }, iterations: kIterations); 64 | 65 | benchmark('Raw class increment', () { 66 | rawIncrementer.doJobOverride(); 67 | }, iterations: kIterations); 68 | 69 | benchmark('Eval function', () { 70 | runtime.executeLib('package:test/main.dart', 'increment', [$Counter.wrap(counter)]); 71 | }, iterations: kIterations); 72 | 73 | benchmark('Eval original', () { 74 | incrementer.doJobOriginal(); 75 | }, iterations: kIterations); 76 | 77 | benchmark('Eval super', () { 78 | incrementer.doJobSuper(); 79 | }, iterations: kIterations); 80 | 81 | benchmark('Eval overidden', () { 82 | incrementer.doJobOverride(); 83 | }, iterations: kIterations); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /lib/plugins.dart: -------------------------------------------------------------------------------- 1 | import 'package:extension_test/plugin_provider.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class PluginsPage extends StatefulWidget { 5 | const PluginsPage({super.key}); 6 | 7 | @override 8 | State createState() => _PluginsPageState(); 9 | } 10 | 11 | class _PluginsPageState extends State { 12 | void _repaint() { 13 | if (mounted) { 14 | setState(() {}); 15 | } 16 | } 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | PluginProvider.instance.addListener(_repaint); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | PluginProvider.instance.removeListener(_repaint); 27 | super.dispose(); 28 | } 29 | 30 | List _buildPluginRow(BuildContext context, Plugin plugin) { 31 | final pp = PluginProvider.instance; 32 | final settings = pp.buildSettingsWidget(context, plugin.id); 33 | return [ 34 | Dismissible( 35 | key: Key(plugin.id), 36 | direction: DismissDirection.endToStart, 37 | background: Container( 38 | color: Colors.red, 39 | padding: EdgeInsets.only(right: 15.0), 40 | alignment: Alignment.centerRight, 41 | child: Icon(Icons.delete, color: Colors.white), 42 | ), 43 | onDismissed: (_) async { 44 | // Plugin deletion is easily reversible (by installing it anew), 45 | // so we don't ask the user again. 46 | await pp.deletePlugin(plugin.id); 47 | if (mounted) { 48 | setState(() {}); 49 | } 50 | }, 51 | child: ListTile( 52 | title: Text(plugin.name), 53 | trailing: pp.isActive(plugin.id) ? Icon(Icons.check_circle) : null, 54 | onTap: () async { 55 | // We need [setState], because enabling the plugin may 56 | // change something in the app state or visually. 57 | await pp.toggle(plugin.id); 58 | setState(() {}); 59 | }, 60 | ), 61 | ), 62 | if (settings != null) 63 | Padding( 64 | padding: const EdgeInsets.only(left: 10.0), 65 | child: settings, 66 | ), 67 | Divider(), 68 | ]; 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | final pp = PluginProvider.instance; 74 | 75 | return Scaffold( 76 | appBar: AppBar(title: Text('Plugins')), 77 | body: ListView( 78 | children: [ 79 | for (final plugin in pp.all) ..._buildPluginRow(context, plugin), 80 | ], 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /plugins/package_plugins.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:io'; 3 | import 'package:archive/archive_io.dart' show ZipFileEncoder; 4 | import 'package:path/path.dart' as path; 5 | import 'package:dart_eval/dart_eval_bridge.dart'; 6 | import 'package:dart_eval/dart_eval.dart' show Compiler; 7 | 8 | Map>? loadPluginData(Directory dir) { 9 | final files = {}; 10 | 11 | // Now iterate over files and also replace the models import. 12 | for (final file in dir.listSync().whereType()) { 13 | // We're not doing subdirectories or external packages. 14 | if (file.path.endsWith('.dart')) { 15 | String code = file.readAsStringSync(); 16 | code = code.replaceFirst('../models.dart', 'package:plugin/_models.dart'); 17 | final fileName = path.relative(file.path, from: dir.path); 18 | files[fileName] = code; 19 | } 20 | } 21 | 22 | // Finally compile. 23 | if (files.isEmpty) return null; 24 | return {'plugin': files}; 25 | } 26 | 27 | void loadBindings(Compiler compiler) { 28 | const bindingFiles = ['flutter_eval.json', 'plugin_base.json']; 29 | for (final name in bindingFiles) { 30 | final file = File(name); 31 | if (!file.existsSync()) { 32 | throw FileSystemException('File $name is missing'); 33 | } 34 | // Copied from dart_eval compiler. 35 | final data = file.readAsStringSync(); 36 | final decoded = (json.decode(data) as Map).cast(); 37 | final classList = (decoded['classes'] as List); 38 | for (final $class in classList.cast()) { 39 | compiler.defineBridgeClass(BridgeClassDef.fromJson($class.cast())); 40 | } 41 | for (final $enum in (decoded['enums'] as List).cast()) { 42 | compiler.defineBridgeEnum(BridgeEnumDef.fromJson($enum.cast())); 43 | } 44 | for (final $source in (decoded['sources'] as List).cast()) { 45 | compiler.addSource(DartSource($source['uri'], $source['source'])); 46 | } 47 | for (final $function in (decoded['functions'] as List).cast()) { 48 | compiler.defineBridgeTopLevelFunction( 49 | BridgeFunctionDeclaration.fromJson($function.cast())); 50 | } 51 | } 52 | } 53 | 54 | void main() async { 55 | final compiler = Compiler(); 56 | loadBindings(compiler); 57 | 58 | for (final dir in Directory(path.current).listSync().whereType()) { 59 | final metadataFile = File(path.join(dir.path, 'metadata.json')); 60 | if (metadataFile.existsSync()) { 61 | // Found a plugin. Compile the code. 62 | final programData = loadPluginData(dir); 63 | if (programData == null) continue; 64 | final program = compiler.compile(programData); 65 | final programBytes = program.write(); 66 | File(path.join(dir.path, 'plugin.evc')).writeAsBytesSync(programBytes); 67 | 68 | // Prepare an archive. 69 | final zipFile = File(path.join(path.current, '${dir.path}.zip')); 70 | await ZipFileEncoder().zipDirectory(dir, filename: zipFile.path); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tool/test_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'dart:ui'; 4 | 5 | import 'package:dart_eval/dart_eval.dart'; 6 | import 'package:dart_eval/dart_eval_security.dart'; 7 | import 'package:extension_test/bridges.dart'; 8 | import 'package:extension_test/models.dart'; 9 | import 'package:flutter/widgets.dart'; 10 | import 'package:flutter_eval/flutter_eval.dart'; 11 | import 'package:flutter_test/flutter_test.dart'; 12 | import 'package:path_provider/path_provider.dart'; 13 | 14 | class PluginContextImpl extends PluginContext { 15 | final Directory pluginDir; 16 | final Map storage = {}; 17 | 18 | PluginContextImpl(super.state, this.pluginDir); 19 | 20 | @override 21 | File getFile(String name) { 22 | return File('${pluginDir.path}/$name'); 23 | } 24 | 25 | @override 26 | Future readPreference(String key) async { 27 | return storage[key]; 28 | } 29 | 30 | @override 31 | Future savePreference(String key, int value) async { 32 | storage[key] = value; 33 | } 34 | 35 | @override 36 | void addListener(VoidCallback listener) {} 37 | 38 | @override 39 | void removeListener(VoidCallback listener) {} 40 | 41 | @override 42 | void repaint() {} 43 | } 44 | 45 | Future installFromArchive(File file) async { 46 | throw UnsupportedError('Sorry, archives are not supported yet.'); 47 | final tmpDir = await getTemporaryDirectory(); 48 | // TODO: unzip 49 | return installFromDirectory(tmpDir); 50 | // TODO: clean up? 51 | } 52 | 53 | PluginBase installFromDirectory(Directory dir) { 54 | final evc = File('${dir.path}/plugin.evc'); 55 | final data = evc.readAsBytesSync(); 56 | final runtime = Runtime(ByteData.sublistView(data)); 57 | runtime.addPlugin(FlutterEvalPlugin()); 58 | runtime.addPlugin(PluginBasePlugin()); 59 | runtime.grant(FilesystemPermission.directory(dir.path)); 60 | final context = PluginContextImpl(AppState(), dir); 61 | return runtime.executeLib( 62 | 'package:plugin/main.dart', 'setup', [$PluginContext.wrap(context)]); 63 | } 64 | 65 | void log(String msg) { 66 | print(msg); 67 | } 68 | 69 | void main() async { 70 | const path = String.fromEnvironment('PLUGIN'); 71 | if (path.isEmpty) { 72 | log('Usage: flutter test test_plugin.dart --dart-define PLUGIN='); 73 | return; 74 | } 75 | 76 | final fileType = FileSystemEntity.typeSync(path); 77 | PluginBase plugin; 78 | switch (fileType) { 79 | case FileSystemEntityType.file: 80 | plugin = await installFromArchive(File(path)); 81 | case FileSystemEntityType.directory: 82 | plugin = installFromDirectory(Directory(path)); 83 | default: 84 | log("Cannot read entity '$path'."); 85 | return; 86 | } 87 | 88 | log('Plugin installed from "$path"'); 89 | final state = plugin.context.state; 90 | if (state.counter == 0) state.counter = plugin.initial; 91 | state.step = plugin.step; 92 | await plugin.init(); 93 | 94 | test('fields', () { 95 | log(' initial = ${plugin.initial}'); 96 | log(' step = ${plugin.step}'); 97 | log(' buttons = ${plugin.buttons}'); 98 | }); 99 | 100 | test('functions', () { 101 | log(' state: $state'); 102 | state.counter += plugin.step; 103 | log(' after increment: $state'); 104 | 105 | for (final button in plugin.buttons) { 106 | plugin.onButtonTapped(button, state); 107 | log(' after pressing "$button": $state'); 108 | } 109 | }); 110 | 111 | testWidgets('widgets', (tester) async { 112 | await tester.pumpWidget(Builder( 113 | builder: (context) { 114 | log(' numberWidget is ${plugin.numberWidget(context, state)}'); 115 | log(' settingsWidget is ${plugin.settingsWidget(context)}'); 116 | return Placeholder(); 117 | }, 118 | )); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:extension_test/plugin_provider.dart'; 4 | import 'package:extension_test/plugins.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:receive_sharing_intent/receive_sharing_intent.dart'; 7 | 8 | void main() async { 9 | // We should have just called [PluginProvider.load] here, 10 | // but this way it's... cleaner? idk. 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | await PluginProvider.instance.ready; 13 | runApp(const MyApp()); 14 | } 15 | 16 | class MyApp extends StatelessWidget { 17 | const MyApp({super.key}); 18 | 19 | // This widget is the root of your application. 20 | @override 21 | Widget build(BuildContext context) { 22 | return MaterialApp( 23 | title: 'Flutter Plugins Demo', 24 | theme: ThemeData( 25 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 26 | useMaterial3: true, 27 | ), 28 | home: const MyHomePage(title: 'Flutter Plugins Demo'), 29 | ); 30 | } 31 | } 32 | 33 | class MyHomePage extends StatefulWidget { 34 | const MyHomePage({super.key, required this.title}); 35 | 36 | final String title; 37 | 38 | @override 39 | State createState() => _MyHomePageState(); 40 | } 41 | 42 | class _MyHomePageState extends State { 43 | late StreamSubscription _intentSub; 44 | static final PluginProvider pp = PluginProvider.instance; 45 | 46 | Future _gotPlugins(List files) async { 47 | for (final sharedFile in files) { 48 | // This is a debug print to learn file's name and mime type. 49 | print(sharedFile.toMap()); 50 | try { 51 | await pp.install(sharedFile.path); 52 | } on PluginLoadException catch (e) { 53 | print('Error: $e'); 54 | } 55 | } 56 | 57 | // If we don't include this line, we will get the same event multiple 58 | // times — but the file would be absent already. 59 | ReceiveSharingIntent.instance.reset(); 60 | 61 | if (mounted) { 62 | setState(() {}); 63 | } 64 | } 65 | 66 | void _repaint() { 67 | if (mounted) { 68 | setState(() {}); 69 | } 70 | } 71 | 72 | @override 73 | void initState() { 74 | super.initState(); 75 | 76 | pp.addListener(_repaint); 77 | 78 | // Subscribing to the incoming files stream. 79 | _intentSub = ReceiveSharingIntent.instance 80 | .getMediaStream() 81 | .listen(_gotPlugins, onError: (err) { 82 | print('Intent data stream error: $err'); 83 | }); 84 | 85 | // Processing the file we've been called with. 86 | ReceiveSharingIntent.instance.getInitialMedia().then(_gotPlugins); 87 | } 88 | 89 | @override 90 | void dispose() { 91 | _intentSub.cancel(); 92 | pp.removeListener(_repaint); 93 | super.dispose(); 94 | } 95 | 96 | void _incrementCounter() { 97 | setState(() { 98 | pp.onIncrementTap(); 99 | }); 100 | } 101 | 102 | @override 103 | Widget build(BuildContext context) { 104 | Widget? numberWidget = pp.buildNumberWidget(context); 105 | numberWidget ??= Column( 106 | mainAxisAlignment: MainAxisAlignment.center, 107 | children: [ 108 | const Text( 109 | 'You have pushed the button this many times:', 110 | ), 111 | Text( 112 | '${pp.state.counter}', 113 | style: Theme.of(context).textTheme.headlineMedium, 114 | ), 115 | ], 116 | ); 117 | 118 | return Scaffold( 119 | appBar: AppBar( 120 | backgroundColor: Theme.of(context).colorScheme.inversePrimary, 121 | title: Text(widget.title), 122 | actions: [ 123 | // One new button, for managing plugins. 124 | IconButton( 125 | icon: Icon(Icons.electrical_services), 126 | onPressed: () { 127 | Navigator.of(context).push(MaterialPageRoute( 128 | builder: (_) => PluginsPage(), 129 | )); 130 | }, 131 | ), 132 | ], 133 | ), 134 | body: Stack(children: [ 135 | Center( 136 | child: numberWidget, 137 | ), 138 | Align( 139 | alignment: Alignment.bottomRight, 140 | child: Padding( 141 | padding: const EdgeInsets.only(bottom: 90, right: 12), 142 | child: Column( 143 | mainAxisAlignment: MainAxisAlignment.end, 144 | children: [ 145 | for (final button in pp.getButtons()) 146 | FloatingActionButton( 147 | heroTag: 'floating_hero_$button', 148 | onPressed: () { 149 | setState(() { 150 | pp.onButtonTap(button); 151 | }); 152 | }, 153 | child: Text(button), 154 | ), 155 | ], 156 | ), 157 | ), 158 | ), 159 | ]), 160 | floatingActionButton: FloatingActionButton( 161 | onPressed: _incrementCounter, 162 | tooltip: 'Increment', 163 | child: const Icon(Icons.add), 164 | ), 165 | ); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /benchmark/benchmark/counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_eval/dart_eval_bridge.dart'; 2 | import 'package:dart_eval/stdlib/core.dart'; 3 | 4 | const kPackageName = 'test'; 5 | const _kModuleName = 'package:$kPackageName/counter.dart'; 6 | 7 | class Counter { 8 | int _count = 0; 9 | 10 | int get count => _count; 11 | 12 | void increment() { 13 | _count += 1; 14 | } 15 | 16 | void reset() { 17 | _count = 0; 18 | } 19 | } 20 | 21 | class Incrementer { 22 | final Counter counter; 23 | 24 | Incrementer(this.counter); 25 | 26 | void doJobOriginal() { 27 | counter.increment(); 28 | } 29 | 30 | void doJobSuper() { 31 | counter.increment(); 32 | } 33 | 34 | void doJobOverride() {} 35 | } 36 | 37 | class CounterPlugin implements EvalPlugin { 38 | const CounterPlugin(); 39 | 40 | @override 41 | String get identifier => _kModuleName.substring(0, _kModuleName.indexOf('/')); 42 | 43 | @override 44 | void configureForCompile(BridgeDeclarationRegistry registry) { 45 | registry.defineBridgeClass($Counter.$declaration); 46 | registry.defineBridgeClass($Incrementer$bridge.$declaration); 47 | } 48 | 49 | @override 50 | void configureForRuntime(Runtime runtime) { 51 | runtime.registerBridgeFunc( 52 | _kModuleName, 53 | 'Incrementer.', 54 | $Incrementer$bridge.$new, 55 | isBridge: true, 56 | ); 57 | } 58 | } 59 | 60 | class $Counter implements $Instance { 61 | static const $type = BridgeTypeRef(BridgeTypeSpec(_kModuleName, 'Counter')); 62 | 63 | static const $declaration = BridgeClassDef( 64 | BridgeClassType($type), 65 | constructors: {}, 66 | methods: { 67 | 'increment': BridgeMethodDef(BridgeFunctionDef( 68 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 69 | )), 70 | 'reset': BridgeMethodDef(BridgeFunctionDef( 71 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 72 | )), 73 | }, 74 | getters: { 75 | 'count': BridgeMethodDef(BridgeFunctionDef( 76 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), 77 | )), 78 | }, 79 | wrap: true, 80 | ); 81 | 82 | @override 83 | final Counter $value; 84 | 85 | @override 86 | get $reified => $value; 87 | 88 | $Counter.wrap(this.$value); 89 | 90 | @override 91 | int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!); 92 | 93 | @override 94 | $Value? $getProperty(Runtime runtime, String identifier) { 95 | switch (identifier) { 96 | case 'increment': 97 | return $Function(_increment); 98 | case 'reset': 99 | return $Function(_reset); 100 | case 'count': 101 | return $int($value.count); 102 | } 103 | return $Object(this).$getProperty(runtime, identifier); 104 | } 105 | 106 | @override 107 | void $setProperty(Runtime runtime, String identifier, $Value value) { 108 | $Object(this).$setProperty(runtime, identifier, value); 109 | } 110 | 111 | static $Value? _increment(Runtime runtime, $Value? target, List<$Value?> args) { 112 | final ctx = target!.$value as Counter; 113 | ctx.increment(); 114 | return null; 115 | } 116 | 117 | static $Value? _reset(Runtime runtime, $Value? target, List<$Value?> args) { 118 | final ctx = target!.$value as Counter; 119 | ctx.reset(); 120 | return null; 121 | } 122 | } 123 | 124 | class $Incrementer$bridge extends Incrementer with $Bridge { 125 | static const $type = BridgeTypeRef(BridgeTypeSpec(_kModuleName, 'Incrementer')); 126 | 127 | static const $declaration = BridgeClassDef( 128 | BridgeClassType($type), 129 | constructors: { 130 | '': BridgeConstructorDef(BridgeFunctionDef( 131 | returns: BridgeTypeAnnotation($type), 132 | params: [ 133 | BridgeParameter( 134 | 'counter', BridgeTypeAnnotation($Counter.$type), false), 135 | ], 136 | )), 137 | }, 138 | methods: { 139 | 'doJobOriginal': BridgeMethodDef(BridgeFunctionDef( 140 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 141 | )), 142 | 'doJobSuper': BridgeMethodDef(BridgeFunctionDef( 143 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 144 | )), 145 | 'doJobOverride': BridgeMethodDef(BridgeFunctionDef( 146 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 147 | )), 148 | }, 149 | fields: { 150 | 'counter': BridgeFieldDef(BridgeTypeAnnotation($Counter.$type)), 151 | }, 152 | bridge: true, 153 | ); 154 | 155 | $Incrementer$bridge(super.counter); 156 | 157 | static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) { 158 | final counter = args[0]! as $Counter; 159 | return $Incrementer$bridge(counter.$value); 160 | } 161 | 162 | @override 163 | $Value? $bridgeGet(String identifier) { 164 | switch (identifier) { 165 | case 'counter': 166 | return $Counter.wrap(super.counter); 167 | case 'doJobOriginal': 168 | return $Function((runtime, target, args) { 169 | super.doJobOriginal(); 170 | return null; 171 | }); 172 | case 'doJobSuper': 173 | return $Function((runtime, target, args) { 174 | super.doJobSuper(); 175 | return null; 176 | }); 177 | case 'doJobOverride': 178 | return $Function((runtime, target, args) { 179 | super.doJobOverride(); 180 | return null; 181 | }); 182 | } 183 | throw UnimplementedError('Property does not exist: "$identifier"'); 184 | } 185 | 186 | @override 187 | void $bridgeSet(String identifier, $Value value) { 188 | throw UnimplementedError('Cannot set property: "$identifier"'); 189 | } 190 | 191 | @override 192 | doJobOriginal() { 193 | $_invoke('doJobOriginal', []); 194 | } 195 | 196 | @override 197 | doJobSuper() { 198 | $_invoke('doJobSuper', []); 199 | } 200 | 201 | @override 202 | doJobOverride() { 203 | $_invoke('doJobOverride', []); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /plugins/plugin_base.json: -------------------------------------------------------------------------------- 1 | {"classes":[{"type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"AppState"},"typeArgs":[]},"isAbstract":false,"$extends":{"unresolved":{"library":"dart:core","name":"Object"},"typeArgs":[]},"$implements":[],"$with":[],"generics":{}},"constructors":{},"methods":{"increment":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"void"},"typeArgs":[]},"nullable":false},"generics":{},"params":[],"namedParams":[]},"isStatic":false}},"getters":{"counter":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"generics":{},"params":[],"namedParams":[]},"isStatic":false}},"setters":{"counter":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"void"},"typeArgs":[]},"nullable":false},"generics":{},"params":[{"name":"value","type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false}},"fields":{"initial":{"type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"isStatic":false},"step":{"type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"isStatic":false}},"bridge":false,"wrap":true},{"type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"PluginContext"},"typeArgs":[]},"isAbstract":true,"$extends":{"unresolved":{"library":"dart:core","name":"Object"},"typeArgs":[]},"$implements":[{"unresolved":{"library":"package:flutter/src/foundation/change_notifier.dart","name":"Listenable"},"typeArgs":[]}],"$with":[],"generics":{}},"constructors":{},"methods":{"readPreference":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"Future"},"typeArgs":[{"unresolved":{"library":"dart:core","name":"dynamic"},"typeArgs":[]}]},"nullable":false},"generics":{},"params":[{"name":"key","type":{"type":{"unresolved":{"library":"dart:core","name":"String"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false},"savePreference":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"Future"},"typeArgs":[{"unresolved":{"library":"dart:core","name":"void"},"typeArgs":[]}]},"nullable":false},"generics":{},"params":[{"name":"key","type":{"type":{"unresolved":{"library":"dart:core","name":"String"},"typeArgs":[]},"nullable":false},"optional":false},{"name":"value","type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false},"repaint":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"void"},"typeArgs":[]},"nullable":false},"generics":{},"params":[],"namedParams":[]},"isStatic":false},"getFile":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:io","name":"File"},"typeArgs":[]},"nullable":false},"generics":{},"params":[{"name":"name","type":{"type":{"unresolved":{"library":"dart:core","name":"String"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false}},"getters":{},"setters":{},"fields":{"state":{"type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"AppState"},"typeArgs":[]},"nullable":false},"isStatic":false}},"bridge":false,"wrap":true},{"type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"PluginBase"},"typeArgs":[]},"isAbstract":false,"$extends":{"unresolved":{"library":"dart:core","name":"Object"},"typeArgs":[]},"$implements":[],"$with":[],"generics":{}},"constructors":{"":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"PluginBase"},"typeArgs":[]},"nullable":false},"generics":{},"params":[{"name":"context","type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"PluginContext"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[{"name":"initial","type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"optional":true},{"name":"step","type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"optional":true}]},"isFactory":false}},"methods":{"init":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"dynamic"},"typeArgs":[]},"nullable":false},"generics":{},"params":[],"namedParams":[]},"isStatic":false},"onButtonTapped":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"void"},"typeArgs":[]},"nullable":false},"generics":{},"params":[{"name":"button","type":{"type":{"unresolved":{"library":"package:flutter/src/widgets/icon_data.dart","name":"IconData"},"typeArgs":[]},"nullable":false},"optional":false},{"name":"state","type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"AppState"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false},"onCounterChanged":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"dart:core","name":"void"},"typeArgs":[]},"nullable":false},"generics":{},"params":[{"name":"state","type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"AppState"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false},"numberWidget":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"package:flutter/widgets.dart","name":"Widget"},"typeArgs":[]},"nullable":true},"generics":{},"params":[{"name":"context","type":{"type":{"unresolved":{"library":"package:flutter/widgets.dart","name":"BuildContext"},"typeArgs":[]},"nullable":false},"optional":false},{"name":"state","type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"AppState"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false},"settingsWidget":{"functionDescriptor":{"returns":{"type":{"unresolved":{"library":"package:flutter/widgets.dart","name":"Widget"},"typeArgs":[]},"nullable":true},"generics":{},"params":[{"name":"context","type":{"type":{"unresolved":{"library":"package:flutter/widgets.dart","name":"BuildContext"},"typeArgs":[]},"nullable":false},"optional":false}],"namedParams":[]},"isStatic":false}},"getters":{},"setters":{},"fields":{"initial":{"type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"isStatic":false},"step":{"type":{"type":{"unresolved":{"library":"dart:core","name":"int"},"typeArgs":[]},"nullable":false},"isStatic":false},"context":{"type":{"type":{"unresolved":{"library":"package:plugin/_models.dart","name":"PluginContext"},"typeArgs":[]},"nullable":false},"isStatic":false},"buttons":{"type":{"type":{"unresolved":{"library":"dart:core","name":"List"},"typeArgs":[{"unresolved":{"library":"package:flutter/src/widgets/icon_data.dart","name":"IconData"},"typeArgs":[]}]},"nullable":false},"isStatic":false}},"bridge":true,"wrap":false}],"enums":[],"functions":[],"sources":[]} -------------------------------------------------------------------------------- /benchmark/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "76.0.0" 12 | _macros: 13 | dependency: transitive 14 | description: dart 15 | source: sdk 16 | version: "0.3.3" 17 | analyzer: 18 | dependency: transitive 19 | description: 20 | name: analyzer 21 | sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" 22 | url: "https://pub.dev" 23 | source: hosted 24 | version: "6.11.0" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "2.6.0" 33 | async: 34 | dependency: transitive 35 | description: 36 | name: async 37 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "2.12.0" 41 | benchmark: 42 | dependency: "direct main" 43 | description: 44 | name: benchmark 45 | sha256: cb3eeea01e3f054df76ee9775ca680f3afa5f19f39b2bb426ba78ba27654493b 46 | url: "https://pub.dev" 47 | source: hosted 48 | version: "0.3.0" 49 | change_case: 50 | dependency: transitive 51 | description: 52 | name: change_case 53 | sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 54 | url: "https://pub.dev" 55 | source: hosted 56 | version: "2.2.0" 57 | checked_yaml: 58 | dependency: transitive 59 | description: 60 | name: checked_yaml 61 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "2.0.3" 65 | collection: 66 | dependency: transitive 67 | description: 68 | name: collection 69 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "1.19.1" 73 | convert: 74 | dependency: transitive 75 | description: 76 | name: convert 77 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "3.1.2" 81 | crypto: 82 | dependency: transitive 83 | description: 84 | name: crypto 85 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "3.0.6" 89 | dart_eval: 90 | dependency: "direct main" 91 | description: 92 | name: dart_eval 93 | sha256: bbad8246a99a3c61925e19b3d2c2bd6311f8186fb4642a16bf3d22153b3ade55 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "0.7.10" 97 | dart_style: 98 | dependency: transitive 99 | description: 100 | name: dart_style 101 | sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" 102 | url: "https://pub.dev" 103 | source: hosted 104 | version: "2.3.7" 105 | directed_graph: 106 | dependency: transitive 107 | description: 108 | name: directed_graph 109 | sha256: "3718b9f697a8e73890dea3d93edb6d58b63778996306b4b19c575710e3e2523d" 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "0.4.4" 113 | exception_templates: 114 | dependency: transitive 115 | description: 116 | name: exception_templates 117 | sha256: "517f7c770da690073663f867ee2057ae2f4ffb28edae9da9faa624aa29ac76eb" 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "0.3.1" 121 | file: 122 | dependency: transitive 123 | description: 124 | name: file 125 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "7.0.1" 129 | glob: 130 | dependency: transitive 131 | description: 132 | name: glob 133 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "2.1.2" 137 | json_annotation: 138 | dependency: transitive 139 | description: 140 | name: json_annotation 141 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "4.9.0" 145 | lazy_memo: 146 | dependency: transitive 147 | description: 148 | name: lazy_memo 149 | sha256: dcb30b4184a6d767e1d779d74ce784d752d38313b8fb4bad6b659ae7af4bb34d 150 | url: "https://pub.dev" 151 | source: hosted 152 | version: "0.2.3" 153 | macros: 154 | dependency: transitive 155 | description: 156 | name: macros 157 | sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" 158 | url: "https://pub.dev" 159 | source: hosted 160 | version: "0.1.3-main.0" 161 | meta: 162 | dependency: transitive 163 | description: 164 | name: meta 165 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 166 | url: "https://pub.dev" 167 | source: hosted 168 | version: "1.16.0" 169 | package_config: 170 | dependency: transitive 171 | description: 172 | name: package_config 173 | sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" 174 | url: "https://pub.dev" 175 | source: hosted 176 | version: "2.1.1" 177 | path: 178 | dependency: transitive 179 | description: 180 | name: path 181 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 182 | url: "https://pub.dev" 183 | source: hosted 184 | version: "1.9.1" 185 | pub_semver: 186 | dependency: transitive 187 | description: 188 | name: pub_semver 189 | sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" 190 | url: "https://pub.dev" 191 | source: hosted 192 | version: "2.1.5" 193 | pubspec_parse: 194 | dependency: transitive 195 | description: 196 | name: pubspec_parse 197 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 198 | url: "https://pub.dev" 199 | source: hosted 200 | version: "1.5.0" 201 | quote_buffer: 202 | dependency: transitive 203 | description: 204 | name: quote_buffer 205 | sha256: c4cd07e55ed1b1645a1cc74278a03b2a642c9f6ea3c0528d51827fdd320acf87 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "0.2.6" 209 | source_span: 210 | dependency: transitive 211 | description: 212 | name: source_span 213 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "1.10.1" 217 | string_scanner: 218 | dependency: transitive 219 | description: 220 | name: string_scanner 221 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "1.4.1" 225 | term_glyph: 226 | dependency: transitive 227 | description: 228 | name: term_glyph 229 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "1.2.2" 233 | typed_data: 234 | dependency: transitive 235 | description: 236 | name: typed_data 237 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "1.4.0" 241 | watcher: 242 | dependency: transitive 243 | description: 244 | name: watcher 245 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "1.1.1" 249 | yaml: 250 | dependency: transitive 251 | description: 252 | name: yaml 253 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "3.1.3" 257 | sdks: 258 | dart: ">=3.6.0 <4.0.0" 259 | -------------------------------------------------------------------------------- /lib/plugin_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show json; 2 | import 'dart:io'; 3 | import 'package:dart_eval/dart_eval.dart'; 4 | import 'package:dart_eval/dart_eval_security.dart'; 5 | import 'package:extension_test/bridges.dart'; 6 | import 'package:extension_test/models.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:flutter_archive/flutter_archive.dart'; 10 | import 'package:flutter_eval/flutter_eval.dart'; 11 | import 'package:path_provider/path_provider.dart'; 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | 14 | /// Plugin description. Basically an id, a name, and some extra things. 15 | class Plugin { 16 | final String id; 17 | final String name; 18 | 19 | Plugin(this.id, [String? name]) : name = name ?? id; 20 | } 21 | 22 | /// Thrown only when loading a plugin. Prints the enclosed exception as well. 23 | class PluginLoadException implements Exception { 24 | final String message; 25 | final Exception? parent; 26 | 27 | PluginLoadException(this.message, [this.parent]); 28 | 29 | @override 30 | String toString() { 31 | return parent == null ? message : "$message: $parent"; 32 | } 33 | } 34 | 35 | class PluginContextImpl extends PluginContext with ChangeNotifier { 36 | final Directory pluginDir; 37 | 38 | PluginContextImpl(super.state, this.pluginDir); 39 | 40 | @override 41 | File getFile(String name) { 42 | return File('${pluginDir.path}/$name'); 43 | } 44 | 45 | @override 46 | Future readPreference(String key) async { 47 | final prefs = SharedPreferencesAsync(); 48 | return await prefs.getInt('plugin/$key'); 49 | } 50 | 51 | @override 52 | Future savePreference(String key, int value) async { 53 | final prefs = SharedPreferencesAsync(); 54 | await prefs.setInt('plugin/$key', value); 55 | } 56 | 57 | @override 58 | void repaint() { 59 | notifyListeners(); 60 | } 61 | } 62 | 63 | /// The main class for working with plugins. A singleton, use [instance]. 64 | class PluginProvider extends ChangeNotifier { 65 | static const _kEnabledKey = 'plugins_enabled'; 66 | static final instance = PluginProvider._(); 67 | final _plugins = []; 68 | final _pluginCode = {}; 69 | final _enabled = {}; 70 | late final Directory _pluginsDirectory; 71 | bool _ready = false; 72 | 73 | /// App state is initialized here, but usually is supplied from outside 74 | /// with [setState]. 75 | AppState state = AppState(); 76 | 77 | PluginProvider._() { 78 | state.addListener(notifyCounterChanged); 79 | _load(); 80 | } 81 | 82 | Future get ready async { 83 | while (!_ready) { 84 | await Future.delayed(Duration(milliseconds: 10)); 85 | } 86 | return true; 87 | } 88 | 89 | int get count => _plugins.length; 90 | List get all => _plugins; 91 | Iterable get active => _plugins.where((p) => _enabled.contains(p.id)); 92 | bool isActive(String id) => _enabled.contains(id); 93 | 94 | Future _load() async { 95 | final docDir = await getApplicationDocumentsDirectory(); 96 | _pluginsDirectory = Directory("${docDir.path}/plugins"); 97 | 98 | // Create plugins dir if not exists. 99 | await _pluginsDirectory.create(recursive: true); 100 | 101 | // Read plugins list. 102 | await for (final entry in _pluginsDirectory.list()) { 103 | if (entry is Directory) { 104 | final metadata = await _readPluginData(entry); 105 | if (metadata != null) _plugins.add(metadata); 106 | } 107 | } 108 | 109 | // Read enabled list. 110 | final prefs = SharedPreferencesAsync(); 111 | final enabledList = await prefs.getStringList(_kEnabledKey); 112 | _enabled.clear(); 113 | if (enabledList != null) _enabled.addAll(enabledList); 114 | 115 | // Enable plugins. 116 | for (final id in _enabled) { 117 | _enable(id, true); 118 | } 119 | 120 | _ready = true; 121 | 122 | try { 123 | await _installFromAssets(); 124 | } on PluginLoadException catch (e) { 125 | print('Failed to install plugin from assets: $e'); 126 | } 127 | } 128 | 129 | Future _saveEnabled() async { 130 | final prefs = SharedPreferencesAsync(); 131 | final enabledList = _enabled.toList(); 132 | enabledList.sort(); 133 | await prefs.setStringList(_kEnabledKey, enabledList); 134 | } 135 | 136 | void _enable(String id, [bool force = false]) { 137 | if (!force && _enabled.contains(id)) return; 138 | final p = _loadCode(id); 139 | _pluginCode[id] = p; 140 | if (state.counter == 0) state.counter = p.initial; 141 | state.initial = getBiggestInitial(); 142 | state.step = p.step; 143 | p.context.addListener(_onRepaint); 144 | p.init(); 145 | _enabled.add(id); 146 | _onRepaint(); 147 | } 148 | 149 | PluginBase _loadCode(String pluginId) { 150 | final dir = _getPluginDirectory(pluginId); 151 | final evc = File('${dir.path}/plugin.evc'); 152 | final data = evc.readAsBytesSync(); 153 | final runtime = Runtime(ByteData.sublistView(data)); 154 | runtime.addPlugin(FlutterEvalPlugin()); 155 | runtime.addPlugin(PluginBasePlugin()); 156 | runtime.grant(FilesystemPermission.directory(dir.path)); 157 | final context = PluginContextImpl(state, dir); 158 | return runtime.executeLib( 159 | 'package:plugin/main.dart', 'setup', [$PluginContext.wrap(context)]); 160 | } 161 | 162 | void _disable(String id) { 163 | if (!_enabled.contains(id)) return; 164 | _enabled.remove(id); 165 | final p = _pluginCode[id]; 166 | if (p != null) { 167 | p.context.removeListener(_onRepaint); 168 | _pluginCode.remove(id); 169 | } 170 | _onRepaint(); 171 | } 172 | 173 | void _onRepaint() { 174 | notifyListeners(); 175 | } 176 | 177 | void onIncrementTap() { 178 | int? increment; 179 | for (final p in _plugins) { 180 | if (isActive(p.id) && _pluginCode.containsKey(p.id)) { 181 | final int step = _pluginCode[p.id]!.step; 182 | if (increment == null || step > increment) increment = step; 183 | } 184 | } 185 | state.counter += increment ?? 1; 186 | } 187 | 188 | int getBiggestInitial() { 189 | int? initial; 190 | for (final p in _plugins) { 191 | if (isActive(p.id) && _pluginCode.containsKey(p.id)) { 192 | final int first = _pluginCode[p.id]!.initial; 193 | if (initial == null || first > initial) initial = first; 194 | } 195 | } 196 | return initial ?? 0; 197 | } 198 | 199 | List getButtons() { 200 | final result = []; 201 | for (final p in _plugins) { 202 | if (isActive(p.id) && _pluginCode.containsKey(p.id)) { 203 | final code = _pluginCode[p.id]!; 204 | result.addAll(code.buttons); 205 | } 206 | } 207 | return result; 208 | } 209 | 210 | void onButtonTap(String button) { 211 | for (final p in _plugins) { 212 | if (isActive(p.id) && _pluginCode.containsKey(p.id)) { 213 | final code = _pluginCode[p.id]!; 214 | if (code.buttons.contains(button)) { 215 | _pluginCode[p.id]!.onButtonTapped(button, state); 216 | } 217 | } 218 | } 219 | } 220 | 221 | void notifyCounterChanged() { 222 | for (final p in _plugins) { 223 | if (isActive(p.id) && _pluginCode.containsKey(p.id)) { 224 | _pluginCode[p.id]!.onCounterChanged(state); 225 | } 226 | } 227 | _onRepaint(); 228 | } 229 | 230 | Widget? buildNumberWidget(BuildContext context) { 231 | for (final p in _plugins) { 232 | if (isActive(p.id) && _pluginCode.containsKey(p.id)) { 233 | final widget = _pluginCode[p.id]!.numberWidget(context, state); 234 | if (widget != null) return widget; 235 | } 236 | } 237 | return null; 238 | } 239 | 240 | Widget? buildSettingsWidget(BuildContext context, String pluginId) { 241 | if (isActive(pluginId) && _pluginCode.containsKey(pluginId)) { 242 | return _pluginCode[pluginId]!.settingsWidget(context); 243 | } 244 | return null; 245 | } 246 | 247 | Future setStateAndSave(String id, bool active) async { 248 | if (!active) { 249 | _disable(id); 250 | } else { 251 | _enable(id); 252 | } 253 | await _saveEnabled(); 254 | } 255 | 256 | Future toggle(String id) async { 257 | await setStateAndSave(id, !isActive(id)); 258 | } 259 | 260 | Future deletePlugin(String id) async { 261 | if (isActive(id)) await setStateAndSave(id, false); 262 | _plugins.removeWhere((p) => p.id == id); 263 | final pluginDir = _getPluginDirectory(id); 264 | if (await pluginDir.exists()) { 265 | await pluginDir.delete(recursive: true); 266 | } 267 | } 268 | 269 | Future _installFromAssets() async { 270 | ByteData pluginFile; 271 | try { 272 | pluginFile = await rootBundle.load('assets/plugin.zip'); 273 | } on FlutterError { 274 | // No plugin packaged. 275 | return; 276 | } 277 | final tmpDir = await getTemporaryDirectory(); 278 | final File tmpPath = File('${tmpDir.path}/bundled_plugin.zip'); 279 | await tmpPath.writeAsBytes(pluginFile.buffer.asUint8List(), flush: true); 280 | try { 281 | await install(tmpPath.path); 282 | } finally { 283 | try { 284 | await tmpPath.delete(); 285 | } on Exception { 286 | // it's fine if we leave it. 287 | } 288 | } 289 | } 290 | 291 | Directory _getPluginDirectory(String id) { 292 | return Directory("${_pluginsDirectory.path}/$id"); 293 | } 294 | 295 | Future _readPluginData(Directory path) async { 296 | // Read the metadata. 297 | final metadataFile = File("${path.path}/metadata.json"); 298 | if (!await metadataFile.exists()) { 299 | return null; 300 | } 301 | 302 | // Parse the metadata.json file. 303 | final metadataContents = await metadataFile.readAsString(); 304 | final Map metadata = json.decode(metadataContents); 305 | 306 | return Plugin(metadata['id'], metadata['name']); 307 | } 308 | 309 | Future install(String path) async { 310 | // Prepare paths. 311 | final file = File(path); 312 | if (!await file.exists()) { 313 | throw PluginLoadException("File is missing: $path"); 314 | } 315 | 316 | // Unpack the file. 317 | final tmpDir = await getTemporaryDirectory(); 318 | final tmpPluginDir = await tmpDir.createTemp("plugin"); 319 | try { 320 | try { 321 | await ZipFile.extractToDirectory( 322 | zipFile: file, 323 | destinationDir: tmpPluginDir, 324 | ); 325 | } on PlatformException catch (e) { 326 | throw PluginLoadException("Failed to unpack $path", e); 327 | } 328 | 329 | // Delete the temporary file if possible. 330 | if (await file.exists()) { 331 | try { 332 | await file.delete(); 333 | } on FileSystemException { 334 | // Does not matter. 335 | } 336 | } 337 | 338 | // Read the metadata. 339 | final metadataFile = File("${tmpPluginDir.path}/metadata.json"); 340 | if (!await metadataFile.exists()) { 341 | throw PluginLoadException("No ${metadataFile.path} found"); 342 | } 343 | 344 | // Parse the metadata.yaml file. 345 | final metadataContents = await metadataFile.readAsString(); 346 | final Map metadata = json.decode(metadataContents); 347 | 348 | // Check for required fields. 349 | const requiredFields = ['id', 'name']; 350 | final missingFields = 351 | requiredFields.where((f) => !metadata.containsKey(f)); 352 | if (missingFields.isNotEmpty) { 353 | throw PluginLoadException( 354 | "Missing fields in metadata: ${missingFields.join(',')}"); 355 | } 356 | 357 | // Extract and validate plugin id. 358 | final String pluginId = metadata['id']!; 359 | if (!RegExp(r'^[a-zA-Z0-9._-]+$').hasMatch(pluginId)) { 360 | throw PluginLoadException( 361 | "Plugin id \"$pluginId\" has bad characters."); 362 | } 363 | 364 | // If this plugin was installed, remove it. 365 | await deletePlugin(pluginId); 366 | 367 | // Create the plugin directory and move files there. 368 | final pluginDir = _getPluginDirectory(pluginId); 369 | await tmpPluginDir.rename(pluginDir.path); 370 | 371 | // Add the plugin record to the list. 372 | final record = Plugin(pluginId, metadata['name']); 373 | _plugins.add(record); 374 | 375 | await setStateAndSave(pluginId, true); 376 | } finally { 377 | // delete the directory and exit 378 | try { 379 | await tmpPluginDir.delete(recursive: true); 380 | } on Exception { 381 | // Oh well, let the trash rest there. 382 | } 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /plugins/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "76.0.0" 12 | _macros: 13 | dependency: transitive 14 | description: dart 15 | source: sdk 16 | version: "0.3.3" 17 | analyzer: 18 | dependency: transitive 19 | description: 20 | name: analyzer 21 | sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" 22 | url: "https://pub.dev" 23 | source: hosted 24 | version: "6.11.0" 25 | archive: 26 | dependency: "direct main" 27 | description: 28 | name: archive 29 | sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "4.0.2" 33 | args: 34 | dependency: transitive 35 | description: 36 | name: args 37 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "2.6.0" 41 | async: 42 | dependency: transitive 43 | description: 44 | name: async 45 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 46 | url: "https://pub.dev" 47 | source: hosted 48 | version: "2.12.0" 49 | change_case: 50 | dependency: transitive 51 | description: 52 | name: change_case 53 | sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 54 | url: "https://pub.dev" 55 | source: hosted 56 | version: "2.2.0" 57 | characters: 58 | dependency: transitive 59 | description: 60 | name: characters 61 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "1.3.0" 65 | checked_yaml: 66 | dependency: transitive 67 | description: 68 | name: checked_yaml 69 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "2.0.3" 73 | collection: 74 | dependency: transitive 75 | description: 76 | name: collection 77 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "1.19.0" 81 | convert: 82 | dependency: transitive 83 | description: 84 | name: convert 85 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "3.1.2" 89 | crypto: 90 | dependency: transitive 91 | description: 92 | name: crypto 93 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "3.0.6" 97 | dart_eval: 98 | dependency: "direct main" 99 | description: 100 | name: dart_eval 101 | sha256: bbad8246a99a3c61925e19b3d2c2bd6311f8186fb4642a16bf3d22153b3ade55 102 | url: "https://pub.dev" 103 | source: hosted 104 | version: "0.7.10" 105 | dart_style: 106 | dependency: transitive 107 | description: 108 | name: dart_style 109 | sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "2.3.7" 113 | directed_graph: 114 | dependency: transitive 115 | description: 116 | name: directed_graph 117 | sha256: fcb45029b4a5089d383b79056b6716d6bf5af79b8e7dbac4b61d26af46410548 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "0.4.3" 121 | exception_templates: 122 | dependency: transitive 123 | description: 124 | name: exception_templates 125 | sha256: "517f7c770da690073663f867ee2057ae2f4ffb28edae9da9faa624aa29ac76eb" 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "0.3.1" 129 | ffi: 130 | dependency: transitive 131 | description: 132 | name: ffi 133 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "2.1.3" 137 | file: 138 | dependency: transitive 139 | description: 140 | name: file 141 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "7.0.1" 145 | flutter: 146 | dependency: transitive 147 | description: flutter 148 | source: sdk 149 | version: "0.0.0" 150 | flutter_eval: 151 | dependency: "direct main" 152 | description: 153 | path: "." 154 | ref: flutter_3_27 155 | resolved-ref: ce3b065694e74d8f8ddd26c53a0e688f145e9cbe 156 | url: "https://github.com/Zverik/flutter_eval.git" 157 | source: git 158 | version: "0.7.6" 159 | glob: 160 | dependency: transitive 161 | description: 162 | name: glob 163 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 164 | url: "https://pub.dev" 165 | source: hosted 166 | version: "2.1.2" 167 | http: 168 | dependency: transitive 169 | description: 170 | name: http 171 | sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 172 | url: "https://pub.dev" 173 | source: hosted 174 | version: "1.2.2" 175 | http_parser: 176 | dependency: transitive 177 | description: 178 | name: http_parser 179 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 180 | url: "https://pub.dev" 181 | source: hosted 182 | version: "4.1.2" 183 | json_annotation: 184 | dependency: transitive 185 | description: 186 | name: json_annotation 187 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 188 | url: "https://pub.dev" 189 | source: hosted 190 | version: "4.9.0" 191 | lazy_memo: 192 | dependency: transitive 193 | description: 194 | name: lazy_memo 195 | sha256: dcb30b4184a6d767e1d779d74ce784d752d38313b8fb4bad6b659ae7af4bb34d 196 | url: "https://pub.dev" 197 | source: hosted 198 | version: "0.2.3" 199 | macros: 200 | dependency: transitive 201 | description: 202 | name: macros 203 | sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" 204 | url: "https://pub.dev" 205 | source: hosted 206 | version: "0.1.3-main.0" 207 | material_color_utilities: 208 | dependency: transitive 209 | description: 210 | name: material_color_utilities 211 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 212 | url: "https://pub.dev" 213 | source: hosted 214 | version: "0.11.1" 215 | meta: 216 | dependency: transitive 217 | description: 218 | name: meta 219 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 220 | url: "https://pub.dev" 221 | source: hosted 222 | version: "1.15.0" 223 | package_config: 224 | dependency: transitive 225 | description: 226 | name: package_config 227 | sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" 228 | url: "https://pub.dev" 229 | source: hosted 230 | version: "2.1.1" 231 | path: 232 | dependency: "direct main" 233 | description: 234 | name: path 235 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 236 | url: "https://pub.dev" 237 | source: hosted 238 | version: "1.9.1" 239 | path_provider: 240 | dependency: transitive 241 | description: 242 | name: path_provider 243 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" 244 | url: "https://pub.dev" 245 | source: hosted 246 | version: "2.1.5" 247 | path_provider_android: 248 | dependency: transitive 249 | description: 250 | name: path_provider_android 251 | sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" 252 | url: "https://pub.dev" 253 | source: hosted 254 | version: "2.2.15" 255 | path_provider_foundation: 256 | dependency: transitive 257 | description: 258 | name: path_provider_foundation 259 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 260 | url: "https://pub.dev" 261 | source: hosted 262 | version: "2.4.1" 263 | path_provider_linux: 264 | dependency: transitive 265 | description: 266 | name: path_provider_linux 267 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 268 | url: "https://pub.dev" 269 | source: hosted 270 | version: "2.2.1" 271 | path_provider_platform_interface: 272 | dependency: transitive 273 | description: 274 | name: path_provider_platform_interface 275 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 276 | url: "https://pub.dev" 277 | source: hosted 278 | version: "2.1.2" 279 | path_provider_windows: 280 | dependency: transitive 281 | description: 282 | name: path_provider_windows 283 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 284 | url: "https://pub.dev" 285 | source: hosted 286 | version: "2.3.0" 287 | platform: 288 | dependency: transitive 289 | description: 290 | name: platform 291 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 292 | url: "https://pub.dev" 293 | source: hosted 294 | version: "3.1.6" 295 | plugin_platform_interface: 296 | dependency: transitive 297 | description: 298 | name: plugin_platform_interface 299 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 300 | url: "https://pub.dev" 301 | source: hosted 302 | version: "2.1.8" 303 | posix: 304 | dependency: transitive 305 | description: 306 | name: posix 307 | sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a 308 | url: "https://pub.dev" 309 | source: hosted 310 | version: "6.0.1" 311 | pub_semver: 312 | dependency: transitive 313 | description: 314 | name: pub_semver 315 | sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" 316 | url: "https://pub.dev" 317 | source: hosted 318 | version: "2.1.5" 319 | pubspec_parse: 320 | dependency: transitive 321 | description: 322 | name: pubspec_parse 323 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 324 | url: "https://pub.dev" 325 | source: hosted 326 | version: "1.5.0" 327 | quote_buffer: 328 | dependency: transitive 329 | description: 330 | name: quote_buffer 331 | sha256: c4cd07e55ed1b1645a1cc74278a03b2a642c9f6ea3c0528d51827fdd320acf87 332 | url: "https://pub.dev" 333 | source: hosted 334 | version: "0.2.6" 335 | sky_engine: 336 | dependency: transitive 337 | description: flutter 338 | source: sdk 339 | version: "0.0.0" 340 | source_span: 341 | dependency: transitive 342 | description: 343 | name: source_span 344 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 345 | url: "https://pub.dev" 346 | source: hosted 347 | version: "1.10.1" 348 | string_scanner: 349 | dependency: transitive 350 | description: 351 | name: string_scanner 352 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 353 | url: "https://pub.dev" 354 | source: hosted 355 | version: "1.4.1" 356 | term_glyph: 357 | dependency: transitive 358 | description: 359 | name: term_glyph 360 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 361 | url: "https://pub.dev" 362 | source: hosted 363 | version: "1.2.2" 364 | typed_data: 365 | dependency: transitive 366 | description: 367 | name: typed_data 368 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 369 | url: "https://pub.dev" 370 | source: hosted 371 | version: "1.4.0" 372 | vector_math: 373 | dependency: transitive 374 | description: 375 | name: vector_math 376 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 377 | url: "https://pub.dev" 378 | source: hosted 379 | version: "2.1.4" 380 | watcher: 381 | dependency: transitive 382 | description: 383 | name: watcher 384 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 385 | url: "https://pub.dev" 386 | source: hosted 387 | version: "1.1.1" 388 | web: 389 | dependency: transitive 390 | description: 391 | name: web 392 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb 393 | url: "https://pub.dev" 394 | source: hosted 395 | version: "1.1.0" 396 | xdg_directories: 397 | dependency: transitive 398 | description: 399 | name: xdg_directories 400 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 401 | url: "https://pub.dev" 402 | source: hosted 403 | version: "1.1.0" 404 | yaml: 405 | dependency: transitive 406 | description: 407 | name: yaml 408 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 409 | url: "https://pub.dev" 410 | source: hosted 411 | version: "3.1.3" 412 | sdks: 413 | dart: ">=3.6.0 <4.0.0" 414 | flutter: ">=3.24.0" 415 | -------------------------------------------------------------------------------- /lib/bridges.dart: -------------------------------------------------------------------------------- 1 | import 'package:extension_test/models.dart'; 2 | import 'package:dart_eval/dart_eval_bridge.dart'; 3 | import 'package:dart_eval/stdlib/core.dart'; 4 | import 'package:dart_eval/stdlib/io.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter_eval/widgets.dart'; 7 | 8 | const pluginBasePlugin = PluginBasePlugin(); 9 | const _kModelsPath = 'package:plugin/_models.dart'; 10 | 11 | class PluginBasePlugin implements EvalPlugin { 12 | const PluginBasePlugin(); 13 | 14 | @override 15 | String get identifier => _kModelsPath.substring(0, _kModelsPath.indexOf('/')); 16 | 17 | @override 18 | void configureForCompile(BridgeDeclarationRegistry registry) { 19 | registry.defineBridgeClass($AppState.$declaration); 20 | registry.defineBridgeClass($PluginContext.$declaration); 21 | registry.defineBridgeClass($PluginBase$bridge.$declaration); 22 | } 23 | 24 | @override 25 | void configureForRuntime(Runtime runtime) { 26 | runtime.registerBridgeFunc( 27 | _kModelsPath, 28 | 'PluginBase.', 29 | $PluginBase$bridge.$new, 30 | isBridge: true, 31 | ); 32 | } 33 | } 34 | 35 | class $AppState implements $Instance { 36 | static const $type = BridgeTypeRef(BridgeTypeSpec(_kModelsPath, 'AppState')); 37 | 38 | static const $declaration = BridgeClassDef( 39 | BridgeClassType($type), 40 | constructors: {}, 41 | methods: { 42 | 'increment': BridgeMethodDef(BridgeFunctionDef( 43 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 44 | )), 45 | }, 46 | getters: { 47 | 'counter': BridgeMethodDef(BridgeFunctionDef( 48 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), 49 | )), 50 | }, 51 | setters: { 52 | 'counter': BridgeMethodDef(BridgeFunctionDef( 53 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 54 | params: [ 55 | BridgeParameter('value', 56 | BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), false), 57 | ])), 58 | }, 59 | fields: { 60 | 'initial': 61 | BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))), 62 | 'step': 63 | BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))), 64 | }, 65 | wrap: true, 66 | ); 67 | 68 | @override 69 | final AppState $value; 70 | 71 | @override 72 | get $reified => $value; 73 | 74 | $AppState.wrap(this.$value); 75 | 76 | @override 77 | int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!); 78 | 79 | @override 80 | $Value? $getProperty(Runtime runtime, String identifier) { 81 | switch (identifier) { 82 | case 'initial': 83 | return $int($value.initial); 84 | case 'step': 85 | return $int($value.step); 86 | case 'counter': 87 | return $int($value.counter); 88 | } 89 | return $Object(this).$getProperty(runtime, identifier); 90 | } 91 | 92 | @override 93 | void $setProperty(Runtime runtime, String identifier, $Value value) { 94 | switch (identifier) { 95 | case 'initial': 96 | $value.initial = value.$value as int; 97 | case 'step': 98 | $value.step = value.$value as int; 99 | case 'counter': 100 | $value.counter = value.$value as int; 101 | default: 102 | $Object(this).$setProperty(runtime, identifier, value); 103 | } 104 | } 105 | } 106 | 107 | class $PluginContext implements $Instance { 108 | static const $type = 109 | BridgeTypeRef(BridgeTypeSpec(_kModelsPath, 'PluginContext')); 110 | 111 | // We cannot import the library directly. 112 | static const $Listenable$type = BridgeTypeRef(BridgeTypeSpec( 113 | 'package:flutter/src/foundation/change_notifier.dart', 'Listenable')); 114 | 115 | static const $declaration = BridgeClassDef( 116 | BridgeClassType($type, $implements: [$Listenable$type], isAbstract: true), 117 | constructors: {}, 118 | fields: { 119 | 'state': BridgeFieldDef(BridgeTypeAnnotation($AppState.$type)), 120 | }, 121 | methods: { 122 | 'readPreference': BridgeMethodDef(BridgeFunctionDef( 123 | returns: BridgeTypeAnnotation( 124 | // TODO: returns Future 125 | BridgeTypeRef( 126 | CoreTypes.future, [BridgeTypeRef(CoreTypes.dynamic)])), 127 | params: [ 128 | BridgeParameter('key', 129 | BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), 130 | ], 131 | )), 132 | 'savePreference': BridgeMethodDef(BridgeFunctionDef( 133 | returns: BridgeTypeAnnotation(BridgeTypeRef( 134 | CoreTypes.future, [BridgeTypeRef(CoreTypes.voidType)])), 135 | params: [ 136 | BridgeParameter('key', 137 | BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), 138 | BridgeParameter('value', 139 | BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), false), 140 | ], 141 | )), 142 | 'repaint': BridgeMethodDef(BridgeFunctionDef( 143 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 144 | )), 145 | 'getFile': BridgeMethodDef(BridgeFunctionDef( 146 | returns: BridgeTypeAnnotation(BridgeTypeRef(IoTypes.file)), 147 | params: [ 148 | BridgeParameter('name', 149 | BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.string)), false), 150 | ], 151 | )), 152 | }, 153 | wrap: true, 154 | ); 155 | 156 | @override 157 | final PluginContext $value; 158 | 159 | @override 160 | get $reified => $value; 161 | 162 | $PluginContext.wrap(this.$value); 163 | 164 | @override 165 | int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!); 166 | 167 | @override 168 | $Value? $getProperty(Runtime runtime, String identifier) { 169 | switch (identifier) { 170 | case 'state': 171 | return $AppState.wrap($value.state); 172 | case 'readPreference': 173 | return $Function(_readPreference); 174 | case 'savePreference': 175 | return $Function(_savePreference); 176 | case 'repaint': 177 | return $Function(_repaint); 178 | case 'getFile': 179 | return $Function(_getFile); 180 | } 181 | return $Object(this).$getProperty(runtime, identifier); 182 | } 183 | 184 | static $Value? _readPreference( 185 | Runtime runtime, $Value? target, List<$Value?> args) { 186 | final ctx = target!.$value as PluginContext; 187 | final key = args[0]!.$value as String; 188 | return $Future.wrap(ctx 189 | .readPreference(key) 190 | .then((value) => value == null ? $null() : $int(value))); 191 | } 192 | 193 | static $Value? _savePreference( 194 | Runtime runtime, $Value? target, List<$Value?> args) { 195 | final ctx = target!.$value as PluginContext; 196 | final key = args[0]!.$value as String; 197 | final value = args[1]!.$value as int; 198 | return $Future.wrap(ctx.savePreference(key, value)); 199 | } 200 | 201 | static $Value? _repaint(Runtime runtime, $Value? target, List<$Value?> args) { 202 | final ctx = target!.$value as PluginContext; 203 | ctx.repaint(); 204 | return null; 205 | } 206 | 207 | static $Value? _getFile(Runtime runtime, $Value? target, List<$Value?> args) { 208 | final ctx = target!.$value as PluginContext; 209 | final name = args[0]!.$value as String; 210 | return $File.wrap(ctx.getFile(name)); 211 | } 212 | 213 | @override 214 | void $setProperty(Runtime runtime, String identifier, $Value value) { 215 | $Object(this).$setProperty(runtime, identifier, value); 216 | } 217 | } 218 | 219 | class $PluginBase$bridge extends PluginBase with $Bridge { 220 | static const $type = 221 | BridgeTypeRef(BridgeTypeSpec(_kModelsPath, 'PluginBase')); 222 | 223 | static const $declaration = BridgeClassDef( 224 | BridgeClassType($type), 225 | constructors: { 226 | '': BridgeConstructorDef(BridgeFunctionDef( 227 | returns: BridgeTypeAnnotation($type), 228 | params: [ 229 | BridgeParameter( 230 | 'context', BridgeTypeAnnotation($PluginContext.$type), false), 231 | ], 232 | namedParams: [ 233 | BridgeParameter('initial', 234 | BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), true), 235 | BridgeParameter( 236 | 'step', BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int)), true), 237 | ], 238 | )), 239 | }, 240 | methods: { 241 | 'init': BridgeMethodDef(BridgeFunctionDef( 242 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.dynamic)), 243 | )), 244 | 'onButtonTapped': BridgeMethodDef(BridgeFunctionDef( 245 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 246 | params: [ 247 | BridgeParameter( 248 | 'button', BridgeTypeAnnotation($IconData.$type), false), 249 | BridgeParameter( 250 | 'state', BridgeTypeAnnotation($AppState.$type), false), 251 | ], 252 | )), 253 | 'onCounterChanged': BridgeMethodDef(BridgeFunctionDef( 254 | returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.voidType)), 255 | params: [ 256 | BridgeParameter( 257 | 'state', BridgeTypeAnnotation($AppState.$type), false), 258 | ], 259 | )), 260 | 'numberWidget': BridgeMethodDef(BridgeFunctionDef( 261 | returns: BridgeTypeAnnotation($Widget.$type, nullable: true), 262 | params: [ 263 | BridgeParameter( 264 | 'context', BridgeTypeAnnotation($BuildContext.$type), false), 265 | BridgeParameter( 266 | 'state', BridgeTypeAnnotation($AppState.$type), false), 267 | ], 268 | )), 269 | 'settingsWidget': BridgeMethodDef(BridgeFunctionDef( 270 | returns: BridgeTypeAnnotation($Widget.$type, nullable: true), 271 | params: [ 272 | BridgeParameter( 273 | 'context', BridgeTypeAnnotation($BuildContext.$type), false), 274 | ], 275 | )), 276 | }, 277 | fields: { 278 | 'initial': 279 | BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))), 280 | 'step': 281 | BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.int))), 282 | 'context': BridgeFieldDef(BridgeTypeAnnotation($PluginContext.$type)), 283 | 'buttons': BridgeFieldDef(BridgeTypeAnnotation( 284 | BridgeTypeRef(CoreTypes.list, [$IconData.$type]))), 285 | }, 286 | bridge: true, 287 | ); 288 | 289 | $PluginBase$bridge(super.context, {super.initial, super.step}); 290 | 291 | static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) { 292 | final context = args[0]! as $PluginContext; 293 | final int initial = args[1]?.$value ?? 0; 294 | final int step = args[2]?.$value ?? 1; 295 | return $PluginBase$bridge(context.$value, initial: initial, step: step); 296 | } 297 | 298 | @override 299 | $Value? $bridgeGet(String identifier) { 300 | switch (identifier) { 301 | case 'initial': 302 | return $int(super.initial); 303 | case 'step': 304 | return $int(super.step); 305 | case 'context': 306 | return $PluginContext.wrap(super.context); 307 | case 'buttons': 308 | return $List<$String>.wrap( 309 | super.buttons.map((d) => $String(d)).toList()); 310 | case 'init': 311 | return $Function((runtime, target, args) { 312 | super.init(); 313 | return null; 314 | }); 315 | case 'onButtonTapped': 316 | return $Function((runtime, target, args) { 317 | final button = (args[1] as $String).$value; 318 | final state = (args[2] as $AppState).$value; 319 | super.onButtonTapped(button, state); 320 | return null; 321 | }); 322 | case 'onCounterChanged': 323 | return $Function((runtime, target, args) { 324 | final state = (args[1] as $AppState).$value; 325 | super.onCounterChanged(state); 326 | return null; 327 | }); 328 | case 'numberWidget': 329 | return $Function((runtime, target, args) { 330 | final context = (args[1] as $BuildContext).$value; 331 | final state = (args[2] as $AppState).$value; 332 | final widget = super.numberWidget(context, state); 333 | return widget == null ? $null() : $Widget.wrap(widget); 334 | }); 335 | case 'settingsWidget': 336 | return $Function((runtime, target, args) { 337 | final context = (args[1] as $BuildContext).$value; 338 | final widget = super.settingsWidget(context); 339 | return widget == null ? $null() : $Widget.wrap(widget); 340 | }); 341 | } 342 | throw UnimplementedError('Property does not exist: "$identifier"'); 343 | } 344 | 345 | @override 346 | void $bridgeSet(String identifier, $Value value) { 347 | switch (identifier) { 348 | case 'initial': 349 | super.initial = value.$value; 350 | case 'step': 351 | super.step = value.$value; 352 | case 'buttons': 353 | final src = value as $List<$String>; 354 | super.buttons = List.of(src.$value.map((d) => d.$value)); 355 | default: 356 | throw UnimplementedError('Cannot set property: "$identifier"'); 357 | } 358 | } 359 | 360 | @override 361 | init() { 362 | $_invoke('init', []); 363 | } 364 | 365 | @override 366 | void onButtonTapped(String button, AppState state) { 367 | $_invoke('onButtonTapped', [ 368 | $String(button), 369 | $AppState.wrap(state), 370 | ]); 371 | } 372 | 373 | @override 374 | void onCounterChanged(AppState state) { 375 | $_invoke('onCounterChanged', [$AppState.wrap(state)]); 376 | } 377 | 378 | @override 379 | Widget? numberWidget(BuildContext context, AppState state) { 380 | return $_invoke('numberWidget', [ 381 | $BuildContext.wrap(context), 382 | $AppState.wrap(state), 383 | ]); 384 | } 385 | 386 | @override 387 | Widget? settingsWidget(BuildContext context) { 388 | return $_invoke('settingsWidget', [ 389 | $BuildContext.wrap(context), 390 | ]); 391 | } 392 | 393 | @override 394 | int get initial => $_get('initial'); 395 | 396 | @override 397 | int get step => $_get('step'); 398 | 399 | @override 400 | List get buttons => 401 | ($_get('buttons') as List).whereType().toList(); 402 | 403 | @override 404 | PluginContext get context => $_get('context'); 405 | } 406 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "76.0.0" 12 | _macros: 13 | dependency: transitive 14 | description: dart 15 | source: sdk 16 | version: "0.3.3" 17 | analyzer: 18 | dependency: transitive 19 | description: 20 | name: analyzer 21 | sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" 22 | url: "https://pub.dev" 23 | source: hosted 24 | version: "6.11.0" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "2.6.0" 33 | async: 34 | dependency: transitive 35 | description: 36 | name: async 37 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "2.11.0" 41 | boolean_selector: 42 | dependency: transitive 43 | description: 44 | name: boolean_selector 45 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 46 | url: "https://pub.dev" 47 | source: hosted 48 | version: "2.1.1" 49 | change_case: 50 | dependency: transitive 51 | description: 52 | name: change_case 53 | sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 54 | url: "https://pub.dev" 55 | source: hosted 56 | version: "2.2.0" 57 | characters: 58 | dependency: transitive 59 | description: 60 | name: characters 61 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "1.3.0" 65 | checked_yaml: 66 | dependency: transitive 67 | description: 68 | name: checked_yaml 69 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "2.0.3" 73 | clock: 74 | dependency: transitive 75 | description: 76 | name: clock 77 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "1.1.1" 81 | collection: 82 | dependency: transitive 83 | description: 84 | name: collection 85 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "1.19.0" 89 | convert: 90 | dependency: transitive 91 | description: 92 | name: convert 93 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "3.1.2" 97 | crypto: 98 | dependency: transitive 99 | description: 100 | name: crypto 101 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 102 | url: "https://pub.dev" 103 | source: hosted 104 | version: "3.0.6" 105 | dart_eval: 106 | dependency: "direct main" 107 | description: 108 | name: dart_eval 109 | sha256: bbad8246a99a3c61925e19b3d2c2bd6311f8186fb4642a16bf3d22153b3ade55 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "0.7.10" 113 | dart_style: 114 | dependency: transitive 115 | description: 116 | name: dart_style 117 | sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "2.3.7" 121 | directed_graph: 122 | dependency: transitive 123 | description: 124 | name: directed_graph 125 | sha256: fcb45029b4a5089d383b79056b6716d6bf5af79b8e7dbac4b61d26af46410548 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "0.4.3" 129 | exception_templates: 130 | dependency: transitive 131 | description: 132 | name: exception_templates 133 | sha256: "517f7c770da690073663f867ee2057ae2f4ffb28edae9da9faa624aa29ac76eb" 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "0.3.1" 137 | fake_async: 138 | dependency: transitive 139 | description: 140 | name: fake_async 141 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "1.3.1" 145 | ffi: 146 | dependency: transitive 147 | description: 148 | name: ffi 149 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" 150 | url: "https://pub.dev" 151 | source: hosted 152 | version: "2.1.3" 153 | file: 154 | dependency: transitive 155 | description: 156 | name: file 157 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 158 | url: "https://pub.dev" 159 | source: hosted 160 | version: "7.0.1" 161 | flutter: 162 | dependency: "direct main" 163 | description: flutter 164 | source: sdk 165 | version: "0.0.0" 166 | flutter_archive: 167 | dependency: "direct main" 168 | description: 169 | name: flutter_archive 170 | sha256: "5ca235f304c12bf468979235f400f79846d204169d715939e39197106f5fc970" 171 | url: "https://pub.dev" 172 | source: hosted 173 | version: "6.0.3" 174 | flutter_eval: 175 | dependency: "direct main" 176 | description: 177 | path: "." 178 | ref: flutter_3_27 179 | resolved-ref: "4c367912a9b56c494cad4f410160887290f08455" 180 | url: "https://github.com/Zverik/flutter_eval.git" 181 | source: git 182 | version: "0.7.6" 183 | flutter_lints: 184 | dependency: "direct dev" 185 | description: 186 | name: flutter_lints 187 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 188 | url: "https://pub.dev" 189 | source: hosted 190 | version: "5.0.0" 191 | flutter_test: 192 | dependency: "direct dev" 193 | description: flutter 194 | source: sdk 195 | version: "0.0.0" 196 | flutter_web_plugins: 197 | dependency: transitive 198 | description: flutter 199 | source: sdk 200 | version: "0.0.0" 201 | glob: 202 | dependency: transitive 203 | description: 204 | name: glob 205 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "2.1.2" 209 | http: 210 | dependency: transitive 211 | description: 212 | name: http 213 | sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "1.2.2" 217 | http_parser: 218 | dependency: transitive 219 | description: 220 | name: http_parser 221 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "4.1.2" 225 | json_annotation: 226 | dependency: transitive 227 | description: 228 | name: json_annotation 229 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "4.9.0" 233 | lazy_memo: 234 | dependency: transitive 235 | description: 236 | name: lazy_memo 237 | sha256: dcb30b4184a6d767e1d779d74ce784d752d38313b8fb4bad6b659ae7af4bb34d 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "0.2.3" 241 | leak_tracker: 242 | dependency: transitive 243 | description: 244 | name: leak_tracker 245 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "10.0.7" 249 | leak_tracker_flutter_testing: 250 | dependency: transitive 251 | description: 252 | name: leak_tracker_flutter_testing 253 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "3.0.8" 257 | leak_tracker_testing: 258 | dependency: transitive 259 | description: 260 | name: leak_tracker_testing 261 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "3.0.1" 265 | lints: 266 | dependency: transitive 267 | description: 268 | name: lints 269 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "5.1.1" 273 | macros: 274 | dependency: transitive 275 | description: 276 | name: macros 277 | sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "0.1.3-main.0" 281 | matcher: 282 | dependency: transitive 283 | description: 284 | name: matcher 285 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 286 | url: "https://pub.dev" 287 | source: hosted 288 | version: "0.12.16+1" 289 | material_color_utilities: 290 | dependency: transitive 291 | description: 292 | name: material_color_utilities 293 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 294 | url: "https://pub.dev" 295 | source: hosted 296 | version: "0.11.1" 297 | meta: 298 | dependency: transitive 299 | description: 300 | name: meta 301 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 302 | url: "https://pub.dev" 303 | source: hosted 304 | version: "1.15.0" 305 | package_config: 306 | dependency: transitive 307 | description: 308 | name: package_config 309 | sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" 310 | url: "https://pub.dev" 311 | source: hosted 312 | version: "2.1.1" 313 | path: 314 | dependency: transitive 315 | description: 316 | name: path 317 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 318 | url: "https://pub.dev" 319 | source: hosted 320 | version: "1.9.0" 321 | path_provider: 322 | dependency: "direct main" 323 | description: 324 | name: path_provider 325 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" 326 | url: "https://pub.dev" 327 | source: hosted 328 | version: "2.1.5" 329 | path_provider_android: 330 | dependency: transitive 331 | description: 332 | name: path_provider_android 333 | sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" 334 | url: "https://pub.dev" 335 | source: hosted 336 | version: "2.2.15" 337 | path_provider_foundation: 338 | dependency: transitive 339 | description: 340 | name: path_provider_foundation 341 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 342 | url: "https://pub.dev" 343 | source: hosted 344 | version: "2.4.1" 345 | path_provider_linux: 346 | dependency: transitive 347 | description: 348 | name: path_provider_linux 349 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 350 | url: "https://pub.dev" 351 | source: hosted 352 | version: "2.2.1" 353 | path_provider_platform_interface: 354 | dependency: transitive 355 | description: 356 | name: path_provider_platform_interface 357 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 358 | url: "https://pub.dev" 359 | source: hosted 360 | version: "2.1.2" 361 | path_provider_windows: 362 | dependency: transitive 363 | description: 364 | name: path_provider_windows 365 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 366 | url: "https://pub.dev" 367 | source: hosted 368 | version: "2.3.0" 369 | platform: 370 | dependency: transitive 371 | description: 372 | name: platform 373 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 374 | url: "https://pub.dev" 375 | source: hosted 376 | version: "3.1.6" 377 | plugin_platform_interface: 378 | dependency: transitive 379 | description: 380 | name: plugin_platform_interface 381 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 382 | url: "https://pub.dev" 383 | source: hosted 384 | version: "2.1.8" 385 | pub_semver: 386 | dependency: transitive 387 | description: 388 | name: pub_semver 389 | sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" 390 | url: "https://pub.dev" 391 | source: hosted 392 | version: "2.1.5" 393 | pubspec_parse: 394 | dependency: transitive 395 | description: 396 | name: pubspec_parse 397 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 398 | url: "https://pub.dev" 399 | source: hosted 400 | version: "1.5.0" 401 | quote_buffer: 402 | dependency: transitive 403 | description: 404 | name: quote_buffer 405 | sha256: c4cd07e55ed1b1645a1cc74278a03b2a642c9f6ea3c0528d51827fdd320acf87 406 | url: "https://pub.dev" 407 | source: hosted 408 | version: "0.2.6" 409 | receive_sharing_intent: 410 | dependency: "direct main" 411 | description: 412 | path: "." 413 | ref: HEAD 414 | resolved-ref: "2cea396843cd3ab1b5ec4334be4233864637874e" 415 | url: "https://github.com/KasemJaffer/receive_sharing_intent.git" 416 | source: git 417 | version: "1.8.1" 418 | shared_preferences: 419 | dependency: "direct main" 420 | description: 421 | name: shared_preferences 422 | sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a 423 | url: "https://pub.dev" 424 | source: hosted 425 | version: "2.3.5" 426 | shared_preferences_android: 427 | dependency: transitive 428 | description: 429 | name: shared_preferences_android 430 | sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" 431 | url: "https://pub.dev" 432 | source: hosted 433 | version: "2.4.0" 434 | shared_preferences_foundation: 435 | dependency: transitive 436 | description: 437 | name: shared_preferences_foundation 438 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" 439 | url: "https://pub.dev" 440 | source: hosted 441 | version: "2.5.4" 442 | shared_preferences_linux: 443 | dependency: transitive 444 | description: 445 | name: shared_preferences_linux 446 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 447 | url: "https://pub.dev" 448 | source: hosted 449 | version: "2.4.1" 450 | shared_preferences_platform_interface: 451 | dependency: transitive 452 | description: 453 | name: shared_preferences_platform_interface 454 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" 455 | url: "https://pub.dev" 456 | source: hosted 457 | version: "2.4.1" 458 | shared_preferences_web: 459 | dependency: transitive 460 | description: 461 | name: shared_preferences_web 462 | sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e 463 | url: "https://pub.dev" 464 | source: hosted 465 | version: "2.4.2" 466 | shared_preferences_windows: 467 | dependency: transitive 468 | description: 469 | name: shared_preferences_windows 470 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 471 | url: "https://pub.dev" 472 | source: hosted 473 | version: "2.4.1" 474 | sky_engine: 475 | dependency: transitive 476 | description: flutter 477 | source: sdk 478 | version: "0.0.0" 479 | source_span: 480 | dependency: transitive 481 | description: 482 | name: source_span 483 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 484 | url: "https://pub.dev" 485 | source: hosted 486 | version: "1.10.0" 487 | stack_trace: 488 | dependency: transitive 489 | description: 490 | name: stack_trace 491 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" 492 | url: "https://pub.dev" 493 | source: hosted 494 | version: "1.12.0" 495 | stream_channel: 496 | dependency: transitive 497 | description: 498 | name: stream_channel 499 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 500 | url: "https://pub.dev" 501 | source: hosted 502 | version: "2.1.2" 503 | string_scanner: 504 | dependency: transitive 505 | description: 506 | name: string_scanner 507 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" 508 | url: "https://pub.dev" 509 | source: hosted 510 | version: "1.3.0" 511 | term_glyph: 512 | dependency: transitive 513 | description: 514 | name: term_glyph 515 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 516 | url: "https://pub.dev" 517 | source: hosted 518 | version: "1.2.1" 519 | test_api: 520 | dependency: transitive 521 | description: 522 | name: test_api 523 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" 524 | url: "https://pub.dev" 525 | source: hosted 526 | version: "0.7.3" 527 | typed_data: 528 | dependency: transitive 529 | description: 530 | name: typed_data 531 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 532 | url: "https://pub.dev" 533 | source: hosted 534 | version: "1.4.0" 535 | vector_math: 536 | dependency: transitive 537 | description: 538 | name: vector_math 539 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 540 | url: "https://pub.dev" 541 | source: hosted 542 | version: "2.1.4" 543 | vm_service: 544 | dependency: transitive 545 | description: 546 | name: vm_service 547 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b 548 | url: "https://pub.dev" 549 | source: hosted 550 | version: "14.3.0" 551 | watcher: 552 | dependency: transitive 553 | description: 554 | name: watcher 555 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 556 | url: "https://pub.dev" 557 | source: hosted 558 | version: "1.1.1" 559 | web: 560 | dependency: transitive 561 | description: 562 | name: web 563 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb 564 | url: "https://pub.dev" 565 | source: hosted 566 | version: "1.1.0" 567 | xdg_directories: 568 | dependency: transitive 569 | description: 570 | name: xdg_directories 571 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 572 | url: "https://pub.dev" 573 | source: hosted 574 | version: "1.1.0" 575 | yaml: 576 | dependency: transitive 577 | description: 578 | name: yaml 579 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 580 | url: "https://pub.dev" 581 | source: hosted 582 | version: "3.1.3" 583 | sdks: 584 | dart: ">=3.6.0 <4.0.0" 585 | flutter: ">=3.24.0" 586 | --------------------------------------------------------------------------------