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 = {}; -}