Skip to content

Commit

Permalink
feat: localize the vocalization in TtsModel (#1280)
Browse files Browse the repository at this point in the history
* Localize the vocalization in TtsModel

* Discard changes to lib/l10n/app_de.arb

* Discard changes to lib/l10n/app_es.arb

* Discard changes to lib/l10n/app_ja.arb

* Discard changes to lib/l10n/app_fr.arb

* Update getVocalization to take an AppLocalizations l10n parameter instead of a BuildContext

* fix: missing additions

* fix: error

* fix: branch and format

* fix: lock file

* fix

* fix: revert alpahbetizer change

* fix: isolate spawn

* fix: localiztions

* revert

---------

Co-authored-by: SputNikPlop <100245448+SputNikPlop@users.noreply.github.com>
  • Loading branch information
kevmo314 and SputNikPlop authored Jul 23, 2024
1 parent 77d9987 commit ec96b52
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 26 deletions.
40 changes: 40 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -952,5 +952,45 @@
"example": "3"
}
}
},
"sampleMessage": "This is a sample message for text to speech.",
"@sampleMessage": {
"description": "Sample message for text to speech"
},
"actionMessage": "{author} is performing an action: {text}",
"@actionMessage": {
"description": "Message for an action performed by the author",
"placeholders": {
"author": {
"type": "String",
"example": "JohnDoe"
},
"text": {
"type": "String",
"example": "is dancing"
}
}
},
"saidMessage": "{author} said: {text}",
"@saidMessage": {
"description": "Message for something said by the author",
"placeholders": {
"author": {
"type": "String",
"example": "JohnDoe"
},
"text": {
"type": "String",
"example": "Hello everyone!"
}
}
},
"textToSpeechEnabled": "Text to speech enabled",
"@textToSpeechEnabled": {
"description": "Message indicating that text to speech has been enabled"
},
"textToSpeechDisabled": "Text to speech disabled",
"@textToSpeechDisabled": {
"description": "Message indicating that text to speech has been disabled"
}
}
8 changes: 7 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,18 @@ void updateChannelSubscription(String? data) {
StreamController<String> channelStreamController =
StreamController<String>.broadcast();

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await MobileAds.instance.initialize();
final prefs = await StreamingSharedPreferences.instance;

final currentLocale = PlatformDispatcher.instance.locale;

await tts_isolate.isolateMain(
ReceivePort().sendPort, channelStreamController, prefs);
ReceivePort().sendPort, channelStreamController, prefs, currentLocale);

if (!kDebugMode) {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
Expand Down Expand Up @@ -293,6 +298,7 @@ class _AppState extends State<App> {
],
child: Consumer<LayoutModel>(builder: (context, layoutModel, child) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'RealtimeChat',
theme: Themes.lightTheme,
darkTheme: Themes.darkTheme,
Expand Down
42 changes: 38 additions & 4 deletions lib/models/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:core';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:rtchat/main.dart';
import 'package:rtchat/models/adapters/messages.dart';
import 'package:rtchat/models/adapters/profiles.dart';
import 'package:rtchat/models/channels.dart';
Expand Down Expand Up @@ -34,7 +36,14 @@ class MessagesModel extends ChangeNotifier {
_separators = {};
_events = [];
_isLive = false;
_tts?.enabled = false;
if (_tts != null) {
final localizations = _getLocalizations();
if (localizations != null) {
_tts!.setEnabled(localizations, false);
} else {
debugPrint("Localizations not available");
}
}
notifyListeners();

_subscription?.cancel();
Expand All @@ -52,7 +61,13 @@ class MessagesModel extends ChangeNotifier {
_messages.insert(index, event.model);
} else {
_messages.add(event.model);
_tts?.say(event.model);
// Pass localizations to the TTS say method
final localizations = _getLocalizations();
if (localizations != null) {
_tts?.say(localizations, event.model);
} else {
debugPrint("Localizations not available");
}
if (_isLive && shouldPing()) {
ProfilesAdapter.instance
.getIsOnline(channelId: channel.toString())
Expand Down Expand Up @@ -199,7 +214,14 @@ class MessagesModel extends ChangeNotifier {
return;
}
_tts = tts;
tts?.enabled = false;
if (_tts != null) {
final localizations = _getLocalizations();
if (localizations != null) {
_tts!.setEnabled(localizations, false);
} else {
debugPrint("Localizations not available");
}
}
notifyListeners();
}

Expand Down Expand Up @@ -252,4 +274,16 @@ class MessagesModel extends ChangeNotifier {
"announcementPinDuration": _announcementPinDuration.inSeconds.toInt(),
"pingMinGapDuration": _pingMinGapDuration.inSeconds.toInt(),
};

BuildContext? _getContext() {
return navigatorKey.currentContext;
}

AppLocalizations? _getLocalizations() {
final context = _getContext();
if (context != null) {
return AppLocalizations.of(context);
}
return null;
}
}
29 changes: 21 additions & 8 deletions lib/models/tts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:rtchat/models/tts/language.dart';
import 'package:rtchat/models/tts/bytes_audio_source.dart';
import 'package:rtchat/models/user.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class TtsModel extends ChangeNotifier {
var _isCloudTtsEnabled = false;
Expand Down Expand Up @@ -104,7 +105,7 @@ class TtsModel extends ChangeNotifier {
notifyListeners();
}

String getVocalization(MessageModel model,
String getVocalization(AppLocalizations l10n, MessageModel model,
{bool includeAuthorPrelude = false}) {
if (model is TwitchMessageModel) {
final text = model.tokenized
Expand All @@ -131,9 +132,14 @@ class TtsModel extends ChangeNotifier {
if (!includeAuthorPrelude || isPreludeMuted) {
return text;
}
return model.isAction ? "$author $text" : "$author said $text";
return model.isAction
? l10n.actionMessage(author, text)
: l10n.saidMessage(author, text);
} else if (model is StreamStateEventModel) {
return model.isOnline ? "Stream is online" : "Stream is offline";
final timestamp = model.timestamp;
return model.isOnline
? l10n.streamOnline(timestamp, timestamp)
: l10n.streamOffline(timestamp, timestamp);
} else if (model is SystemMessageModel) {
return model.text;
}
Expand All @@ -159,7 +165,7 @@ class TtsModel extends ChangeNotifier {
return _isEnabled;
}

set enabled(bool value) {
void setEnabled(AppLocalizations localizations, bool value) {
if (value == _isEnabled) {
return;
}
Expand All @@ -168,8 +174,11 @@ class TtsModel extends ChangeNotifier {
_lastMessageTime = DateTime.now();
}
say(
localizations,
SystemMessageModel(
text: "Text to speech ${value ? "enabled" : "disabled"}"),
text: value
? localizations.textToSpeechEnabled
: localizations.textToSpeechDisabled),
force: true);
WidgetsBinding.instance.addPostFrameCallback((_) {
notifyListeners();
Expand Down Expand Up @@ -285,7 +294,8 @@ class TtsModel extends ChangeNotifier {
}
}

void say(MessageModel model, {bool force = false}) async {
void say(AppLocalizations localizations, MessageModel model,
{bool force = false}) async {
if (!enabled && !force) {
return;
}
Expand Down Expand Up @@ -316,8 +326,11 @@ class TtsModel extends ChangeNotifier {
includeAuthorPrelude = !(activeMessage.author == model.author);
}

final vocalization =
getVocalization(model, includeAuthorPrelude: includeAuthorPrelude);
final vocalization = getVocalization(
localizations,
model,
includeAuthorPrelude: includeAuthorPrelude,
);

// if the vocalization is empty, skip the message
if (vocalization.isEmpty) {
Expand Down
2 changes: 1 addition & 1 deletion lib/notifications_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class NotificationsPlugin {
}
}

static Future<void> listenToTTs(TtsModel model) async {
static Future<void> listenToTts(TtsModel model) async {
try {
debugPrint("Listening to TTS");

Expand Down
7 changes: 4 additions & 3 deletions lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
final model = Provider.of<AudioModel>(context, listen: false);
final ttsModel = Provider.of<TtsModel>(context, listen: false);

NotificationsPlugin.listenToTTs(ttsModel);
NotificationsPlugin.listenToTts(ttsModel);

if (model.sources.isEmpty || (await AudioChannel.hasPermission())) {
return;
Expand Down Expand Up @@ -265,7 +265,8 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
tooltip: AppLocalizations.of(context)!.textToSpeech,
onPressed: () async {
if (!kDebugMode) {
ttsModel.enabled = !ttsModel.enabled;
ttsModel.setEnabled(AppLocalizations.of(context)!,
ttsModel.enabled ? false : true);
// Toggle newTtsEnabled and notify listeners immediately
} else {
ttsModel.newTtsEnabled = !ttsModel.newTtsEnabled;
Expand All @@ -290,7 +291,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
"${userModel.activeChannel?.provider}:${userModel.activeChannel?.channelId}",
);
NotificationsPlugin.showNotification();
NotificationsPlugin.listenToTTs(ttsModel);
NotificationsPlugin.listenToTts(ttsModel);
}
}
},
Expand Down
7 changes: 4 additions & 3 deletions lib/screens/settings/tts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
import 'package:rtchat/models/messages/message.dart';
import 'package:rtchat/models/tts.dart';
import 'package:rtchat/models/tts/bytes_audio_source.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class TextToSpeechScreen extends StatelessWidget {
const TextToSpeechScreen({super.key});
Expand Down Expand Up @@ -154,16 +155,16 @@ class TextToSpeechScreen extends StatelessWidget {
"voice": model.voice,
"rate": model.speed * 1.5 + 0.5,
"pitch": model.pitch * 4 - 2,
"text":
"kevin calmly and collectively consumes cheesecake",
"text": AppLocalizations.of(context)!.sampleMessage,
});
final bytes = const Base64Decoder().convert(response.data);
audioPlayer.setAudioSource(BytesAudioSource(bytes));
audioPlayer.play();
} else {
model.say(
AppLocalizations.of(context)!,
SystemMessageModel(
text: "muxfd said have you followed muxfd on twitch?",
text: AppLocalizations.of(context)!.sampleMessage,
),
force: true);
}
Expand Down
12 changes: 8 additions & 4 deletions lib/tts_isolate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,35 @@ import 'dart:ui';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:rtchat/models/tts.dart';
import 'package:rtchat/models/messages/twitch/message.dart';
import 'package:rtchat/models/messages/twitch/user.dart';
import 'package:rtchat/models/messages/twitch/reply.dart';
import 'package:rtchat/tts_plugin.dart';

import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';

final DateTime ttsTimeStampListener = DateTime.now();
StreamSubscription? messagesSubscription;
StreamSubscription? channelSubscription;

@pragma("vm:entry-point")
Future<void> isolateMain(
SendPort sendPort,
StreamController<String> channelStream,
StreamingSharedPreferences prefs) async {
StreamingSharedPreferences prefs,
Locale currentLocale) async {
DartPluginRegistrant.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();

final localizations = await AppLocalizations.delegate.load(currentLocale);

final ttsQueue = TTSQueue();

final ttsModel = TtsModel.fromJson(
jsonDecode(prefs.getString("tts", defaultValue: '{}').getValue()));

// listen for changes to the tts preferences and update the isolates ttsModel
// Listen for changes to the tts preferences and update the isolates ttsModel
final ttsPrefs = prefs.getString('tts', defaultValue: '{}');
ttsPrefs.listen((value) async {
ttsModel.updateFromJson(jsonDecode(value));
Expand Down Expand Up @@ -93,6 +96,7 @@ Future<void> isolateMain(
return; // Skip vocalization for bot messages
}
final finalMessage = ttsModel.getVocalization(
localizations,
messageModel,
includeAuthorPrelude: !ttsModel.isPreludeMuted,
);
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ dev_dependencies:
sdk: flutter

flutter:
uses-material-design: true
generate: true
assets:
- assets/
- assets/providers/
generate: true
uses-material-design: true

0 comments on commit ec96b52

Please sign in to comment.