From ccf3daa6de53b00c6a06d15f0ab1bc6937978c02 Mon Sep 17 00:00:00 2001 From: Sanjay Rathor Date: Tue, 7 Nov 2023 21:51:34 +0530 Subject: [PATCH 01/12] Added video position indicator --- assets/schema/ensemble_schema.json | 24 ++++++++++++--------- lib/widget/youtube.dart | 34 ++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 4c2fab2e8..2746b45cf 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -6011,48 +6011,52 @@ "description": "The URL source to the youtube video" }, "aspectRatio" : { - "type": "double", + "type": "number", "description" : "Video aspect ratio" }, "autoplay": { - "type" : "bool", + "type" : "boolean", "description" : "Automatically start the video when player is loaded. (default False)" }, "videoList" : { - "type" : "array", + "type" : "object", "description" : "List of videos to be played within a single player." }, "startSeconds": { - "type" : "double", + "type" : "number", "description" : "specifies the time from which the first video in the list(or single video) should start playing" }, "endSeconds": { - "type": "double", + "type": "number", "description": "Ends the video after the certain number of seconds (works with Single video)" }, "showAnnotations" : { - "type" : "bool", + "type" : "boolean", "description" : "Showing the annotations of the video" }, "playbackRate" : { - "type": "double", + "type": "number", "description": "For changing the speed at which the video is displayed" }, "showControls": { - "type" : "bool", + "type" : "boolean", "description" : "For showing the controls on the video within the player(Like in youtube)" }, "showFullScreenButton": { - "type" : "bool", + "type" : "boolean", "description" : "To show the fullscreen button of the video" }, "enableCaptions" : { - "type": "bool", + "type": "boolean", "description" : "To enable any captions in the video(default language of caption is English)" }, "volume": { "type": "integer", "description" : "Changes the volume. (max = 100, min = 0)" + }, + "videoPosition": { + "type": "boolean", + "description": "To add an indicator for the amount of video that is completed" } } }, diff --git a/lib/widget/youtube.dart b/lib/widget/youtube.dart index 07ce49422..1879b3a25 100644 --- a/lib/widget/youtube.dart +++ b/lib/widget/youtube.dart @@ -66,7 +66,9 @@ class Youtube extends StatefulWidget _controller.youtubeMethods ?.setVolume(Utils.getInt(value, fallback: 100, max: 100, min: 0)); _controller.volume = Utils.optionalInt(value, max: 100, min: 0); - } + }, + 'videoPosition': (value) => + _controller.videoPosition = Utils.getBool(value, fallback: false) }; } @@ -132,6 +134,10 @@ class YoutubeState extends WidgetState with YoutubeMethods { @override void didUpdateWidget(covariant Youtube oldWidget) { widget._controller.youtubeMethods = this; + widget._controller.youtubeMethods! + .setPlaybackRate(oldWidget.controller.playbackRate ?? 1); + widget._controller.youtubeMethods! + .setVolume(oldWidget.controller.volume ?? 100); super.didUpdateWidget(oldWidget); } @@ -163,7 +169,30 @@ class YoutubeState extends WidgetState with YoutubeMethods { player.seekTo(seconds: currentTime, allowSeekAhead: true); } }), - builder: (context, player) => player, + builder: (context, youtube) { + if (playerController.videoPosition) { + return YoutubeValueBuilder( + builder: (context, youtubeValue) => Column( + children: [ + youtube, + StreamBuilder( + stream: player.videoStateStream, + builder: (context, snapshot) { + final int position = + snapshot.data?.position.inMilliseconds ?? 0; + final int duration = + youtubeValue.metaData.duration.inMilliseconds; + return LinearProgressIndicator( + value: duration == 0 ? 0 : position / duration, + minHeight: 3, + ); + }) + ], + )); + } else { + return youtube; + } + }, ); } @@ -209,6 +238,7 @@ class PlayerController extends WidgetController { bool enableCaptions = true; double? playbackRate; int? volume; + bool videoPosition = false; List videoList = []; From 523d0df18073ea7a45f515777f9ae15254735ce4 Mon Sep 17 00:00:00 2001 From: Sanjay Rathor Date: Wed, 8 Nov 2023 16:43:19 +0530 Subject: [PATCH 02/12] created webinstance, conditionally importing package --- assets/schema/ensemble_schema.json | 12 ++++++------ lib/widget/widget_registry.dart | 3 +-- .../youtube/native/youtube_platform_native.dart | 8 ++++++++ lib/widget/youtube/web/youtube_platform_web.dart | 12 ++++++++++++ lib/widget/{ => youtube}/youtube.dart | 4 +++- lib/widget/youtube/youtubestate.dart | 7 +++++++ 6 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 lib/widget/youtube/native/youtube_platform_native.dart create mode 100644 lib/widget/youtube/web/youtube_platform_web.dart rename lib/widget/{ => youtube}/youtube.dart (98%) create mode 100644 lib/widget/youtube/youtubestate.dart diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 18ef77641..d41474ebb 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -5382,13 +5382,13 @@ } }, { - "title": "Youtube", + "title": "YouTube", "required": [ - "Youtube" + "YouTube" ], "properties": { - "Youtube" : { - "$ref" : "#/$defs/Youtube-payload" + "YouTube" : { + "$ref" : "#/$defs/YouTube-payload" } } }, @@ -6017,7 +6017,7 @@ } } }, - "Youtube-payload":{ + "YouTube-payload":{ "type" : "object", "properties" : { "id" : { @@ -6026,7 +6026,7 @@ }, "url" : { "type": "string", - "description": "The URL source to the youtube video" + "description": "The URL source to the youTube video" }, "aspectRatio" : { "type": "number", diff --git a/lib/widget/widget_registry.dart b/lib/widget/widget_registry.dart index 44f33df50..e6a95e7eb 100644 --- a/lib/widget/widget_registry.dart +++ b/lib/widget/widget_registry.dart @@ -44,7 +44,6 @@ import 'package:ensemble/widget/popup_menu.dart'; import 'package:ensemble/widget/progress_indicator.dart'; import 'package:ensemble/widget/qr_code.dart'; import 'package:ensemble/widget/rating.dart'; -import 'package:ensemble/widget/shape.dart'; import 'package:ensemble/widget/signature.dart'; import 'package:ensemble/widget/spacer.dart'; import 'package:ensemble/widget/staggered_grid.dart'; @@ -57,7 +56,7 @@ import 'package:ensemble/widget/visualization/chart_js.dart'; import 'package:ensemble/widget/visualization/line_area_chart.dart'; import 'package:ensemble/widget/visualization/topology_chart.dart'; import 'package:ensemble/widget/webview/webview.dart'; -import 'package:ensemble/widget/youtube.dart'; +import 'package:ensemble/widget/youtube/youtube.dart'; import 'package:ensemble/widget/weeklyscheduler.dart'; import 'package:get_it/get_it.dart'; diff --git a/lib/widget/youtube/native/youtube_platform_native.dart b/lib/widget/youtube/native/youtube_platform_native.dart new file mode 100644 index 000000000..bf4c7689b --- /dev/null +++ b/lib/widget/youtube/native/youtube_platform_native.dart @@ -0,0 +1,8 @@ +import 'package:ensemble/widget/youtube/youtubestate.dart'; + +YoutubeWeb getInstance() => YoutubeNativeInstance(); + +class YoutubeNativeInstance implements YoutubeWeb { + @override + void createWebInstance() {} +} diff --git a/lib/widget/youtube/web/youtube_platform_web.dart b/lib/widget/youtube/web/youtube_platform_web.dart new file mode 100644 index 000000000..b1094b6c8 --- /dev/null +++ b/lib/widget/youtube/web/youtube_platform_web.dart @@ -0,0 +1,12 @@ +import 'package:ensemble/widget/youtube/youtubestate.dart'; +import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; +import 'package:youtube_player_iframe_web/src/web_youtube_player_iframe_platform.dart'; + +YoutubeWebInstance getInstance() => YoutubeWebInstance(); + +class YoutubeWebInstance implements YoutubeWeb { + @override + void createWebInstance() { + WebViewPlatform.instance = WebYoutubePlayerIframePlatform(); + } +} diff --git a/lib/widget/youtube.dart b/lib/widget/youtube/youtube.dart similarity index 98% rename from lib/widget/youtube.dart rename to lib/widget/youtube/youtube.dart index 1879b3a25..d70c0b558 100644 --- a/lib/widget/youtube.dart +++ b/lib/widget/youtube/youtube.dart @@ -1,6 +1,7 @@ import 'package:ensemble/framework/widget/widget.dart'; import 'package:ensemble/util/utils.dart'; import 'package:ensemble/widget/helpers/controllers.dart'; +import 'package:ensemble/widget/youtube/youtubestate.dart'; import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -11,7 +12,7 @@ class Youtube extends StatefulWidget with Invokable, HasController { Youtube({super.key}); - static const String type = "Youtube"; + static const String type = "YouTube"; final PlayerController _controller = PlayerController(); @@ -88,6 +89,7 @@ class YoutubeState extends WidgetState with YoutubeMethods { late YoutubePlayerController player; @override void initState() { + YoutubeWeb().createWebInstance(); PlayerController playerController = widget._controller; player = YoutubePlayerController( params: YoutubePlayerParams( diff --git a/lib/widget/youtube/youtubestate.dart b/lib/widget/youtube/youtubestate.dart new file mode 100644 index 000000000..88ea96f08 --- /dev/null +++ b/lib/widget/youtube/youtubestate.dart @@ -0,0 +1,7 @@ +import 'package:ensemble/widget/youtube/native/youtube_platform_native.dart' + if (dart.library.html) './web/youtube_platform_web.dart'; + +abstract class YoutubeWeb { + factory YoutubeWeb() => getInstance(); + void createWebInstance(); +} From d59c0eae1d54d3336ce03c4b6c3fb0acf2399325 Mon Sep 17 00:00:00 2001 From: Sanjay Rathor Date: Fri, 10 Nov 2023 21:12:24 +0530 Subject: [PATCH 03/12] changes the T in youtube, import modifications, --- lib/widget/widget_registry.dart | 2 +- .../youtube/native/unsupported_platform.dart | 11 ++++++ .../native/youtube_platform_native.dart | 4 +-- .../youtube/web/youtube_platform_web.dart | 4 +-- lib/widget/youtube/youtube.dart | 34 ++++++++++++------- lib/widget/youtube/youtubestate.dart | 9 ++--- 6 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 lib/widget/youtube/native/unsupported_platform.dart diff --git a/lib/widget/widget_registry.dart b/lib/widget/widget_registry.dart index e6a95e7eb..4663c0ba0 100644 --- a/lib/widget/widget_registry.dart +++ b/lib/widget/widget_registry.dart @@ -105,7 +105,7 @@ class WidgetRegistry { EnsembleMap.type: () => EnsembleMap(), // legacy maps Carousel.type: () => Carousel(), Video.type: () => Video(), - Youtube.type: () => Youtube(), + YouTube.type: () => YouTube(), EnsembleLottie.type: () => EnsembleLottie(), EnsembleSignature.type: () => EnsembleSignature(), WeeklyScheduler.type: () => WeeklyScheduler(), diff --git a/lib/widget/youtube/native/unsupported_platform.dart b/lib/widget/youtube/native/unsupported_platform.dart new file mode 100644 index 000000000..7ae15400a --- /dev/null +++ b/lib/widget/youtube/native/unsupported_platform.dart @@ -0,0 +1,11 @@ +import 'package:ensemble/framework/error_handling.dart'; +import 'package:ensemble/widget/youtube/youtubestate.dart'; + +YouTubeBase getInstance() => YouTubeUnsupported(); + +class YouTubeUnsupported implements YouTubeBase { + @override + void createWebInstance() { + throw LanguageError("Incorrect Platform for the Youtube widget"); + } +} diff --git a/lib/widget/youtube/native/youtube_platform_native.dart b/lib/widget/youtube/native/youtube_platform_native.dart index bf4c7689b..2cdcd7cb3 100644 --- a/lib/widget/youtube/native/youtube_platform_native.dart +++ b/lib/widget/youtube/native/youtube_platform_native.dart @@ -1,8 +1,8 @@ import 'package:ensemble/widget/youtube/youtubestate.dart'; -YoutubeWeb getInstance() => YoutubeNativeInstance(); +YouTubeBase getInstance() => YouTubeNativeInstance(); -class YoutubeNativeInstance implements YoutubeWeb { +class YouTubeNativeInstance implements YouTubeBase { @override void createWebInstance() {} } diff --git a/lib/widget/youtube/web/youtube_platform_web.dart b/lib/widget/youtube/web/youtube_platform_web.dart index b1094b6c8..0b1f6c1d6 100644 --- a/lib/widget/youtube/web/youtube_platform_web.dart +++ b/lib/widget/youtube/web/youtube_platform_web.dart @@ -2,9 +2,9 @@ import 'package:ensemble/widget/youtube/youtubestate.dart'; import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; import 'package:youtube_player_iframe_web/src/web_youtube_player_iframe_platform.dart'; -YoutubeWebInstance getInstance() => YoutubeWebInstance(); +YouTubeWebInstance getInstance() => YouTubeWebInstance(); -class YoutubeWebInstance implements YoutubeWeb { +class YouTubeWebInstance implements YouTubeBase { @override void createWebInstance() { WebViewPlatform.instance = WebYoutubePlayerIframePlatform(); diff --git a/lib/widget/youtube/youtube.dart b/lib/widget/youtube/youtube.dart index d70c0b558..fef854c26 100644 --- a/lib/widget/youtube/youtube.dart +++ b/lib/widget/youtube/youtube.dart @@ -8,9 +8,9 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:youtube_player_iframe/youtube_player_iframe.dart'; -class Youtube extends StatefulWidget - with Invokable, HasController { - Youtube({super.key}); +class YouTube extends StatefulWidget + with Invokable, HasController { + YouTube({super.key}); static const String type = "YouTube"; @@ -20,7 +20,7 @@ class Youtube extends StatefulWidget PlayerController get controller => _controller; @override - State createState() => YoutubeState(); + State createState() => YouTubeState(); @override Map getters() => {}; @@ -73,7 +73,7 @@ class Youtube extends StatefulWidget }; } -mixin YoutubeMethods on WidgetState { +mixin YouTubeMethods on WidgetState { void nextVideo(); void previousVideo(); void stopVideo(); @@ -85,11 +85,11 @@ mixin YoutubeMethods on WidgetState { void setVolume(int volume); } -class YoutubeState extends WidgetState with YoutubeMethods { +class YouTubeState extends WidgetState with YouTubeMethods { late YoutubePlayerController player; @override void initState() { - YoutubeWeb().createWebInstance(); + YouTubeBase().createWebInstance(); PlayerController playerController = widget._controller; player = YoutubePlayerController( params: YoutubePlayerParams( @@ -134,7 +134,7 @@ class YoutubeState extends WidgetState with YoutubeMethods { } @override - void didUpdateWidget(covariant Youtube oldWidget) { + void didUpdateWidget(covariant YouTube oldWidget) { widget._controller.youtubeMethods = this; widget._controller.youtubeMethods! .setPlaybackRate(oldWidget.controller.playbackRate ?? 1); @@ -143,17 +143,25 @@ class YoutubeState extends WidgetState with YoutubeMethods { super.didUpdateWidget(oldWidget); } + @override + void dispose() { + player.close(); + super.dispose(); + } + @override Widget buildWidget(BuildContext context) { PlayerController playerController = widget._controller; - + Set> gesture = {}; + gesture + .add(Factory(() => EagerGestureRecognizer())); if (widget._controller.broken) { return const SizedBox.shrink(); } return YoutubePlayerScaffold( - gestureRecognizers: >{}..add( - Factory(() => EagerGestureRecognizer()), - ), + gestureRecognizers: (playerController.videoList.isEmpty) + ? const >{} + : gesture, enableFullScreenOnVerticalDrag: false, autoFullScreen: false, aspectRatio: playerController.aspectRatio ?? 16 / 9, @@ -227,7 +235,7 @@ class YoutubeState extends WidgetState with YoutubeMethods { } class PlayerController extends WidgetController { - YoutubeMethods? youtubeMethods; + YouTubeMethods? youtubeMethods; String url = ""; double? aspectRatio; bool autoplay = false; diff --git a/lib/widget/youtube/youtubestate.dart b/lib/widget/youtube/youtubestate.dart index 88ea96f08..83aadc43c 100644 --- a/lib/widget/youtube/youtubestate.dart +++ b/lib/widget/youtube/youtubestate.dart @@ -1,7 +1,8 @@ -import 'package:ensemble/widget/youtube/native/youtube_platform_native.dart' - if (dart.library.html) './web/youtube_platform_web.dart'; +import 'package:ensemble/widget/youtube/native/unsupported_platform.dart' + if (dart.library.html) 'package:ensemble/widget/youtube/web/youtube_platform_web.dart' + if (dart.library.io) 'package:ensemble/widget/youtube/native/youtube_platform_native.dart'; -abstract class YoutubeWeb { - factory YoutubeWeb() => getInstance(); +abstract class YouTubeBase { + factory YouTubeBase() => getInstance(); void createWebInstance(); } From a79e9abcfdf888b3a4ec5b4aee213b576f4845e6 Mon Sep 17 00:00:00 2001 From: Sanjay Rathor Date: Tue, 14 Nov 2023 05:58:42 +0530 Subject: [PATCH 04/12] youtube multiple instance fix --- lib/widget/youtube/youtube.dart | 151 +++++++++++++++++++------------- 1 file changed, 89 insertions(+), 62 deletions(-) diff --git a/lib/widget/youtube/youtube.dart b/lib/widget/youtube/youtube.dart index fef854c26..efc8d8755 100644 --- a/lib/widget/youtube/youtube.dart +++ b/lib/widget/youtube/youtube.dart @@ -6,6 +6,7 @@ import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:youtube_player_iframe/youtube_player_iframe.dart'; class YouTube extends StatefulWidget @@ -93,33 +94,11 @@ class YouTubeState extends WidgetState with YouTubeMethods { PlayerController playerController = widget._controller; player = YoutubePlayerController( params: YoutubePlayerParams( + enableJavaScript: false, enableCaption: playerController.enableCaptions, showControls: playerController.showControls, showFullscreenButton: playerController.showFullScreenButton, showVideoAnnotations: playerController.showVideoAnnotation)); - List list = playerController.videoList; - - if (!playerController.autoplay) { - (list.isEmpty) - ? player.cueVideoById( - videoId: playerController.url, - startSeconds: playerController.startSeconds, - endSeconds: playerController.endSeconds) - : player.cuePlaylist( - list: playerController.videoList, - startSeconds: playerController.startSeconds, - listType: ListType.playlist); - } else { - (list.isEmpty) - ? player.loadVideoById( - videoId: playerController.url, - startSeconds: playerController.startSeconds, - endSeconds: playerController.endSeconds) - : player.loadPlaylist( - list: playerController.videoList, - startSeconds: playerController.startSeconds, - listType: ListType.playlist); - } super.initState(); } @@ -158,51 +137,64 @@ class YouTubeState extends WidgetState with YouTubeMethods { if (widget._controller.broken) { return const SizedBox.shrink(); } - return YoutubePlayerScaffold( - gestureRecognizers: (playerController.videoList.isEmpty) - ? const >{} - : gesture, - enableFullScreenOnVerticalDrag: false, - autoFullScreen: false, - aspectRatio: playerController.aspectRatio ?? 16 / 9, - controller: player - ..setFullScreenListener((value) async { - final videoData = await player.videoData; - final startSeconds = await player.currentTime; - if (!context.mounted) return; - final currentTime = await FullscreenYoutubePlayer.launch( - context, - videoId: videoData.videoId, - startSeconds: startSeconds, - ); - if (currentTime != null) { - player.seekTo(seconds: currentTime, allowSeekAhead: true); - } - }), - builder: (context, youtube) { - if (playerController.videoPosition) { - return YoutubeValueBuilder( - builder: (context, youtubeValue) => Column( - children: [ - youtube, - StreamBuilder( + return ChangeNotifierProvider( + create: (context) => YoutubeNotifier(), + child: Consumer(builder: (context, ref, child) { + if (!ref.isCalled) { + ref.loadYouTube( + playerController, widget.controller.videoList, player); + ref.isCalled = true; + } + return YoutubePlayerScaffold( + gestureRecognizers: (playerController.videoList.isEmpty) + ? const >{} + : gesture, + enableFullScreenOnVerticalDrag: false, + autoFullScreen: false, + aspectRatio: playerController.aspectRatio ?? 16 / 9, + controller: player + ..setFullScreenListener((value) async { + final videoData = await player.videoData; + final startSeconds = await player.currentTime; + if (!context.mounted) return; + final currentTime = await FullscreenYoutubePlayer.launch( + context, + videoId: videoData.videoId, + startSeconds: startSeconds, + ); + if (currentTime != null) { + player.seekTo(seconds: currentTime, allowSeekAhead: true); + } + }), + builder: (context, youtube) { + return Column( + children: [ + youtube, + if (playerController.videoPosition) + YoutubeValueBuilder( + controller: player, + builder: (context, youtubeValue) { + return StreamBuilder( stream: player.videoStateStream, builder: (context, snapshot) { - final int position = - snapshot.data?.position.inMilliseconds ?? 0; - final int duration = + final int totalDuration = youtubeValue.metaData.duration.inMilliseconds; + final int current = + snapshot.data?.position.inMilliseconds ?? 0; return LinearProgressIndicator( - value: duration == 0 ? 0 : position / duration, + value: totalDuration == 0 + ? 0 + : current / totalDuration, minHeight: 3, ); - }) - ], - )); - } else { - return youtube; - } - }, + }); + }, + ) + ], + ); + }, + ); + }), ); } @@ -282,3 +274,38 @@ class PlayerController extends WidgetController { } } } + +class YoutubeNotifier extends ChangeNotifier { + bool isCalled = false; + Future initializeYoutube(PlayerController playerController, + List list, YoutubePlayerController player) async { + if (!playerController.autoplay) { + (list.isEmpty) + ? await player.cueVideoById( + videoId: playerController.url, + startSeconds: playerController.startSeconds, + endSeconds: playerController.endSeconds) + : await player.cuePlaylist( + list: playerController.videoList, + startSeconds: playerController.startSeconds, + listType: ListType.playlist); + } else { + (list.isEmpty) + ? await player.loadVideoById( + videoId: playerController.url, + startSeconds: playerController.startSeconds, + endSeconds: playerController.endSeconds) + : await player.loadPlaylist( + list: playerController.videoList, + startSeconds: playerController.startSeconds, + listType: ListType.playlist); + } + } + + Future loadYouTube(PlayerController playerController, List list, + YoutubePlayerController player) async { + await initializeYoutube(playerController, list, player); + return await Future.delayed(const Duration(seconds: 1)) + .then((value) => initializeYoutube(playerController, list, player)); + } +} From 2a175ffc66caa4d3f25d885bc5d720b87468fda7 Mon Sep 17 00:00:00 2001 From: Hemish Pancholi Date: Sat, 25 Nov 2023 00:46:11 +0530 Subject: [PATCH 05/12] Fixed TextOverflow bug --- assets/schema/ensemble_schema.json | 2 +- lib/widget/html.dart | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 221273dd9..464cb41dc 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -456,7 +456,7 @@ }, "cssStyles": { "type": "array", - "oneOf": [ + "items": [ { "type": "object", "properties": { diff --git a/lib/widget/html.dart b/lib/widget/html.dart index de7a6f263..3643af9eb 100644 --- a/lib/widget/html.dart +++ b/lib/widget/html.dart @@ -14,9 +14,6 @@ import 'package:ensemble/framework/action.dart' as ensemble; import 'package:yaml/yaml.dart'; class CSSStyle { - StringBuffer cssBuffer; - Map> cssMap; - CSSStyle._({required this.cssBuffer, required this.cssMap}); factory CSSStyle.fromYaml(List yaml) { @@ -47,6 +44,9 @@ class CSSStyle { return CSSStyle._(cssBuffer: rtnCssBuffer, cssMap: rtnCssMap); } + StringBuffer cssBuffer; + Map> cssMap; + Map getStyle() { Map style = Style.fromCss( cssBuffer.toString(), @@ -66,8 +66,9 @@ class CSSStyle { if (containsMatch) { style[key] = style[key]!.copyWith( maxLines: value['maxLines'], - textOverflow: TextOverflow.values.asNameMap()['textOverflow'], - textTransform: TextTransform.values.asNameMap()['textTransform'], + textOverflow: TextOverflow.values.asNameMap()[value['textOverflow']], + textTransform: + TextTransform.values.asNameMap()[value['textTransform']], ); } }); @@ -79,10 +80,12 @@ class CSSStyle { /// widget to render Html content class EnsembleHtml extends StatefulWidget with Invokable, HasController { - static const type = 'Html'; EnsembleHtml({Key? key}) : super(key: key); + static const type = 'Html'; + final HtmlController _controller = HtmlController(); + @override HtmlController get controller => _controller; @@ -94,6 +97,11 @@ class EnsembleHtml extends StatefulWidget return {'text': () => _controller.text}; } + @override + Map methods() { + return {}; + } + @override Map setters() { return { @@ -107,18 +115,12 @@ class EnsembleHtml extends StatefulWidget }, }; } - - @override - Map methods() { - return {}; - } } class HtmlController extends BoxController { - String? text; - ensemble.EnsembleAction? onLinkTap; - CSSStyle? cssStyle; + ensemble.EnsembleAction? onLinkTap; + String? text; } class HtmlState extends framework.WidgetState { From 6046ca27a0654edaaf73a63ebd3e891e4516b760 Mon Sep 17 00:00:00 2001 From: Hemish Pancholi Date: Mon, 27 Nov 2023 22:39:37 +0530 Subject: [PATCH 06/12] Fixed a minor bug --- lib/util/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/utils.dart b/lib/util/utils.dart index 082ebbe77..c0d7fe975 100644 --- a/lib/util/utils.dart +++ b/lib/util/utils.dart @@ -287,7 +287,7 @@ class Utils { } static List? getListOfYamlMap(dynamic value) { - if (value is YamlList) { + if (value is YamlList || value is List) { List results = []; for (var item in value) { if (item is YamlMap) { From 6307085056a375a1100b59ab6dc57ff178c3fdea Mon Sep 17 00:00:00 2001 From: vinothvino42 Date: Wed, 29 Nov 2023 16:02:30 +0530 Subject: [PATCH 07/12] feat: add keyboard toolbar with done button --- lib/widget/input/form_textfield.dart | 59 ++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/widget/input/form_textfield.dart b/lib/widget/input/form_textfield.dart index 77752c8b2..411651655 100644 --- a/lib/widget/input/form_textfield.dart +++ b/lib/widget/input/form_textfield.dart @@ -14,6 +14,7 @@ import 'package:ensemble/util/utils.dart'; import 'package:ensemble/widget/input/form_helper.dart'; import 'package:ensemble/widget/helpers/widgets.dart'; import 'package:ensemble_ts_interpreter/invokables/invokablecontroller.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:email_validator/email_validator.dart'; @@ -217,6 +218,15 @@ class TextInputState extends FormFieldWidgetState // This is so we can be consistent with the other input widgets' onChange String previousText = ''; bool didItChange = false; + // password is obscure by default + late bool currentlyObscured; + late List _inputFormatter; + OverlayEntry? overlayEntry; + bool get toolbarDoneStatus { + final status = widget.keyboardType == TextInputType.phone || + widget.keyboardType == TextInputType.number; + return status; + } void evaluateChanges() { if (didItChange) { @@ -232,9 +242,28 @@ class TextInputState extends FormFieldWidgetState } } - // password is obscure by default - late bool currentlyObscured; - late List _inputFormatter; + void showOverlay(BuildContext context) { + if (overlayEntry != null || !toolbarDoneStatus) return; + OverlayState overlayState = Overlay.of(context); + overlayEntry = OverlayEntry(builder: (context) { + return Positioned( + bottom: MediaQuery.of(context).viewInsets.bottom, + right: 0.0, + left: 0.0, + child: const _InputDoneButton(), + ); + }); + + overlayState.insert(overlayEntry!); + } + + void removeOverlayAndUnfocus() { + if (overlayEntry != null) { + overlayEntry!.remove(); + overlayEntry = null; + } + FocusManager.instance.primaryFocus?.unfocus(); + } @override void initState() { @@ -410,6 +439,8 @@ class TextInputState extends FormFieldWidgetState controller: widget.textController, focusNode: focusNode, enabled: isEnabled(), + onTap: () => showOverlay(context), + onTapOutside: (_) => removeOverlayAndUnfocus(), onFieldSubmitted: (value) => widget.controller.submitForm(context), onChanged: (String txt) { if (txt != previousText) { @@ -494,3 +525,25 @@ class TextInputState extends FormFieldWidgetState } enum InputType { email, phone, ipAddress, number, text, url, datetime } + +class _InputDoneButton extends StatelessWidget { + const _InputDoneButton(); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + color: Colors.grey[200], + alignment: Alignment.topRight, + padding: const EdgeInsets.only(top: 1.0, bottom: 1.0), + child: CupertinoButton( + padding: const EdgeInsets.only(right: 24.0, top: 2.0, bottom: 2.0), + onPressed: () => FocusScope.of(context).requestFocus(FocusNode()), + child: const Text( + 'Done', + style: TextStyle(color: Colors.black, fontWeight: FontWeight.normal), + ), + ), + ); + } +} From 0a8daf28faf193ad9ec8a9a7225f27add49bf5ee Mon Sep 17 00:00:00 2001 From: Hemish Pancholi Date: Thu, 30 Nov 2023 13:44:10 +0530 Subject: [PATCH 08/12] Added updateMaxLines method --- lib/widget/html.dart | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/widget/html.dart b/lib/widget/html.dart index 3643af9eb..64e4b1abe 100644 --- a/lib/widget/html.dart +++ b/lib/widget/html.dart @@ -47,6 +47,10 @@ class CSSStyle { StringBuffer cssBuffer; Map> cssMap; + void updateMaxLines(String selector, int maxLines) { + cssMap[selector]?['maxLines'] = maxLines; + } + Map getStyle() { Map style = Style.fromCss( cssBuffer.toString(), @@ -99,7 +103,10 @@ class EnsembleHtml extends StatefulWidget @override Map methods() { - return {}; + return { + // Updates the max lines for a given selector. Takes two attributes as selector and maxLines + 'updateMaxLines': controller.htmlAction!.updateMaxLines, + }; } @override @@ -121,9 +128,38 @@ class HtmlController extends BoxController { CSSStyle? cssStyle; ensemble.EnsembleAction? onLinkTap; String? text; + + // Added action so it becomes easy to add additional methods in future + HtmlAction? htmlAction; } -class HtmlState extends framework.WidgetState { +mixin HtmlAction on framework.WidgetState { + void updateMaxLines(String selector, int maxLines); +} + +class HtmlState extends framework.WidgetState with HtmlAction { + @override + void updateMaxLines(String selector, int maxLines) { + // Need to add it here to be able to do setState + setState(() { + widget.controller.cssStyle?.updateMaxLines(selector, maxLines); + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + widget.controller.htmlAction = this; + } + + @override + void didUpdateWidget(covariant EnsembleHtml oldWidget) { + super.didUpdateWidget(oldWidget); + + widget.controller.htmlAction = this; + } + @override Widget buildWidget(BuildContext context) { return BoxWrapper( From 832bb5a3977e56622088f4248a17576e787c8d74 Mon Sep 17 00:00:00 2001 From: snehmehta Date: Thu, 30 Nov 2023 21:56:05 +0530 Subject: [PATCH 09/12] close tooltip on tap --- lib/widget/calendar.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/widget/calendar.dart b/lib/widget/calendar.dart index 0e5e970bb..7e9e2b05f 100644 --- a/lib/widget/calendar.dart +++ b/lib/widget/calendar.dart @@ -490,6 +490,7 @@ int getHashCode(DateTime key) { class CalendarState extends WidgetState { final CalendarFormat _calendarFormat = CalendarFormat.month; + bool showTooltip = true; @override void initState() { @@ -558,6 +559,7 @@ class CalendarState extends WidgetState { widget._controller.focusedDay.value = focusedDay; widget._controller.rangeStart = start; widget._controller.rangeEnd = end; + showTooltip = false; }); if (end != null && widget._controller.onRangeComplete != null) { widget._controller.range = DateTimeRange(start: start!, end: end); @@ -636,13 +638,18 @@ class CalendarState extends WidgetState { toolTip: widget._controller.tooltip, toolTipBackgroundColor: widget._controller.tooltipBackgroundColor, toolTipStyle: widget._controller.tooltipTextStyle, + showTooltip: showTooltip, calendarBuilders: CalendarBuilders( - overlayDefaultBuilder: (context) { + overlayDefaultBuilder: (context, collapsedLength) { + Map data = {}; + if (collapsedLength != null) { + data['collapsedLength'] = collapsedLength; + } if (widget._controller.overlapOverflowBuilder == null) { return null; } return widgetBuilder( - context, widget._controller.overlapOverflowBuilder, {}); + context, widget._controller.overlapOverflowBuilder, data); }, overlayBuilder: widget._controller.rowSpans.value.isEmpty ? null From c61e7294e46f0f087a2055ec10d6d1928579f79b Mon Sep 17 00:00:00 2001 From: Sanjay Rathor Date: Fri, 1 Dec 2023 17:11:55 +0530 Subject: [PATCH 10/12] added boxstyles --- assets/schema/ensemble_schema.json | 3 ++ lib/widget/youtube/youtube.dart | 54 ++++++++++++++++-------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 38e95c183..95902675f 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -6549,6 +6549,9 @@ "type" : "string", "description": "The unique identifier for this widget" }, + "styles": { + "$ref": "#/$defs/boxStyles" + }, "url" : { "type": "string", "description": "The URL source to the youTube video" diff --git a/lib/widget/youtube/youtube.dart b/lib/widget/youtube/youtube.dart index efc8d8755..666ac40cf 100644 --- a/lib/widget/youtube/youtube.dart +++ b/lib/widget/youtube/youtube.dart @@ -1,6 +1,7 @@ import 'package:ensemble/framework/widget/widget.dart'; import 'package:ensemble/util/utils.dart'; import 'package:ensemble/widget/helpers/controllers.dart'; +import 'package:ensemble/widget/helpers/widgets.dart'; import 'package:ensemble/widget/youtube/youtubestate.dart'; import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/foundation.dart'; @@ -167,30 +168,33 @@ class YouTubeState extends WidgetState with YouTubeMethods { } }), builder: (context, youtube) { - return Column( - children: [ - youtube, - if (playerController.videoPosition) - YoutubeValueBuilder( - controller: player, - builder: (context, youtubeValue) { - return StreamBuilder( - stream: player.videoStateStream, - builder: (context, snapshot) { - final int totalDuration = - youtubeValue.metaData.duration.inMilliseconds; - final int current = - snapshot.data?.position.inMilliseconds ?? 0; - return LinearProgressIndicator( - value: totalDuration == 0 - ? 0 - : current / totalDuration, - minHeight: 3, - ); - }); - }, - ) - ], + return BoxWrapper( + boxController: widget.controller, + widget: Column( + children: [ + youtube, + if (playerController.videoPosition) + YoutubeValueBuilder( + controller: player, + builder: (context, youtubeValue) { + return StreamBuilder( + stream: player.videoStateStream, + builder: (context, snapshot) { + final int totalDuration = + youtubeValue.metaData.duration.inMilliseconds; + final int current = + snapshot.data?.position.inMilliseconds ?? 0; + return LinearProgressIndicator( + value: totalDuration == 0 + ? 0 + : current / totalDuration, + minHeight: 3, + ); + }); + }, + ) + ], + ), ); }, ); @@ -226,7 +230,7 @@ class YouTubeState extends WidgetState with YouTubeMethods { void setVolume(int volume) => player.setVolume(volume); } -class PlayerController extends WidgetController { +class PlayerController extends BoxController { YouTubeMethods? youtubeMethods; String url = ""; double? aspectRatio; From fd807f58894c4a33c106d56e2c8e8d39416afc79 Mon Sep 17 00:00:00 2001 From: snehmehta Date: Mon, 4 Dec 2023 08:10:00 +0530 Subject: [PATCH 11/12] calendar: js controlled tooltip --- lib/widget/calendar.dart | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/widget/calendar.dart b/lib/widget/calendar.dart index 7e9e2b05f..1c28ac7f4 100644 --- a/lib/widget/calendar.dart +++ b/lib/widget/calendar.dart @@ -99,6 +99,8 @@ class EnsembleCalendar extends StatefulWidget _controller.headerTextStyle = Utils.getTextStyle(value), 'header': (value) => _controller.header = value, 'tootlip': (value) => setTooltip(value), + 'showTooltip': (value) => + _controller.showTooltip = Utils.getBool(value, fallback: false) }; } @@ -463,6 +465,7 @@ class CalendarController extends WidgetController { DateTime? tooltipDate; TextStyle? tooltipTextStyle; Color? tooltipBackgroundColor; + bool showTooltip = false; final ValueNotifier> markedDays = ValueNotifier( LinkedHashSet( @@ -490,27 +493,27 @@ int getHashCode(DateTime key) { class CalendarState extends WidgetState { final CalendarFormat _calendarFormat = CalendarFormat.month; - bool showTooltip = true; @override void initState() { - widget._controller.selectedDays.addListener(() { - setState(() {}); - }); - - widget._controller.markedDays.addListener(() { - setState(() {}); - }); - - widget._controller.disableDays.addListener(() { - setState(() {}); - }); + widget._controller.selectedDays.addListener(listener); + widget._controller.markedDays.addListener(listener); + widget._controller.disableDays.addListener(listener); + widget._controller.rowSpans.addListener(listener); + super.initState(); + } - widget._controller.rowSpans.addListener(() { - setState(() {}); - }); + void listener() { + setState(() {}); + } - super.initState(); + @override + void dispose() { + widget._controller.selectedDays.removeListener(listener); + widget._controller.markedDays.removeListener(listener); + widget._controller.disableDays.removeListener(listener); + widget._controller.rowSpans.removeListener(listener); + super.dispose(); } void _onDaySelected(DateTime selectedDay, DateTime focusedDay) { @@ -559,7 +562,6 @@ class CalendarState extends WidgetState { widget._controller.focusedDay.value = focusedDay; widget._controller.rangeStart = start; widget._controller.rangeEnd = end; - showTooltip = false; }); if (end != null && widget._controller.onRangeComplete != null) { widget._controller.range = DateTimeRange(start: start!, end: end); @@ -638,7 +640,7 @@ class CalendarState extends WidgetState { toolTip: widget._controller.tooltip, toolTipBackgroundColor: widget._controller.tooltipBackgroundColor, toolTipStyle: widget._controller.tooltipTextStyle, - showTooltip: showTooltip, + showTooltip: widget._controller.showTooltip, calendarBuilders: CalendarBuilders( overlayDefaultBuilder: (context, collapsedLength) { Map data = {}; From 9e9bf42936eee67eb07c752170ca78cecc8a64f2 Mon Sep 17 00:00:00 2001 From: vinothvino42 Date: Mon, 4 Dec 2023 20:04:32 +0530 Subject: [PATCH 12/12] fix: update textinput's toolbar with toolbarDone style --- assets/schema/ensemble_schema.json | 4 ++++ lib/widget/input/form_textfield.dart | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 024413aff..9e899f04a 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -1558,6 +1558,10 @@ "type": "boolean", "description": "enable the toggling between plain and obscure text." }, + "toolbarDone": { + "type": "boolean", + "description": "enable the toolbar with done button in the TextInput. (defaults to False)" + }, "enableClearText": { "type": "boolean", "description": "It enables the default suffix clear icon button for the text input field to clear the values. Default (false)" diff --git a/lib/widget/input/form_textfield.dart b/lib/widget/input/form_textfield.dart index 411651655..00cb42623 100644 --- a/lib/widget/input/form_textfield.dart +++ b/lib/widget/input/form_textfield.dart @@ -131,6 +131,8 @@ abstract class BaseTextInput extends StatefulWidget _controller.enableClearText = Utils.optionalBool(value), 'obscureToggle': (value) => _controller.obscureToggle = Utils.optionalBool(value), + 'toolbarDone': (value) => + _controller.toolbarDoneButton = Utils.optionalBool(value), 'keyboardAction': (value) => _controller.keyboardAction = _getKeyboardAction(value), 'maxLines': (value) => _controller.maxLines = @@ -200,6 +202,8 @@ class TextInputController extends FormFieldController { // applicable only for Password or obscure TextInput, to toggle between plain and secure text bool? obscureToggle; + bool? toolbarDoneButton; + model.InputValidator? validator; String? inputType; String? mask; @@ -223,9 +227,7 @@ class TextInputState extends FormFieldWidgetState late List _inputFormatter; OverlayEntry? overlayEntry; bool get toolbarDoneStatus { - final status = widget.keyboardType == TextInputType.phone || - widget.keyboardType == TextInputType.number; - return status; + return widget.controller.toolbarDoneButton ?? false; } void evaluateChanges() {