diff --git a/.rive_head b/.rive_head index dc6b946..4162606 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -4b77d3dea084fddf89c63efcdeb03c33fa533ee9 +eec2f66855e208b215db1db4411c3e904432b398 diff --git a/CHANGELOG.md b/CHANGELOG.md index fb85e52..bb78299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.14.0-dev.2 + +### Fixes + +- Fixed a crash on iOS for the Flutter renderer on cleanup + ## 0.14.0-dev.1 This is a significant update for Rive Flutter. We've completely removed all of the Dart code that was used for the Rive runtime and replaced it with our underlying [C++ Runtime](https://github.com/rive-app/rive-runtime). diff --git a/example/.metadata b/example/.metadata index d40f586..95ae968 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" + revision: "ea121f8859e4b13e47a8f845e4586164519588bc" channel: "[user-branch]" project_type: app @@ -13,26 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - platform: android - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - - platform: ios - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - - platform: linux - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - - platform: macos - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - - platform: web - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - - platform: windows - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc # User provided section diff --git a/example/android/.gitignore b/example/android/.gitignore index bc2100d..be3943c 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -5,3 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index 8d16ecb..0000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,64 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 34 - ndkVersion "27.2.12479018" - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.rive_example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion 31 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 0000000..23b21a3 --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.rive_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.rive_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 2796d8f..399f698 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index e1a0d07..a4fce5e 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,40 +1,21 @@ - - + + android:windowSoftInputMode="adjustResize"> - - - - + + + + + + + diff --git a/example/android/app/src/main/kotlin/app/rive/rive_example/MainActivity.kt b/example/android/app/src/main/kotlin/app/rive/rive_example/MainActivity.kt deleted file mode 100644 index 0768ca8..0000000 --- a/example/android/app/src/main/kotlin/app/rive/rive_example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package app.rive.rive_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/example/android/app/src/main/kotlin/com/example/rive_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/rive_example/MainActivity.kt index f0aa16f..a850c1e 100644 --- a/example/android/app/src/main/kotlin/com/example/rive_example/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/rive_example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.rive_example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 1f83a33..cb1ef88 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 2796d8f..399f698 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index f475bc5..0000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.9.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 94adc3a..f018a61 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 10b64c5..afa1e8e 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index d3b6a40..0000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 0000000..a439442 --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/example/assets/ball_elements.riv b/example/assets/ball_elements.riv new file mode 100644 index 0000000..2052008 Binary files /dev/null and b/example/assets/ball_elements.riv differ diff --git a/example/assets/databinding_images.riv b/example/assets/databinding_images.riv deleted file mode 100644 index 396d12e..0000000 Binary files a/example/assets/databinding_images.riv and /dev/null differ diff --git a/example/assets/images/Basketball.webp b/example/assets/images/Basketball.webp new file mode 100644 index 0000000..734fd81 Binary files /dev/null and b/example/assets/images/Basketball.webp differ diff --git a/example/assets/images/Beach ball.webp b/example/assets/images/Beach ball.webp new file mode 100644 index 0000000..42487be Binary files /dev/null and b/example/assets/images/Beach ball.webp differ diff --git a/example/assets/images/Coffee.webp b/example/assets/images/Coffee.webp new file mode 100644 index 0000000..8b9bf5b Binary files /dev/null and b/example/assets/images/Coffee.webp differ diff --git a/example/assets/images/Cola.webp b/example/assets/images/Cola.webp new file mode 100644 index 0000000..d291feb Binary files /dev/null and b/example/assets/images/Cola.webp differ diff --git a/example/assets/images/Cookie.webp b/example/assets/images/Cookie.webp new file mode 100644 index 0000000..aa604db Binary files /dev/null and b/example/assets/images/Cookie.webp differ diff --git a/example/assets/images/Donut.webp b/example/assets/images/Donut.webp new file mode 100644 index 0000000..0344dd0 Binary files /dev/null and b/example/assets/images/Donut.webp differ diff --git a/example/assets/images/Earth.webp b/example/assets/images/Earth.webp new file mode 100644 index 0000000..8602721 Binary files /dev/null and b/example/assets/images/Earth.webp differ diff --git a/example/assets/images/Egg.webp b/example/assets/images/Egg.webp new file mode 100644 index 0000000..26fe282 Binary files /dev/null and b/example/assets/images/Egg.webp differ diff --git a/example/assets/images/Football.webp b/example/assets/images/Football.webp new file mode 100644 index 0000000..7eb949b Binary files /dev/null and b/example/assets/images/Football.webp differ diff --git a/example/assets/images/Paper.webp b/example/assets/images/Paper.webp new file mode 100644 index 0000000..702dce2 Binary files /dev/null and b/example/assets/images/Paper.webp differ diff --git a/example/assets/images/Pizza.webp b/example/assets/images/Pizza.webp new file mode 100644 index 0000000..80074e4 Binary files /dev/null and b/example/assets/images/Pizza.webp differ diff --git a/example/assets/images/Vinyl record.webp b/example/assets/images/Vinyl record.webp new file mode 100644 index 0000000..4fbf447 Binary files /dev/null and b/example/assets/images/Vinyl record.webp differ diff --git a/example/assets/images/databound_image_1.jpg b/example/assets/images/databound_image_1.jpg deleted file mode 100644 index 288f9da..0000000 Binary files a/example/assets/images/databound_image_1.jpg and /dev/null differ diff --git a/example/assets/images/databound_image_2.jpg b/example/assets/images/databound_image_2.jpg deleted file mode 100644 index 331bfe1..0000000 Binary files a/example/assets/images/databound_image_2.jpg and /dev/null differ diff --git a/example/assets/light_switch.riv b/example/assets/light_switch.riv deleted file mode 100644 index a779e6b..0000000 Binary files a/example/assets/light_switch.riv and /dev/null differ diff --git a/example/assets/lists_demo.riv b/example/assets/lists_demo.riv new file mode 100644 index 0000000..7164a0b Binary files /dev/null and b/example/assets/lists_demo.riv differ diff --git a/example/assets/teeny_tiny.riv b/example/assets/teeny_tiny.riv deleted file mode 100644 index 83808c8..0000000 Binary files a/example/assets/teeny_tiny.riv and /dev/null differ diff --git a/example/assets/text_flutter.riv b/example/assets/text_flutter.riv deleted file mode 100644 index d14938d..0000000 Binary files a/example/assets/text_flutter.riv and /dev/null differ diff --git a/example/assets/trans_text.riv b/example/assets/trans_text.riv deleted file mode 100644 index ff2c004..0000000 Binary files a/example/assets/trans_text.riv and /dev/null differ diff --git a/example/assets/travel_icons_pack.riv b/example/assets/travel_icons_pack.riv new file mode 100644 index 0000000..33f30ce Binary files /dev/null and b/example/assets/travel_icons_pack.riv differ diff --git a/example/assets/web_icons_pack.riv b/example/assets/web_icons_pack.riv new file mode 100644 index 0000000..0ac9503 Binary files /dev/null and b/example/assets/web_icons_pack.riv differ diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 0000000..f28f457 --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - Flutter (1.0.0) + - rive_native (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - rive_native (from `.symlinks/plugins/rive_native/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + rive_native: + :path: ".symlinks/plugins/rive_native/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + rive_native: a57df296c558867395bfe82fafe857ec67640f58 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + +PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 + +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..15cada4 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/example/lib/colors.dart b/example/lib/colors.dart new file mode 100644 index 0000000..7660128 --- /dev/null +++ b/example/lib/colors.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart' show Color; + +const appBarColor = Color(0xFF323232); +const backgroundColor = Color(0xFF1D1D1D); +const primaryColor = Color(0xFF57A5E0); diff --git a/example/lib/examples/databinding.dart b/example/lib/examples/databinding.dart index 50f0b68..65067a0 100644 --- a/example/lib/examples/databinding.dart +++ b/example/lib/examples/databinding.dart @@ -6,15 +6,15 @@ import 'package:rive_example/main.dart' show RiveExampleApp; /// Example using Rive data binding at runtime. /// -/// See: https://rive.app/docs/dart/data-binding +/// See: https://rive.app/docs/runtimes/data-binding class ExampleDataBinding extends StatefulWidget { const ExampleDataBinding({super.key}); @override - State createState() => _ExampleBasicState(); + State createState() => _ExampleDataBindingState(); } -class _ExampleBasicState extends State { +class _ExampleDataBindingState extends State { File? file; RiveWidgetController? controller; diff --git a/example/lib/examples/databinding_artboards.dart b/example/lib/examples/databinding_artboards.dart new file mode 100644 index 0000000..7c6d93d --- /dev/null +++ b/example/lib/examples/databinding_artboards.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:rive/rive.dart'; +import 'package:rive_example/main.dart' show RiveExampleApp; + +/// Example using Rive data binding artboards at runtime. +/// +/// See: https://rive.app/docs/runtimes/data-binding +class ExampleDataBindingArtboards extends StatefulWidget { + const ExampleDataBindingArtboards({super.key}); + + @override + State createState() => + _ExampleDataBindingArtboardsState(); +} + +class _ExampleDataBindingArtboardsState + extends State { + late final File travelPackFile; + late final File webPackFile; + late final RiveWidgetController controller; + late final ViewModelInstance viewModelInstance; + late ViewModelInstanceArtboard iconProperty; + late BindableArtboard bindableArtboard; + bool isInitialized = false; + + // Available artboard options + final List travelPackOptions = [ + 'map', + 'car', + 'gas', + 'compass', + 'walk', + 'food', + 'GPS', + 'coffee' + ]; + final List webPackOptions = [ + 'download', + 'refresh', + 'lock', + 'wifi', + 'email', + 'www' + ]; + String selectedTravelPackArtboard = 'map'; + String selectedWebPackArtboard = 'download'; + + @override + void initState() { + super.initState(); + initRive(); + } + + void initRive() async { + travelPackFile = (await File.asset( + 'assets/travel_icons_pack.riv', + riveFactory: RiveExampleApp.getCurrentFactory, + ))!; + webPackFile = (await File.asset( + 'assets/web_icons_pack.riv', + riveFactory: RiveExampleApp.getCurrentFactory, + ))!; + controller = + RiveWidgetController(travelPackFile); // uses default artboard: "Demo" + viewModelInstance = controller.dataBind(DataBind.auto()); + iconProperty = viewModelInstance.artboard('icon')!; + bindableArtboard = + travelPackFile.artboardToBind(selectedTravelPackArtboard)!; + iconProperty.value = bindableArtboard; + setState(() => isInitialized = true); + } + + void _onTravelIconSelected(String? newValue) { + if (newValue != null && newValue != selectedTravelPackArtboard) { + setState(() { + selectedTravelPackArtboard = newValue; + bindableArtboard = travelPackFile.artboardToBind(newValue)!; + iconProperty.value = bindableArtboard; + }); + } + } + + void _onWebPackIconSelected(String? newValue) { + if (newValue != null && newValue != selectedWebPackArtboard) { + setState(() { + selectedWebPackArtboard = newValue; + bindableArtboard = webPackFile.artboardToBind(newValue)!; + iconProperty.value = bindableArtboard; + }); + } + } + + @override + void dispose() { + travelPackFile.dispose(); + webPackFile.dispose(); + bindableArtboard.dispose(); + controller.dispose(); + viewModelInstance.dispose(); + iconProperty.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (!isInitialized) { + return const Center(child: CircularProgressIndicator()); + } + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Wrap( + children: [ + _buildDropdown( + 'Travel Pack', + travelPackOptions, + selectedTravelPackArtboard, + _onTravelIconSelected, + ), + _buildDropdown( + 'Web Pack', + webPackOptions, + selectedWebPackArtboard, + _onWebPackIconSelected, + ), + ], + ), + Expanded( + child: RiveWidget(controller: controller), + ), + ], + ); + } + + Widget _buildDropdown(String title, List options, String selected, + Function(String?) onChanged) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(title), + Padding( + padding: const EdgeInsets.all(16.0), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4.0), + ), + child: DropdownButton( + value: selected, + items: options.map((String artboard) { + return DropdownMenuItem( + value: artboard, + child: Text(artboard), + ); + }).toList(), + onChanged: (newValue) => onChanged(newValue), + underline: Container(), + hint: const Text('Select Artboard'), + ), + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/examples/databinding_images.dart b/example/lib/examples/databinding_images.dart index 79e9dde..c8b9798 100644 --- a/example/lib/examples/databinding_images.dart +++ b/example/lib/examples/databinding_images.dart @@ -1,64 +1,171 @@ +// ignore_for_file: deprecated_member_use + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:rive/rive.dart'; +import 'package:rive_example/colors.dart'; import 'package:rive_example/main.dart' show RiveExampleApp; /// Example using Rive data binding images at runtime. /// -/// See: https://rive.app/docs/dart/data-binding +/// See: https://rive.app/docs/runtimes/data-binding class ExampleDataBindingImages extends StatefulWidget { const ExampleDataBindingImages({super.key}); @override - State createState() => _ExampleBasicState(); + State createState() => + _ExampleDataBindingImagesState(); } -class _ExampleBasicState extends State { +class _ExampleDataBindingImagesState extends State { late ViewModelInstance viewModelInstance; + late ViewModelInstanceAssetImage imageProperty; + FileLoader fileLoader = FileLoader.fromAsset( - 'assets/databinding_images.riv', + 'assets/ball_elements.riv', riveFactory: RiveExampleApp.getCurrentFactory, ); - Future loadBundleAsset(int index) async { - final ByteData data = - await rootBundle.load('assets/images/databound_image_$index.jpg'); + int selectedImageIndex = 0; + final Map _imageCache = {}; + + final List images = [ + 'Basketball.webp', + 'Beach ball.webp', + 'Coffee.webp', + 'Cola.webp', + 'Cookie.webp', + 'Donut.webp', + 'Earth.webp', + 'Egg.webp', + 'Football.webp', + 'Paper.webp', + 'Pizza.webp', + 'Vinyl record.webp', + ]; + + @override + void initState() { + super.initState(); + _loadAllImages(); + } + + Future _loadAllImages() async { + for (String imageName in images) { + try { + final bytes = await loadBundleAsset(imageName); + _imageCache[imageName] = bytes; + } catch (e) { + debugPrint('Failed to load image: $imageName - $e'); + } + } + if (mounted) { + setState(() {}); + } + } + + Future loadBundleAsset(String name) async { + final ByteData data = await rootBundle.load('assets/images/$name'); return data.buffer.asUint8List(); } - Future _clearImage() async { - final imageProperty = viewModelInstance.image('bound_image')!; - imageProperty.value = null; - setState(() {}); - } - Future _swapImage(int index) async { - final imageProperty = viewModelInstance.image('bound_image')!; - final bytes = await loadBundleAsset(index); - final renderImage = - await RiveExampleApp.getCurrentFactory.decodeImage(bytes); - if (renderImage != null) { - imageProperty.value = renderImage; + if (index >= 0 && index < images.length) { + final imageName = images[index]; + final cachedBytes = _imageCache[imageName]; + + if (cachedBytes != null) { + final renderImage = + await RiveExampleApp.getCurrentFactory.decodeImage(cachedBytes); + if (renderImage != null) { + imageProperty.value = renderImage; + setState(() { + selectedImageIndex = index; + }); + } + } } - setState(() {}); } @override void dispose() { fileLoader.dispose(); + imageProperty.dispose(); super.dispose(); } + Widget _buildImageCarousel() { + return Container( + height: 100, + decoration: const BoxDecoration( + color: Colors.black, + ), + child: ListView.builder( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + itemCount: images.length, + itemBuilder: (context, index) { + final isSelected = index == selectedImageIndex; + final imageName = images[index]; + final cachedBytes = _imageCache[imageName]; + + return GestureDetector( + onTap: () => _swapImage(index), + child: Container( + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + border: Border.all( + color: isSelected ? primaryColor : Colors.grey[300]!, + width: isSelected ? 1.5 : 1.5, + ), + borderRadius: BorderRadius.circular(8), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(7), + child: cachedBytes != null + ? Image.memory( + cachedBytes, + width: 80, + height: 80, + fit: BoxFit.cover, + ) + : Container( + width: 80, + height: 80, + color: Colors.grey[200], + child: const Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ), + ), + ), + ); + }, + ), + ); + } + @override Widget build(BuildContext context) { return Column( children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: Center( + child: Text('Tap the image!!'), + ), + ), Expanded( child: RiveWidgetBuilder( fileLoader: fileLoader, dataBind: DataBind.auto(), onLoaded: (state) { viewModelInstance = state.viewModelInstance!; + imageProperty = viewModelInstance.image('ball_image')!; }, builder: (context, state) => switch (state) { RiveLoading() => const Center(child: CircularProgressIndicator()), @@ -70,36 +177,21 @@ class _ExampleBasicState extends State { }, ), ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: _clearImage, - child: const Text('Clear image'), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: () { - _swapImage(1); - }, - child: const Text('Swap image 1'), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: () { - _swapImage(2); - }, - child: const Text('Swap image 2'), - ), - ), - ], + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () { + imageProperty.value = null; + setState(() {}); + }, + child: const Text('Clear image'), + ), ), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text('Select an image'), + ), + _buildImageCarousel(), ], ); } diff --git a/example/lib/examples/databinding_lists.dart b/example/lib/examples/databinding_lists.dart new file mode 100644 index 0000000..29a195f --- /dev/null +++ b/example/lib/examples/databinding_lists.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'package:rive/rive.dart'; +import 'package:rive_example/main.dart' show RiveExampleApp; + +/// Example using Rive data binding lists at runtime. +/// +/// See: https://rive.app/docs/runtimes/data-binding +class ExampleDataBindingLists extends StatefulWidget { + const ExampleDataBindingLists({super.key}); + + @override + State createState() => + _ExampleDataBindingListsState(); +} + +class _ExampleDataBindingListsState extends State { + late ViewModelInstance viewModelInstance; + late ViewModelInstanceList menuList; + late ViewModel todoItemVM; + + // Text controllers for input fields + final TextEditingController _textController = TextEditingController(); + final TextEditingController _num1Controller = TextEditingController(); + final TextEditingController _num2Controller = TextEditingController(); + final TextEditingController _deleteController = TextEditingController(); + + FileLoader fileLoader = FileLoader.fromAsset( + 'assets/lists_demo.riv', + riveFactory: RiveExampleApp.getCurrentFactory, + ); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + fileLoader.dispose(); + _textController.dispose(); + _num1Controller.dispose(); + _num2Controller.dispose(); + _deleteController.dispose(); + super.dispose(); + } + + void _onLoaded(RiveLoaded state) { + viewModelInstance = state.viewModelInstance!; + + // Get the menu list + menuList = viewModelInstance.list('menu')!; + + // Get the TodoItem View Model + todoItemVM = state.file.viewModelByName('listItem')!; + + debugPrint('Rive file loaded!'); + debugPrint('Menu list: $menuList'); + debugPrint('TodoItem VM: $todoItemVM'); + } + + void _onSubmit() { + final inputText = _textController.text.trim(); + if (inputText.isEmpty) return; + + debugPrint('Submitted text: $inputText'); + var myTodo = todoItemVM.createInstance()!; + myTodo.string('label')!.value = inputText; + myTodo.string('fontIcon')!.value = ""; + myTodo.color('hoverColor')!.value = const Color(0xffefefef); + menuList.add(myTodo); + myTodo.dispose(); + + // Clear the input + _textController.clear(); + } + + void _onSwap() { + final num1Text = _num1Controller.text.trim(); + final num2Text = _num2Controller.text.trim(); + + if (num1Text.isEmpty || num2Text.isEmpty) return; + + final num1 = int.tryParse(num1Text); + final num2 = int.tryParse(num2Text); + + if (num1 != null && num2 != null) { + try { + menuList.swap(num1, num2); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error swapping items: $e')), + ); + } + } + } + + void _onDelete() { + final deleteText = _deleteController.text.trim(); + if (deleteText.isEmpty) return; + + final deleteIndex = int.tryParse(deleteText); + if (deleteIndex != null) { + try { + menuList.removeAt(deleteIndex); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error deleting item: $e')), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Data Binding Lists'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: Column( + children: [ + // Rive Widget + Expanded( + child: RiveWidgetBuilder( + fileLoader: fileLoader, + dataBind: DataBind.auto(), + onLoaded: _onLoaded, + builder: (context, state) => switch (state) { + RiveLoading() => + const Center(child: CircularProgressIndicator()), + RiveFailed() => ErrorWidget.withDetails( + message: state.error.toString(), + error: FlutterError(state.error.toString()), + ), + RiveLoaded() => RiveWidget(controller: state.controller), + }, + ), + ), + + // Controls Panel + Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: Colors.grey[300]!), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Add Item Section + const Text( + 'Add New Item', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: TextField( + controller: _textController, + decoration: const InputDecoration( + hintText: 'Enter item text', + border: OutlineInputBorder(), + ), + onSubmitted: (_) => _onSubmit(), + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: _onSubmit, + child: const Text('Add'), + ), + ], + ), + + const SizedBox(height: 16), + + // Swap Items Section + const Text( + 'Swap Items', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: TextField( + controller: _num1Controller, + decoration: const InputDecoration( + hintText: 'Index 1', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextField( + controller: _num2Controller, + decoration: const InputDecoration( + hintText: 'Index 2', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: _onSwap, + child: const Text('Swap'), + ), + ], + ), + + const SizedBox(height: 16), + + // Delete Item Section + const Text( + 'Delete Item', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: TextField( + controller: _deleteController, + decoration: const InputDecoration( + hintText: 'Index to delete', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: _onDelete, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + child: const Text('Delete'), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/examples/examples.dart b/example/lib/examples/examples.dart index 297c04a..ec7e3d1 100644 --- a/example/lib/examples/examples.dart +++ b/example/lib/examples/examples.dart @@ -1,5 +1,8 @@ export 'animation_painter.dart'; export 'databinding.dart'; +export 'databinding_artboards.dart'; +export 'databinding_images.dart'; +export 'databinding_lists.dart'; export 'events.dart'; export 'hit_test_behaviour.dart'; export 'inputs.dart'; diff --git a/example/lib/examples/hit_test_behaviour.dart b/example/lib/examples/hit_test_behaviour.dart index baf87ff..1d718eb 100644 --- a/example/lib/examples/hit_test_behaviour.dart +++ b/example/lib/examples/hit_test_behaviour.dart @@ -28,6 +28,7 @@ class _ExampleHitTestBehaviourState extends State { @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Stack( @@ -76,15 +77,20 @@ class _ExampleHitTestBehaviourState extends State { ], ), ), - Text('Underlying Flutter container tapped: $count'), - const SizedBox( - height: 16, - ), - Text('Current hit test behaviour: $hitTestBehavior'), Padding( padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + child: Text('Underlying Flutter container tapped: $count'), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text('Current hit test behaviour: $hitTestBehavior'), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 8, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.start, children: [ ElevatedButton( onPressed: () { @@ -121,45 +127,52 @@ class _ExampleHitTestBehaviourState extends State { ], ), ), - Text('Current mouse cursor: $cursor'), Padding( padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - setState(() { - cursor = MouseCursor.defer; - }); - }, - child: const Text('defer'), - ), - ElevatedButton( - onPressed: () { - setState(() { - cursor = MouseCursor.uncontrolled; - }); - }, - child: const Text('unconrolled'), - ), - ElevatedButton( - onPressed: () { - setState(() { - cursor = SystemMouseCursors.contextMenu; - }); - }, - child: const Text('context menu'), - ), - ElevatedButton( - onPressed: () { - setState(() { - cursor = SystemMouseCursors.text; - }); - }, - child: const Text('text'), - ), - ], + child: Text('Current mouse cursor: $cursor'), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + setState(() { + cursor = MouseCursor.defer; + }); + }, + child: const Text('defer'), + ), + ElevatedButton( + onPressed: () { + setState(() { + cursor = MouseCursor.uncontrolled; + }); + }, + child: const Text('unconrolled'), + ), + ElevatedButton( + onPressed: () { + setState(() { + cursor = SystemMouseCursors.contextMenu; + }); + }, + child: const Text('context menu'), + ), + ElevatedButton( + onPressed: () { + setState(() { + cursor = SystemMouseCursors.text; + }); + }, + child: const Text('text'), + ), + ], + ), ), ) ], diff --git a/example/lib/main.dart b/example/lib/main.dart index 80e21c8..ba5a0d9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,7 @@ // ignore_for_file: deprecated_member_use import 'package:flutter/material.dart'; -import 'package:rive_example/examples/databinding_images.dart'; +import 'package:rive_example/colors.dart'; import 'package:rive_example/examples/examples.dart'; import 'package:rive/rive.dart' as rive; @@ -16,10 +16,10 @@ void main() async { darkTheme: ThemeData( fontFamily: 'JetBrainsMono', brightness: Brightness.dark, - scaffoldBackgroundColor: _backgroundColor, - appBarTheme: const AppBarTheme(backgroundColor: _appBarColor), + scaffoldBackgroundColor: backgroundColor, + appBarTheme: const AppBarTheme(backgroundColor: appBarColor), colorScheme: ColorScheme.fromSwatch(brightness: Brightness.dark) - .copyWith(primary: _primaryColor), + .copyWith(primary: primaryColor), ), themeMode: ThemeMode.dark, ), @@ -76,7 +76,9 @@ class _RiveExampleAppState extends State { 'Example using Rive data binding at runtime.'), _Page('Data Binding - Images', ExampleDataBindingImages(), 'Example using Rive data binding images at runtime.'), - _Page('Data Binding - Lists', Todo(), + _Page('Data Binding - Artboards', ExampleDataBindingArtboards(), + 'Example using Rive data binding artboards at runtime.'), + _Page('Data Binding - Lists', ExampleDataBindingLists(), 'Example using Rive data binding lists at runtime.'), _Page('Responsive Layouts', ExampleResponsiveLayouts(), 'Create responsive Rive graphics that adapt to screen size.'), @@ -203,10 +205,13 @@ class _RiveExampleAppState extends State { const SizedBox(height: 16), Padding( padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + child: Wrap( + alignment: WrapAlignment.center, + spacing: 16, + runSpacing: 8, children: [ Row( + mainAxisSize: MainAxisSize.min, children: [ Radio( value: RiveFactoryToUse.rive, @@ -222,8 +227,8 @@ class _RiveExampleAppState extends State { style: TextStyle(fontSize: 14)), ], ), - const SizedBox(width: 32), Row( + mainAxisSize: MainAxisSize.min, children: [ Radio( value: RiveFactoryToUse.flutter, @@ -293,12 +298,12 @@ class _SectionHeader extends StatelessWidget { child: Text( title, style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: _primaryColor, + color: primaryColor, ), ), ), const Divider( - color: _primaryColor, + color: primaryColor, thickness: 0.5, height: 1, ), @@ -352,7 +357,7 @@ class _NavButtonState extends State<_NavButton> { decoration: BoxDecoration( color: Colors.black.withOpacity(0.9), borderRadius: BorderRadius.circular(8), - border: Border.all(color: _primaryColor, width: 1), + border: Border.all(color: primaryColor, width: 1), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), @@ -392,7 +397,7 @@ class _NavButtonState extends State<_NavButton> { child: Center( child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: _isHovered ? _primaryColor.withOpacity(0.1) : null, + backgroundColor: _isHovered ? primaryColor.withOpacity(0.1) : null, elevation: _isHovered ? 8 : 2, ), child: SizedBox( @@ -433,7 +438,3 @@ class _WrappedPage extends StatelessWidget { ); } } - -const _appBarColor = Color(0xFF323232); -const _backgroundColor = Color(0xFF1D1D1D); -const _primaryColor = Color(0xFF57A5E0); diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..fb89776 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,12 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('blah', (WidgetTester tester) async {}); +} diff --git a/pubspec.yaml b/pubspec.yaml index 4fda905..6a81eb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: rive -version: 0.14.0-dev.1 +version: 0.14.0-dev.2 homepage: https://rive.app -description: Rive Flutter Runtime. This package provides runtime functionality for Rive graphics built with the Rive editor available at https://rive.app. +description: Rive Flutter Runtime. This package provides runtime functionality for Rive graphics built with the Rive editor available at https://rive.app repository: https://github.com/rive-app/rive-flutter topics: - animation @@ -19,7 +19,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - rive_native: 0.0.4 + rive_native: 0.0.5 dev_dependencies: flutter_test: