From 1404cafe8d5bcbe5b98251ecfdaa521d25f1a035 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 4 Nov 2023 13:25:33 -0700 Subject: [PATCH 1/9] AlertDialog uses global state --- package/lib/src/controls/alert_dialog.dart | 57 ++++++++++++------- package/lib/src/flet_app_services.dart | 4 ++ .../lib/src/utils/control_global_state.dart | 34 +++++++++++ 3 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 package/lib/src/utils/control_global_state.dart diff --git a/package/lib/src/controls/alert_dialog.dart b/package/lib/src/controls/alert_dialog.dart index 3eb38d005..b9337acde 100644 --- a/package/lib/src/controls/alert_dialog.dart +++ b/package/lib/src/controls/alert_dialog.dart @@ -8,6 +8,7 @@ import '../models/control.dart'; import '../protocol/update_control_props_payload.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; +import '../utils/control_global_state.dart'; import '../utils/edge_insets.dart'; import 'create_control.dart'; import 'error.dart'; @@ -33,7 +34,23 @@ class AlertDialogControl extends StatefulWidget { } class _AlertDialogControlState extends State { - bool _open = false; + String? _id; + ControlsGlobalState? _globalState; + + @override + void initState() { + super.initState(); + debugPrint("AlertDialog initState() ($hashCode)"); + } + + @override + void dispose() { + debugPrint("AlertDialog dispose() ($hashCode)"); + if (_id != null) { + _globalState?.remove(_id!, "open", hashCode); + } + super.dispose(); + } Widget _createAlertDialog() { bool disabled = widget.control.isDisabled || widget.parentDisabled; @@ -72,7 +89,13 @@ class _AlertDialogControlState extends State { @override Widget build(BuildContext context) { - debugPrint("AlertDialog build: ${widget.control.id}"); + debugPrint("AlertDialog build ($hashCode): ${widget.control.id}"); + + _id = widget.control.id; + _globalState = FletAppServices.of(context).globalState; + var server = FletAppServices.of(context).server; + + bool lastOpen = _globalState?.get(widget.control.id, "open") ?? false; return StoreConnector( distinct: true, @@ -82,30 +105,28 @@ class _AlertDialogControlState extends State { var open = widget.control.attrBool("open", false)!; var modal = widget.control.attrBool("modal", false)!; - // var removeCurrentSnackbar = - // widget.control.attrBool("removeCurrentSnackBar", false)!; - debugPrint("Current open state: $_open"); + debugPrint("Current open state: $lastOpen"); debugPrint("New open state: $open"); - if (open && (open != _open)) { + if (open && (open != lastOpen)) { var dialog = _createAlertDialog(); if (dialog is ErrorControl) { return dialog; } - WidgetsBinding.instance.addPostFrameCallback((_) { - // if (removeCurrentSnackbar) { - // ScaffoldMessenger.of(context).removeCurrentSnackBar(); - // } + _globalState?.set(widget.control.id, "open", open, hashCode); + WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( barrierDismissible: !modal, context: context, builder: (context) => _createAlertDialog()).then((value) { - debugPrint("Dialog dismissed: $_open"); - bool shouldDismiss = _open; - _open = false; + lastOpen = + _globalState?.get(widget.control.id, "open") ?? false; + debugPrint("Dialog should be dismissed ($hashCode): $lastOpen"); + bool shouldDismiss = lastOpen; + _globalState?.set(widget.control.id, "open", false, hashCode); if (shouldDismiss) { List> props = [ @@ -113,22 +134,18 @@ class _AlertDialogControlState extends State { ]; dispatch(UpdateControlPropsAction( UpdateControlPropsPayload(props: props))); - FletAppServices.of(context) - .server - .updateControlProps(props: props); - FletAppServices.of(context).server.sendPageEvent( + server.updateControlProps(props: props); + server.sendPageEvent( eventTarget: widget.control.id, eventName: "dismiss", eventData: ""); } }); }); - } else if (open != _open && _open) { + } else if (open != lastOpen && lastOpen) { Navigator.pop(context); } - _open = open; - return widget.nextChild ?? const SizedBox.shrink(); }); } diff --git a/package/lib/src/flet_app_services.dart b/package/lib/src/flet_app_services.dart index bd367a241..d82e2747d 100644 --- a/package/lib/src/flet_app_services.dart +++ b/package/lib/src/flet_app_services.dart @@ -7,6 +7,7 @@ import 'flet_server.dart'; import 'flet_server_protocol.dart'; import 'models/app_state.dart'; import 'reducers.dart'; +import 'utils/control_global_state.dart'; class FletAppServices extends InheritedWidget { final FletAppServices? parentAppServices; @@ -20,6 +21,7 @@ class FletAppServices extends InheritedWidget { late final FletServer server; late final Store store; final Map globalKeys = {}; + final ControlsGlobalState globalState = ControlsGlobalState(); final Map controlInvokeMethods = {}; FletAppServices( @@ -34,6 +36,8 @@ class FletAppServices extends InheritedWidget { this.reconnectIntervalMs, this.reconnectTimeoutMs}) : super(key: key, child: child) { + debugPrint("FletAppServices()"); + store = Store(appReducer, initialState: AppState.initial()); server = FletServer(store, controlInvokeMethods, reconnectIntervalMs: reconnectIntervalMs, diff --git a/package/lib/src/utils/control_global_state.dart b/package/lib/src/utils/control_global_state.dart new file mode 100644 index 000000000..e0f6d60f7 --- /dev/null +++ b/package/lib/src/utils/control_global_state.dart @@ -0,0 +1,34 @@ +class ControlsGlobalState { + final Map _state = {}; + + dynamic get(String controlId, String key) { + return _state["$controlId $key"]?.value; + } + + set(String controlId, String key, dynamic value, int widgetHashCode) { + String gkey = "$controlId $key"; + ControlGlobalStateValue? state = _state[gkey]; + if (state == null) { + state = ControlGlobalStateValue(); + _state[gkey] = state; + } + state.value = value; + state.widgetHashCodes.add(widgetHashCode); + } + + remove(String controlId, String key, int widgetHashCode) { + String gkey = "$controlId $key"; + ControlGlobalStateValue? state = _state[gkey]; + if (state != null) { + state.widgetHashCodes.remove(widgetHashCode); + if (state.widgetHashCodes.isEmpty) { + _state.remove(gkey); + } + } + } +} + +class ControlGlobalStateValue { + dynamic value; + final Set widgetHashCodes = {}; +} From 03b9c9d6b1980b508ed076a630d14bd75d77c1e4 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 4 Nov 2023 13:31:03 -0700 Subject: [PATCH 2/9] DatePicker uses global state --- package/lib/src/controls/date_picker.dart | 35 +++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/package/lib/src/controls/date_picker.dart b/package/lib/src/controls/date_picker.dart index d65c07e14..9dbff164b 100644 --- a/package/lib/src/controls/date_picker.dart +++ b/package/lib/src/controls/date_picker.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; + import '../actions.dart'; import '../flet_app_services.dart'; import '../models/control.dart'; import '../protocol/update_control_props_payload.dart'; -import 'form_field.dart'; +import '../utils/control_global_state.dart'; import '../utils/icons.dart'; +import 'form_field.dart'; class DatePickerControl extends StatefulWidget { final Control? parent; @@ -27,11 +29,33 @@ class DatePickerControl extends StatefulWidget { } class _DatePickerControlState extends State { - bool _open = false; + String? _id; + ControlsGlobalState? _globalState; + + @override + void initState() { + super.initState(); + debugPrint("DatePicker initState() ($hashCode)"); + } + + @override + void dispose() { + debugPrint("DatePicker dispose() ($hashCode)"); + if (_id != null) { + _globalState?.remove(_id!, "open", hashCode); + } + super.dispose(); + } @override Widget build(BuildContext context) { debugPrint("DatePicker build: ${widget.control.id}"); + + _id = widget.control.id; + _globalState = FletAppServices.of(context).globalState; + + bool lastOpen = _globalState?.get(widget.control.id, "open") ?? false; + var open = widget.control.attrBool("open", false)!; DateTime? value = widget.control.attrDateTime("value"); DateTime? firstDate = widget.control.attrDateTime("firstDate"); @@ -83,6 +107,7 @@ class _DatePickerControlState extends State { stringValue = dateValue.toIso8601String(); eventName = "change"; } + _globalState?.set(widget.control.id, "open", false, hashCode); List> props = [ {"i": widget.control.id, "value": stringValue, "open": "false"} ]; @@ -129,7 +154,9 @@ class _DatePickerControlState extends State { return dialog; } - if (open && !_open) { + if (open && (open != lastOpen)) { + _globalState?.set(widget.control.id, "open", open, hashCode); + WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( context: context, @@ -139,8 +166,6 @@ class _DatePickerControlState extends State { }); }); } - - _open = open; return const SizedBox.shrink(); } } From 2b3b6ef4d8773dc69d40db97cdc573cd57c71c58 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sun, 5 Nov 2023 10:44:03 -0800 Subject: [PATCH 3/9] State on the control level --- package/lib/src/controls/alert_dialog.dart | 20 +++-------- package/lib/src/controls/create_control.dart | 2 +- package/lib/src/controls/date_picker.dart | 18 +++------- package/lib/src/models/app_state.dart | 10 +++--- package/lib/src/models/control.dart | 37 ++++++++++++-------- 5 files changed, 37 insertions(+), 50 deletions(-) diff --git a/package/lib/src/controls/alert_dialog.dart b/package/lib/src/controls/alert_dialog.dart index b9337acde..d5bea8e7a 100644 --- a/package/lib/src/controls/alert_dialog.dart +++ b/package/lib/src/controls/alert_dialog.dart @@ -8,7 +8,6 @@ import '../models/control.dart'; import '../protocol/update_control_props_payload.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; -import '../utils/control_global_state.dart'; import '../utils/edge_insets.dart'; import 'create_control.dart'; import 'error.dart'; @@ -34,21 +33,15 @@ class AlertDialogControl extends StatefulWidget { } class _AlertDialogControlState extends State { - String? _id; - ControlsGlobalState? _globalState; - @override void initState() { - super.initState(); debugPrint("AlertDialog initState() ($hashCode)"); + super.initState(); } @override void dispose() { debugPrint("AlertDialog dispose() ($hashCode)"); - if (_id != null) { - _globalState?.remove(_id!, "open", hashCode); - } super.dispose(); } @@ -91,11 +84,9 @@ class _AlertDialogControlState extends State { Widget build(BuildContext context) { debugPrint("AlertDialog build ($hashCode): ${widget.control.id}"); - _id = widget.control.id; - _globalState = FletAppServices.of(context).globalState; var server = FletAppServices.of(context).server; - bool lastOpen = _globalState?.get(widget.control.id, "open") ?? false; + bool lastOpen = widget.control.state["open"] ?? false; return StoreConnector( distinct: true, @@ -115,18 +106,17 @@ class _AlertDialogControlState extends State { return dialog; } - _globalState?.set(widget.control.id, "open", open, hashCode); + widget.control.state["open"] = open; WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( barrierDismissible: !modal, context: context, builder: (context) => _createAlertDialog()).then((value) { - lastOpen = - _globalState?.get(widget.control.id, "open") ?? false; + lastOpen = widget.control.state["open"] ?? false; debugPrint("Dialog should be dismissed ($hashCode): $lastOpen"); bool shouldDismiss = lastOpen; - _globalState?.set(widget.control.id, "open", false, hashCode); + widget.control.state["open"] = false; if (shouldDismiss) { List> props = [ diff --git a/package/lib/src/controls/create_control.dart b/package/lib/src/controls/create_control.dart index 7c11c0acc..f6fdc6c9c 100644 --- a/package/lib/src/controls/create_control.dart +++ b/package/lib/src/controls/create_control.dart @@ -57,6 +57,7 @@ import 'progress_bar.dart'; import 'progress_ring.dart'; import 'radio.dart'; import 'radio_group.dart'; +import 'range_slider.dart'; import 'responsive_row.dart'; import 'row.dart'; import 'safe_area.dart'; @@ -75,7 +76,6 @@ import 'tooltip.dart'; import 'transparent_pointer.dart'; import 'vertical_divider.dart'; import 'window_drag_area.dart'; -import 'range_slider.dart'; Widget createControl(Control? parent, String id, bool parentDisabled, {Widget? nextChild}) { diff --git a/package/lib/src/controls/date_picker.dart b/package/lib/src/controls/date_picker.dart index 9dbff164b..732c36d1d 100644 --- a/package/lib/src/controls/date_picker.dart +++ b/package/lib/src/controls/date_picker.dart @@ -4,7 +4,6 @@ import '../actions.dart'; import '../flet_app_services.dart'; import '../models/control.dart'; import '../protocol/update_control_props_payload.dart'; -import '../utils/control_global_state.dart'; import '../utils/icons.dart'; import 'form_field.dart'; @@ -29,21 +28,15 @@ class DatePickerControl extends StatefulWidget { } class _DatePickerControlState extends State { - String? _id; - ControlsGlobalState? _globalState; - @override void initState() { - super.initState(); debugPrint("DatePicker initState() ($hashCode)"); + super.initState(); } @override void dispose() { debugPrint("DatePicker dispose() ($hashCode)"); - if (_id != null) { - _globalState?.remove(_id!, "open", hashCode); - } super.dispose(); } @@ -51,10 +44,7 @@ class _DatePickerControlState extends State { Widget build(BuildContext context) { debugPrint("DatePicker build: ${widget.control.id}"); - _id = widget.control.id; - _globalState = FletAppServices.of(context).globalState; - - bool lastOpen = _globalState?.get(widget.control.id, "open") ?? false; + bool lastOpen = widget.control.state["open"] ?? false; var open = widget.control.attrBool("open", false)!; DateTime? value = widget.control.attrDateTime("value"); @@ -107,7 +97,7 @@ class _DatePickerControlState extends State { stringValue = dateValue.toIso8601String(); eventName = "change"; } - _globalState?.set(widget.control.id, "open", false, hashCode); + widget.control.state["open"] = false; List> props = [ {"i": widget.control.id, "value": stringValue, "open": "false"} ]; @@ -155,7 +145,7 @@ class _DatePickerControlState extends State { } if (open && (open != lastOpen)) { - _globalState?.set(widget.control.id, "open", open, hashCode); + widget.control.state["open"] = open; WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( diff --git a/package/lib/src/models/app_state.dart b/package/lib/src/models/app_state.dart index 25fa9b245..16e462382 100644 --- a/package/lib/src/models/app_state.dart +++ b/package/lib/src/models/app_state.dart @@ -39,7 +39,7 @@ class AppState extends Equatable { required this.displayBrightness, required this.controls}); - factory AppState.initial() => const AppState( + factory AppState.initial() => AppState( pageUri: null, assetsDir: "", route: "", @@ -49,8 +49,8 @@ class AppState extends Equatable { isRegistered: false, reconnectDelayMs: 0, error: "", - size: Size(0, 0), - sizeBreakpoints: { + size: const Size(0, 0), + sizeBreakpoints: const { "xs": 0, "sm": 576, "md": 768, @@ -65,8 +65,8 @@ class AppState extends Equatable { pid: "", type: "page", name: "", - childIds: [], - attrs: {}) + childIds: const [], + attrs: const {}) }); AppState copyWith( diff --git a/package/lib/src/models/control.dart b/package/lib/src/models/control.dart index 52fd2a8df..d96ac636b 100644 --- a/package/lib/src/models/control.dart +++ b/package/lib/src/models/control.dart @@ -9,8 +9,9 @@ class Control extends Equatable { final String? name; final List childIds; final Map attrs; + final Map state = {}; - const Control( + Control( {required this.id, required this.pid, required this.type, @@ -46,7 +47,7 @@ class Control extends Equatable { bool get isNonVisual { return [ //"alertdialog", - "audio", + //"audio", "banner", //"bottomsheet", "clipboard", @@ -95,19 +96,25 @@ class Control extends Equatable { } Control copyWith( - {String? id, - String? pid, - String? type, - String? name, - List? childIds, - Map? attrs}) => - Control( - id: id ?? this.id, - pid: pid ?? this.pid, - type: type ?? this.type, - name: name ?? this.name, - childIds: childIds ?? this.childIds, - attrs: attrs ?? this.attrs); + {String? id, + String? pid, + String? type, + String? name, + List? childIds, + Map? attrs, + Map? state}) { + Control c = Control( + id: id ?? this.id, + pid: pid ?? this.pid, + type: type ?? this.type, + name: name ?? this.name, + childIds: childIds ?? this.childIds, + attrs: attrs ?? this.attrs); + for (var element in this.state.entries) { + c.state[element.key] = element.value; + } + return c; + } @override List get props => [id, pid, type, name, childIds, attrs]; From dc66f9356a1a755bbeac70bb6d8d60a613311ce0 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sun, 5 Nov 2023 19:23:31 -0800 Subject: [PATCH 4/9] Control-level state Fix #2025, Fix #1807, Fix #1236, Fix #1772 --- package/lib/src/controls/alert_dialog.dart | 12 -- package/lib/src/controls/audio.dart | 127 +++++++++++------- package/lib/src/controls/date_picker.dart | 12 -- package/lib/src/flet_app_services.dart | 2 - package/lib/src/models/control.dart | 2 + package/lib/src/reducers.dart | 5 + .../lib/src/utils/control_global_state.dart | 34 ----- 7 files changed, 89 insertions(+), 105 deletions(-) delete mode 100644 package/lib/src/utils/control_global_state.dart diff --git a/package/lib/src/controls/alert_dialog.dart b/package/lib/src/controls/alert_dialog.dart index d5bea8e7a..4fdea6940 100644 --- a/package/lib/src/controls/alert_dialog.dart +++ b/package/lib/src/controls/alert_dialog.dart @@ -33,18 +33,6 @@ class AlertDialogControl extends StatefulWidget { } class _AlertDialogControlState extends State { - @override - void initState() { - debugPrint("AlertDialog initState() ($hashCode)"); - super.initState(); - } - - @override - void dispose() { - debugPrint("AlertDialog dispose() ($hashCode)"); - super.dispose(); - } - Widget _createAlertDialog() { bool disabled = widget.control.isDisabled || widget.parentDisabled; var titleCtrls = diff --git a/package/lib/src/controls/audio.dart b/package/lib/src/controls/audio.dart index 29237a4ec..b8f8ef75a 100644 --- a/package/lib/src/controls/audio.dart +++ b/package/lib/src/controls/audio.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:audioplayers/audioplayers.dart'; @@ -33,33 +34,37 @@ class AudioControl extends StatefulWidget { } class _AudioControlState extends State { - String _src = ""; - String _srcBase64 = ""; - ReleaseMode? _releaseMode; - double? _volume; - double? _balance; - double? _playbackRate; + AudioPlayer? player; void Function(Duration)? _onDurationChanged; void Function(PlayerState)? _onStateChanged; void Function(int)? _onPositionChanged; Duration? _duration; int _position = -1; void Function()? _onSeekComplete; - late final AudioPlayer player; + StreamSubscription? _onDurationChangedSubscription; + StreamSubscription? _onStateChangedSubscription; + StreamSubscription? _onPositionChangedSubscription; + StreamSubscription? _onSeekCompleteSubscription; FletServer? _server; @override void initState() { - super.initState(); - player = AudioPlayer(); - player.onDurationChanged.listen((duration) { + debugPrint("Audio.initState($hashCode)"); + player = widget.control.state["player"]; + if (player == null) { + player = AudioPlayer(); + player = widget.control.state["player"] = player; + } + _onDurationChangedSubscription = + player?.onDurationChanged.listen((duration) { _onDurationChanged?.call(duration); _duration = duration; }); - player.onPlayerStateChanged.listen((state) { + _onStateChangedSubscription = player?.onPlayerStateChanged.listen((state) { _onStateChanged?.call(state); }); - player.onPositionChanged.listen((position) { + _onPositionChangedSubscription = + player?.onPositionChanged.listen((position) { int posMs = (position.inMilliseconds / 1000).round() * 1000; if (posMs != _position) { _position = posMs; @@ -70,21 +75,35 @@ class _AudioControlState extends State { } _onPositionChanged?.call(_position); }); - player.onSeekComplete.listen((event) { + _onSeekCompleteSubscription = player?.onSeekComplete.listen((event) { _onSeekComplete?.call(); }); + + widget.control.onRemove.clear(); + widget.control.onRemove.add(_onRemove); + super.initState(); + } + + void _onRemove() { + debugPrint("Audio.remove($hashCode)"); + widget.control.state["player"]?.dispose(); + _server?.controlInvokeMethods.remove(widget.control.id); } @override void deactivate() { - _server?.controlInvokeMethods.remove(widget.control.id); - player.dispose(); + debugPrint("Audio.deactivate($hashCode)"); + _onDurationChangedSubscription?.cancel(); + _onStateChangedSubscription?.cancel(); + _onPositionChangedSubscription?.cancel(); + _onSeekCompleteSubscription?.cancel(); super.deactivate(); } @override Widget build(BuildContext context) { - debugPrint("Audio build: ${widget.control.id}"); + debugPrint( + "Audio build: ${widget.control.id} (${widget.control.hashCode})"); var src = widget.control.attrString("src", "")!; var srcBase64 = widget.control.attrString("srcBase64", "")!; @@ -104,6 +123,13 @@ class _AudioControlState extends State { var server = FletAppServices.of(context).server; + final String prevSrc = widget.control.state["src"] ?? ""; + final String prevSrcBase64 = widget.control.state["srcBase64"] ?? ""; + final ReleaseMode? prevReleaseMode = widget.control.state["releaseMode"]; + final double? prevVolume = widget.control.state["volume"]; + final double? prevBalance = widget.control.state["balance"]; + final double? prevPlaybackRate = widget.control.state["playbackRate"]; + return StoreConnector( distinct: true, converter: (store) => PageArgsModel.fromStore(store), @@ -116,6 +142,7 @@ class _AudioControlState extends State { }; _onStateChanged = (state) { + debugPrint("Audio($hashCode) - state_changed: ${state.name}"); server.sendPageEvent( eventTarget: widget.control.id, eventName: "state_changed", @@ -139,64 +166,74 @@ class _AudioControlState extends State { }; () async { + debugPrint("Audio ($hashCode) src=$src, prevSrc=$prevSrc"); + debugPrint( + "Audio ($hashCode) srcBase64=$srcBase64, prevSrcBase64=$prevSrcBase64"); + bool srcChanged = false; - if (src != "" && src != _src) { - _src = src; + if (src != "" && src != prevSrc) { + widget.control.state["src"] = src; srcChanged = true; // URL or file? var assetSrc = getAssetSrc(src, pageArgs.pageUri!, pageArgs.assetsDir); if (assetSrc.isFile) { - await player.setSourceDeviceFile(assetSrc.path); + await player?.setSourceDeviceFile(assetSrc.path); } else { - await player.setSourceUrl(assetSrc.path); + await player?.setSourceUrl(assetSrc.path); } - } else if (srcBase64 != "" && srcBase64 != _srcBase64) { - _srcBase64 = srcBase64; + } else if (srcBase64 != "" && srcBase64 != prevSrcBase64) { + widget.control.state["srcBase64"] = srcBase64; srcChanged = true; - await player.setSourceBytes(base64Decode(srcBase64)); + await player?.setSourceBytes(base64Decode(srcBase64)); } if (srcChanged) { + debugPrint("Audio.srcChanged!"); server.sendPageEvent( eventTarget: widget.control.id, eventName: "loaded", eventData: ""); } - if (releaseMode != null && releaseMode != _releaseMode) { - _releaseMode = releaseMode; - await player.setReleaseMode(releaseMode); + if (releaseMode != null && releaseMode != prevReleaseMode) { + debugPrint("Audio.setReleaseMode($releaseMode)"); + widget.control.state["releaseMode"] = releaseMode; + await player?.setReleaseMode(releaseMode); } if (volume != null && - volume != _volume && + volume != prevVolume && volume >= 0 && volume <= 1) { - _volume = volume; - await player.setVolume(volume); + widget.control.state["volume"] = volume; + debugPrint("Audio.setVolume($volume)"); + await player?.setVolume(volume); } if (playbackRate != null && - playbackRate != _playbackRate && + playbackRate != prevPlaybackRate && playbackRate >= 0 && playbackRate <= 2) { - _playbackRate = playbackRate; - await player.setPlaybackRate(playbackRate); + widget.control.state["playbackRate"] = playbackRate; + debugPrint("Audio.setPlaybackRate($playbackRate)"); + await player?.setPlaybackRate(playbackRate); } if (!kIsWeb && balance != null && - balance != _balance && + balance != prevBalance && balance >= -1 && balance <= 1) { - _balance = balance; - await player.setBalance(balance); + widget.control.state["balance"] = balance; + debugPrint("Audio.setBalance($balance)"); + await player?.setBalance(balance); } if (srcChanged && autoplay) { - await player.resume(); + debugPrint("Audio.resume($srcChanged, $autoplay)"); + await player?.resume(); } _server = server; @@ -204,28 +241,28 @@ class _AudioControlState extends State { (methodName, args) async { switch (methodName) { case "play": - await player.seek(const Duration(milliseconds: 0)); - await player.resume(); + await player?.seek(const Duration(milliseconds: 0)); + await player?.resume(); break; case "resume": - await player.resume(); + await player?.resume(); break; case "pause": - await player.pause(); + await player?.pause(); break; case "release": - await player.release(); + await player?.release(); break; case "seek": - await player.seek(Duration( + await player?.seek(Duration( milliseconds: int.tryParse(args["position"] ?? "") ?? 0)); break; case "get_duration": - return (await player.getDuration()) + return (await player?.getDuration()) ?.inMilliseconds .toString(); case "get_current_position": - return (await player.getCurrentPosition()) + return (await player?.getCurrentPosition()) ?.inMilliseconds .toString(); } @@ -233,7 +270,7 @@ class _AudioControlState extends State { }; }(); - return widget.nextChild ?? const SizedBox.shrink(); + return const SizedBox.shrink(); }); } } diff --git a/package/lib/src/controls/date_picker.dart b/package/lib/src/controls/date_picker.dart index 732c36d1d..63063becb 100644 --- a/package/lib/src/controls/date_picker.dart +++ b/package/lib/src/controls/date_picker.dart @@ -28,18 +28,6 @@ class DatePickerControl extends StatefulWidget { } class _DatePickerControlState extends State { - @override - void initState() { - debugPrint("DatePicker initState() ($hashCode)"); - super.initState(); - } - - @override - void dispose() { - debugPrint("DatePicker dispose() ($hashCode)"); - super.dispose(); - } - @override Widget build(BuildContext context) { debugPrint("DatePicker build: ${widget.control.id}"); diff --git a/package/lib/src/flet_app_services.dart b/package/lib/src/flet_app_services.dart index d82e2747d..ccfc6ead7 100644 --- a/package/lib/src/flet_app_services.dart +++ b/package/lib/src/flet_app_services.dart @@ -7,7 +7,6 @@ import 'flet_server.dart'; import 'flet_server_protocol.dart'; import 'models/app_state.dart'; import 'reducers.dart'; -import 'utils/control_global_state.dart'; class FletAppServices extends InheritedWidget { final FletAppServices? parentAppServices; @@ -21,7 +20,6 @@ class FletAppServices extends InheritedWidget { late final FletServer server; late final Store store; final Map globalKeys = {}; - final ControlsGlobalState globalState = ControlsGlobalState(); final Map controlInvokeMethods = {}; FletAppServices( diff --git a/package/lib/src/models/control.dart b/package/lib/src/models/control.dart index d96ac636b..9de0569ec 100644 --- a/package/lib/src/models/control.dart +++ b/package/lib/src/models/control.dart @@ -10,6 +10,7 @@ class Control extends Equatable { final List childIds; final Map attrs; final Map state = {}; + final Set onRemove = {}; Control( {required this.id, @@ -113,6 +114,7 @@ class Control extends Equatable { for (var element in this.state.entries) { c.state[element.key] = element.value; } + c.onRemove.addAll(onRemove); return c; } diff --git a/package/lib/src/reducers.dart b/package/lib/src/reducers.dart index 0e14b2069..748384d9c 100644 --- a/package/lib/src/reducers.dart +++ b/package/lib/src/reducers.dart @@ -469,6 +469,11 @@ removeControls(Map controls, List ids) { } // delete control itself + if (ctrl != null) { + for (var handler in ctrl.onRemove) { + handler(); + } + } controls.remove(id); // remove control's ID from parent's children collection diff --git a/package/lib/src/utils/control_global_state.dart b/package/lib/src/utils/control_global_state.dart deleted file mode 100644 index e0f6d60f7..000000000 --- a/package/lib/src/utils/control_global_state.dart +++ /dev/null @@ -1,34 +0,0 @@ -class ControlsGlobalState { - final Map _state = {}; - - dynamic get(String controlId, String key) { - return _state["$controlId $key"]?.value; - } - - set(String controlId, String key, dynamic value, int widgetHashCode) { - String gkey = "$controlId $key"; - ControlGlobalStateValue? state = _state[gkey]; - if (state == null) { - state = ControlGlobalStateValue(); - _state[gkey] = state; - } - state.value = value; - state.widgetHashCodes.add(widgetHashCode); - } - - remove(String controlId, String key, int widgetHashCode) { - String gkey = "$controlId $key"; - ControlGlobalStateValue? state = _state[gkey]; - if (state != null) { - state.widgetHashCodes.remove(widgetHashCode); - if (state.widgetHashCodes.isEmpty) { - _state.remove(gkey); - } - } - } -} - -class ControlGlobalStateValue { - dynamic value; - final Set widgetHashCodes = {}; -} From fbd5e15292814de7368480c37a14ed45508a7a4a Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sun, 5 Nov 2023 19:27:56 -0800 Subject: [PATCH 5/9] Fix control tests --- package/lib/src/flet_app_services.dart | 2 -- package/test/controls/control_test.dart | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/package/lib/src/flet_app_services.dart b/package/lib/src/flet_app_services.dart index ccfc6ead7..bd367a241 100644 --- a/package/lib/src/flet_app_services.dart +++ b/package/lib/src/flet_app_services.dart @@ -34,8 +34,6 @@ class FletAppServices extends InheritedWidget { this.reconnectIntervalMs, this.reconnectTimeoutMs}) : super(key: key, child: child) { - debugPrint("FletAppServices()"); - store = Store(appReducer, initialState: AppState.initial()); server = FletServer(store, controlInvokeMethods, reconnectIntervalMs: reconnectIntervalMs, diff --git a/package/test/controls/control_test.dart b/package/test/controls/control_test.dart index da08021f5..ae83a5911 100644 --- a/package/test/controls/control_test.dart +++ b/package/test/controls/control_test.dart @@ -5,21 +5,21 @@ import 'package:flutter_test/flutter_test.dart'; void main() { test("Two controls are equal", () { - Control c1 = const Control( + Control c1 = Control( id: "i1", pid: "p1", type: "stack", name: null, - childIds: ["txt1", "btn1"], - attrs: {"text": "Hello!", "width": "200"}); + childIds: const ["txt1", "btn1"], + attrs: const {"text": "Hello!", "width": "200"}); - Control c2 = const Control( + Control c2 = Control( id: "i1", pid: "p1", type: "stack", name: null, - childIds: ["txt1", "btn1"], - attrs: {"width": "200", "text": "Hello!"}); + childIds: const ["txt1", "btn1"], + attrs: const {"width": "200", "text": "Hello!"}); expect(c1 == c2, true); }); From a139c44eb750e7d66b3c59b39b9c463696797bda Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Mon, 6 Nov 2023 08:18:10 -0800 Subject: [PATCH 6/9] Bump fl_chart to 0.64.0 --- client/pubspec.lock | 4 ++-- package/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 06d944cf0..458e29a52 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: fl_chart - sha256: c1e26c7e48496be85104c16c040950b0436674cdf0737f3f6e95511b2529b592 + sha256: "6b9eb2b3017241d05c482c01f668dd05cc909ec9a0114fdd49acd958ff2432fa" url: "https://pub.dev" source: hosted - version: "0.63.0" + version: "0.64.0" flet: dependency: "direct main" description: diff --git a/package/pubspec.yaml b/package/pubspec.yaml index 61384efd8..b904eecc6 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -40,7 +40,7 @@ dependencies: shake: ^2.2.0 path: ^1.8.2 js: ^0.6.5 - fl_chart: ^0.63.0 + fl_chart: ^0.64.0 dev_dependencies: flutter_test: From f673bbe235dad3f86bc68f621119fe368cbd47ee Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Mon, 6 Nov 2023 08:59:04 -0800 Subject: [PATCH 7/9] Fix `scroll_to` with 0 animation duration Fix #1659 --- .../lib/src/controls/scrollable_control.dart | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/package/lib/src/controls/scrollable_control.dart b/package/lib/src/controls/scrollable_control.dart index 3193bcd6c..962de6b9c 100644 --- a/package/lib/src/controls/scrollable_control.dart +++ b/package/lib/src/controls/scrollable_control.dart @@ -102,9 +102,7 @@ class _ScrollableControlState extends State { var params = Map.from(mj["p"] as Map); if (name == "scroll_to") { - var duration = params["duration"] != null - ? Duration(milliseconds: parseInt(params["duration"])) - : Duration.zero; + var duration = parseInt(params["duration"]); var curve = params["curve"] != null ? parseCurve(params["curve"] as String) : Curves.ease; @@ -114,7 +112,11 @@ class _ScrollableControlState extends State { if (key != null) { var ctx = key.currentContext; if (ctx != null) { - Scrollable.ensureVisible(ctx, duration: duration, curve: curve); + Scrollable.ensureVisible(ctx, + duration: duration > 0 + ? Duration(milliseconds: duration) + : Duration.zero, + curve: curve); } } }); @@ -124,21 +126,29 @@ class _ScrollableControlState extends State { if (offset < 0) { offset = _controller.position.maxScrollExtent + offset + 1; } - _controller.animateTo( - offset, - duration: duration, - curve: curve, - ); + if (duration < 1) { + _controller.jumpTo(offset); + } else { + _controller.animateTo( + offset, + duration: Duration(milliseconds: duration), + curve: curve, + ); + } }); } else if (params["delta"] != null) { WidgetsBinding.instance.addPostFrameCallback((_) { var delta = parseDouble(params["delta"]); var offset = _controller.position.pixels + delta; - _controller.animateTo( - offset, - duration: duration, - curve: curve, - ); + if (duration < 1) { + _controller.jumpTo(offset); + } else { + _controller.animateTo( + offset, + duration: Duration(milliseconds: duration), + curve: curve, + ); + } }); } } From c59383e1b3fc985b4afd0532e01d933ca634a935 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Mon, 6 Nov 2023 17:29:13 -0800 Subject: [PATCH 8/9] Fix page.width/.height on session start Fix #1960 --- package/lib/src/controls/page.dart | 15 ++++++++++----- package/lib/src/routing/router_delegate.dart | 1 - 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package/lib/src/controls/page.dart b/package/lib/src/controls/page.dart index 959b63b40..bbdc23234 100644 --- a/package/lib/src/controls/page.dart +++ b/package/lib/src/controls/page.dart @@ -482,11 +482,16 @@ class _PageControlState extends State { if (routesView.views.isEmpty) { pages.add(FadeTransitionPage( child: hideLoadingPage - ? const Scaffold() - : LoadingPage( - isLoading: routesView.isLoading, - message: routesView.error, - ))); + ? const Scaffold( + body: PageMedia(), + ) + : Stack(children: [ + const PageMedia(), + LoadingPage( + isLoading: routesView.isLoading, + message: routesView.error, + ) + ]))); } else { Widget? loadingPage; // offstage diff --git a/package/lib/src/routing/router_delegate.dart b/package/lib/src/routing/router_delegate.dart index 89a163bd7..9b389e6ec 100644 --- a/package/lib/src/routing/router_delegate.dart +++ b/package/lib/src/routing/router_delegate.dart @@ -30,7 +30,6 @@ class SimpleRouterDelegate extends RouterDelegate @override String get currentConfiguration { - debugPrint("currentConfiguration"); return routeState.route; } From 091c9151e153ff6be651fec3774dd164791cbc17 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 7 Nov 2023 08:53:24 -0800 Subject: [PATCH 9/9] Fix Flet version retrieval on non-English environments Fix #1997 --- sdk/python/packages/flet/src/flet/version.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/version.py b/sdk/python/packages/flet/src/flet/version.py index 5ccf6ed2f..cd6a50383 100644 --- a/sdk/python/packages/flet/src/flet/version.py +++ b/sdk/python/packages/flet/src/flet/version.py @@ -16,11 +16,15 @@ def update_version(): """Return the current version or default.""" working = Path().absolute() os.chdir(Path(flet.__file__).absolute().parent) - in_repo = which("git.exe" if is_windows() else "git") and sp.run( - ["git", "status"], - capture_output=True, - text=True, - ).stdout.startswith("On branch ") + in_repo = ( + which("git.exe" if is_windows() else "git") + and sp.run( + ["git", "status"], + capture_output=True, + text=True, + ).returncode + == 0 + ) if in_repo: # NOTE: this may break if there is a tag name starting with