添加语言切换

This commit is contained in:
rang 2025-10-22 17:58:29 +08:00
parent e0deb84123
commit e06c834d0e
21 changed files with 1416 additions and 281 deletions

View file

@ -18,6 +18,7 @@ import 'package:vaani/features/item_viewer/view/library_item_page.dart';
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
import 'package:vaani/features/player/providers/audiobook_player.dart';
import 'package:vaani/features/player/providers/player_form.dart';
import 'package:vaani/generated/l10n.dart';
import 'package:vaani/main.dart';
import 'package:vaani/router/router.dart';
import 'package:vaani/settings/api_settings_provider.dart';
@ -76,11 +77,9 @@ class LibraryItemActions extends HookConsumerWidget {
IconButton(
onPressed: () {
appLogger.fine('Sharing');
var currentServerUrl =
apiSettings.activeServer!.serverUrl;
var currentServerUrl = apiSettings.activeServer!.serverUrl;
if (!currentServerUrl.hasScheme) {
currentServerUrl =
Uri.https(currentServerUrl.toString());
currentServerUrl = Uri.https(currentServerUrl.toString());
}
handleLaunchUrl(
Uri.parse(
@ -139,8 +138,7 @@ class LibraryItemActions extends HookConsumerWidget {
FileDownloader()
.database
.deleteRecordWithId(
record
.task.taskId,
record.task.taskId,
);
Navigator.pop(context);
},
@ -159,8 +157,7 @@ class LibraryItemActions extends HookConsumerWidget {
},
onTap: () async {
// open the file location
final didOpen =
await FileDownloader().openFile(
final didOpen = await FileDownloader().openFile(
task: record.task,
);
@ -229,9 +226,7 @@ class LibItemDownloadButton extends HookConsumerWidget {
onPressed: () {
appLogger.fine('Pressed download button');
ref
.read(downloadManagerProvider.notifier)
.queueAudioBookDownload(item);
ref.read(downloadManagerProvider.notifier).queueAudioBookDownload(item);
},
icon: const Icon(
Icons.download_rounded,
@ -250,10 +245,7 @@ class ItemCurrentlyInDownloadQueue extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final progress = ref
.watch(itemDownloadProgressProvider(item.id))
.valueOrNull
?.clamp(0.05, 1.0);
final progress = ref.watch(itemDownloadProgressProvider(item.id)).valueOrNull?.clamp(0.05, 1.0);
if (progress == 1) {
return AlreadyItemDownloadedButton(item: item);
@ -366,7 +358,7 @@ class DownloadSheet extends HookConsumerWidget {
// },
// ),
ListTile(
title: const Text('Delete'),
title: Text(S.of(context).delete),
leading: const Icon(
Icons.delete_rounded,
),
@ -377,28 +369,26 @@ class DownloadSheet extends HookConsumerWidget {
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Delete'),
title: Text(S.of(context).delete),
content: Text(
'Are you sure you want to delete ${item.media.metadata.title}?',
S.of(context).deleteDialog(item.media.metadata.title ?? ''),
),
actions: [
TextButton(
onPressed: () {
// delete the file
ref
.read(downloadManagerProvider.notifier)
.deleteDownloadedItem(
ref.read(downloadManagerProvider.notifier).deleteDownloadedItem(
item,
);
GoRouter.of(context).pop(true);
},
child: const Text('Yes'),
child: Text(S.of(context).yes),
),
TextButton(
onPressed: () {
GoRouter.of(context).pop(false);
},
child: const Text('No'),
child: Text(S.of(context).no),
),
],
);
@ -406,12 +396,12 @@ class DownloadSheet extends HookConsumerWidget {
);
if (wasDeleted ?? false) {
appLogger.fine('Deleted ${item.media.metadata.title}');
appLogger.fine(S.of(context).deleted(item.media.metadata.title ?? ''));
GoRouter.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Deleted ${item.media.metadata.title}',
S.of(context).deleted(item.media.metadata.title ?? ''),
),
),
);
@ -445,19 +435,19 @@ class _LibraryItemPlayButton extends HookConsumerWidget {
if (!isCurrentBookSetInPlayer) {
// either play or resume or listen again based on the progress
if (isBookCompleted) {
return 'Listen Again';
return S.of(context).homeListenAgain;
}
// if some progress is made, then 'continue listening'
if (userMediaProgress?.progress != null) {
return 'Continue Listening';
return S.of(context).homeContinueListening;
}
return 'Start Listening';
return S.of(context).homeStartListening;
} else {
// if book is set to player
if (isPlayingThisBook) {
return 'Pause';
return S.of(context).pause;
}
return 'Resume';
return S.of(context).resume;
}
}
@ -529,8 +519,7 @@ Future<void> libraryItemPlayButtonOnPressed({
appLogger.info('Setting the book ${book.libraryItemId}');
appLogger.info('Initial position: ${userMediaProgress?.currentTime}');
final downloadManager = ref.watch(simpleDownloadManagerProvider);
final libItem =
await ref.read(libraryItemProvider(book.libraryItemId).future);
final libItem = await ref.read(libraryItemProvider(book.libraryItemId).future);
final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
setSourceFuture = player.setSourceAudiobook(
book,
@ -546,27 +535,23 @@ Future<void> libraryItemPlayButtonOnPressed({
}
}
// set the volume as this is the first time playing and dismissing causes the volume to go to 0
var bookPlayerSettings =
ref.read(bookSettingsProvider(book.libraryItemId)).playerSettings;
var bookPlayerSettings = ref.read(bookSettingsProvider(book.libraryItemId)).playerSettings;
var appPlayerSettings = ref.read(appSettingsProvider).playerSettings;
var configurePlayerForEveryBook =
appPlayerSettings.configurePlayerForEveryBook;
var configurePlayerForEveryBook = appPlayerSettings.configurePlayerForEveryBook;
await Future.wait([
setSourceFuture ?? Future.value(),
// set the volume
player.setVolume(
configurePlayerForEveryBook
? bookPlayerSettings.preferredDefaultVolume ??
appPlayerSettings.preferredDefaultVolume
? bookPlayerSettings.preferredDefaultVolume ?? appPlayerSettings.preferredDefaultVolume
: appPlayerSettings.preferredDefaultVolume,
),
// set the speed
player.setSpeed(
configurePlayerForEveryBook
? bookPlayerSettings.preferredDefaultSpeed ??
appPlayerSettings.preferredDefaultSpeed
? bookPlayerSettings.preferredDefaultSpeed ?? appPlayerSettings.preferredDefaultSpeed
: appPlayerSettings.preferredDefaultSpeed,
),
]);

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/api/library_item_provider.dart';
import 'package:vaani/generated/l10n.dart';
import 'package:vaani/shared/extensions/model_conversions.dart';
class LibraryItemMetadata extends HookConsumerWidget {
@ -24,9 +25,8 @@ class LibraryItemMetadata extends HookConsumerWidget {
if (book == null) {
return null;
}
final duration = book.audioFiles
.map((e) => e.duration)
.reduce((value, element) => value + element);
final duration =
book.audioFiles.map((e) => e.duration).reduce((value, element) => value + element);
final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60);
return '${hours}h ${minutes}m';
@ -41,10 +41,12 @@ class LibraryItemMetadata extends HookConsumerWidget {
if (book == null) {
return null;
}
final size = book.audioFiles
.map((e) => e.metadata.size)
.reduce((value, element) => value + element);
return '${size / 1024 ~/ 1024} MB';
final size =
book.audioFiles.map((e) => e.metadata.size).reduce((value, element) => value + element);
if (size / 1024 / 1024 < 1024) {
return '${(size / 1024 / 1024).toStringAsFixed(2)} MB';
}
return '${(size / 1024 / 1024 / 1024).toStringAsFixed(2)} GB';
}
/// will return the codec and bitrate of the book
@ -64,21 +66,21 @@ class LibraryItemMetadata extends HookConsumerWidget {
// duration of the book
_MetadataItem(
title: switch (itemBookMetadata?.abridged) {
true => 'Abridged',
false => 'Unabridged',
_ => 'Length',
true => S.of(context).bookMetadataAbridged,
false => S.of(context).bookMetadataUnabridged,
_ => S.of(context).bookMetadataLength,
},
value: getDurationFormatted() ?? 'time is just a concept',
),
_MetadataItem(
title: 'Published',
title: S.of(context).bookMetadataPublished,
value: itemBookMetadata?.publishedDate ??
itemBookMetadata?.publishedYear ??
'Unknown',
S.of(context).unknown,
),
_MetadataItem(
title: getCodecAndBitrate() ?? 'Codec & Bitrate',
value: getSizeFormatted() ?? 'Unknown',
value: getSizeFormatted() ?? S.of(context).unknown,
),
];
return Padding(
@ -96,10 +98,7 @@ class LibraryItemMetadata extends HookConsumerWidget {
return VerticalDivider(
indent: 6,
endIndent: 6,
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.6),
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
);
},
),

View file

@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/api/library_item_provider.dart';
import 'package:vaani/features/item_viewer/view/library_item_sliver_app_bar.dart';
import 'package:vaani/features/player/view/mini_player_bottom_padding.dart';
import 'package:vaani/generated/l10n.dart';
import 'package:vaani/router/models/library_item_extras.dart';
import 'package:vaani/shared/widgets/expandable_description.dart';
@ -27,8 +28,7 @@ class LibraryItemPage extends HookConsumerWidget {
static const double _showFabThreshold = 300.0;
@override
Widget build(BuildContext context, WidgetRef ref) {
final additionalItemData =
extra is LibraryItemExtras ? extra as LibraryItemExtras : null;
final additionalItemData = extra is LibraryItemExtras ? extra as LibraryItemExtras : null;
final scrollController = useScrollController();
final showFab = useState(false);
@ -150,8 +150,10 @@ class LibraryItemDescription extends HookConsumerWidget {
return const SizedBox();
}
return ExpandableDescription(
title: 'About the Book',
content: item.media.metadata.description ?? 'Sorry, no description found',
title: S.of(context).bookAbout,
content: item.media.metadata.description ?? S.of(context).bookAboutDefault,
readMoreText: S.of(context).readMore,
readLessText: S.of(context).readLess,
);
}
}
@ -166,10 +168,8 @@ double calculateWidth(
/// height ratio of the cover image to the available height
double maxHeightToUse = 0.25,
}) {
final availHeight =
min(constraints.maxHeight, MediaQuery.of(context).size.height);
final availWidth =
min(constraints.maxWidth, MediaQuery.of(context).size.width);
final availHeight = min(constraints.maxHeight, MediaQuery.of(context).size.height);
final availWidth = min(constraints.maxWidth, MediaQuery.of(context).size.width);
// make the width widthRatio of the available width
var width = availWidth * widthRatio;

View file

@ -31,6 +31,8 @@ class PlayerWhenMinimized extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider);
final currentChapter = ref.watch(currentPlayingChapterProvider);
final vanishingPercentage = 1 - percentageMiniplayer;
final progress = useStream(player.slowPositionStream, initialData: Duration.zero);
@ -77,7 +79,7 @@ class PlayerWhenMinimized extends HookConsumerWidget {
children: [
// AutoScrollText(
Text(
bookMetaExpanded?.title ?? '',
'${bookMetaExpanded?.title ?? ''} - ${currentChapter?.title ?? ''}',
maxLines: 1, overflow: TextOverflow.ellipsis,
// velocity:
// const Velocity(pixelsPerSecond: Offset(16, 0)),

View file

@ -0,0 +1,72 @@
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that looks up messages for specific locales by
// delegating to the appropriate library.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:implementation_imports, file_names, unnecessary_new
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
// ignore_for_file:argument_type_not_assignable, invalid_assignment
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
// ignore_for_file:comment_references
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
import 'package:intl/src/intl_helpers.dart';
import 'messages_en.dart' as messages_en;
import 'messages_zh.dart' as messages_zh;
typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
'en': () => new SynchronousFuture(null),
'zh': () => new SynchronousFuture(null),
};
MessageLookupByLibrary? _findExact(String localeName) {
switch (localeName) {
case 'en':
return messages_en.messages;
case 'zh':
return messages_zh.messages;
default:
return null;
}
}
/// User programs should call this before using [localeName] for messages.
Future<bool> initializeMessages(String localeName) {
var availableLocale = Intl.verifiedLocale(
localeName,
(locale) => _deferredLibraries[locale] != null,
onFailure: (_) => null,
);
if (availableLocale == null) {
return new SynchronousFuture(false);
}
var lib = _deferredLibraries[availableLocale];
lib == null ? new SynchronousFuture(false) : lib();
initializeInternalMessageLookup(() => new CompositeMessageLookup());
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
return new SynchronousFuture(true);
}
bool _messagesExistFor(String locale) {
try {
return _findExact(locale) != null;
} catch (e) {
return false;
}
}
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
var actualLocale = Intl.verifiedLocale(
locale,
_messagesExistFor,
onFailure: (_) => null,
);
if (actualLocale == null) return null;
return _findExact(actualLocale);
}

View file

@ -0,0 +1,150 @@
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
final messages = new MessageLookup();
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'en';
static String m0(item) => "Are you sure you want to delete ${item}?";
static String m1(item) => "Deleted ${item}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"appSettings": MessageLookupByLibrary.simpleMessage("App Settings"),
"appearance": MessageLookupByLibrary.simpleMessage("Appearance"),
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage(
"Auto Turn On Sleep Timer",
),
"automaticallyDescription": MessageLookupByLibrary.simpleMessage(
"Automatically turn on the sleep timer based on the time of day",
),
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage(
"Backup and Restore",
),
"bookAbout": MessageLookupByLibrary.simpleMessage("About the Book"),
"bookAboutDefault": MessageLookupByLibrary.simpleMessage(
"Sorry, no description found",
),
"bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("Abridged"),
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"),
"bookMetadataPublished": MessageLookupByLibrary.simpleMessage("Published"),
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage(
"Unabridged",
),
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
"copyToClipboard": MessageLookupByLibrary.simpleMessage(
"Copy to Clipboard",
),
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
"Copy the app settings to the clipboard",
),
"copyToClipboardToast": MessageLookupByLibrary.simpleMessage(
"Settings copied to clipboard",
),
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteDialog": m0,
"deleted": m1,
"explore": MessageLookupByLibrary.simpleMessage("explore"),
"exploreTooltip": MessageLookupByLibrary.simpleMessage(
"Search and Explore",
),
"general": MessageLookupByLibrary.simpleMessage("General"),
"home": MessageLookupByLibrary.simpleMessage("Home"),
"homeContinueListening": MessageLookupByLibrary.simpleMessage(
"Continue Listening",
),
"homeListenAgain": MessageLookupByLibrary.simpleMessage("Listen Again"),
"homePageSettings": MessageLookupByLibrary.simpleMessage(
"Home Page Settings",
),
"homePageSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Customize the home page",
),
"homeStartListening": MessageLookupByLibrary.simpleMessage(
"Start Listening",
),
"language": MessageLookupByLibrary.simpleMessage("Language"),
"languageDescription": MessageLookupByLibrary.simpleMessage(
"Language switch",
),
"library": MessageLookupByLibrary.simpleMessage("Library"),
"libraryTooltip": MessageLookupByLibrary.simpleMessage(
"Browse your library",
),
"no": MessageLookupByLibrary.simpleMessage("No"),
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage(
"Notification Media Player",
),
"notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage(
"Customize the media player in notifications",
),
"ok": MessageLookupByLibrary.simpleMessage("OK"),
"pause": MessageLookupByLibrary.simpleMessage("Pause"),
"play": MessageLookupByLibrary.simpleMessage("Play"),
"playerSettings": MessageLookupByLibrary.simpleMessage("Player Settings"),
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Customize the player settings",
),
"readLess": MessageLookupByLibrary.simpleMessage("Read Less"),
"readMore": MessageLookupByLibrary.simpleMessage("Read More"),
"reset": MessageLookupByLibrary.simpleMessage("Reset"),
"resetAppSettings": MessageLookupByLibrary.simpleMessage(
"Reset App Settings",
),
"resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Reset the app settings to the default values",
),
"resetAppSettingsDialog": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to reset the app settings?",
),
"restore": MessageLookupByLibrary.simpleMessage("Restore"),
"restoreBackup": MessageLookupByLibrary.simpleMessage("Restore Backup"),
"restoreBackupHint": MessageLookupByLibrary.simpleMessage(
"Paste the backup here",
),
"restoreBackupInvalid": MessageLookupByLibrary.simpleMessage(
"Invalid backup",
),
"restoreBackupSuccess": MessageLookupByLibrary.simpleMessage(
"Settings restored",
),
"restoreBackupValidator": MessageLookupByLibrary.simpleMessage(
"Please paste the backup here",
),
"restoreDescription": MessageLookupByLibrary.simpleMessage(
"Restore the app settings from the backup",
),
"resume": MessageLookupByLibrary.simpleMessage("Resume"),
"shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"),
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
"Customize the shake detector settings",
),
"themeSettings": MessageLookupByLibrary.simpleMessage("Theme Settings"),
"themeSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Customize the app theme",
),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"yes": MessageLookupByLibrary.simpleMessage("Yes"),
"you": MessageLookupByLibrary.simpleMessage("You"),
"youTooltip": MessageLookupByLibrary.simpleMessage(
"Your Profile and Settings",
),
};
}

View file

@ -0,0 +1,104 @@
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a zh locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
final messages = new MessageLookup();
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'zh';
static String m0(item) => "确定要删除 ${item} 吗?";
static String m1(item) => "已删除 ${item}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"appSettings": MessageLookupByLibrary.simpleMessage("应用设置"),
"appearance": MessageLookupByLibrary.simpleMessage("外观"),
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"),
"automaticallyDescription": MessageLookupByLibrary.simpleMessage(
"根据一天中的时间自动打开睡眠定时器",
),
"backup": MessageLookupByLibrary.simpleMessage("备份"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"),
"bookAbout": MessageLookupByLibrary.simpleMessage("关于本书"),
"bookAboutDefault": MessageLookupByLibrary.simpleMessage("抱歉,找不到描述"),
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
"copyToClipboard": MessageLookupByLibrary.simpleMessage("复制到剪贴板"),
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
"将应用程序设置复制到剪贴板",
),
"copyToClipboardToast": MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteDialog": m0,
"deleted": m1,
"explore": MessageLookupByLibrary.simpleMessage("探索"),
"exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"),
"general": MessageLookupByLibrary.simpleMessage("通用"),
"home": MessageLookupByLibrary.simpleMessage("首页"),
"homeContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"),
"homeListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"),
"homePageSettings": MessageLookupByLibrary.simpleMessage("主页设置"),
"homePageSettingsDescription": MessageLookupByLibrary.simpleMessage(
"自定义主页",
),
"homeStartListening": MessageLookupByLibrary.simpleMessage("开始收听"),
"language": MessageLookupByLibrary.simpleMessage("语言"),
"languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"),
"library": MessageLookupByLibrary.simpleMessage("媒体库"),
"libraryTooltip": MessageLookupByLibrary.simpleMessage("浏览您的媒体库"),
"no": MessageLookupByLibrary.simpleMessage(""),
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage("通知媒体播放器"),
"notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage(
"在通知中自定义媒体播放器",
),
"ok": MessageLookupByLibrary.simpleMessage("确定"),
"pause": MessageLookupByLibrary.simpleMessage("暂停"),
"play": MessageLookupByLibrary.simpleMessage("播放"),
"playerSettings": MessageLookupByLibrary.simpleMessage("播放器设置"),
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
"自定义播放器设置",
),
"readLess": MessageLookupByLibrary.simpleMessage("折叠"),
"readMore": MessageLookupByLibrary.simpleMessage("展开"),
"reset": MessageLookupByLibrary.simpleMessage("重置"),
"resetAppSettings": MessageLookupByLibrary.simpleMessage("重置应用程序设置"),
"resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage(
"将应用程序设置重置为默认值",
),
"resetAppSettingsDialog": MessageLookupByLibrary.simpleMessage(
"您确定要重置应用程序设置吗?",
),
"restore": MessageLookupByLibrary.simpleMessage("恢复"),
"restoreBackup": MessageLookupByLibrary.simpleMessage("恢复备份"),
"restoreBackupHint": MessageLookupByLibrary.simpleMessage("将备份粘贴到此处"),
"restoreBackupInvalid": MessageLookupByLibrary.simpleMessage("无效备份"),
"restoreBackupSuccess": MessageLookupByLibrary.simpleMessage("设置已恢复"),
"restoreBackupValidator": MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"),
"restoreDescription": MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"),
"resume": MessageLookupByLibrary.simpleMessage("继续"),
"shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"),
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
"自定义抖动检测器设置",
),
"themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"),
"themeSettingsDescription": MessageLookupByLibrary.simpleMessage("自定义应用主题"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
"yes": MessageLookupByLibrary.simpleMessage(""),
"you": MessageLookupByLibrary.simpleMessage("我的"),
"youTooltip": MessageLookupByLibrary.simpleMessage("您的个人资料和设置"),
};
}

598
lib/generated/l10n.dart Normal file
View file

@ -0,0 +1,598 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'intl/messages_all.dart';
// **************************************************************************
// Generator: Flutter Intl IDE plugin
// Made by Localizely
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes
class S {
S();
static S? _current;
static S get current {
assert(
_current != null,
'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.',
);
return _current!;
}
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> load(Locale locale) {
final name = (locale.countryCode?.isEmpty ?? false)
? locale.languageCode
: locale.toString();
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
final instance = S();
S._current = instance;
return instance;
});
}
static S of(BuildContext context) {
final instance = S.maybeOf(context);
assert(
instance != null,
'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?',
);
return instance!;
}
static S? maybeOf(BuildContext context) {
return Localizations.of<S>(context, S);
}
/// `Yes`
String get yes {
return Intl.message('Yes', name: 'yes', desc: '', args: []);
}
/// `No`
String get no {
return Intl.message('No', name: 'no', desc: '', args: []);
}
/// `OK`
String get ok {
return Intl.message('OK', name: 'ok', desc: '', args: []);
}
/// `Cancel`
String get cancel {
return Intl.message('Cancel', name: 'cancel', desc: '', args: []);
}
/// `Reset`
String get reset {
return Intl.message('Reset', name: 'reset', desc: '', args: []);
}
/// `Delete`
String get delete {
return Intl.message('Delete', name: 'delete', desc: '', args: []);
}
/// `Are you sure you want to delete {item}?`
String deleteDialog(String item) {
return Intl.message(
'Are you sure you want to delete $item?',
name: 'deleteDialog',
desc: '',
args: [item],
);
}
/// `Deleted {item}`
String deleted(String item) {
return Intl.message(
'Deleted $item',
name: 'deleted',
desc: '已删除 {}',
args: [item],
);
}
/// `Pause`
String get pause {
return Intl.message('Pause', name: 'pause', desc: '', args: []);
}
/// `Play`
String get play {
return Intl.message('Play', name: 'play', desc: '', args: []);
}
/// `Resume`
String get resume {
return Intl.message('Resume', name: 'resume', desc: '', args: []);
}
/// `Unknown`
String get unknown {
return Intl.message('Unknown', name: 'unknown', desc: '', args: []);
}
/// `Read More`
String get readMore {
return Intl.message('Read More', name: 'readMore', desc: '', args: []);
}
/// `Read Less`
String get readLess {
return Intl.message('Read Less', name: 'readLess', desc: '', args: []);
}
/// `Home`
String get home {
return Intl.message('Home', name: 'home', desc: '', args: []);
}
/// `Listen Again`
String get homeListenAgain {
return Intl.message(
'Listen Again',
name: 'homeListenAgain',
desc: '',
args: [],
);
}
/// `Continue Listening`
String get homeContinueListening {
return Intl.message(
'Continue Listening',
name: 'homeContinueListening',
desc: '',
args: [],
);
}
/// `Start Listening`
String get homeStartListening {
return Intl.message(
'Start Listening',
name: 'homeStartListening',
desc: '',
args: [],
);
}
/// `About the Book`
String get bookAbout {
return Intl.message(
'About the Book',
name: 'bookAbout',
desc: '',
args: [],
);
}
/// `Sorry, no description found`
String get bookAboutDefault {
return Intl.message(
'Sorry, no description found',
name: 'bookAboutDefault',
desc: '',
args: [],
);
}
/// `Abridged`
String get bookMetadataAbridged {
return Intl.message(
'Abridged',
name: 'bookMetadataAbridged',
desc: '',
args: [],
);
}
/// `Unabridged`
String get bookMetadataUnabridged {
return Intl.message(
'Unabridged',
name: 'bookMetadataUnabridged',
desc: '',
args: [],
);
}
/// `Length`
String get bookMetadataLength {
return Intl.message(
'Length',
name: 'bookMetadataLength',
desc: '',
args: [],
);
}
/// `Published`
String get bookMetadataPublished {
return Intl.message(
'Published',
name: 'bookMetadataPublished',
desc: '',
args: [],
);
}
/// `Library`
String get library {
return Intl.message('Library', name: 'library', desc: '', args: []);
}
/// `Browse your library`
String get libraryTooltip {
return Intl.message(
'Browse your library',
name: 'libraryTooltip',
desc: '',
args: [],
);
}
/// `explore`
String get explore {
return Intl.message('explore', name: 'explore', desc: '', args: []);
}
/// `Search and Explore`
String get exploreTooltip {
return Intl.message(
'Search and Explore',
name: 'exploreTooltip',
desc: '',
args: [],
);
}
/// `You`
String get you {
return Intl.message('You', name: 'you', desc: '', args: []);
}
/// `Your Profile and Settings`
String get youTooltip {
return Intl.message(
'Your Profile and Settings',
name: 'youTooltip',
desc: '',
args: [],
);
}
/// `App Settings`
String get appSettings {
return Intl.message(
'App Settings',
name: 'appSettings',
desc: '',
args: [],
);
}
/// `General`
String get general {
return Intl.message('General', name: 'general', desc: '', args: []);
}
/// `Language`
String get language {
return Intl.message('Language', name: 'language', desc: '', args: []);
}
/// `Language switch`
String get languageDescription {
return Intl.message(
'Language switch',
name: 'languageDescription',
desc: '',
args: [],
);
}
/// `Player Settings`
String get playerSettings {
return Intl.message(
'Player Settings',
name: 'playerSettings',
desc: '',
args: [],
);
}
/// `Customize the player settings`
String get playerSettingsDescription {
return Intl.message(
'Customize the player settings',
name: 'playerSettingsDescription',
desc: '',
args: [],
);
}
/// `Auto Turn On Sleep Timer`
String get autoTurnOnSleepTimer {
return Intl.message(
'Auto Turn On Sleep Timer',
name: 'autoTurnOnSleepTimer',
desc: '',
args: [],
);
}
/// `Automatically turn on the sleep timer based on the time of day`
String get automaticallyDescription {
return Intl.message(
'Automatically turn on the sleep timer based on the time of day',
name: 'automaticallyDescription',
desc: '',
args: [],
);
}
/// `Shake Detector`
String get shakeDetector {
return Intl.message(
'Shake Detector',
name: 'shakeDetector',
desc: '',
args: [],
);
}
/// `Customize the shake detector settings`
String get shakeDetectorDescription {
return Intl.message(
'Customize the shake detector settings',
name: 'shakeDetectorDescription',
desc: '',
args: [],
);
}
/// `Appearance`
String get appearance {
return Intl.message('Appearance', name: 'appearance', desc: '', args: []);
}
/// `Theme Settings`
String get themeSettings {
return Intl.message(
'Theme Settings',
name: 'themeSettings',
desc: '',
args: [],
);
}
/// `Customize the app theme`
String get themeSettingsDescription {
return Intl.message(
'Customize the app theme',
name: 'themeSettingsDescription',
desc: '',
args: [],
);
}
/// `Notification Media Player`
String get notificationMediaPlayer {
return Intl.message(
'Notification Media Player',
name: 'notificationMediaPlayer',
desc: '',
args: [],
);
}
/// `Customize the media player in notifications`
String get notificationMediaPlayerDescription {
return Intl.message(
'Customize the media player in notifications',
name: 'notificationMediaPlayerDescription',
desc: '',
args: [],
);
}
/// `Home Page Settings`
String get homePageSettings {
return Intl.message(
'Home Page Settings',
name: 'homePageSettings',
desc: '',
args: [],
);
}
/// `Customize the home page`
String get homePageSettingsDescription {
return Intl.message(
'Customize the home page',
name: 'homePageSettingsDescription',
desc: '',
args: [],
);
}
/// `Backup and Restore`
String get backupAndRestore {
return Intl.message(
'Backup and Restore',
name: 'backupAndRestore',
desc: '',
args: [],
);
}
/// `Copy to Clipboard`
String get copyToClipboard {
return Intl.message(
'Copy to Clipboard',
name: 'copyToClipboard',
desc: '',
args: [],
);
}
/// `Copy the app settings to the clipboard`
String get copyToClipboardDescription {
return Intl.message(
'Copy the app settings to the clipboard',
name: 'copyToClipboardDescription',
desc: '',
args: [],
);
}
/// `Settings copied to clipboard`
String get copyToClipboardToast {
return Intl.message(
'Settings copied to clipboard',
name: 'copyToClipboardToast',
desc: '',
args: [],
);
}
/// `Restore`
String get restore {
return Intl.message('Restore', name: 'restore', desc: '', args: []);
}
/// `Restore the app settings from the backup`
String get restoreDescription {
return Intl.message(
'Restore the app settings from the backup',
name: 'restoreDescription',
desc: '',
args: [],
);
}
/// `Restore Backup`
String get restoreBackup {
return Intl.message(
'Restore Backup',
name: 'restoreBackup',
desc: '',
args: [],
);
}
/// `Backup`
String get backup {
return Intl.message('Backup', name: 'backup', desc: '', args: []);
}
/// `Paste the backup here`
String get restoreBackupHint {
return Intl.message(
'Paste the backup here',
name: 'restoreBackupHint',
desc: '',
args: [],
);
}
/// `Please paste the backup here`
String get restoreBackupValidator {
return Intl.message(
'Please paste the backup here',
name: 'restoreBackupValidator',
desc: '',
args: [],
);
}
/// `Invalid backup`
String get restoreBackupInvalid {
return Intl.message(
'Invalid backup',
name: 'restoreBackupInvalid',
desc: '',
args: [],
);
}
/// `Settings restored`
String get restoreBackupSuccess {
return Intl.message(
'Settings restored',
name: 'restoreBackupSuccess',
desc: '',
args: [],
);
}
/// `Reset App Settings`
String get resetAppSettings {
return Intl.message(
'Reset App Settings',
name: 'resetAppSettings',
desc: '',
args: [],
);
}
/// `Reset the app settings to the default values`
String get resetAppSettingsDescription {
return Intl.message(
'Reset the app settings to the default values',
name: 'resetAppSettingsDescription',
desc: '',
args: [],
);
}
/// `Are you sure you want to reset the app settings?`
String get resetAppSettingsDialog {
return Intl.message(
'Are you sure you want to reset the app settings?',
name: 'resetAppSettingsDialog',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
const AppLocalizationDelegate();
List<Locale> get supportedLocales {
return const <Locale>[
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'zh'),
];
}
@override
bool isSupported(Locale locale) => _isSupported(locale);
@override
Future<S> load(Locale locale) => S.load(locale);
@override
bool shouldReload(AppLocalizationDelegate old) => false;
bool _isSupported(Locale locale) {
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
return true;
}
}
return false;
}
}

87
lib/l10n/intl_en.arb Normal file
View file

@ -0,0 +1,87 @@
{
"@@locale": "en",
"yes": "Yes",
"no": "No",
"ok": "OK",
"cancel": "Cancel",
"reset": "Reset",
"delete": "Delete",
"deleteDialog": "Are you sure you want to delete {item}?",
"@deleteDialog": {
"placeholders": {
"item": {
"type": "String"
}
}
},
"deleted": "Deleted {item}",
"@deleted": {
"description": "已删除 {}",
"placeholders": {
"item": {
"type": "String"
}
}
},
"pause": "Pause",
"play": "Play",
"resume": "Resume",
"unknown": "Unknown",
"readMore": "Read More",
"readLess": "Read Less",
"home": "Home",
"homeListenAgain": "Listen Again",
"homeContinueListening": "Continue Listening",
"homeStartListening": "Start Listening",
"bookAbout": "About the Book",
"bookAboutDefault": "Sorry, no description found",
"bookMetadataAbridged": "Abridged",
"bookMetadataUnabridged": "Unabridged",
"bookMetadataLength": "Length",
"bookMetadataPublished": "Published",
"library": "Library",
"libraryTooltip": "Browse your library",
"explore": "explore",
"exploreTooltip": "Search and Explore",
"you": "You",
"youTooltip": "Your Profile and Settings",
"appSettings": "App Settings",
"general": "General",
"language": "Language",
"languageDescription": "Language switch",
"playerSettings": "Player Settings",
"playerSettingsDescription": "Customize the player settings",
"autoTurnOnSleepTimer": "Auto Turn On Sleep Timer",
"automaticallyDescription": "Automatically turn on the sleep timer based on the time of day",
"shakeDetector": "Shake Detector",
"shakeDetectorDescription": "Customize the shake detector settings",
"appearance": "Appearance",
"themeSettings": "Theme Settings",
"themeSettingsDescription": "Customize the app theme",
"notificationMediaPlayer": "Notification Media Player",
"notificationMediaPlayerDescription": "Customize the media player in notifications",
"homePageSettings": "Home Page Settings",
"homePageSettingsDescription": "Customize the home page",
"backupAndRestore": "Backup and Restore",
"copyToClipboard": "Copy to Clipboard",
"copyToClipboardDescription": "Copy the app settings to the clipboard",
"copyToClipboardToast": "Settings copied to clipboard",
"restore": "Restore",
"restoreDescription": "Restore the app settings from the backup",
"restoreBackup": "Restore Backup",
"backup": "Backup",
"restoreBackupHint": "Paste the backup here",
"restoreBackupValidator": "Please paste the backup here",
"restoreBackupInvalid": "Invalid backup",
"restoreBackupSuccess": "Settings restored",
"resetAppSettings": "Reset App Settings",
"resetAppSettingsDescription": "Reset the app settings to the default values",
"resetAppSettingsDialog": "Are you sure you want to reset the app settings?"
}

84
lib/l10n/intl_zh.arb Normal file
View file

@ -0,0 +1,84 @@
{
"@@locale": "zh",
"yes": "是",
"no": "否",
"ok": "确定",
"cancel": "取消",
"reset": "重置",
"delete": "Delete",
"deleteDialog": "确定要删除 {item} 吗?",
"@deleteDialog": {
"placeholders": {
"item": {
"type": "String"
}
}
},
"deleted": "已删除 {item}",
"@deleted": {
"description": "已删除 {}",
"placeholders": {
"item": {
"type": "String"
}
}
},
"pause": "暂停",
"play": "播放",
"resume": "继续",
"unknown": "未知",
"readMore": "展开",
"readLess": "折叠",
"home": "首页",
"homeListenAgain": "再听一遍",
"homeContinueListening": "继续收听",
"homeStartListening": "开始收听",
"bookAbout": "关于本书",
"bookAboutDefault": "抱歉,找不到描述",
"library": "媒体库",
"libraryTooltip": "浏览您的媒体库",
"explore": "探索",
"exploreTooltip": "搜索和探索",
"you": "我的",
"youTooltip": "您的个人资料和设置",
"appSettings": "应用设置",
"general": "通用",
"language": "语言",
"languageDescription": "语言切换",
"playerSettings": "播放器设置",
"playerSettingsDescription": "自定义播放器设置",
"autoTurnOnSleepTimer": "自动开启睡眠定时器",
"automaticallyDescription": "根据一天中的时间自动打开睡眠定时器",
"shakeDetector": "抖动检测器",
"shakeDetectorDescription": "自定义抖动检测器设置",
"appearance": "外观",
"themeSettings": "主题设置",
"themeSettingsDescription": "自定义应用主题",
"notificationMediaPlayer": "通知媒体播放器",
"notificationMediaPlayerDescription": "在通知中自定义媒体播放器",
"homePageSettings": "主页设置",
"homePageSettingsDescription": "自定义主页",
"backupAndRestore": "备份与恢复",
"copyToClipboard": "复制到剪贴板",
"copyToClipboardDescription": "将应用程序设置复制到剪贴板",
"copyToClipboardToast": "设置已复制到剪贴板",
"restore": "恢复",
"restoreDescription": "从备份中还原应用程序设置",
"restoreBackup": "恢复备份",
"backup": "备份",
"restoreBackupHint": "将备份粘贴到此处",
"restoreBackupValidator": "请将备份粘贴到此处",
"restoreBackupInvalid": "无效备份",
"restoreBackupSuccess": "设置已恢复",
"resetAppSettings": "重置应用程序设置",
"resetAppSettingsDescription": "将应用程序设置重置为默认值",
"resetAppSettingsDialog": "您确定要重置应用程序设置吗?"
}

View file

@ -1,5 +1,6 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:vaani/api/server_provider.dart';
@ -12,6 +13,7 @@ import 'package:vaani/features/player/providers/audiobook_player.dart'
show audiobookPlayerProvider, simpleAudiobookPlayerProvider;
import 'package:vaani/features/shake_detection/providers/shake_detector.dart';
import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart';
import 'package:vaani/generated/l10n.dart';
import 'package:vaani/router/router.dart';
import 'package:vaani/settings/api_settings_provider.dart';
import 'package:vaani/settings/app_settings_provider.dart';
@ -126,6 +128,15 @@ class MyApp extends ConsumerWidget {
try {
return MaterialApp.router(
// debugShowCheckedModeBanner: false,
locale: Locale(appSettings.language),
localizationsDelegates: [
//
S.delegate, //String
GlobalMaterialLocalizations.delegate, //Material Widgets
GlobalWidgetsLocalizations.delegate, // Widgets
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
theme: appThemeLight,
darkTheme: appThemeDark,
themeMode: themeSettings.themeMode,

View file

@ -25,8 +25,7 @@ import 'transitions/slide.dart';
part 'constants.dart';
final GlobalKey<NavigatorState> rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> sectionHomeNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'HomeNavigator');
@ -94,10 +93,8 @@ class MyAppRouter {
// itemId: itemId, extra: state.extra);
// },
pageBuilder: (context, state) {
final itemId = state
.pathParameters[Routes.libraryItem.pathParamName]!;
final child =
LibraryItemPage(itemId: itemId, extra: state.extra);
final itemId = state.pathParameters[Routes.libraryItem.pathParamName]!;
final child = LibraryItemPage(itemId: itemId, extra: state.extra);
return buildPageWithDefaultTransition(
context: context,
state: state,
@ -204,8 +201,7 @@ class MyAppRouter {
GoRoute(
path: Routes.playerSettings.pathName,
name: Routes.playerSettings.name,
pageBuilder:
defaultPageBuilder(const PlayerSettingsPage()),
pageBuilder: defaultPageBuilder(const PlayerSettingsPage()),
),
GoRoute(
path: Routes.shakeDetectorSettings.pathName,
@ -253,8 +249,7 @@ class MyAppRouter {
final stateParam = state.uri.queryParameters['state'];
appLogger.fine('deep linking callback: code: $code, state: $stateParam');
var callbackPage =
CallbackPage(code: code, state: stateParam, key: ValueKey(stateParam));
var callbackPage = CallbackPage(code: code, state: stateParam, key: ValueKey(stateParam));
return buildPageWithDefaultTransition(
context: context,
state: state,

View file

@ -10,6 +10,7 @@ import 'package:vaani/features/player/providers/player_form.dart';
import 'package:vaani/features/player/view/audiobook_player.dart';
import 'package:vaani/features/player/view/player_when_expanded.dart';
import 'package:vaani/features/you/view/widgets/library_switch_chip.dart';
import 'package:vaani/generated/l10n.dart';
import 'package:vaani/main.dart';
import 'package:vaani/router/router.dart';
import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons;
@ -36,7 +37,7 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final playerProgress = ref.watch(playerHeightProvider);
final isMobile = Platform.isAndroid || Platform.isIOS;
final isMobile = Platform.isFuchsia || Platform.isAndroid || Platform.isIOS;
onBackButtonPressed() async {
final isPlayerExpanded = playerProgress != playerMinHeight;
@ -90,75 +91,72 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
return BackButtonListener(
onBackButtonPressed: onBackButtonPressed,
child: Scaffold(
body: isMobile
? Stack(
children: [
navigationShell,
const AudiobookPlayer(),
],
)
: buildNavLeft(context, ref),
body: Stack(
children: [
isMobile ? navigationShell : buildNavLeft(context, ref),
const AudiobookPlayer(),
],
),
bottomNavigationBar: isMobile ? buildNavBottom(context, ref) : null,
),
);
}
Widget buildNavLeft(BuildContext context, WidgetRef ref) {
return Row(
children: [
SafeArea(
child: NavigationRail(
minWidth: 60,
minExtendedWidth: 120,
extended: MediaQuery.of(context).size.width > 640,
// extended: false,
destinations: _navigationItems.map((item) {
final isDestinationLibrary = item.name == 'Library';
var currentLibrary = ref.watch(currentLibraryProvider).valueOrNull;
final libraryIcon = AbsIcons.getIconByName(
currentLibrary?.icon,
);
final destinationWidget = NavigationRailDestination(
icon: Icon(
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
),
selectedIcon: Icon(
isDestinationLibrary ? libraryIcon ?? item.activeIcon : item.activeIcon,
),
label: Text(isDestinationLibrary ? currentLibrary?.name ?? item.name : item.name),
// tooltip: item.tooltip,
);
// if (isDestinationLibrary) {
// return GestureDetector(
// onSecondaryTap: () => showLibrarySwitcher(context, ref),
// onDoubleTap: () => showLibrarySwitcher(context, ref),
// child:
// destinationWidget, // Wrap the actual NavigationDestination
// );
// } else {
// // Return the unwrapped destination for other items
// return destinationWidget;
// }
return destinationWidget;
// return NavigationRailDestination(icon: Icon(nav.icon), label: Text(nav.name));
}).toList(),
selectedIndex: navigationShell.currentIndex,
onDestinationSelected: (int index) {
print(index);
_onTap(context, index, ref);
},
final isPlayerActive = ref.watch(isPlayerActiveProvider);
return Padding(
padding: EdgeInsets.only(bottom: isPlayerActive ? playerMinHeight : 0),
child: Row(
children: [
SafeArea(
child: NavigationRail(
minWidth: 60,
minExtendedWidth: 120,
extended: MediaQuery.of(context).size.width > 640,
// extended: false,
destinations: _navigationItems(context).map((item) {
final isDestinationLibrary = item.name == S.of(context).library;
var currentLibrary = ref.watch(currentLibraryProvider).valueOrNull;
final libraryIcon = AbsIcons.getIconByName(
currentLibrary?.icon,
);
final destinationWidget = NavigationRailDestination(
icon: Icon(
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
),
selectedIcon: Icon(
isDestinationLibrary ? libraryIcon ?? item.activeIcon : item.activeIcon,
),
label: Text(isDestinationLibrary ? currentLibrary?.name ?? item.name : item.name),
// tooltip: item.tooltip,
);
// if (isDestinationLibrary) {
// return GestureDetector(
// onSecondaryTap: () => showLibrarySwitcher(context, ref),
// onDoubleTap: () => showLibrarySwitcher(context, ref),
// child:
// destinationWidget, // Wrap the actual NavigationDestination
// );
// } else {
// // Return the unwrapped destination for other items
// return destinationWidget;
// }
return destinationWidget;
// return NavigationRailDestination(icon: Icon(nav.icon), label: Text(nav.name));
}).toList(),
selectedIndex: navigationShell.currentIndex,
onDestinationSelected: (int index) {
print(index);
_onTap(context, index, ref);
},
),
),
),
VerticalDivider(width: 0.5, thickness: 0.5),
Expanded(
child: Stack(
children: [
navigationShell,
const AudiobookPlayer(),
],
VerticalDivider(width: 0.5, thickness: 0.5),
Expanded(
child: navigationShell,
),
),
],
],
),
);
}
@ -184,8 +182,8 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
// world scenario, the items would most likely be generated from the
// branches of the shell route, which can be fetched using
// `navigationShell.route.branches`.
destinations: _navigationItems.map((item) {
final isDestinationLibrary = item.name == 'Library';
destinations: _navigationItems(context).map((item) {
final isDestinationLibrary = item.name == S.of(context).library;
var currentLibrary = ref.watch(currentLibraryProvider).valueOrNull;
final libraryIcon = AbsIcons.getIconByName(
currentLibrary?.icon,
@ -217,6 +215,42 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
);
}
List<_NavigationItem> _navigationItems(BuildContext context) {
return [
_NavigationItem(
// name: 'Home',
name: S.of(context).home,
icon: Icons.home_outlined,
activeIcon: Icons.home,
),
// Library
_NavigationItem(
// name: 'Library',
name: S.of(context).library,
icon: Icons.book_outlined,
activeIcon: Icons.book,
// tooltip: 'Browse your library',
tooltip: S.of(context).exploreTooltip,
),
_NavigationItem(
// name: 'Explore',
name: S.of(context).explore,
icon: Icons.search_outlined,
activeIcon: Icons.search,
// tooltip: 'Search and Explore',
tooltip: S.of(context).exploreTooltip,
),
_NavigationItem(
// name: 'You',
name: S.of(context).you,
icon: Icons.account_circle_outlined,
activeIcon: Icons.account_circle,
// tooltip: 'Your Profile and Settings',
tooltip: S.of(context).youTooltip,
),
];
}
/// Navigate to the current location of the branch at the provided index when
/// tapping an item in the BottomNavigationBar.
void _onTap(BuildContext context, int index, WidgetRef ref) {
@ -267,32 +301,32 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
// list of constants with names and icons so that they can be used in the bottom navigation bar
// and reused for nav rail and other places
const _navigationItems = [
_NavigationItem(
name: 'Home',
icon: Icons.home_outlined,
activeIcon: Icons.home,
),
// Library
_NavigationItem(
name: 'Library',
icon: Icons.book_outlined,
activeIcon: Icons.book,
tooltip: 'Browse your library',
),
_NavigationItem(
name: 'Explore',
icon: Icons.search_outlined,
activeIcon: Icons.search,
tooltip: 'Search and Explore',
),
_NavigationItem(
name: 'You',
icon: Icons.account_circle_outlined,
activeIcon: Icons.account_circle,
tooltip: 'Your Profile and Settings',
),
];
// const _navigationItems = [
// _NavigationItem(
// name: 'Home',
// icon: Icons.home_outlined,
// activeIcon: Icons.home,
// ),
// // Library
// _NavigationItem(
// name: 'Library',
// icon: Icons.book_outlined,
// activeIcon: Icons.book,
// tooltip: 'Browse your library',
// ),
// _NavigationItem(
// name: 'Explore',
// icon: Icons.search_outlined,
// activeIcon: Icons.search,
// tooltip: 'Search and Explore',
// ),
// _NavigationItem(
// name: 'You',
// icon: Icons.account_circle_outlined,
// activeIcon: Icons.account_circle,
// tooltip: 'Your Profile and Settings',
// ),
// ];
class _NavigationItem {
const _NavigationItem({

View file

@ -12,18 +12,17 @@ part 'app_settings.g.dart';
@freezed
class AppSettings with _$AppSettings {
const factory AppSettings({
@Default('zh') String language,
@Default(ThemeSettings()) ThemeSettings themeSettings,
@Default(PlayerSettings()) PlayerSettings playerSettings,
@Default(SleepTimerSettings()) SleepTimerSettings sleepTimerSettings,
@Default(DownloadSettings()) DownloadSettings downloadSettings,
@Default(NotificationSettings()) NotificationSettings notificationSettings,
@Default(ShakeDetectionSettings())
ShakeDetectionSettings shakeDetectionSettings,
@Default(ShakeDetectionSettings()) ShakeDetectionSettings shakeDetectionSettings,
@Default(HomePageSettings()) HomePageSettings homePageSettings,
}) = _AppSettings;
factory AppSettings.fromJson(Map<String, dynamic> json) =>
_$AppSettingsFromJson(json);
factory AppSettings.fromJson(Map<String, dynamic> json) => _$AppSettingsFromJson(json);
}
@freezed
@ -37,17 +36,14 @@ class ThemeSettings with _$ThemeSettings {
@Default(true) bool useCurrentPlayerThemeThroughoutApp,
}) = _ThemeSettings;
factory ThemeSettings.fromJson(Map<String, dynamic> json) =>
_$ThemeSettingsFromJson(json);
factory ThemeSettings.fromJson(Map<String, dynamic> json) => _$ThemeSettingsFromJson(json);
}
@freezed
class PlayerSettings with _$PlayerSettings {
const factory PlayerSettings({
@Default(MinimizedPlayerSettings())
MinimizedPlayerSettings miniPlayerSettings,
@Default(ExpandedPlayerSettings())
ExpandedPlayerSettings expandedPlayerSettings,
@Default(MinimizedPlayerSettings()) MinimizedPlayerSettings miniPlayerSettings,
@Default(ExpandedPlayerSettings()) ExpandedPlayerSettings expandedPlayerSettings,
@Default(1) double preferredDefaultVolume,
@Default(1) double preferredDefaultSpeed,
@Default([1, 1.25, 1.5, 1.75, 2]) List<double> speedOptions,
@ -60,8 +56,7 @@ class PlayerSettings with _$PlayerSettings {
@Default(true) bool configurePlayerForEveryBook,
}) = _PlayerSettings;
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
_$PlayerSettingsFromJson(json);
factory PlayerSettings.fromJson(Map<String, dynamic> json) => _$PlayerSettingsFromJson(json);
}
@freezed
@ -144,8 +139,7 @@ class DownloadSettings with _$DownloadSettings {
@Default(3) int maxConcurrentByGroup,
}) = _DownloadSettings;
factory DownloadSettings.fromJson(Map<String, dynamic> json) =>
_$DownloadSettingsFromJson(json);
factory DownloadSettings.fromJson(Map<String, dynamic> json) => _$DownloadSettingsFromJson(json);
}
@freezed
@ -202,8 +196,7 @@ class ShakeDetectionSettings with _$ShakeDetectionSettings {
@Default(ShakeDirection.horizontal) ShakeDirection direction,
@Default(5) double threshold,
@Default(ShakeAction.resetSleepTimer) ShakeAction shakeAction,
@Default({ShakeDetectedFeedback.vibrate})
Set<ShakeDetectedFeedback> feedback,
@Default({ShakeDetectedFeedback.vibrate}) Set<ShakeDetectedFeedback> feedback,
@Default(0.5) double beepVolume,
/// the duration to wait before the shake detection is enabled again
@ -241,6 +234,5 @@ class HomePageSettings with _$HomePageSettings {
@Default(false) bool showPlayButtonOnListenAgainShelf,
}) = _HomePageSettings;
factory HomePageSettings.fromJson(Map<String, dynamic> json) =>
_$HomePageSettingsFromJson(json);
factory HomePageSettings.fromJson(Map<String, dynamic> json) => _$HomePageSettingsFromJson(json);
}

View file

@ -20,6 +20,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$AppSettings {
String get language => throw _privateConstructorUsedError;
ThemeSettings get themeSettings => throw _privateConstructorUsedError;
PlayerSettings get playerSettings => throw _privateConstructorUsedError;
SleepTimerSettings get sleepTimerSettings =>
@ -48,7 +49,8 @@ abstract class $AppSettingsCopyWith<$Res> {
_$AppSettingsCopyWithImpl<$Res, AppSettings>;
@useResult
$Res call(
{ThemeSettings themeSettings,
{String language,
ThemeSettings themeSettings,
PlayerSettings playerSettings,
SleepTimerSettings sleepTimerSettings,
DownloadSettings downloadSettings,
@ -80,6 +82,7 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? language = null,
Object? themeSettings = null,
Object? playerSettings = null,
Object? sleepTimerSettings = null,
@ -89,6 +92,10 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
Object? homePageSettings = null,
}) {
return _then(_value.copyWith(
language: null == language
? _value.language
: language // ignore: cast_nullable_to_non_nullable
as String,
themeSettings: null == themeSettings
? _value.themeSettings
: themeSettings // ignore: cast_nullable_to_non_nullable
@ -203,7 +210,8 @@ abstract class _$$AppSettingsImplCopyWith<$Res>
@override
@useResult
$Res call(
{ThemeSettings themeSettings,
{String language,
ThemeSettings themeSettings,
PlayerSettings playerSettings,
SleepTimerSettings sleepTimerSettings,
DownloadSettings downloadSettings,
@ -240,6 +248,7 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? language = null,
Object? themeSettings = null,
Object? playerSettings = null,
Object? sleepTimerSettings = null,
@ -249,6 +258,10 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
Object? homePageSettings = null,
}) {
return _then(_$AppSettingsImpl(
language: null == language
? _value.language
: language // ignore: cast_nullable_to_non_nullable
as String,
themeSettings: null == themeSettings
? _value.themeSettings
: themeSettings // ignore: cast_nullable_to_non_nullable
@ -285,7 +298,8 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
@JsonSerializable()
class _$AppSettingsImpl implements _AppSettings {
const _$AppSettingsImpl(
{this.themeSettings = const ThemeSettings(),
{this.language = 'zh',
this.themeSettings = const ThemeSettings(),
this.playerSettings = const PlayerSettings(),
this.sleepTimerSettings = const SleepTimerSettings(),
this.downloadSettings = const DownloadSettings(),
@ -296,6 +310,9 @@ class _$AppSettingsImpl implements _AppSettings {
factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) =>
_$$AppSettingsImplFromJson(json);
@override
@JsonKey()
final String language;
@override
@JsonKey()
final ThemeSettings themeSettings;
@ -320,7 +337,7 @@ class _$AppSettingsImpl implements _AppSettings {
@override
String toString() {
return 'AppSettings(themeSettings: $themeSettings, playerSettings: $playerSettings, sleepTimerSettings: $sleepTimerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings, shakeDetectionSettings: $shakeDetectionSettings, homePageSettings: $homePageSettings)';
return 'AppSettings(language: $language, themeSettings: $themeSettings, playerSettings: $playerSettings, sleepTimerSettings: $sleepTimerSettings, downloadSettings: $downloadSettings, notificationSettings: $notificationSettings, shakeDetectionSettings: $shakeDetectionSettings, homePageSettings: $homePageSettings)';
}
@override
@ -328,6 +345,8 @@ class _$AppSettingsImpl implements _AppSettings {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AppSettingsImpl &&
(identical(other.language, language) ||
other.language == language) &&
(identical(other.themeSettings, themeSettings) ||
other.themeSettings == themeSettings) &&
(identical(other.playerSettings, playerSettings) ||
@ -348,6 +367,7 @@ class _$AppSettingsImpl implements _AppSettings {
@override
int get hashCode => Object.hash(
runtimeType,
language,
themeSettings,
playerSettings,
sleepTimerSettings,
@ -374,7 +394,8 @@ class _$AppSettingsImpl implements _AppSettings {
abstract class _AppSettings implements AppSettings {
const factory _AppSettings(
{final ThemeSettings themeSettings,
{final String language,
final ThemeSettings themeSettings,
final PlayerSettings playerSettings,
final SleepTimerSettings sleepTimerSettings,
final DownloadSettings downloadSettings,
@ -385,6 +406,8 @@ abstract class _AppSettings implements AppSettings {
factory _AppSettings.fromJson(Map<String, dynamic> json) =
_$AppSettingsImpl.fromJson;
@override
String get language;
@override
ThemeSettings get themeSettings;
@override

View file

@ -8,6 +8,7 @@ part of 'app_settings.dart';
_$AppSettingsImpl _$$AppSettingsImplFromJson(Map<String, dynamic> json) =>
_$AppSettingsImpl(
language: json['language'] as String? ?? 'zh',
themeSettings: json['themeSettings'] == null
? const ThemeSettings()
: ThemeSettings.fromJson(
@ -40,6 +41,7 @@ _$AppSettingsImpl _$$AppSettingsImplFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
<String, dynamic>{
'language': instance.language,
'themeSettings': instance.themeSettings,
'playerSettings': instance.playerSettings,
'sleepTimerSettings': instance.sleepTimerSettings,

View file

@ -7,6 +7,7 @@ import 'package:flutter_settings_ui/flutter_settings_ui.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:vaani/generated/l10n.dart';
import 'package:vaani/router/router.dart';
import 'package:vaani/settings/app_settings_provider.dart';
import 'package:vaani/settings/models/app_settings.dart' as model;
@ -18,14 +19,13 @@ class AppSettingsPage extends HookConsumerWidget {
const AppSettingsPage({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appSettings = ref.watch(appSettingsProvider);
final sleepTimerSettings = appSettings.sleepTimerSettings;
final locales = {'en': 'English', 'zh': '中文'};
return SimpleSettingsPage(
title: const Text('App Settings'),
title: Text(S.of(context).appSettings),
sections: [
// General section
SettingsSection(
@ -34,25 +34,42 @@ class AppSettingsPage extends HookConsumerWidget {
vertical: 8.0,
),
title: Text(
'General',
S.of(context).general,
style: Theme.of(context).textTheme.titleLarge,
),
tiles: [
SettingsTile(
title: const Text('Player Settings'),
title: Text(S.of(context).language),
leading: const Icon(Icons.play_arrow),
description: const Text(
'Customize the player settings',
trailing: DropdownButton(
value: appSettings.language,
items: S.delegate.supportedLocales.map((locale) {
return DropdownMenuItem(
value: locale.languageCode,
child: Text(locales[locale.languageCode] ?? 'unknown'),
);
}).toList(),
onChanged: (value) {
ref.read(appSettingsProvider.notifier).update(
appSettings.copyWith(
language: value!,
),
);
},
),
description: Text(S.of(context).languageDescription),
),
SettingsTile(
title: Text(S.of(context).playerSettings),
leading: const Icon(Icons.play_arrow),
description: Text(S.of(context).playerSettingsDescription),
onPressed: (context) {
context.pushNamed(Routes.playerSettings.name);
},
),
NavigationWithSwitchTile(
title: const Text('Auto Turn On Sleep Timer'),
description: const Text(
'Automatically turn on the sleep timer based on the time of day',
),
title: Text(S.of(context).autoTurnOnSleepTimer),
description: Text(S.of(context).automaticallyDescription),
leading: sleepTimerSettings.autoTurnOnTimer
? const Icon(Symbols.time_auto, fill: 1)
: const Icon(Symbols.timer_off, fill: 1),
@ -69,10 +86,10 @@ class AppSettingsPage extends HookConsumerWidget {
},
),
NavigationWithSwitchTile(
title: const Text('Shake Detector'),
title: Text(S.of(context).shakeDetector),
leading: const Icon(Icons.vibration),
description: const Text(
'Customize the shake detector settings',
description: Text(
S.of(context).shakeDetectorDescription,
),
value: appSettings.shakeDetectionSettings.isEnabled,
onPressed: (context) {
@ -96,36 +113,30 @@ class AppSettingsPage extends HookConsumerWidget {
vertical: 8.0,
),
title: Text(
'Appearance',
S.of(context).appearance,
style: Theme.of(context).textTheme.titleLarge,
),
tiles: [
SettingsTile.navigation(
leading: const Icon(Icons.color_lens),
title: const Text('Theme Settings'),
description: const Text(
'Customize the app theme',
),
title: Text(S.of(context).themeSettings),
description: Text(S.of(context).themeSettingsDescription),
onPressed: (context) {
context.pushNamed(Routes.themeSettings.name);
},
),
SettingsTile(
title: const Text('Notification Media Player'),
title: Text(S.of(context).notificationMediaPlayer),
leading: const Icon(Icons.play_lesson),
description: const Text(
'Customize the media player in notifications',
),
description: Text(S.of(context).notificationMediaPlayerDescription),
onPressed: (context) {
context.pushNamed(Routes.notificationSettings.name);
},
),
SettingsTile.navigation(
leading: const Icon(Icons.home_filled),
title: const Text('Home Page Settings'),
description: const Text(
'Customize the home page',
),
title: Text(S.of(context).homePageSettings),
description: Text(S.of(context).homePageSettingsDescription),
onPressed: (context) {
context.pushNamed(Routes.homePageSettings.name);
},
@ -140,15 +151,15 @@ class AppSettingsPage extends HookConsumerWidget {
vertical: 8.0,
),
title: Text(
'Backup and Restore',
S.of(context).backupAndRestore,
style: Theme.of(context).textTheme.titleLarge,
),
tiles: [
SettingsTile(
title: const Text('Copy to Clipboard'),
title: Text(S.of(context).copyToClipboard),
leading: const Icon(Icons.copy),
description: const Text(
'Copy the app settings to the clipboard',
description: Text(
S.of(context).copyToClipboardDescription,
),
onPressed: (context) async {
// copy to clipboard
@ -159,18 +170,16 @@ class AppSettingsPage extends HookConsumerWidget {
);
// show toast
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Settings copied to clipboard'),
SnackBar(
content: Text(S.of(context).copyToClipboardToast),
),
);
},
),
SettingsTile(
title: const Text('Restore'),
title: Text(S.of(context).restore),
leading: const Icon(Icons.restore),
description: const Text(
'Restore the app settings from the backup',
),
description: Text(S.of(context).restoreDescription),
onPressed: (context) {
// show a dialog to get the backup
showDialog(
@ -184,33 +193,29 @@ class AppSettingsPage extends HookConsumerWidget {
// a button to reset the app settings
SettingsTile(
title: const Text('Reset App Settings'),
title: Text(S.of(context).resetAppSettings),
leading: const Icon(Icons.settings_backup_restore),
description: const Text(
'Reset the app settings to the default values',
),
description: Text(S.of(context).resetAppSettingsDescription),
onPressed: (context) async {
// confirm the reset
final res = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Reset App Settings'),
content: const Text(
'Are you sure you want to reset the app settings?',
),
title: Text(S.of(context).resetAppSettings),
content: Text(S.of(context).resetAppSettingsDialog),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel'),
child: Text(S.of(context).cancel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('Reset'),
child: Text(S.of(context).reset),
),
],
);
@ -242,14 +247,14 @@ class RestoreDialogue extends HookConsumerWidget {
final settingsInputController = useTextEditingController();
return AlertDialog(
title: const Text('Restore Backup'),
title: Text(S.of(context).restoreBackup),
content: Form(
key: formKey,
child: TextFormField(
autofocus: true,
decoration: InputDecoration(
labelText: 'Backup',
hintText: 'Paste the backup here',
labelText: S.of(context).backup,
hintText: S.of(context).restoreBackupHint,
// clear button
suffixIcon: IconButton(
icon: Icon(Icons.clear),
@ -260,7 +265,7 @@ class RestoreDialogue extends HookConsumerWidget {
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please paste the backup here';
return S.of(context).restoreBackupValidator;
}
try {
// try to decode the backup
@ -268,7 +273,7 @@ class RestoreDialogue extends HookConsumerWidget {
jsonDecode(value),
);
} catch (e) {
return 'Invalid backup';
return S.of(context).restoreBackupInvalid;
}
return null;
},
@ -281,8 +286,8 @@ class RestoreDialogue extends HookConsumerWidget {
if (formKey.currentState!.validate()) {
if (settings.value == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid backup'),
SnackBar(
content: Text(S.of(context).restoreBackupInvalid),
),
);
return;
@ -291,19 +296,19 @@ class RestoreDialogue extends HookConsumerWidget {
settingsInputController.clear();
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Settings restored'),
SnackBar(
content: Text(S.of(context).restoreBackupSuccess),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid backup'),
SnackBar(
content: Text(S.of(context).restoreBackupInvalid),
),
);
}
},
child: const Text('Restore'),
child: Text(S.of(context).restore),
),
],
);

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:vaani/generated/l10n.dart';
class OkButton<T> extends StatelessWidget {
const OkButton({
@ -12,7 +13,7 @@ class OkButton<T> extends StatelessWidget {
Widget build(BuildContext context) {
return TextButton(
onPressed: onPressed,
child: const Text('OK'),
child: Text(S.of(context).ok),
);
}
}
@ -32,7 +33,7 @@ class CancelButton extends StatelessWidget {
onPressed?.call();
Navigator.of(context).pop();
},
child: const Text('Cancel'),
child: Text(S.of(context).cancel),
);
}
}

View file

@ -99,8 +99,7 @@ class BookOnShelf extends HookConsumerWidget {
onTap: handleTapOnBook,
borderRadius: BorderRadius.circular(10),
child: Padding(
padding:
const EdgeInsets.only(bottom: 8.0, right: 4.0, left: 4.0),
padding: const EdgeInsets.only(bottom: 8.0, right: 4.0, left: 4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -112,9 +111,7 @@ class BookOnShelf extends HookConsumerWidget {
alignment: Alignment.bottomRight,
children: [
Hero(
tag: HeroTagPrefixes.bookCover +
item.id +
heroTagSuffix,
tag: HeroTagPrefixes.bookCover + item.id + heroTagSuffix,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: AnimatedSwitcher(
@ -128,17 +125,13 @@ class BookOnShelf extends HookConsumerWidget {
var imageWidget = Image.memory(
image,
fit: BoxFit.fill,
cacheWidth: (height *
1.2 *
MediaQuery.of(context)
.devicePixelRatio)
.round(),
cacheWidth:
(height * 1.2 * MediaQuery.of(context).devicePixelRatio)
.round(),
);
return Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
child: imageWidget,
);
@ -213,8 +206,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final me = ref.watch(meProvider);
final player = ref.watch(audiobookPlayerProvider);
final isCurrentBookSetInPlayer =
player.book?.libraryItemId == libraryItemId;
final isCurrentBookSetInPlayer = player.book?.libraryItemId == libraryItemId;
final isPlayingThisBook = player.playing && isCurrentBookSetInPlayer;
final userProgress = me.valueOrNull?.mediaProgress
@ -242,8 +234,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
return Theme(
// if current book is set in player, get theme from the cover image
data: ThemeData(
colorScheme:
coverColorScheme.valueOrNull ?? Theme.of(context).colorScheme,
colorScheme: coverColorScheme.valueOrNull ?? Theme.of(context).colorScheme,
),
child: Padding(
padding: EdgeInsets.all(strokeWidth / 2 + 2),
@ -258,10 +249,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
child: CircularProgressIndicator(
value: userProgress.progress,
strokeWidth: strokeWidth,
backgroundColor: Theme.of(context)
.colorScheme
.onPrimary
.withValues(alpha: 0.8),
backgroundColor: Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.8),
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
@ -279,15 +267,11 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
const Size(size, size),
),
backgroundColor: WidgetStateProperty.all(
Theme.of(context)
.colorScheme
.onPrimary
.withValues(alpha: 0.9),
Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.9),
),
),
onPressed: () async {
final book =
await ref.watch(libraryItemProvider(libraryItemId).future);
final book = await ref.watch(libraryItemProvider(libraryItemId).future);
libraryItemPlayButtonOnPressed(
ref: ref,
@ -324,10 +308,8 @@ class BookCoverSkeleton extends StatelessWidget {
child: SizedBox(
width: 150,
child: Shimmer.fromColors(
baseColor:
Theme.of(context).colorScheme.surface.withValues(alpha: 0.3),
highlightColor:
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1),
baseColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.3),
highlightColor: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1),
child: Container(
color: Theme.of(context).colorScheme.surface,
),