Skip to content

Commit

Permalink
Control-level state
Browse files Browse the repository at this point in the history
Fix #2025, Fix #1807, Fix #1236, Fix #1772
  • Loading branch information
FeodorFitsner committed Nov 6, 2023
1 parent 2b3b6ef commit dc66f93
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 105 deletions.
12 changes: 0 additions & 12 deletions package/lib/src/controls/alert_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,6 @@ class AlertDialogControl extends StatefulWidget {
}

class _AlertDialogControlState extends State<AlertDialogControl> {
@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 =
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();
});
}
}
12 changes: 0 additions & 12 deletions package/lib/src/controls/date_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,6 @@ class DatePickerControl extends StatefulWidget {
}

class _DatePickerControlState extends State<DatePickerControl> {
@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}");
Expand Down
2 changes: 0 additions & 2 deletions package/lib/src/flet_app_services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +20,6 @@ class FletAppServices extends InheritedWidget {
late final FletServer server;
late final Store<AppState> store;
final Map<String, GlobalKey> globalKeys = {};
final ControlsGlobalState globalState = ControlsGlobalState();
final Map<String, ControlInvokeMethodCallback> controlInvokeMethods = {};

FletAppServices(
Expand Down
2 changes: 2 additions & 0 deletions package/lib/src/models/control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Control extends Equatable {
final List<String> childIds;
final Map<String, String> attrs;
final Map<String, dynamic> state = {};
final Set<void Function()> onRemove = {};

Control(
{required this.id,
Expand Down Expand Up @@ -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;
}

Expand Down
5 changes: 5 additions & 0 deletions package/lib/src/reducers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,11 @@ removeControls(Map<String, Control> controls, List<String> 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
Expand Down
34 changes: 0 additions & 34 deletions package/lib/src/utils/control_global_state.dart

This file was deleted.

0 comments on commit dc66f93

Please sign in to comment.