fix: make TTS button pause when it's stopped by the other TTS button playing (#2831)

This commit is contained in:
ggurdin 2025-05-19 12:19:17 -04:00 committed by GitHub
parent c5b7b550f2
commit 3359cfe25d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 174 additions and 261 deletions

View file

@ -11,6 +11,7 @@ import 'package:fluffychat/pages/chat/events/video_player.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
@ -142,7 +143,7 @@ class MessageContent extends StatelessWidget {
const Duration(
milliseconds: AppConfig.overlayAnimationDuration,
), () {
controller.choreographer.tts.tryToSpeak(
TtsController.tryToSpeak(
token.text.content,
langCode: pangeaMessageEvent!.messageDisplayLangCode,
);

View file

@ -56,6 +56,7 @@ class VocabDetailsView extends StatelessWidget {
),
iconSize: _iconSize,
uniqueID: "${_construct.lemma}-${_construct.category}",
langCode: _userL2!,
),
subtitle: Column(
children: [
@ -140,8 +141,12 @@ class VocabDetailsView extends StatelessWidget {
children: [
WordTextWithAudioButton(
text: form,
style: Theme.of(context).textTheme.bodyLarge,
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(
color: textColor,
),
uniqueID: "$form-${_construct.lemma}-$i",
langCode: _userL2!,
),
if (i != forms.length - 1) const Text(", "),
],

View file

@ -43,7 +43,6 @@ class Choreographer {
late ITController itController;
late IgcController igc;
late ErrorService errorService;
late TtsController tts;
bool isFetching = false;
int _timesClicked = 0;
@ -64,7 +63,6 @@ class Choreographer {
_initialize();
}
_initialize() {
tts = TtsController(chatController: chatController);
_textController = PangeaTextController(choreographer: this);
InputPasteListener(_textController, onPaste);
itController = ITController(this);
@ -566,7 +564,7 @@ class Choreographer {
_textController.dispose();
_languageStream?.cancel();
stateStream.close();
tts.dispose();
TtsController.stop();
}
LanguageModel? get l2Lang {

View file

@ -29,10 +29,6 @@ class ChoicesArray extends StatefulWidget {
final int? selectedChoiceIndex;
final String originalSpan;
/// If null then should not be used
/// We don't want tts in the case of L1 options
final TtsController? tts;
final bool enableAudio;
/// language code for the TTS
@ -62,7 +58,6 @@ class ChoicesArray extends StatefulWidget {
required this.onPressed,
required this.originalSpan,
required this.selectedChoiceIndex,
required this.tts,
this.enableAudio = true,
this.langCode,
this.isActive = true,
@ -111,10 +106,8 @@ class ChoicesArrayState extends State<ChoicesArray> {
? (String value, int index) {
widget.onPressed(value, index);
// TODO - what to pass here as eventID?
if (widget.enableAudio &&
widget.tts != null &&
widget.langCode != null) {
widget.tts?.tryToSpeak(
if (widget.enableAudio && widget.langCode != null) {
TtsController.tryToSpeak(
value,
targetID: null,
langCode: widget.langCode!,

View file

@ -60,12 +60,10 @@ class SpanCardState extends State<SpanCard> {
@override
void dispose() {
tts.stop();
TtsController.stop();
super.dispose();
}
TtsController get tts => widget.scm.choreographer.tts;
//get selected choice
SpanChoice? get selectedChoice {
if (selectedChoiceIndex == null) return null;
@ -263,7 +261,6 @@ class WordMatchContent extends StatelessWidget {
onPressed: (value, index) =>
controller.onChoiceSelect(index),
selectedChoiceIndex: controller.selectedChoiceIndex,
tts: controller.tts,
id: controller.widget.scm.pangeaMatch!.hashCode
.toString(),
langCode: MatrixState.pangeaController.languageController

View file

@ -418,7 +418,6 @@ class ITChoices extends StatelessWidget {
onPressed: (value, index) => selectContinuance(index, context),
onLongPress: (value, index) => showCard(context, index),
selectedChoiceIndex: null,
tts: controller.choreographer.tts,
langCode: controller.choreographer.pangeaController.languageController
.activeL2Code(),
);

View file

@ -35,7 +35,6 @@ class SettingsLearning extends StatefulWidget {
class SettingsLearningController extends State<SettingsLearning> {
PangeaController pangeaController = MatrixState.pangeaController;
late Profile _profile;
final tts = TtsController();
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
String? languageMatchError;
@ -46,12 +45,12 @@ class SettingsLearningController extends State<SettingsLearning> {
void initState() {
super.initState();
_profile = pangeaController.userController.profile.copy();
tts.setAvailableLanguages().then((_) => setState(() {}));
TtsController.setAvailableLanguages().then((_) => setState(() {}));
}
@override
void dispose() {
tts.dispose();
TtsController.stop();
scrollController.dispose();
super.dispose();
}

View file

@ -24,55 +24,37 @@ import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
class TtsController {
final ChatController? chatController;
TtsController({this.chatController}) {
static void initialize() {
setAvailableLanguages();
_languageSubscription =
MatrixState.pangeaController.userController.stateStream.listen(
(_) => setAvailableLanguages(),
);
}
List<String> _availableLangCodes = [];
StreamSubscription? _languageSubscription;
static List<String> _availableLangCodes = [];
final flutter_tts.FlutterTts _tts = flutter_tts.FlutterTts();
final TextToSpeech _alternativeTTS = TextToSpeech();
final StreamController<bool> loadingChoreoStream =
static final flutter_tts.FlutterTts _tts = flutter_tts.FlutterTts();
static final TextToSpeech _alternativeTTS = TextToSpeech();
static final StreamController<bool> loadingChoreoStream =
StreamController<bool>.broadcast();
bool get _useAlternativeTTS {
static bool get _useAlternativeTTS {
return PlatformInfos.isWindows;
}
Future<void> dispose() async {
await _tts.stop();
await _languageSubscription?.cancel();
await loadingChoreoStream.close();
}
void _onError(dynamic message) {
// the package treats this as an error, but it's not
// don't send to sentry
if (message == 'canceled' || message == 'interrupted') {
return;
static Future<void> _onError(dynamic message) async {
if (message != 'canceled' && message != 'interrupted') {
ErrorHandler.logError(
e: 'TTS error',
data: {
'message': message,
},
);
}
ErrorHandler.logError(
e: 'TTS error',
data: {
'message': message,
},
);
}
Future<void> setAvailableLanguages() async {
static Future<void> setAvailableLanguages() async {
try {
if (_useAlternativeTTS) {
await _setAvailableAltLanguages();
} else {
_tts.setErrorHandler(_onError);
await _tts.awaitSpeakCompletion(true);
await _setAvailableBaseLanguages();
}
@ -86,7 +68,7 @@ class TtsController {
}
}
Future<void> _setAvailableBaseLanguages() async {
static Future<void> _setAvailableBaseLanguages() async {
final voices = (await _tts.getVoices) as List?;
_availableLangCodes = (voices ?? [])
.map((v) {
@ -100,12 +82,12 @@ class TtsController {
.toList();
}
Future<void> _setAvailableAltLanguages() async {
static Future<void> _setAvailableAltLanguages() async {
final languages = await _alternativeTTS.getLanguages();
_availableLangCodes = languages.toSet().toList();
}
Future<void> _setSpeakingLanguage(String langCode) async {
static Future<void> _setSpeakingLanguage(String langCode) async {
String? selectedLangCode;
final langCodeShort = langCode.split("-").first;
if (_availableLangCodes.contains(langCode)) {
@ -132,7 +114,7 @@ class TtsController {
}
}
Future<void> stop() async {
static Future<void> stop() async {
try {
// return type is dynamic but apparent its supposed to be 1
// https://pub.dev/packages/flutter_tts
@ -157,26 +139,67 @@ class TtsController {
}
}
/// A safer version of speak, that handles the case of
/// the language not being supported by the TTS engine
Future<void> tryToSpeak(
static VoidCallback? _onStop;
static Future<void> tryToSpeak(
String text, {
required String langCode,
// Target ID for where to show warning popup
String? targetID,
BuildContext? context,
ChatController? chatController,
VoidCallback? onStart,
VoidCallback? onStop,
}) async {
final prevOnStop = _onStop;
_onStop = onStop;
_tts.setErrorHandler((message) {
_onError(message);
prevOnStop?.call();
});
onStart?.call();
await _tryToSpeak(
text,
langCode: langCode,
targetID: targetID,
context: context,
chatController: chatController,
onStart: onStart,
onStop: onStop,
);
onStop?.call();
}
/// A safer version of speak, that handles the case of
/// the language not being supported by the TTS engine
static Future<void> _tryToSpeak(
String text, {
required String langCode,
// Target ID for where to show warning popup
String? targetID,
BuildContext? context,
ChatController? chatController,
VoidCallback? onStart,
VoidCallback? onStop,
}) async {
chatController?.stopMediaStream.add(null);
await _setSpeakingLanguage(langCode);
final enableTTS = MatrixState
.pangeaController.userController.profile.toolSettings.enableTTS;
if (enableTTS) {
final token = PangeaTokenText(
offset: 0,
content: text,
length: text.length,
);
onStart?.call();
await (_isLangFullySupported(langCode)
? _speak(
text,
@ -191,31 +214,33 @@ class TtsController {
} else if (targetID != null && context != null) {
await _showTTSDisabledPopup(context, targetID);
}
onStop?.call();
}
Future<void> _speak(
static Future<void> _speak(
String text,
String langCode,
List<PangeaTokenText> tokens,
) async {
try {
stop();
await stop();
text = text.toLowerCase();
Logs().i('Speaking: $text, langCode: $langCode');
final result = await Future(
() => (_useAlternativeTTS
? _alternativeTTS.speak(text)
: _tts.speak(text))
.timeout(
const Duration(seconds: 5),
onTimeout: () {
ErrorHandler.logError(
e: "Timeout on tts.speak",
data: {"text": text},
);
},
),
? _alternativeTTS.speak(text)
: _tts.speak(text)),
// .timeout(
// const Duration(seconds: 5),
// // onTimeout: () {
// // ErrorHandler.logError(
// // e: "Timeout on tts.speak",
// // data: {"text": text},
// // );
// // },
// ),
);
Logs().i('Finished speaking: $text, result: $result');
@ -241,10 +266,12 @@ class TtsController {
},
);
await _speakFromChoreo(text, langCode, tokens);
} finally {
stop();
}
}
Future<void> _speakFromChoreo(
static Future<void> _speakFromChoreo(
String text,
String langCode,
List<PangeaTokenText> tokens,
@ -252,7 +279,7 @@ class TtsController {
TextToSpeechResponse? ttsRes;
try {
loadingChoreoStream.add(true);
ttsRes = await chatController?.pangeaController.textToSpeech.get(
ttsRes = await MatrixState.pangeaController.textToSpeech.get(
TextToSpeechRequest(
text: text,
langCode: langCode,
@ -304,7 +331,7 @@ class TtsController {
}
}
bool _isLangFullySupported(String langCode) {
static bool _isLangFullySupported(String langCode) {
if (_availableLangCodes.contains(langCode)) {
return true;
}
@ -317,7 +344,7 @@ class TtsController {
return _availableLangCodes.any((lang) => lang.startsWith(langCodeShort));
}
Future<void> _showTTSDisabledPopup(
static Future<void> _showTTSDisabledPopup(
BuildContext context,
String targetID,
) async =>

View file

@ -39,9 +39,6 @@ class PracticeMatchItemState extends State<PracticeMatchItem> {
bool _isHovered = false;
bool _isPlaying = false;
TtsController get tts =>
widget.overlayController.widget.chatController.choreographer.tts;
bool get isSelected => widget.isSelected;
bool? get isCorrect => widget.isCorrect;
@ -52,7 +49,7 @@ class PracticeMatchItemState extends State<PracticeMatchItem> {
}
if (_isPlaying) {
await tts.stop();
await TtsController.stop();
if (mounted) {
setState(() => _isPlaying = false);
}
@ -64,7 +61,7 @@ class PracticeMatchItemState extends State<PracticeMatchItem> {
final l2 =
MatrixState.pangeaController.languageController.activeL2Code();
if (l2 != null) {
await tts.tryToSpeak(
await TtsController.tryToSpeak(
widget.audioContent!,
context: context,
targetID: 'word-audio-button',

View file

@ -27,6 +27,7 @@ import 'package:fluffychat/pangea/practice_activities/practice_choice.dart';
import 'package:fluffychat/pangea/practice_activities/practice_selection.dart';
import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart';
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart';
@ -546,7 +547,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
) ==
false ||
!hideWordCardContent) {
widget.chatController.choreographer.tts.tryToSpeak(
TtsController.tryToSpeak(
token.text.content,
targetID: null,
langCode: pangeaMessageEvent!.messageDisplayLangCode,

View file

@ -13,7 +13,6 @@ import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_record.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart';
@ -80,9 +79,6 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
}
}
TtsController get tts =>
widget.overlayController.widget.chatController.choreographer.tts;
void updateChoice(String value, int index) {
final bool isCorrect =
widget.currentActivity.multipleChoiceContent!.isCorrect(value, index);
@ -232,7 +228,7 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
text: practiceActivity.multipleChoiceContent!.answers.first,
uniqueID: "audio-activity-${widget.event.eventId}",
langCode: widget
.overlayController.pangeaMessageEvent?.messageDisplayLangCode,
.overlayController.pangeaMessageEvent!.messageDisplayLangCode,
),
if (practiceActivity.activityType ==
ActivityTypeEnum.hiddenWordListening)
@ -251,8 +247,8 @@ class MultipleChoiceActivityState extends State<MultipleChoiceActivity> {
choices: choices(context),
isActive: true,
id: currentRecordModel?.hashCode.toString(),
tts: practiceActivity.activityType.includeTTSOnClick ? tts : null,
enableAudio: !widget.overlayController.isPlayingAudio,
enableAudio: !widget.overlayController.isPlayingAudio &&
practiceActivity.activityType.includeTTSOnClick,
langCode:
MatrixState.pangeaController.languageController.activeL2Code(),
getDisplayCopy: _getDisplayCopy,

View file

@ -15,6 +15,7 @@ import 'package:fluffychat/pangea/practice_activities/practice_activity_model.da
import 'package:fluffychat/pangea/practice_activities/practice_generation_repo.dart';
import 'package:fluffychat/pangea/practice_activities/practice_record.dart';
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/event_wrappers/practice_activity_event.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/practice_match_card.dart';
@ -231,7 +232,7 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
widget.overlayController
.onActivityFinish(currentActivity!.activityType, null);
widget.overlayController.widget.chatController.choreographer.tts.stop();
TtsController.stop();
} catch (e, s) {
_onError();
debugger(when: kDebugMode);

View file

@ -1,8 +1,9 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -11,7 +12,7 @@ class WordAudioButton extends StatefulWidget {
final bool isSelected;
final double baseOpacity;
final String uniqueID;
final String? langCode;
final String langCode;
final EdgeInsets? padding;
/// If defined, this callback will be called instead of the default one
@ -21,10 +22,10 @@ class WordAudioButton extends StatefulWidget {
super.key,
required this.text,
required this.uniqueID,
required this.langCode,
this.isSelected = false,
this.baseOpacity = 1,
this.callbackOverride,
this.langCode,
this.padding,
});
@ -33,8 +34,19 @@ class WordAudioButton extends StatefulWidget {
}
class WordAudioButtonState extends State<WordAudioButton> {
final TtsController tts = TtsController();
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) {
@ -47,7 +59,8 @@ class WordAudioButtonState extends State<WordAudioButton> {
@override
void dispose() {
tts.dispose();
TtsController.stop();
_loadingChoreoSubscription?.cancel();
super.dispose();
}
@ -71,45 +84,34 @@ class WordAudioButtonState extends State<WordAudioButton> {
onTap: widget.callbackOverride ??
() async {
if (_isPlaying) {
await tts.stop();
if (mounted) {
setState(() => _isPlaying = false);
}
await TtsController.stop();
} else {
if (mounted) {
setState(() => _isPlaying = true);
}
try {
if (widget.langCode != null) {
await tts.tryToSpeak(
widget.text,
context: context,
targetID: 'word-audio-button-${widget.uniqueID}',
langCode: widget.langCode!,
);
}
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
"text": widget.text,
},
);
} finally {
if (mounted) {
setState(() => _isPlaying = false);
}
}
await TtsController.tryToSpeak(
widget.text,
context: context,
targetID: 'word-audio-button-${widget.uniqueID}',
langCode: widget.langCode,
onStart: () => setState(() => _isPlaying = true),
onStop: () => setState(() => _isPlaying = false),
);
}
},
child: Padding(
padding: widget.padding ?? const EdgeInsets.all(0.0),
child: Icon(
_isPlaying ? Icons.pause_outlined : Icons.volume_up,
color:
_isPlaying ? Theme.of(context).colorScheme.primary : null,
),
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,
),
),
),
),

View file

@ -1,139 +1,46 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
class WordTextWithAudioButton extends StatefulWidget {
class WordTextWithAudioButton extends StatelessWidget {
final String text;
final String uniqueID;
final TextStyle? style;
final double? iconSize;
final String langCode;
const WordTextWithAudioButton({
super.key,
required this.text,
required this.uniqueID,
required this.langCode,
this.style,
this.iconSize,
});
@override
WordAudioButtonState createState() => WordAudioButtonState();
}
class WordAudioButtonState extends State<WordTextWithAudioButton> {
// initialize as null because we don't know if we need to load
// audio from choreo yet. This shall remain null if user device support
// text to speech
final bool? _isLoadingAudio = null;
final TtsController tts = TtsController();
bool _isPlaying = false;
bool _isLoading = false;
StreamSubscription? _loadingChoreoSubscription;
@override
void initState() {
super.initState();
_loadingChoreoSubscription = tts.loadingChoreoStream.stream.listen((val) {
if (mounted) setState(() => _isLoading = val);
});
}
@override
void dispose() {
_loadingChoreoSubscription?.cancel();
tts.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey('text-audio-button-${widget.uniqueID}')
.link,
child: MouseRegion(
key: MatrixState.pAnyState
.layerLinkAndKey('text-audio-button-${widget.uniqueID}')
.key,
cursor: SystemMouseCursors.click,
onEnter: (event) => setState(() {}),
onExit: (event) => setState(() {}),
child: GestureDetector(
onTap: () async {
if (_isLoadingAudio == true) {
return;
}
if (_isPlaying) {
await tts.stop();
if (mounted) {
setState(() => _isPlaying = false);
}
} else {
if (mounted) {
setState(() => _isPlaying = true);
}
try {
final l2 = MatrixState.pangeaController.languageController
.activeL2Code();
if (l2 != null) {
await tts.tryToSpeak(
widget.text,
context: context,
targetID: 'text-audio-button-${widget.uniqueID}',
langCode: l2,
);
}
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {
"text": widget.text,
},
);
} finally {
if (mounted) {
setState(() => _isPlaying = false);
}
}
}
},
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8.0,
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 180),
child: Text(
widget.text,
style: widget.style ?? Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
),
),
if (_isLoading)
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 3,
),
)
else
Icon(
_isPlaying ? Icons.pause_outlined : Icons.volume_up,
color:
_isPlaying ? Theme.of(context).colorScheme.primary : null,
size: widget.iconSize,
),
],
return Row(
mainAxisSize: MainAxisSize.min,
spacing: 8.0,
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 180),
child: Text(
text,
style: style ?? Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
),
),
),
WordAudioButton(
text: text,
uniqueID: uniqueID,
isSelected: false,
baseOpacity: 1,
langCode: langCode,
padding: const EdgeInsets.only(left: 8.0),
),
],
);
}
}

View file

@ -9,7 +9,6 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_unsubscribed_card.dart';
@ -38,9 +37,6 @@ class ReadingAssistanceContent extends StatefulWidget {
}
class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
TtsController get ttsController =>
widget.overlayController.widget.chatController.choreographer.tts;
Widget? toolbarContent(BuildContext context) {
final bool? subscribed =
MatrixState.pangeaController.subscriptionController.isSubscribed;
@ -123,7 +119,6 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
return WordZoomWidget(
token: widget.overlayController.selectedToken!,
messageEvent: widget.overlayController.pangeaMessageEvent!,
tts: ttsController,
overlayController: widget.overlayController,
);
}

View file

@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dar
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
@ -18,7 +17,6 @@ class LemmaWidget extends StatefulWidget {
final PangeaMessageEvent pangeaMessageEvent;
final VoidCallback onEdit;
final VoidCallback onEditDone;
final TtsController tts;
final MessageOverlayController? overlayController;
const LemmaWidget({
@ -27,7 +25,6 @@ class LemmaWidget extends StatefulWidget {
required this.pangeaMessageEvent,
required this.onEdit,
required this.onEditDone,
required this.tts,
required this.overlayController,
});

View file

@ -10,7 +10,6 @@ import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart';
import 'package:fluffychat/pangea/lemmas/lemma_emoji_row.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart';
@ -22,14 +21,12 @@ import 'package:fluffychat/widgets/matrix.dart';
class WordZoomWidget extends StatelessWidget {
final PangeaToken token;
final PangeaMessageEvent messageEvent;
final TtsController tts;
final MessageOverlayController overlayController;
const WordZoomWidget({
super.key,
required this.token,
required this.messageEvent,
required this.tts,
required this.overlayController,
});
@ -93,7 +90,6 @@ class WordZoomWidget extends StatelessWidget {
debugPrint("what are we doing edits with?");
_onEditDone();
},
tts: tts,
overlayController: overlayController,
),
ConstructXpWidget(
@ -181,7 +177,7 @@ class WordZoomWidget extends StatelessWidget {
baseOpacity: 0.4,
uniqueID: "word-zoom-audio-${_selectedToken.text.content}",
langCode: overlayController
.pangeaMessageEvent?.messageDisplayLangCode,
.pangeaMessageEvent!.messageDisplayLangCode,
),
],
..._selectedToken.morphsBasicallyEligibleForPracticeByPriority

View file

@ -20,6 +20,7 @@ import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/common/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
@ -249,6 +250,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
),
);
pangeaController = PangeaController(matrix: widget, matrixState: this);
TtsController.initialize();
// Pangea#
}