Skip to content

Commit

Permalink
feat: added option to choose a voice for TTS (#258)
Browse files Browse the repository at this point in the history
* feat: added option to choose a voice for TTS

* com#2 refactoring voice dropdown & using setConfig
  • Loading branch information
maiHydrogen authored and SankethBK committed Oct 26, 2024
1 parent 3fdf93a commit eaed9d0
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 16 deletions.
4 changes: 3 additions & 1 deletion lib/core/pages/settings_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:dairy_app/core/widgets/glassmorphism_cover.dart';
import 'package:dairy_app/core/widgets/language_dropdown.dart';
import 'package:dairy_app/core/widgets/logout_button.dart';
import 'package:dairy_app/core/widgets/theme_dropdown.dart';
import 'package:dairy_app/core/widgets/voice_dropdown.dart';
import 'package:dairy_app/features/auth/core/constants.dart';
import 'package:dairy_app/features/auth/presentation/bloc/auth_session/auth_session_bloc.dart';
import 'package:dairy_app/features/auth/presentation/widgets/security_settings.dart';
Expand Down Expand Up @@ -160,8 +161,9 @@ class _SettingsDetailPageState extends State<SettingsDetailPage> {
const FontDropdown(),
const SizedBox(height: 15),
const LanguageDropDown(),
const SizedBox(height: 15),
const VoiceDropdown(),
];

case SettingCategoriesConstants.importAndExport:
return [
const ExportNotes(),
Expand Down
122 changes: 122 additions & 0 deletions lib/core/widgets/voice_dropdown.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import 'package:dairy_app/app/themes/theme_extensions/note_create_page_theme_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_tts/flutter_tts.dart';

import '../../features/auth/core/constants.dart';
import '../../features/auth/presentation/bloc/locale/locale_cubit.dart';
import '../../features/auth/presentation/bloc/user_config/user_config_cubit.dart';

class VoiceDropdown extends StatefulWidget {
const VoiceDropdown({Key? key}) : super(key: key);

@override
State<VoiceDropdown> createState() => _VoiceDropdownState();
}

class _VoiceDropdownState extends State<VoiceDropdown> {
final FlutterTts _flutterTts = FlutterTts();
List<Map>? _voices;
Map? selectedVoice;
final bool _isInitialized = false;

@override
void didChangeDependencies() {
super.didChangeDependencies();

if (!_isInitialized) {
_initTts();
// _isInitialized = true;
}
}

void _initTts() {
final userConfigCubit = context.read<UserConfigCubit>();
final currentSelectedVoice =
userConfigCubit.state.userConfigModel?.prefKeyVoice;

final localeCubit = context.watch<LocaleCubit>().state;

_flutterTts.getVoices.then((data) {
List<Map> voices = List<Map>.from(data);

voices = voices
.where((voice) =>
voice["name"].contains(localeCubit.currentLocale.toString()))
.toList();

print("voices = $voices");

setState(() {
_voices = voices;

selectedVoice = voices.firstWhere(
(voice) => voice['name'] == currentSelectedVoice?['name'],
orElse: () => voices.isNotEmpty
? voices.first
: {'name': 'Default', 'locale': 'en-US'},
);
});
});
}

@override
Widget build(BuildContext context) {
final userConfigCubit = BlocProvider.of<UserConfigCubit>(context);
final mainTextColor = Theme.of(context)
.extension<NoteCreatePageThemeExtensions>()!
.mainTextColor;

return Row(
children: [
Text(
'Select Voice',
style: TextStyle(
fontSize: 16.0,
color: mainTextColor,
),
),
const Spacer(),
PopupMenuButton<Map>(
padding: const EdgeInsets.only(bottom: 0.0),
onSelected: (value) async {
setState(() {
selectedVoice = value;
});

userConfigCubit.setUserConfig(
UserConfigConstants.prefKeyVoice, selectedVoice);
},
itemBuilder: (context) =>
_voices?.map((voice) {
return PopupMenuItem<Map>(
value: voice,
child: Text(
voice['name'],
style: TextStyle(color: mainTextColor),
),
);
}).toList() ??
[],
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
selectedVoice?['name'] ?? 'Default',
style: TextStyle(
color: mainTextColor,
fontSize: 16,
),
),
const SizedBox(width: 10),
Icon(
Icons.keyboard_arrow_down,
color: mainTextColor,
),
],
),
),
],
);
}
}
2 changes: 1 addition & 1 deletion lib/features/auth/core/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class UserConfigConstants {
static String isPINPrintLoginEnabled = "is_pin_print_log_enabled";
static String isPINLoginEnabled = "is_pin_log_enabled";
static String enablePINLogin = "enable_pin_login";

static String prefKeyVoice = 'pref_key_voice';
static String isAutoSaveEnabled = "is_auto_save_enabled";
static String isDailyReminderEnabled = "is_daily_reminder_enabled";
static String reminderTime = "reminder_time";
Expand Down
7 changes: 6 additions & 1 deletion lib/features/auth/data/models/user_config_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class UserConfigModel extends Equatable {
final NoteSortType? noteSortType;
final String? nextCloudUserInfo;
final DateTime? lastNextCloudSync;
final Map<String, dynamic>? prefKeyVoice;

const UserConfigModel({
required this.userId,
Expand All @@ -58,6 +59,7 @@ class UserConfigModel extends Equatable {
this.noteSortType,
this.nextCloudUserInfo,
this.lastNextCloudSync,
this.prefKeyVoice,
});
@override
List<Object?> get props => [
Expand All @@ -75,7 +77,8 @@ class UserConfigModel extends Equatable {
reminderTime,
noteSortType,
nextCloudUserInfo,
lastNextCloudSync
lastNextCloudSync,
prefKeyVoice,
];

static TimeOfDay? getTimeOfDayFromTimeString(String? timeString) {
Expand Down Expand Up @@ -109,6 +112,7 @@ class UserConfigModel extends Equatable {

factory UserConfigModel.fromJson(Map<String, dynamic> jsonMap) {
return UserConfigModel(
prefKeyVoice: jsonMap[UserConfigConstants.prefKeyVoice],
userId: jsonMap[UserConfigConstants.userId],
preferredSyncOption: jsonMap[UserConfigConstants.preferredSyncOption],
lastGoogleDriveSync:
Expand Down Expand Up @@ -145,6 +149,7 @@ class UserConfigModel extends Equatable {

Map<String, dynamic> toJson() {
return {
UserConfigConstants.prefKeyVoice: prefKeyVoice,
UserConfigConstants.userId: userId,
UserConfigConstants.preferredSyncOption: preferredSyncOption,
UserConfigConstants.lastGoogleDriveSync:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:dairy_app/core/logger/logger.dart';
import 'package:dairy_app/features/auth/data/models/user_config_model.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ part of 'user_config_cubit.dart';

abstract class UserConfigState {
final UserConfigModel? userConfigModel;

const UserConfigState({required this.userConfigModel});
}

Expand Down
20 changes: 18 additions & 2 deletions lib/features/notes/presentation/widgets/note_read_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:dairy_app/features/notes/presentation/bloc/notes/notes_bloc.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../../../auth/presentation/bloc/user_config/user_config_cubit.dart';

class NoteReadIconButton extends StatefulWidget {
const NoteReadIconButton({Key? key}) : super(key: key);
Expand All @@ -14,7 +15,7 @@ class _NoteReadIconButtonState extends State<NoteReadIconButton> {
ValueNotifier<bool> isPlayingNotifier = ValueNotifier(false);

final flutterTts = FlutterTts()
..setLanguage("en-US")
//..setLanguage("en-US")
..setSpeechRate(0.5)
..setVolume(1)
..setPitch(1.0);
Expand Down Expand Up @@ -61,7 +62,22 @@ class _NoteReadIconButtonState extends State<NoteReadIconButton> {
);
}

Future _speak(String text) async => await flutterTts.speak(text);
Future<void> _speak(String text) async {
// Get selected voice from UserConfigCubit
final userConfigCubit = context.read<UserConfigCubit>();
final selectedVoice = userConfigCubit.state.userConfigModel?.prefKeyVoice;

// Set voice if available, and await completion
if (selectedVoice != null) {
await flutterTts.setVoice({
'name': selectedVoice['name'],
'locale': selectedVoice['locale'],
});
}

// Now start speaking
await flutterTts.speak(text);
}

@override
void dispose() {
Expand Down
8 changes: 4 additions & 4 deletions macos/Flutter/ephemeral/Flutter-Generated.xcconfig
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/sanketh.bk/sdk/flutter3
FLUTTER_APPLICATION_PATH=/Users/sanketh.bk/my_projects/flutter/apps in production/dairy_app
FLUTTER_ROOT=C:\dev\flutter
FLUTTER_APPLICATION_PATH=C:\dev\Hacktoberfest\DV\diaryvault
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=2.1.9
FLUTTER_BUILD_NUMBER=15
FLUTTER_BUILD_NAME=2.2.5
FLUTTER_BUILD_NUMBER=21
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
Expand Down
8 changes: 4 additions & 4 deletions macos/Flutter/ephemeral/flutter_export_environment.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/sanketh.bk/sdk/flutter3"
export "FLUTTER_APPLICATION_PATH=/Users/sanketh.bk/my_projects/flutter/apps in production/dairy_app"
export "FLUTTER_ROOT=C:\dev\flutter"
export "FLUTTER_APPLICATION_PATH=C:\dev\Hacktoberfest\DV\diaryvault"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=2.1.9"
export "FLUTTER_BUILD_NUMBER=15"
export "FLUTTER_BUILD_NAME=2.2.5"
export "FLUTTER_BUILD_NUMBER=21"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

0 comments on commit eaed9d0

Please sign in to comment.