import 'package:flutter/material.dart'; import 'package:gomix_flutter/mixer_state.dart' as mixer; class PortConnectionCard extends StatefulWidget { final mixer.Port sourcePort; final mixer.State connection; final mixer.MixerState mixerState; final Function(Map data) sendAction; final bool isOutput; const PortConnectionCard( {super.key, required this.sourcePort, required this.connection, required this.mixerState, required this.sendAction, required this.isOutput}); @override State createState() => _PortConnectionCardState(); } class _PortConnectionCardState extends State { double _sliderValue = 0; int _debounceTimer = 0; bool _sliderActive = false; String toUUID = ""; String portName = ""; void updatePortData() { toUUID = widget.connection.toUuid ?? ""; mixer.Port toPort = widget.mixerState.outputs.firstWhere( (el) => el.uuid == toUUID, orElse: () => mixer.Port( name: "Invalid Port", properties: mixer.Properties(backend: "N/A", channels: 0), state: mixer.State(balance: 1, mute: true, volume: 0), uuid: toUUID, route: [])); portName = widget.isOutput ? widget.sourcePort.name : toPort.name; } @override void initState() { _sliderValue = widget.connection.volume; updatePortData(); super.initState(); } @override void didUpdateWidget(PortConnectionCard oldWidget) { if (_sliderActive) return; _sliderValue = widget.connection.volume; super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(portName), Row( children: [ Expanded( child: Slider( min: 0, max: 4, value: _sliderValue, label: _sliderValue.toStringAsFixed(2), allowedInteraction: SliderInteraction.slideThumb, onChangeStart: ((val) { _sliderActive = true; }), onChangeEnd: ((val) { // Send the current state to make sure we // don't miss the last value due to the debounce widget.sendAction({ "method": "setRouteState", "UUID": widget.sourcePort.uuid, "toUUID": widget.connection.toUuid ?? "", "routeStateData": {"volume": val} }); // Make sure the slider value is still correct // after suppressing updates _sliderActive = false; setState(() { _sliderValue = widget.connection.volume; }); }), onChanged: (val) { setState(() { _sliderValue = val; }); if (DateTime.now().millisecondsSinceEpoch - _debounceTimer < 30) { return; } _debounceTimer = DateTime.now().millisecondsSinceEpoch; widget.sendAction({ "method": "setRouteState", "UUID": widget.sourcePort.uuid, "toUUID": widget.connection.toUuid ?? "", "routeStateData": {"volume": val} }); })), IconButton.filledTonal( onPressed: () { widget.sendAction({ "method": "setRouteState", "UUID": widget.sourcePort.uuid, "toUUID": widget.connection.toUuid ?? "", "routeStateData": {"mute": !widget.connection.mute} }); }, iconSize: 20, icon: widget.connection.mute ? const Icon(Icons.volume_off_outlined) : const Icon(Icons.volume_up_outlined)), const SizedBox(width: 5), IconButton.filled( onPressed: () { widget.sendAction({ "method": "deleteRoute", "UUID": widget.sourcePort.uuid, "toUUID": widget.connection.toUuid ?? "" }); }, iconSize: 20, icon: const Icon(Icons.delete)), ], ) ], ); } }