move text to speech controller out of pangea controller

This commit is contained in:
ggurdin 2025-12-03 15:39:29 -05:00
parent 9cb155fcf1
commit e184e9a76f
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
18 changed files with 308 additions and 290 deletions

View file

@ -10,7 +10,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/text_to_speech/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/utils/event_checkbox_extension.dart';

View file

@ -21,7 +21,7 @@ import 'package:fluffychat/pangea/events/repo/tokens_repo.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import '../../widgets/matrix.dart';
import 'choreographer_error_controller.dart';

View file

@ -22,8 +22,7 @@ import 'package:fluffychat/pangea/learning_settings/utils/locale_provider.dart';
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
import 'package:fluffychat/pangea/spaces/controllers/space_code_controller.dart';
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.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/text_to_speech/tts_controller.dart';
import 'package:fluffychat/pangea/user/controllers/permissions_controller.dart';
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -38,7 +37,6 @@ class PangeaController {
late GetAnalyticsController getAnalytics;
late PutAnalyticsController putAnalytics;
late SubscriptionController subscriptionController;
late TextToSpeechController textToSpeech;
///store Services
final pLanguageStore = PLanguageStore();
@ -82,7 +80,6 @@ class PangeaController {
getAnalytics = GetAnalyticsController(this);
putAnalytics = PutAnalyticsController(this);
subscriptionController = SubscriptionController(this);
textToSpeech = TextToSpeechController(this);
PAuthGaurd.pController = this;
}

View file

@ -5,7 +5,7 @@ import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/common/widgets/choice_animation.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../bot/utils/bot_style.dart';
import '../../choreographer/it/it_shimmer.dart';

View file

@ -24,7 +24,9 @@ import 'package:fluffychat/pangea/speech_to_text/audio_encoding_enum.dart';
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_repo.dart';
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_request_model.dart';
import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart';
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
@ -360,7 +362,7 @@ class PangeaMessageEvent {
final rep = representationByLanguage(langCode);
final tokensResp = await rep?.requestTokens();
final request = TextToSpeechRequest(
final request = TextToSpeechRequestModel(
text: rep?.content.text ?? body,
tokens: tokensResp?.result?.map((t) => t.text).toList() ?? [],
langCode: langCode,
@ -368,10 +370,18 @@ class PangeaMessageEvent {
userL2: _l2Code ?? LanguageKeys.unknownLanguage,
);
final response = await MatrixState.pangeaController.textToSpeech.get(
final result = await TextToSpeechRepo.get(
MatrixState.pangeaController.userController.accessToken,
request,
);
if (result.error != null) {
throw Exception(
"Error getting text to speech: ${result.error}",
);
}
final response = result.result!;
final audioBytes = base64.decode(response.audioContent);
final fileName =
"audio_for_${_event.eventId}_$langCode.${response.fileExtension}";

View file

@ -11,7 +11,7 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
extension PangeaEvent on Event {

View file

@ -14,7 +14,7 @@ import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning_view.dart';
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
import 'package:fluffychat/pangea/spaces/models/space_model.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/pangea/user/models/user_model.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';

View file

@ -10,7 +10,7 @@ import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart';
import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/widgets/hover_builder.dart';
import 'package:fluffychat/widgets/matrix.dart';

View file

@ -0,0 +1,105 @@
import 'dart:convert';
import 'package:async/async.dart';
import 'package:http/http.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
class _TextToSpeechCacheItem {
final Future<TextToSpeechResponseModel> data;
final DateTime timestamp;
const _TextToSpeechCacheItem({
required this.data,
required this.timestamp,
});
}
class TextToSpeechRepo {
static final Map<String, _TextToSpeechCacheItem> _cache = {};
static const Duration _cacheDuration = Duration(minutes: 10);
static Future<Result<TextToSpeechResponseModel>> get(
String accessToken,
TextToSpeechRequestModel request,
) {
final cached = _getCached(request);
if (cached != null) {
return _getResult(request, cached);
}
final future = _fetch(accessToken, request);
_setCached(request, future);
return _getResult(request, future);
}
static Future<TextToSpeechResponseModel> _fetch(
String accessToken,
TextToSpeechRequestModel request,
) async {
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: accessToken,
);
final Response res = await req.post(
url: PApiUrls.textToSpeech,
body: request.toJson(),
);
if (res.statusCode != 200) {
throw Exception(
'Failed to convert text to speech: ${res.statusCode} ${res.reasonPhrase}',
);
}
return TextToSpeechResponseModel.fromJson(
jsonDecode(utf8.decode(res.bodyBytes)),
);
}
static Future<Result<TextToSpeechResponseModel>> _getResult(
TextToSpeechRequestModel request,
Future<TextToSpeechResponseModel> future,
) async {
try {
final res = await future;
return Result.value(res);
} catch (e, s) {
_cache.remove(request.hashCode.toString());
ErrorHandler.logError(
e: e,
s: s,
data: request.toJson(),
);
return Result.error(e);
}
}
static Future<TextToSpeechResponseModel>? _getCached(
TextToSpeechRequestModel request,
) {
final cacheKeys = [..._cache.keys];
for (final key in cacheKeys) {
if (DateTime.now().difference(_cache[key]!.timestamp) >= _cacheDuration) {
_cache.remove(key);
}
}
return _cache[request.hashCode.toString()]?.data;
}
static void _setCached(
TextToSpeechRequestModel request,
Future<TextToSpeechResponseModel> response,
) =>
_cache[request.hashCode.toString()] = _TextToSpeechCacheItem(
data: response,
timestamp: DateTime.now(),
);
}

View file

@ -0,0 +1,38 @@
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
class TextToSpeechRequestModel {
String text;
String langCode;
String userL1;
String userL2;
List<PangeaTokenText> tokens;
TextToSpeechRequestModel({
required this.text,
required this.langCode,
required this.userL1,
required this.userL2,
required this.tokens,
});
Map<String, dynamic> toJson() => {
ModelKey.text: text,
ModelKey.langCode: langCode,
ModelKey.userL1: userL1,
ModelKey.userL2: userL2,
ModelKey.tokens: tokens.map((token) => token.toJson()).toList(),
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TextToSpeechRequestModel &&
other.text == text &&
other.langCode == langCode;
}
@override
int get hashCode => text.hashCode ^ langCode.hashCode;
}

View file

@ -0,0 +1,117 @@
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
class TextToSpeechResponseModel {
String audioContent;
String mimeType;
int durationMillis;
List<int> waveform;
String fileExtension;
List<TTSToken> ttsTokens;
TextToSpeechResponseModel({
required this.audioContent,
required this.mimeType,
required this.durationMillis,
required this.waveform,
required this.fileExtension,
required this.ttsTokens,
});
factory TextToSpeechResponseModel.fromJson(
Map<String, dynamic> json,
) =>
TextToSpeechResponseModel(
audioContent: json["audio_content"],
mimeType: json["mime_type"],
durationMillis: json["duration_millis"],
waveform: List<int>.from(json["wave_form"]),
fileExtension: json["file_extension"],
ttsTokens: List<TTSToken>.from(
json["tts_tokens"].map((x) => TTSToken.fromJson(x)),
),
);
Map<String, dynamic> toJson() => {
"audio_content": audioContent,
"mime_type": mimeType,
"duration_millis": durationMillis,
"wave_form": List<dynamic>.from(waveform.map((x) => x)),
"file_extension": fileExtension,
"tts_tokens": List<dynamic>.from(ttsTokens.map((x) => x.toJson())),
};
PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) {
return PangeaAudioEventData(
text: text,
langCode: langCode,
tokens: ttsTokens,
);
}
}
class TTSToken {
final int startMS;
final int endMS;
final PangeaTokenText text;
TTSToken({
required this.startMS,
required this.endMS,
required this.text,
});
factory TTSToken.fromJson(Map<String, dynamic> json) => TTSToken(
startMS: json["start_ms"],
endMS: json["end_ms"],
text: PangeaTokenText.fromJson(json["text"]),
);
Map<String, dynamic> toJson() => {
"start_ms": startMS,
"end_ms": endMS,
"text": text.toJson(),
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TTSToken &&
other.startMS == startMS &&
other.endMS == endMS &&
other.text == text;
}
@override
int get hashCode => startMS.hashCode ^ endMS.hashCode ^ text.hashCode;
}
class PangeaAudioEventData {
final String text;
final String langCode;
final List<TTSToken> tokens;
PangeaAudioEventData({
required this.text,
required this.langCode,
required this.tokens,
});
factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData(
text: json[ModelKey.text] as String,
langCode: json[ModelKey.langCode] as String,
tokens: List<TTSToken>.from(
(json[ModelKey.tokens] as Iterable)
.map((x) => TTSToken.fromJson(x))
.toList(),
),
);
Map<String, dynamic> toJson() => {
ModelKey.text: text,
ModelKey.langCode: langCode,
ModelKey.tokens:
List<Map<String, dynamic>>.from(tokens.map((x) => x.toJson())),
};
}

View file

@ -20,7 +20,10 @@ import 'package:fluffychat/pangea/common/widgets/card_header.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart'
@ -263,35 +266,26 @@ class TtsController {
String langCode,
List<PangeaTokenText> tokens,
) async {
TextToSpeechResponse? ttsRes;
try {
loadingChoreoStream.add(true);
ttsRes = await MatrixState.pangeaController.textToSpeech.get(
TextToSpeechRequest(
text: text,
langCode: langCode,
tokens: tokens,
userL1:
MatrixState.pangeaController.languageController.activeL1Code() ??
LanguageKeys.unknownLanguage,
userL2:
MatrixState.pangeaController.languageController.activeL2Code() ??
LanguageKeys.unknownLanguage,
),
);
} catch (e, s) {
error_handler.ErrorHandler.logError(
e: e,
s: s,
data: {
'text': text,
},
);
} finally {
loadingChoreoStream.add(false);
}
TextToSpeechResponseModel? ttsRes;
if (ttsRes == null) return;
loadingChoreoStream.add(true);
final result = await TextToSpeechRepo.get(
MatrixState.pangeaController.userController.accessToken,
TextToSpeechRequestModel(
text: text,
langCode: langCode,
tokens: tokens,
userL1:
MatrixState.pangeaController.languageController.activeL1Code() ??
LanguageKeys.unknownLanguage,
userL2:
MatrixState.pangeaController.languageController.activeL2Code() ??
LanguageKeys.unknownLanguage,
),
);
loadingChoreoStream.add(false);
if (result.isError) return;
ttsRes = result.result!;
try {
Logs().i('Speaking from choreo: $text, langCode: $langCode');

View file

@ -1,243 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:http/http.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
class PangeaAudioEventData {
final String text;
final String langCode;
final List<TTSToken> tokens;
PangeaAudioEventData({
required this.text,
required this.langCode,
required this.tokens,
});
factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData(
text: json[ModelKey.text] as String,
langCode: json[ModelKey.langCode] as String,
tokens: List<TTSToken>.from(
(json[ModelKey.tokens] as Iterable)
.map((x) => TTSToken.fromJson(x))
.toList(),
),
);
Map<String, dynamic> toJson() => {
ModelKey.text: text,
ModelKey.langCode: langCode,
ModelKey.tokens:
List<Map<String, dynamic>>.from(tokens.map((x) => x.toJson())),
};
}
class TTSToken {
final int startMS;
final int endMS;
final PangeaTokenText text;
TTSToken({
required this.startMS,
required this.endMS,
required this.text,
});
factory TTSToken.fromJson(Map<String, dynamic> json) => TTSToken(
startMS: json["start_ms"],
endMS: json["end_ms"],
text: PangeaTokenText.fromJson(json["text"]),
);
Map<String, dynamic> toJson() => {
"start_ms": startMS,
"end_ms": endMS,
"text": text.toJson(),
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TTSToken &&
other.startMS == startMS &&
other.endMS == endMS &&
other.text == text;
}
@override
int get hashCode => startMS.hashCode ^ endMS.hashCode ^ text.hashCode;
}
class TextToSpeechRequest {
String text;
String langCode;
String userL1;
String userL2;
List<PangeaTokenText> tokens;
TextToSpeechRequest({
required this.text,
required this.langCode,
required this.userL1,
required this.userL2,
required this.tokens,
});
Map<String, dynamic> toJson() => {
ModelKey.text: text,
ModelKey.langCode: langCode,
ModelKey.userL1: userL1,
ModelKey.userL2: userL2,
ModelKey.tokens: tokens.map((token) => token.toJson()).toList(),
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TextToSpeechRequest &&
other.text == text &&
other.langCode == langCode;
}
@override
int get hashCode => text.hashCode ^ langCode.hashCode;
}
class TextToSpeechResponse {
String audioContent;
String mimeType;
int durationMillis;
List<int> waveform;
String fileExtension;
List<TTSToken> ttsTokens;
TextToSpeechResponse({
required this.audioContent,
required this.mimeType,
required this.durationMillis,
required this.waveform,
required this.fileExtension,
required this.ttsTokens,
});
factory TextToSpeechResponse.fromJson(
Map<String, dynamic> json,
) =>
TextToSpeechResponse(
audioContent: json["audio_content"],
mimeType: json["mime_type"],
durationMillis: json["duration_millis"],
waveform: List<int>.from(json["wave_form"]),
fileExtension: json["file_extension"],
ttsTokens: List<TTSToken>.from(
json["tts_tokens"].map((x) => TTSToken.fromJson(x)),
),
);
Map<String, dynamic> toJson() => {
"audio_content": audioContent,
"mime_type": mimeType,
"duration_millis": durationMillis,
"wave_form": List<dynamic>.from(waveform.map((x) => x)),
"file_extension": fileExtension,
"tts_tokens": List<dynamic>.from(ttsTokens.map((x) => x.toJson())),
};
PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) {
return PangeaAudioEventData(
text: text,
langCode: langCode,
tokens: ttsTokens,
);
}
}
class _TextToSpeechCacheItem {
Future<TextToSpeechResponse> data;
_TextToSpeechCacheItem({
required this.data,
});
}
class TextToSpeechController {
static final Map<TextToSpeechRequest, _TextToSpeechCacheItem> _cache = {};
late final PangeaController _pangeaController;
Timer? _cacheClearTimer;
TextToSpeechController(PangeaController pangeaController) {
_pangeaController = pangeaController;
_initializeCacheClearing();
}
void _initializeCacheClearing() {
const duration = Duration(minutes: 15); // Adjust the duration as needed
_cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
}
void _clearCache() {
_cache.clear();
}
void dispose() {
_cacheClearTimer?.cancel();
}
Future<TextToSpeechResponse> get(
TextToSpeechRequest params,
) async {
if (_cache.containsKey(params)) {
return _cache[params]!.data;
} else {
final Future<TextToSpeechResponse> response = _fetchResponse(
_pangeaController.userController.accessToken,
params,
);
_cache[params] = _TextToSpeechCacheItem(data: response);
return response;
}
}
static Future<TextToSpeechResponse> _fetchResponse(
String accessToken,
TextToSpeechRequest params,
) async {
final Requests request = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: accessToken,
);
final Response res = await request.post(
url: PApiUrls.textToSpeech,
body: params.toJson(),
);
final Map<String, dynamic> json = jsonDecode(res.body);
return TextToSpeechResponse.fromJson(json);
}
static bool isOggFile(Uint8List bytes) {
// Check if the file has enough bytes for the header
if (bytes.length < 4) {
return false;
}
// Check the magic number for OGG file
return bytes[0] == 0x4F &&
bytes[1] == 0x67 &&
bytes[2] == 0x67 &&
bytes[3] == 0x53;
}
}

View file

@ -7,7 +7,7 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_choice.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';

View file

@ -10,7 +10,7 @@ import 'package:fluffychat/pages/chat/events/audio_player.dart';
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
class MessageAudioCard extends StatefulWidget {
final PangeaMessageEvent messageEvent;

View file

@ -20,7 +20,7 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_ev
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/message_token_text/tokens_util.dart';
import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart';
import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart';
import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart';

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart';
import 'package:fluffychat/widgets/matrix.dart';
class WordAudioButton extends StatefulWidget {

View file

@ -17,7 +17,7 @@ import 'package:fluffychat/pangea/common/widgets/pressable_button.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/utils/report_message.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/text_to_speech/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/select_mode_controller.dart';