diff --git a/packages/app/lib/ui/screen/slide/component/custom_slide_builder.dart b/packages/app/lib/ui/screen/slide/component/custom_slide_builder.dart index df68e1e..35c22a9 100644 --- a/packages/app/lib/ui/screen/slide/component/custom_slide_builder.dart +++ b/packages/app/lib/ui/screen/slide/component/custom_slide_builder.dart @@ -1,7 +1,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; Widget Function(BuildContext) customSlideBuilder({ required String title, @@ -26,7 +25,7 @@ Widget Function(BuildContext) customSlideBuilder({ ), ), ), - Gap(context.slideSize.height * 0.05), + // Gap(context.slideSize.height * 0.02), Expanded( child: Align( alignment: alignment, diff --git a/packages/app/lib/ui/screen/slide/component/link_text.dart b/packages/app/lib/ui/screen/slide/component/link_text.dart new file mode 100644 index 0000000..578421c --- /dev/null +++ b/packages/app/lib/ui/screen/slide/component/link_text.dart @@ -0,0 +1,38 @@ +import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/custom_slide_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_html/html.dart'; + +class LinkText extends StatelessWidget { + const LinkText({ + required this.text, + required this.url, + required this.textAreaHeight, + this.style, + this.alignment = Alignment.center, + super.key, + }); + + final String text; + final String url; + final double textAreaHeight; + final AlignmentGeometry alignment; + final TextStyle? style; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + window.open(url, ''); + }, + child: AutoResizedText( + text, + textAreaHeight: textAreaHeight, + style: (style ?? context.text.bodyLarge)!.copyWith( + color: Colors.blue, + ), + alignment: alignment, + ), + ); + } +} diff --git a/packages/app/lib/ui/screen/slide/component/empty.dart b/packages/app/lib/ui/screen/slide/component/rive/empty.dart similarity index 100% rename from packages/app/lib/ui/screen/slide/component/empty.dart rename to packages/app/lib/ui/screen/slide/component/rive/empty.dart diff --git a/packages/app/lib/ui/screen/slide/component/like_button.dart b/packages/app/lib/ui/screen/slide/component/rive/like_button.dart similarity index 78% rename from packages/app/lib/ui/screen/slide/component/like_button.dart rename to packages/app/lib/ui/screen/slide/component/rive/like_button.dart index 7d92589..3df3164 100644 --- a/packages/app/lib/ui/screen/slide/component/like_button.dart +++ b/packages/app/lib/ui/screen/slide/component/rive/like_button.dart @@ -12,32 +12,28 @@ class LikeButton extends HookWidget { static const _stateMachineName = 'LikeStateMachine'; static const _inputPressed = 'Pressed'; - static const _inputHover = 'Hover'; @override Widget build(BuildContext context) { final stateMachineController = useRef(null); - final hover = useRef(null); final pressed = useRef(null); return SizedBox( width: width, height: height, child: GestureDetector( - onTap: () { - pressed.value!.value = !pressed.value!.value; - }, + onTap: () => pressed.value!.value = !pressed.value!.value, child: Assets.rive.lightLike.rive( onInit: (artboard) { + // artboard から StateMachineController を取得 stateMachineController.value = StateMachineController.fromArtboard( artboard, _stateMachineName, ); + // artboard に StateMachineController を紐付ける artboard.addController(stateMachineController.value!); - hover.value ??= stateMachineController.value! - .findInput(_inputHover)! as SMIBool; - hover.value!.value = true; + // StateMachineController から SMIBool を取得 pressed.value ??= stateMachineController.value! .findInput(_inputPressed)! as SMIBool; }, diff --git a/packages/app/lib/ui/screen/slide/component/rating.dart b/packages/app/lib/ui/screen/slide/component/rive/rating.dart similarity index 100% rename from packages/app/lib/ui/screen/slide/component/rating.dart rename to packages/app/lib/ui/screen/slide/component/rive/rating.dart diff --git a/packages/app/lib/ui/screen/slide/content/create_rive_animation.dart b/packages/app/lib/ui/screen/slide/content/create_rive_animation.dart index b745683..3edcd27 100644 --- a/packages/app/lib/ui/screen/slide/content/create_rive_animation.dart +++ b/packages/app/lib/ui/screen/slide/content/create_rive_animation.dart @@ -1,7 +1,8 @@ import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/empty.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/like_button.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/rating.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/custom_slide_builder.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/empty.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/like_button.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/rating.dart'; import 'package:flutter/material.dart'; import 'package:flutter_deck/flutter_deck.dart'; import 'package:gap/gap.dart'; @@ -11,72 +12,73 @@ class CreateRiveAnimationSlide extends FlutterDeckSlideWidget { : super( configuration: const FlutterDeckSlideConfiguration( route: '/create-rive-animation', - header: FlutterDeckHeaderConfiguration( - title: 'Rive アニメーション作成', - ), ), ); @override FlutterDeckSlide build(BuildContext context) { final demoWidth = context.slideSize.height * 0.4; + final textAreaHeight = context.slideSize.height * 0.05; + return FlutterDeckSlide.blank( - builder: (context) { - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Rive 公式の UseCase より引用', - style: context.text.displayMedium, - ), - Gap(context.slideSize.height * 0.1), - Table( - children: [ - TableRow( - children: [ - TableCell( - child: Column( - children: [ - Text( - 'いいねボタン', - style: context.text.displayMedium, - ), - LikeButton(width: demoWidth, height: demoWidth), - ], - ), + builder: customSlideBuilder( + title: 'Rive でアニメーション作成', + builder: (context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AutoResizedText( + 'いいねボタンをサンプル', + textAreaHeight: textAreaHeight * 2, + style: context.text.displayMedium, + ), + Gap(context.slideSize.height * 0.1), + Table( + children: [ + TableRow( + children: [ + TableCell( + child: Column( + children: [ + AutoResizedText( + 'いいね', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + ), + LikeButton(width: demoWidth, height: demoWidth), + ], ), - TableCell( - child: Column( - children: [ - Text( - 'レーティングボタン', - style: context.text.displayMedium, - ), - Rating(width: demoWidth, height: demoWidth), - ], - ), + ), + TableCell( + child: Column( + children: [ + AutoResizedText( + 'レーティング', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + ), + Rating(width: demoWidth, height: demoWidth), + ], ), - TableCell( - child: Column( - children: [ - Text( - 'アニメーション', - style: context.text.displayMedium, - ), - Empty(width: demoWidth, height: demoWidth), - ], - ), + ), + TableCell( + child: Column( + children: [ + AutoResizedText( + 'Empty', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + ), + Empty(width: demoWidth, height: demoWidth), + ], ), - ], - ), - ], - ), - ], - ), - ); - }, + ), + ], + ), + ], + ), + ], + ), + ), ); } } diff --git a/packages/app/lib/ui/screen/slide/content/interactive_animation_slide.dart b/packages/app/lib/ui/screen/slide/content/interactive_animation_slide.dart index 9bb9fb5..b627375 100644 --- a/packages/app/lib/ui/screen/slide/content/interactive_animation_slide.dart +++ b/packages/app/lib/ui/screen/slide/content/interactive_animation_slide.dart @@ -1,7 +1,8 @@ import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; import 'package:ca_flutter_slide/gen/assets.gen.dart'; import 'package:ca_flutter_slide/ui/screen/slide/component/custom_slide_builder.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/like_button.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/link_text.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/like_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_deck/flutter_deck.dart'; import 'package:gap/gap.dart'; @@ -89,13 +90,20 @@ class InteractiveAnimationSlide3 extends FlutterDeckSlideWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Assets.images.rivePipeline.image( - height: context.slideSize.height * 0.45, + height: context.slideSize.height * 0.42, ), Assets.images.riveFileSize.image( - height: context.slideSize.height * 0.45, + height: context.slideSize.height * 0.42, ), ], ), + Gap(context.slideSize.height * 0.05), + LinkText( + text: 'Rive 公式の Rive vs Lottie より', + url: 'https://rive.app/blog/rive-as-a-lottie-alternative', + textAreaHeight: context.slideSize.height * 0.04, + style: context.text.displayMedium, + ), ], ), ), diff --git a/packages/app/lib/ui/screen/slide/content/rive_flutter_sample_slide.dart b/packages/app/lib/ui/screen/slide/content/rive_flutter_sample_slide.dart index 01efa89..2cafd28 100644 --- a/packages/app/lib/ui/screen/slide/content/rive_flutter_sample_slide.dart +++ b/packages/app/lib/ui/screen/slide/content/rive_flutter_sample_slide.dart @@ -1,8 +1,9 @@ import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; import 'package:ca_flutter_slide/ui/screen/slide/component/custom_slide_builder.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/empty.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/like_button.dart'; -import 'package:ca_flutter_slide/ui/screen/slide/component/rating.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/link_text.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/empty.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/like_button.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/rive/rating.dart'; import 'package:flutter/material.dart'; import 'package:flutter_deck/flutter_deck.dart'; import 'package:gap/gap.dart'; @@ -26,8 +27,9 @@ class RiveFlutterSampleSlide extends FlutterDeckSlideWidget { builder: (context) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - AutoResizedText( - 'Rive 公式の UseCase より引用', + LinkText( + text: 'Rive 公式の UseCase より', + url: 'https://rive.app/use-cases', textAreaHeight: textAreaHeight * 2, style: context.text.displayMedium, ), @@ -80,63 +82,5 @@ class RiveFlutterSampleSlide extends FlutterDeckSlideWidget { ), ), ); - return FlutterDeckSlide.blank( - builder: (context) { - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Rive 公式の UseCase より引用', - style: context.text.displayMedium, - ), - Gap(context.slideSize.height * 0.1), - Table( - children: [ - TableRow( - children: [ - TableCell( - child: Column( - children: [ - Text( - 'いいねボタン', - style: context.text.displayMedium, - ), - LikeButton(width: demoWidth, height: demoWidth), - ], - ), - ), - TableCell( - child: Column( - children: [ - Text( - 'レーティングボタン', - style: context.text.displayMedium, - ), - Rating(width: demoWidth, height: demoWidth), - ], - ), - ), - TableCell( - child: Column( - children: [ - Text( - 'アニメーション', - style: context.text.displayMedium, - ), - Empty(width: demoWidth, height: demoWidth), - ], - ), - ), - ], - ), - ], - ), - ], - ), - ); - }, - ); } } diff --git a/packages/app/lib/ui/screen/slide/content/rive_to_flutter.dart b/packages/app/lib/ui/screen/slide/content/rive_to_flutter.dart new file mode 100644 index 0000000..7f7fced --- /dev/null +++ b/packages/app/lib/ui/screen/slide/content/rive_to_flutter.dart @@ -0,0 +1,178 @@ +import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/custom_slide_builder.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/component/link_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_deck/flutter_deck.dart'; +import 'package:gap/gap.dart'; + +class RiveToFlutterSlide1 extends FlutterDeckSlideWidget { + const RiveToFlutterSlide1() + : super( + configuration: const FlutterDeckSlideConfiguration( + route: '/rive-to-flutter/1', + ), + ); + + @override + FlutterDeckSlide build(BuildContext context) { + final textAreaHeight = context.slideSize.height * 0.1; + + return FlutterDeckSlide.blank( + builder: customSlideBuilder( + title: 'Flutter での実装', + builder: (context) => Padding( + padding: + EdgeInsets.symmetric(horizontal: context.slideSize.width * 0.02), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AutoResizedText( + '使用する package', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + alignment: Alignment.centerLeft, + ), + Gap(context.slideSize.height * 0.01), + LinkText( + text: '・rive', + url: 'https://pub.dev/packages/rive', + textAreaHeight: textAreaHeight * 1.2, + style: context.text.displayMedium, + alignment: Alignment.centerLeft, + ), + AutoResizedText( + ' RiveAnimation Widget などを提供する公式 package', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + alignment: Alignment.centerLeft, + ), + LinkText( + text: '・flutter_gen', + url: 'https://pub.dev/packages/flutter_gen', + textAreaHeight: textAreaHeight * 1.2, + style: context.text.displayMedium, + alignment: Alignment.centerLeft, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 3, + child: AutoResizedText( + ' .riv ファイルを文字列ではなく型安全に', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + alignment: Alignment.centerLeft, + ), + ), + const Flexible( + flex: 2, + child: FractionallySizedBox( + child: SingleChildScrollView( + child: FlutterDeckCodeHighlight( + code: _code, + language: 'dart', + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + static const _code = ''' + // flutter_gen なし + RiveAnimation.asset('assets/rive/like_button.riv'), + + // flutter_gen あり + Assets.rive.likeButton.rive(), + '''; +} + +class RiveToFlutterSlide2 extends FlutterDeckSlideWidget { + const RiveToFlutterSlide2() + : super( + configuration: const FlutterDeckSlideConfiguration( + route: '/rive-to-flutter/2', + ), + ); + + @override + FlutterDeckSlide build(BuildContext context) { + final textAreaHeight = context.slideSize.height * 0.1; + + return FlutterDeckSlide.blank( + builder: customSlideBuilder( + title: 'Flutter での実装', + builder: (context) => Padding( + padding: + EdgeInsets.symmetric(horizontal: context.slideSize.width * 0.02), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + AutoResizedText( + 'RiveAnimation Widget などを提供する公式 package', + textAreaHeight: textAreaHeight, + style: context.text.displayMedium, + alignment: Alignment.centerLeft, + ), + ], + ), + ), + SizedBox( + width: context.slideSize.width * 0.5, + child: const SingleChildScrollView( + child: Align( + alignment: Alignment.centerRight, + child: FlutterDeckCodeHighlight( + code: _code, + language: 'dart', + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + + static const _code = ''' +class LikeButton extends HookWidget { + const LikeButton({super.key}); + + @override + Widget build(BuildContext context) { + final stateMachineController = useRef(null); + // SMI = StateMachineInstance + final pressed = useRef(null); + + return GestureDetector( + onTap: () => pressed.value!.value = !pressed.value!.value, + child: Assets.rive.lightLike.rive( + onInit: (artboard) { + // artboard から StateMachineController を取得 + stateMachineController.value = StateMachineController.fromArtboard( + artboard, + _stateMachineName, + ); + // artboard に StateMachineController を紐付ける + artboard.addController(stateMachineController.value!); + // StateMachineController から SMIBool を取得 + pressed.value ??= stateMachineController.value! + .findInput(_inputPressed)! as SMIBool; + }, + ), + ); + } +} + '''; +} diff --git a/packages/app/lib/ui/screen/slide/slide_screen.dart b/packages/app/lib/ui/screen/slide/slide_screen.dart index 7bca3c4..1e73e5e 100644 --- a/packages/app/lib/ui/screen/slide/slide_screen.dart +++ b/packages/app/lib/ui/screen/slide/slide_screen.dart @@ -3,6 +3,7 @@ import 'package:ca_flutter_slide/foundation/build_context_exe.dart'; import 'package:ca_flutter_slide/ui/screen/slide/content/create_rive_animation.dart'; import 'package:ca_flutter_slide/ui/screen/slide/content/interactive_animation_slide.dart'; import 'package:ca_flutter_slide/ui/screen/slide/content/rive_flutter_sample_slide.dart'; +import 'package:ca_flutter_slide/ui/screen/slide/content/rive_to_flutter.dart'; import 'package:ca_flutter_slide/ui/screen/slide/content/self_introduction_slide.dart'; import 'package:ca_flutter_slide/ui/screen/slide/content/title_slide.dart'; import 'package:flutter/material.dart'; @@ -60,6 +61,8 @@ class SlideScreen extends HookWidget { InteractiveAnimationSlide3(), RiveFlutterSampleSlide(), CreateRiveAnimationSlide(), + RiveToFlutterSlide1(), + RiveToFlutterSlide2(), ], ); } diff --git a/packages/app/pubspec.lock b/packages/app/pubspec.lock index 98f3b0e..bd3d4ee 100644 --- a/packages/app/pubspec.lock +++ b/packages/app/pubspec.lock @@ -145,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" checked_yaml: dependency: transitive description: @@ -225,6 +233,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" custom_lint: dependency: transitive description: @@ -437,6 +453,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.10" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: @@ -842,6 +866,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_html: + dependency: "direct main" + description: + name: universal_html + sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" uuid: dependency: transitive description: diff --git a/packages/app/pubspec.yaml b/packages/app/pubspec.yaml index b2d5ff6..c3f76a4 100644 --- a/packages/app/pubspec.yaml +++ b/packages/app/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: auto_size_text: ^3.0.0 + universal_html: ^2.2.3 + dev_dependencies: # For Test flutter_test: