Skip to content

Commit

Permalink
Global state for dialog-based controls (flet-dev#2032)
Browse files Browse the repository at this point in the history
* AlertDialog uses global state

* DatePicker uses global state

* State on the control level

* Control-level state

Fix flet-dev#2025, Fix flet-dev#1807, Fix flet-dev#1236, Fix flet-dev#1772

* Fix control tests

* Bump fl_chart to 0.64.0

* Fix `scroll_to` with 0 animation duration

Fix flet-dev#1659

* Fix page.width/.height on session start

Fix flet-dev#1960

* Fix Flet version retrieval on non-English environments

Fix flet-dev#1997
  • Loading branch information
FeodorFitsner authored and zrr1999 committed Jul 17, 2024
1 parent b5cbde9 commit b237911
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 128 deletions.
4 changes: 2 additions & 2 deletions client/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
37 changes: 16 additions & 21 deletions package/lib/src/controls/alert_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ class AlertDialogControl extends StatefulWidget {
}

class _AlertDialogControlState extends State<AlertDialogControl> {
bool _open = false;

Widget _createAlertDialog() {
bool disabled = widget.control.isDisabled || widget.parentDisabled;
var titleCtrls =
Expand Down Expand Up @@ -72,7 +70,11 @@ class _AlertDialogControlState extends State<AlertDialogControl> {

@override
Widget build(BuildContext context) {
debugPrint("AlertDialog build: ${widget.control.id}");
debugPrint("AlertDialog build ($hashCode): ${widget.control.id}");

var server = FletAppServices.of(context).server;

bool lastOpen = widget.control.state["open"] ?? false;

return StoreConnector<AppState, Function>(
distinct: true,
Expand All @@ -82,53 +84,46 @@ class _AlertDialogControlState extends State<AlertDialogControl> {

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();
// }
widget.control.state["open"] = open;

WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
barrierDismissible: !modal,
context: context,
builder: (context) => _createAlertDialog()).then((value) {
debugPrint("Dialog dismissed: $_open");
bool shouldDismiss = _open;
_open = false;
lastOpen = widget.control.state["open"] ?? false;
debugPrint("Dialog should be dismissed ($hashCode): $lastOpen");
bool shouldDismiss = lastOpen;
widget.control.state["open"] = false;

if (shouldDismiss) {
List<Map<String, String>> props = [
{"i": widget.control.id, "open": "false"}
];
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();
});
}
Expand Down
127 changes: 82 additions & 45 deletions package/lib/src/controls/audio.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';

import 'package:audioplayers/audioplayers.dart';
Expand Down Expand Up @@ -33,33 +34,37 @@ class AudioControl extends StatefulWidget {
}

class _AudioControlState extends State<AudioControl> {
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;
Expand All @@ -70,21 +75,35 @@ class _AudioControlState extends State<AudioControl> {
}
_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", "")!;
Expand All @@ -104,6 +123,13 @@ class _AudioControlState extends State<AudioControl> {

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<AppState, PageArgsModel>(
distinct: true,
converter: (store) => PageArgsModel.fromStore(store),
Expand All @@ -116,6 +142,7 @@ class _AudioControlState extends State<AudioControl> {
};

_onStateChanged = (state) {
debugPrint("Audio($hashCode) - state_changed: ${state.name}");
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "state_changed",
Expand All @@ -139,101 +166,111 @@ class _AudioControlState extends State<AudioControl> {
};

() 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;
_server?.controlInvokeMethods[widget.control.id] =
(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();
}
return null;
};
}();

return widget.nextChild ?? const SizedBox.shrink();
return const SizedBox.shrink();
});
}
}
4 changes: 2 additions & 2 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../utils/transforms.dart';
import 'alert_dialog.dart';
import 'animated_switcher.dart';
import 'audio.dart';
import 'badge.dart';
import 'banner.dart';
import 'barchart.dart';
import 'bottom_sheet.dart';
Expand Down Expand Up @@ -57,6 +58,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';
Expand All @@ -75,8 +77,6 @@ import 'tooltip.dart';
import 'transparent_pointer.dart';
import 'vertical_divider.dart';
import 'window_drag_area.dart';
import 'range_slider.dart';
import 'badge.dart';

Widget createControl(Control? parent, String id, bool parentDisabled,
{Widget? nextChild}) {
Expand Down
Loading

0 comments on commit b237911

Please sign in to comment.