import 'package:flutter/material.dart'; import 'package:gomix_flutter/mixer_state.dart' as mixer; import 'package:gomix_flutter/port_connection_dialog.dart'; import 'package:gomix_flutter/utils.dart'; class MixingCard extends StatefulWidget { final mixer.Port port; final mixer.MixerState mixerState; final Function(Map data) sendAction; final bool isOutput; const MixingCard( {super.key, required this.port, required this.mixerState, required this.sendAction, this.isOutput = false}); @override State createState() => _MixingCardState(); } class DialogDataModel extends ChangeNotifier { String? portUUID; late mixer.MixerState mixerState; late mixer.Port port; void updatePort(bool force) { if (portUUID == null) { return; } // Only update port reference if it is actually outdated if (force || (!mixerState.inputs.contains(port) && !mixerState.outputs.contains(port))) { mixer.Port? newPort = findPort(mixerState, portUUID!); if (newPort != null) port = newPort; } } void setPortUUID(String uuid) { portUUID = uuid; updatePort(true); } void setMixerState(mixer.MixerState state) { mixerState = state; updatePort(false); notifyListeners(); } } class _MixingCardState extends State { double _sliderValue = 0; int _debounceTimer = 0; bool _sliderActive = false; DialogDataModel dataModel = DialogDataModel(); @override void initState() { super.initState(); dataModel.setMixerState(widget.mixerState); _sliderValue = widget.port.state.volume; } @override void didUpdateWidget(MixingCard oldWidget) { if (_sliderActive) return; dataModel.setMixerState(widget.mixerState); _sliderValue = widget.port.state.volume; super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { var labelStyle = Theme.of(context) .textTheme .labelLarge ?.apply(color: Theme.of(context).colorScheme.primary); return Card( margin: const EdgeInsets.all(0), shadowColor: Colors.transparent, child: Column( children: [ Padding( padding: const EdgeInsets.only(left: 15, right: 15, top: 15), child: Row( children: [ Expanded(child: Text(widget.port.name, style: labelStyle)), SizedBox( height: 40, width: 40, child: IconButton.filledTonal( iconSize: 20, isSelected: widget.port.state.mute, onPressed: () { widget.sendAction({ "method": "setPortState", "UUID": widget.port.uuid, "stateData": {"mute": !widget.port.state.mute} }); }, icon: widget.isOutput ? (widget.port.state.mute ? const Icon(Icons.volume_off_outlined) : const Icon(Icons.volume_up_outlined)) : (widget.port.state.mute ? const Icon(Icons.mic_off_outlined) : const Icon(Icons.mic_none_outlined)), ), ), const SizedBox(width: 5), SizedBox( height: 40, width: 40, child: IconButton.outlined( iconSize: 20, isSelected: false, onPressed: () { dataModel.setPortUUID(widget.port.uuid); PortConnectionDialog( sendAction: widget.sendAction, port: widget.port, isOutput: widget.isOutput, dataModel: dataModel, mixerState: widget.mixerState) .show(context); }, icon: const Icon(Icons.unfold_more))) ], ), ), SizedBox( width: MediaQuery.of(context).size.width + 180, child: Slider( value: _sliderValue, min: 0, max: 4, 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": "setPortState", "UUID": widget.port.uuid, "stateData": {"volume": val} }); // Make sure the slider value is still correct // after suppressing updates _sliderActive = false; setState(() { _sliderValue = widget.port.state.volume; }); }), onChanged: (val) { setState(() { _sliderValue = val; }); if (DateTime.now().millisecondsSinceEpoch - _debounceTimer < 30) { return; } _debounceTimer = DateTime.now().millisecondsSinceEpoch; widget.sendAction({ "method": "setPortState", "UUID": widget.port.uuid, "stateData": {"volume": val} }); }, ), ), ], ), ); } }