fluffychat/lib/pangea/common/widgets/word_audio_button.dart
2025-12-04 16:36:04 -05:00

132 lines
4 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
class WordAudioButton extends StatefulWidget {
final String text;
final bool isSelected;
final double baseOpacity;
final String uniqueID;
final String langCode;
final EdgeInsets? padding;
final double? iconSize;
/// If defined, this callback will be called instead of the default one
final void Function()? callbackOverride;
const WordAudioButton({
super.key,
required this.text,
required this.uniqueID,
required this.langCode,
this.isSelected = false,
this.baseOpacity = 1,
this.callbackOverride,
this.padding,
this.iconSize,
});
@override
WordAudioButtonState createState() => WordAudioButtonState();
}
class WordAudioButtonState extends State<WordAudioButton> {
late TtsController tts;
bool _isPlaying = false;
bool _isLoading = false;
StreamSubscription? _loadingChoreoSubscription;
@override
void initState() {
super.initState();
_loadingChoreoSubscription =
TtsController.loadingChoreoStream.stream.listen((val) {
if (mounted) setState(() => _isLoading = val);
});
}
@override
void didUpdateWidget(covariant WordAudioButton oldWidget) {
if (oldWidget.isSelected != widget.isSelected ||
oldWidget.callbackOverride != widget.callbackOverride) {
setState(() {});
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
TtsController.stop();
_loadingChoreoSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey('word-audio-button-${widget.uniqueID}')
.link,
child: Opacity(
key: MatrixState.pAnyState
.layerLinkAndKey('word-audio-button-${widget.uniqueID}')
.key,
opacity: widget.isSelected || _isPlaying ? 1 : widget.baseOpacity,
child: Tooltip(
message:
_isPlaying ? L10n.of(context).stop : L10n.of(context).playAudio,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: widget.callbackOverride ??
() async {
if (_isPlaying) {
await TtsController.stop();
} else {
await TtsController.tryToSpeak(
widget.text,
context: context,
targetID: 'word-audio-button-${widget.uniqueID}',
langCode: widget.langCode,
onStart: () {
if (mounted) {
setState(() => _isPlaying = true);
}
},
onStop: () {
if (mounted) {
setState(() => _isPlaying = false);
}
},
);
}
},
child: Padding(
padding: widget.padding ?? const EdgeInsets.all(0.0),
child: _isLoading
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 3,
),
)
: Icon(
_isPlaying ? Icons.pause_outlined : Icons.volume_up,
color: _isPlaying
? Theme.of(context).colorScheme.primary
: null,
size: widget.iconSize,
),
),
),
),
),
),
);
}
}