格式化代码

This commit is contained in:
rang 2025-10-25 10:38:56 +08:00
parent a28547685b
commit b3a9d76c78
24 changed files with 611 additions and 451 deletions

View file

@ -26,8 +26,8 @@ class ExplorePage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// hooks for the dark mode // hooks for the dark mode
final settings = ref.watch(appSettingsProvider); ref.watch(appSettingsProvider);
final api = ref.watch(authenticatedApiProvider); // final api = ref.watch(authenticatedApiProvider);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(S.of(context).explore), title: Text(S.of(context).explore),
@ -62,8 +62,8 @@ class MySearchBar extends HookConsumerWidget {
currentQuery = query; currentQuery = query;
// In a real application, there should be some error handling here. // In a real application, there should be some error handling here.
final options = final options = await api.libraries
await api.libraries.search(libraryId: settings.activeLibraryId!, query: query, limit: 3); .search(libraryId: settings.activeLibraryId!, query: query, limit: 3);
// If another search happened after this one, throw away these options. // If another search happened after this one, throw away these options.
if (currentQuery != query) { if (currentQuery != query) {
@ -98,7 +98,10 @@ class MySearchBar extends HookConsumerWidget {
// opacity: 0.5 for the hint text // opacity: 0.5 for the hint text
hintStyle: WidgetStatePropertyAll( hintStyle: WidgetStatePropertyAll(
Theme.of(context).textTheme.bodyMedium!.copyWith( Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5), color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.5),
), ),
), ),
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
@ -231,8 +234,9 @@ class BookSearchResultMini extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final item = ref.watch(libraryItemProvider(book.libraryItemId)).valueOrNull; final item = ref.watch(libraryItemProvider(book.libraryItemId)).valueOrNull;
final image = final image = item == null
item == null ? const AsyncValue.loading() : ref.watch(coverImageProvider(item.id)); ? const AsyncValue.loading()
: ref.watch(coverImageProvider(item.id));
return ListTile( return ListTile(
leading: SizedBox( leading: SizedBox(
width: 50, width: 50,

View file

@ -77,9 +77,11 @@ class LibraryItemActions extends HookConsumerWidget {
IconButton( IconButton(
onPressed: () { onPressed: () {
appLogger.fine('Sharing'); appLogger.fine('Sharing');
var currentServerUrl = apiSettings.activeServer!.serverUrl; var currentServerUrl =
apiSettings.activeServer!.serverUrl;
if (!currentServerUrl.hasScheme) { if (!currentServerUrl.hasScheme) {
currentServerUrl = Uri.https(currentServerUrl.toString()); currentServerUrl =
Uri.https(currentServerUrl.toString());
} }
handleLaunchUrl( handleLaunchUrl(
Uri.parse( Uri.parse(
@ -138,7 +140,8 @@ class LibraryItemActions extends HookConsumerWidget {
FileDownloader() FileDownloader()
.database .database
.deleteRecordWithId( .deleteRecordWithId(
record.task.taskId, record
.task.taskId,
); );
Navigator.pop(context); Navigator.pop(context);
}, },
@ -157,7 +160,8 @@ class LibraryItemActions extends HookConsumerWidget {
}, },
onTap: () async { onTap: () async {
// open the file location // open the file location
final didOpen = await FileDownloader().openFile( final didOpen =
await FileDownloader().openFile(
task: record.task, task: record.task,
); );
@ -226,7 +230,9 @@ class LibItemDownloadButton extends HookConsumerWidget {
onPressed: () { onPressed: () {
appLogger.fine('Pressed download button'); appLogger.fine('Pressed download button');
ref.read(downloadManagerProvider.notifier).queueAudioBookDownload(item); ref
.read(downloadManagerProvider.notifier)
.queueAudioBookDownload(item);
}, },
icon: const Icon( icon: const Icon(
Icons.download_rounded, Icons.download_rounded,
@ -245,7 +251,10 @@ class ItemCurrentlyInDownloadQueue extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { 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) { if (progress == 1) {
return AlreadyItemDownloadedButton(item: item); return AlreadyItemDownloadedButton(item: item);
@ -333,7 +342,7 @@ class DownloadSheet extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final manager = ref.watch(downloadManagerProvider); // final manager = ref.watch(downloadManagerProvider);
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -377,7 +386,9 @@ class DownloadSheet extends HookConsumerWidget {
TextButton( TextButton(
onPressed: () { onPressed: () {
// delete the file // delete the file
ref.read(downloadManagerProvider.notifier).deleteDownloadedItem( ref
.read(downloadManagerProvider.notifier)
.deleteDownloadedItem(
item, item,
); );
GoRouter.of(context).pop(true); GoRouter.of(context).pop(true);
@ -396,7 +407,8 @@ class DownloadSheet extends HookConsumerWidget {
); );
if (wasDeleted ?? false) { if (wasDeleted ?? false) {
appLogger.fine(S.of(context).deleted(item.media.metadata.title ?? '')); appLogger
.fine(S.of(context).deleted(item.media.metadata.title ?? ''));
GoRouter.of(context).pop(); GoRouter.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -520,7 +532,8 @@ Future<void> libraryItemPlayButtonOnPressed({
appLogger.info('Setting the book ${book.libraryItemId}'); appLogger.info('Setting the book ${book.libraryItemId}');
appLogger.info('Initial position: ${userMediaProgress?.currentTime}'); appLogger.info('Initial position: ${userMediaProgress?.currentTime}');
final downloadManager = ref.watch(simpleDownloadManagerProvider); 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); final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
setSourceFuture = player.setSourceAudiobook( setSourceFuture = player.setSourceAudiobook(
book, book,
@ -536,23 +549,27 @@ Future<void> libraryItemPlayButtonOnPressed({
} }
} }
// set the volume as this is the first time playing and dismissing causes the volume to go to 0 // 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 appPlayerSettings = ref.read(appSettingsProvider).playerSettings;
var configurePlayerForEveryBook = appPlayerSettings.configurePlayerForEveryBook; var configurePlayerForEveryBook =
appPlayerSettings.configurePlayerForEveryBook;
await Future.wait([ await Future.wait([
setSourceFuture ?? Future.value(), setSourceFuture ?? Future.value(),
// set the volume // set the volume
player.setVolume( player.setVolume(
configurePlayerForEveryBook configurePlayerForEveryBook
? bookPlayerSettings.preferredDefaultVolume ?? appPlayerSettings.preferredDefaultVolume ? bookPlayerSettings.preferredDefaultVolume ??
appPlayerSettings.preferredDefaultVolume
: appPlayerSettings.preferredDefaultVolume, : appPlayerSettings.preferredDefaultVolume,
), ),
// set the speed // set the speed
player.setSpeed( player.setSpeed(
configurePlayerForEveryBook configurePlayerForEveryBook
? bookPlayerSettings.preferredDefaultSpeed ?? appPlayerSettings.preferredDefaultSpeed ? bookPlayerSettings.preferredDefaultSpeed ??
appPlayerSettings.preferredDefaultSpeed
: appPlayerSettings.preferredDefaultSpeed, : appPlayerSettings.preferredDefaultSpeed,
), ),
]); ]);

View file

@ -25,8 +25,9 @@ class LibraryItemMetadata extends HookConsumerWidget {
if (book == null) { if (book == null) {
return null; return null;
} }
final duration = final duration = book.audioFiles
book.audioFiles.map((e) => e.duration).reduce((value, element) => value + element); .map((e) => e.duration)
.reduce((value, element) => value + element);
final hours = duration.inHours; final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60); final minutes = duration.inMinutes.remainder(60);
return '${hours}h ${minutes}m'; return '${hours}h ${minutes}m';
@ -41,8 +42,9 @@ class LibraryItemMetadata extends HookConsumerWidget {
if (book == null) { if (book == null) {
return null; return null;
} }
final size = final size = book.audioFiles
book.audioFiles.map((e) => e.metadata.size).reduce((value, element) => value + element); .map((e) => e.metadata.size)
.reduce((value, element) => value + element);
if (size / 1024 / 1024 < 1024) { if (size / 1024 / 1024 < 1024) {
return '${(size / 1024 / 1024).toStringAsFixed(2)} MB'; return '${(size / 1024 / 1024).toStringAsFixed(2)} MB';
} }
@ -98,7 +100,10 @@ class LibraryItemMetadata extends HookConsumerWidget {
return VerticalDivider( return VerticalDivider(
indent: 6, indent: 6,
endIndent: 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

@ -28,7 +28,8 @@ class LibraryItemPage extends HookConsumerWidget {
static const double _showFabThreshold = 300.0; static const double _showFabThreshold = 300.0;
@override @override
Widget build(BuildContext context, WidgetRef ref) { 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 scrollController = useScrollController();
final showFab = useState(false); final showFab = useState(false);
@ -151,7 +152,8 @@ class LibraryItemDescription extends HookConsumerWidget {
} }
return ExpandableDescription( return ExpandableDescription(
title: S.of(context).bookAbout, title: S.of(context).bookAbout,
content: item.media.metadata.description ?? S.of(context).bookAboutDefault, content:
item.media.metadata.description ?? S.of(context).bookAboutDefault,
readMoreText: S.of(context).readMore, readMoreText: S.of(context).readMore,
readLessText: S.of(context).readLess, readLessText: S.of(context).readLess,
); );
@ -168,8 +170,10 @@ double calculateWidth(
/// height ratio of the cover image to the available height /// height ratio of the cover image to the available height
double maxHeightToUse = 0.25, double maxHeightToUse = 0.25,
}) { }) {
final availHeight = min(constraints.maxHeight, MediaQuery.of(context).size.height); final availHeight =
final availWidth = min(constraints.maxWidth, MediaQuery.of(context).size.width); 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 // make the width widthRatio of the available width
var width = availWidth * widthRatio; var width = availWidth * widthRatio;

View file

@ -2,11 +2,13 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; import 'package:vaani/api/library_provider.dart' show currentLibraryProvider;
import 'package:vaani/features/you/view/widgets/library_switch_chip.dart' show showLibrarySwitcher; import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'
show showLibrarySwitcher;
import 'package:vaani/generated/l10n.dart'; import 'package:vaani/generated/l10n.dart';
import 'package:vaani/router/router.dart' show Routes; import 'package:vaani/router/router.dart' show Routes;
import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons; import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons;
import 'package:vaani/shared/widgets/not_implemented.dart' show showNotImplementedToast; import 'package:vaani/shared/widgets/not_implemented.dart'
show showNotImplementedToast;
class LibraryBrowserPage extends HookConsumerWidget { class LibraryBrowserPage extends HookConsumerWidget {
const LibraryBrowserPage({super.key}); const LibraryBrowserPage({super.key});
@ -32,7 +34,9 @@ class LibraryBrowserPage extends HookConsumerWidget {
// true, // Optional: uncomment if you want snapping behavior (usually with floating: true) // true, // Optional: uncomment if you want snapping behavior (usually with floating: true)
leading: IconButton( leading: IconButton(
icon: Icon(libraryIconData), icon: Icon(libraryIconData),
tooltip: S.of(context).librarySwitchTooltip, // Helpful tooltip for users tooltip: S
.of(context)
.librarySwitchTooltip, // Helpful tooltip for users
onPressed: () { onPressed: () {
showLibrarySwitcher(context, ref); showLibrarySwitcher(context, ref);
}, },

View file

@ -1,7 +1,8 @@
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:vaani/db/available_boxes.dart'; import 'package:vaani/db/available_boxes.dart';
import 'package:vaani/features/per_book_settings/models/book_settings.dart' as model; import 'package:vaani/features/per_book_settings/models/book_settings.dart'
as model;
import 'package:vaani/features/per_book_settings/models/nullable_player_settings.dart'; import 'package:vaani/features/per_book_settings/models/nullable_player_settings.dart';
part 'book_settings_provider.g.dart'; part 'book_settings_provider.g.dart';

View file

@ -122,9 +122,11 @@ class AudiobookPlayer extends AudioPlayer {
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero); final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
final initialIndex = book.tracks.indexOf(trackToPlay); final initialIndex = book.tracks.indexOf(trackToPlay);
final initialPositionInTrack = final initialPositionInTrack = initialPosition != null
initialPosition != null ? initialPosition - trackToPlay.startOffset : null; ? initialPosition - trackToPlay.startOffset
await setAudioSourceTrack(initialIndex, initialPosition: initialPositionInTrack); : null;
await setAudioSourceTrack(initialIndex,
initialPosition: initialPositionInTrack);
// _logger.finer('Setting audioSource'); // _logger.finer('Setting audioSource');
// await setAudioSource( // await setAudioSource(
// preload: preload, // preload: preload,
@ -158,7 +160,8 @@ class AudiobookPlayer extends AudioPlayer {
// }); // });
} }
Future<void> setAudioSourceTrack(int index, {Duration? initialPosition}) async { Future<void> setAudioSourceTrack(int index,
{Duration? initialPosition}) async {
if (_book == null) { if (_book == null) {
return stop(); return stop();
} }
@ -168,9 +171,11 @@ class AudiobookPlayer extends AudioPlayer {
_currentIndex = index; _currentIndex = index;
AudioTrack track = _book!.tracks[index]; AudioTrack track = _book!.tracks[index];
final appSettings = loadOrCreateAppSettings(); final appSettings = loadOrCreateAppSettings();
final playerSettings = readFromBoxOrCreate(_book!.libraryItemId).playerSettings; final playerSettings =
readFromBoxOrCreate(_book!.libraryItemId).playerSettings;
final retrievedUri = _getUri(track, _downloadedUris, baseUrl: baseUrl, token: token); final retrievedUri =
_getUri(track, _downloadedUris, baseUrl: baseUrl, token: token);
await setAudioSource( await setAudioSource(
initialPosition: initialPosition == null || initialPosition <= Duration() initialPosition: initialPosition == null || initialPosition <= Duration()
@ -185,8 +190,10 @@ class AudiobookPlayer extends AudioPlayer {
// Specify a unique ID for each media item: // Specify a unique ID for each media item:
id: '${book?.libraryItemId}${track.index}', id: '${book?.libraryItemId}${track.index}',
// Metadata to display in the notification: // Metadata to display in the notification:
title: appSettings.notificationSettings.primaryTitle.formatNotificationTitle(book!), title: appSettings.notificationSettings.primaryTitle
album: appSettings.notificationSettings.secondaryTitle.formatNotificationTitle(book!), .formatNotificationTitle(book!),
album: appSettings.notificationSettings.secondaryTitle
.formatNotificationTitle(book!),
artUri: Uri.parse( artUri: Uri.parse(
'$baseUrl/api/items/${book?.libraryItemId}/cover?token=$token&width=800', '$baseUrl/api/items/${book?.libraryItemId}/cover?token=$token&width=800',
), ),
@ -392,7 +399,8 @@ Uri _getUri(
}, },
); );
return uri ?? Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token'); return uri ??
Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token');
} }
extension FormatNotificationTitle on String { extension FormatNotificationTitle on String {

View file

@ -31,7 +31,8 @@ class AudiobookPlayer extends HookConsumerWidget {
if (currentBook == null) { if (currentBook == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final itemBeingPlayed = ref.watch(libraryItemProvider(currentBook.libraryItemId)); final itemBeingPlayed =
ref.watch(libraryItemProvider(currentBook.libraryItemId));
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
final imageOfItemBeingPlayed = itemBeingPlayed.valueOrNull != null final imageOfItemBeingPlayed = itemBeingPlayed.valueOrNull != null
? ref.watch( ? ref.watch(
@ -64,7 +65,8 @@ class AudiobookPlayer extends HookConsumerWidget {
themeOfLibraryItemProvider( themeOfLibraryItemProvider(
itemBeingPlayed.valueOrNull?.id, itemBeingPlayed.valueOrNull?.id,
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
highContrast: appSettings.themeSettings.highContrast || MediaQuery.of(context).highContrast, highContrast: appSettings.themeSettings.highContrast ||
MediaQuery.of(context).highContrast,
), ),
); );
@ -86,7 +88,8 @@ class AudiobookPlayer extends HookConsumerWidget {
onDragDown: (percentage) async { onDragDown: (percentage) async {
// preferred volume // preferred volume
// set volume to 0 when dragging down // set volume to 0 when dragging down
await player.setVolume(preferredVolume * (1 - percentage.clamp(0, .75))); await player
.setVolume(preferredVolume * (1 - percentage.clamp(0, .75)));
}, },
minHeight: playerMinHeight, minHeight: playerMinHeight,
// subtract the height of notches and other system UI // subtract the height of notches and other system UI
@ -106,8 +109,10 @@ class AudiobookPlayer extends HookConsumerWidget {
// at what point should the player switch from miniplayer to expanded player // at what point should the player switch from miniplayer to expanded player
// also at this point the image should be at its max size and in the center of the player // also at this point the image should be at its max size and in the center of the player
final miniplayerPercentageDeclaration = final miniplayerPercentageDeclaration =
(maxImgSize - playerMinHeight) / (playerMaxHeight - playerMinHeight); (maxImgSize - playerMinHeight) /
final bool isFormMiniplayer = percentage < miniplayerPercentageDeclaration; (playerMaxHeight - playerMinHeight);
final bool isFormMiniplayer =
percentage < miniplayerPercentageDeclaration;
if (!isFormMiniplayer) { if (!isFormMiniplayer) {
// this calculation needs a refactor // this calculation needs a refactor
@ -207,14 +212,17 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
// now find the chapter that corresponds to the current time // now find the chapter that corresponds to the current time
// and calculate the progress of the current chapter // and calculate the progress of the current chapter
final currentChapterProgress = final currentChapterProgress = currentChapter == null
currentChapter == null ? null : (player.positionInBook - currentChapter.start); ? null
: (player.positionInBook - currentChapter.start);
final currentChapterBuffered = final currentChapterBuffered = currentChapter == null
currentChapter == null ? null : (player.bufferedPositionInBook - currentChapter.start); ? null
: (player.bufferedPositionInBook - currentChapter.start);
return ProgressBar( return ProgressBar(
progress: currentChapterProgress ?? position.data ?? const Duration(seconds: 0), progress:
currentChapterProgress ?? position.data ?? const Duration(seconds: 0),
total: currentChapter == null total: currentChapter == null
? player.book?.duration ?? const Duration(seconds: 0) ? player.book?.duration ?? const Duration(seconds: 0)
: currentChapter.end - currentChapter.start, : currentChapter.end - currentChapter.start,
@ -226,7 +234,8 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
player.seek(duration); player.seek(duration);
}, },
thumbRadius: 8, thumbRadius: 8,
buffered: currentChapterBuffered ?? buffered.data ?? const Duration(seconds: 0), buffered:
currentChapterBuffered ?? buffered.data ?? const Duration(seconds: 0),
bufferedBarColor: Theme.of(context).colorScheme.secondary, bufferedBarColor: Theme.of(context).colorScheme.secondary,
timeLabelType: TimeLabelType.remainingTime, timeLabelType: TimeLabelType.remainingTime,
timeLabelLocation: TimeLabelLocation.below, timeLabelLocation: TimeLabelLocation.below,

View file

@ -105,7 +105,10 @@ class PlayerWhenExpanded extends HookConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), color: Theme.of(context)
.colorScheme
.primary
.withValues(alpha: 0.1),
blurRadius: 32 * earlyPercentage, blurRadius: 32 * earlyPercentage,
spreadRadius: 8 * earlyPercentage, spreadRadius: 8 * earlyPercentage,
// offset: Offset(0, 16 * earlyPercentage), // offset: Offset(0, 16 * earlyPercentage),
@ -171,7 +174,10 @@ class PlayerWhenExpanded extends HookConsumerWidget {
currentBookMetadata?.authorName ?? '', currentBookMetadata?.authorName ?? '',
].join(' - '), ].join(' - '),
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.7),
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View file

@ -34,7 +34,8 @@ class PlayerWhenMinimized extends HookConsumerWidget {
final currentChapter = ref.watch(currentPlayingChapterProvider); final currentChapter = ref.watch(currentPlayingChapterProvider);
final vanishingPercentage = 1 - percentageMiniplayer; final vanishingPercentage = 1 - percentageMiniplayer;
final progress = useStream(player.slowPositionStreamInBook, initialData: Duration.zero); final progress =
useStream(player.slowPositionStreamInBook, initialData: Duration.zero);
final bookMetaExpanded = ref.watch(currentBookMetadataProvider); final bookMetaExpanded = ref.watch(currentBookMetadataProvider);
@ -56,7 +57,8 @@ class PlayerWhenMinimized extends HookConsumerWidget {
context.pushNamed( context.pushNamed(
Routes.libraryItem.name, Routes.libraryItem.name,
pathParameters: { pathParameters: {
Routes.libraryItem.pathParamName!: player.book!.libraryItemId, Routes.libraryItem.pathParamName!:
player.book!.libraryItemId,
}, },
); );
}, },
@ -90,7 +92,10 @@ class PlayerWhenMinimized extends HookConsumerWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.7),
), ),
), ),
], ],
@ -134,7 +139,8 @@ class PlayerWhenMinimized extends HookConsumerWidget {
SizedBox( SizedBox(
height: barHeight, height: barHeight,
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: (progress.data ?? Duration.zero).inSeconds / player.book!.duration.inSeconds, value: (progress.data ?? Duration.zero).inSeconds /
player.book!.duration.inSeconds,
color: Theme.of(context).colorScheme.onPrimaryContainer, color: Theme.of(context).colorScheme.onPrimaryContainer,
backgroundColor: Theme.of(context).colorScheme.primaryContainer, backgroundColor: Theme.of(context).colorScheme.primaryContainer,
), ),

View file

@ -1,14 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/features/player/providers/audiobook_player.dart' show audiobookPlayerProvider; import 'package:vaani/features/player/providers/audiobook_player.dart'
show audiobookPlayerProvider;
import 'package:vaani/features/player/providers/currently_playing_provider.dart' import 'package:vaani/features/player/providers/currently_playing_provider.dart'
show currentPlayingChapterProvider, currentlyPlayingBookProvider; show currentPlayingChapterProvider, currentlyPlayingBookProvider;
import 'package:vaani/features/player/view/player_when_expanded.dart' show pendingPlayerModals; import 'package:vaani/features/player/view/player_when_expanded.dart'
show pendingPlayerModals;
import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart'; import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart';
import 'package:vaani/main.dart' show appLogger; import 'package:vaani/main.dart' show appLogger;
import 'package:vaani/shared/extensions/chapter.dart' show ChapterDuration; import 'package:vaani/shared/extensions/chapter.dart' show ChapterDuration;
import 'package:vaani/shared/extensions/duration_format.dart' show DurationFormat; import 'package:vaani/shared/extensions/duration_format.dart'
show DurationFormat;
import 'package:vaani/shared/hooks.dart' show useTimer; import 'package:vaani/shared/hooks.dart' show useTimer;
class ChapterSelectionButton extends HookConsumerWidget { class ChapterSelectionButton extends HookConsumerWidget {
@ -88,11 +91,13 @@ class ChapterSelectionModal extends HookConsumerWidget {
children: currentBook!.chapters.map( children: currentBook!.chapters.map(
(chapter) { (chapter) {
final isCurrent = currentChapterIndex == chapter.id; final isCurrent = currentChapterIndex == chapter.id;
final isPlayed = final isPlayed = currentChapterIndex != null &&
currentChapterIndex != null && chapter.id < currentChapterIndex; chapter.id < currentChapterIndex;
return ListTile( return ListTile(
autofocus: isCurrent, autofocus: isCurrent,
iconColor: isPlayed && !isCurrent ? theme.disabledColor : null, iconColor: isPlayed && !isCurrent
? theme.disabledColor
: null,
title: Text( title: Text(
chapter.title, chapter.title,
style: isPlayed && !isCurrent style: isPlayed && !isCurrent

View file

@ -50,7 +50,8 @@ class PlayerSkipChapterStartEnd extends HookConsumerWidget {
body: Column( body: Column(
children: [ children: [
ListTile( ListTile(
title: Text('跳过片头 ${bookSettings.playerSettings.skipChapterStart.inSeconds}s'), title: Text(
'跳过片头 ${bookSettings.playerSettings.skipChapterStart.inSeconds}s'),
), ),
Expanded( Expanded(
child: TimeIntervalSlider( child: TimeIntervalSlider(
@ -65,14 +66,16 @@ class PlayerSkipChapterStartEnd extends HookConsumerWidget {
bookSettingsProvider(bookId).notifier, bookSettingsProvider(bookId).notifier,
) )
.update( .update(
bookSettings.copyWith.playerSettings(skipChapterStart: interval), bookSettings.copyWith
.playerSettings(skipChapterStart: interval),
); );
ref.read(audiobookPlayerProvider).setClip(start: interval); ref.read(audiobookPlayerProvider).setClip(start: interval);
}, },
), ),
), ),
ListTile( ListTile(
title: Text('跳过片尾 ${bookSettings.playerSettings.skipChapterEnd.inSeconds}s'), title: Text(
'跳过片尾 ${bookSettings.playerSettings.skipChapterEnd.inSeconds}s'),
), ),
Expanded( Expanded(
child: TimeIntervalSlider( child: TimeIntervalSlider(
@ -87,7 +90,8 @@ class PlayerSkipChapterStartEnd extends HookConsumerWidget {
bookSettingsProvider(bookId).notifier, bookSettingsProvider(bookId).notifier,
) )
.update( .update(
bookSettings.copyWith.playerSettings(skipChapterEnd: interval), bookSettings.copyWith
.playerSettings(skipChapterEnd: interval),
); );
}, },
), ),

View file

@ -3,7 +3,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelfsdk/audiobookshelf_api.dart' show Library; import 'package:shelfsdk/audiobookshelf_api.dart' show Library;
import 'package:vaani/api/library_provider.dart'; import 'package:vaani/api/library_provider.dart';
import 'package:vaani/generated/l10n.dart'; import 'package:vaani/generated/l10n.dart';
import 'package:vaani/settings/api_settings_provider.dart' show apiSettingsProvider; import 'package:vaani/settings/api_settings_provider.dart'
show apiSettingsProvider;
import 'package:vaani/shared/icons/abs_icons.dart'; import 'package:vaani/shared/icons/abs_icons.dart';
import 'dart:io' show Platform; import 'dart:io' show Platform;
@ -99,7 +100,8 @@ void showLibrarySwitcher(
// Make it scrollable and control height // Make it scrollable and control height
isScrollControlled: true, isScrollControlled: true,
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.6, // Max 60% of screen maxHeight:
MediaQuery.of(context).size.height * 0.6, // Max 60% of screen
), ),
builder: (sheetContext) => Padding( builder: (sheetContext) => Padding(
// Add padding within the bottom sheet // Add padding within the bottom sheet

View file

@ -68,7 +68,8 @@ class YouPage extends HookConsumerWidget {
}, },
), ),
librariesAsyncValue.when( librariesAsyncValue.when(
data: (libraries) => LibrarySwitchChip(libraries: libraries), data: (libraries) =>
LibrarySwitchChip(libraries: libraries),
loading: () => const ActionChip( loading: () => const ActionChip(
avatar: SizedBox( avatar: SizedBox(
width: 18, width: 18,
@ -88,7 +89,8 @@ class YouPage extends HookConsumerWidget {
// Maybe show error details or allow retry // Maybe show error details or allow retry
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Failed to load libraries: $error'), content:
Text('Failed to load libraries: $error'),
), ),
); );
}, },
@ -141,7 +143,8 @@ class YouPage extends HookConsumerWidget {
icon: const Icon(Icons.info), icon: const Icon(Icons.info),
applicationName: AppMetadata.appName, applicationName: AppMetadata.appName,
applicationVersion: AppMetadata.version, applicationVersion: AppMetadata.version,
applicationLegalese: 'Made with ❤️ by ${AppMetadata.author}', applicationLegalese:
'Made with ❤️ by ${AppMetadata.author}',
aboutBoxChildren: [ aboutBoxChildren: [
// link to github repo // link to github repo
ListTile( ListTile(
@ -216,7 +219,8 @@ class UserBar extends HookConsumerWidget {
Text( Text(
api.baseUrl.toString(), api.baseUrl.toString(),
style: textTheme.bodyMedium?.copyWith( style: textTheme.bodyMedium?.copyWith(
color: themeData.colorScheme.onSurface.withValues(alpha: 0.6), color:
themeData.colorScheme.onSurface.withValues(alpha: 0.6),
), ),
), ),
], ],

View file

@ -28,199 +28,207 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages); final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"account": MessageLookupByLibrary.simpleMessage("Account"), "account": MessageLookupByLibrary.simpleMessage("Account"),
"accountSwitch": MessageLookupByLibrary.simpleMessage("Switch Account"), "accountSwitch": MessageLookupByLibrary.simpleMessage("Switch Account"),
"appSettings": MessageLookupByLibrary.simpleMessage("App Settings"), "appSettings": MessageLookupByLibrary.simpleMessage("App Settings"),
"appearance": MessageLookupByLibrary.simpleMessage("Appearance"), "appearance": MessageLookupByLibrary.simpleMessage("Appearance"),
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage( "autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage(
"Auto Turn On Sleep Timer", "Auto Turn On Sleep Timer",
), ),
"automaticallyDescription": MessageLookupByLibrary.simpleMessage( "automaticallyDescription": MessageLookupByLibrary.simpleMessage(
"Automatically turn on the sleep timer based on the time of day", "Automatically turn on the sleep timer based on the time of day",
), ),
"backup": MessageLookupByLibrary.simpleMessage("Backup"), "backup": MessageLookupByLibrary.simpleMessage("Backup"),
"backupAndRestore": MessageLookupByLibrary.simpleMessage( "backupAndRestore": MessageLookupByLibrary.simpleMessage(
"Backup and Restore", "Backup and Restore",
), ),
"bookAbout": MessageLookupByLibrary.simpleMessage("About the Book"), "bookAbout": MessageLookupByLibrary.simpleMessage("About the Book"),
"bookAboutDefault": MessageLookupByLibrary.simpleMessage( "bookAboutDefault": MessageLookupByLibrary.simpleMessage(
"Sorry, no description found", "Sorry, no description found",
), ),
"bookAuthors": MessageLookupByLibrary.simpleMessage("Authors"), "bookAuthors": MessageLookupByLibrary.simpleMessage("Authors"),
"bookDownloads": MessageLookupByLibrary.simpleMessage("Downloads"), "bookDownloads": MessageLookupByLibrary.simpleMessage("Downloads"),
"bookGenres": MessageLookupByLibrary.simpleMessage("Genres"), "bookGenres": MessageLookupByLibrary.simpleMessage("Genres"),
"bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("Abridged"), "bookMetadataAbridged":
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"), MessageLookupByLibrary.simpleMessage("Abridged"),
"bookMetadataPublished": MessageLookupByLibrary.simpleMessage("Published"), "bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"),
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage( "bookMetadataPublished":
"Unabridged", MessageLookupByLibrary.simpleMessage("Published"),
), "bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage(
"bookSeries": MessageLookupByLibrary.simpleMessage("Series"), "Unabridged",
"bookShelveEmpty": MessageLookupByLibrary.simpleMessage("Try again"), ),
"bookShelveEmptyText": MessageLookupByLibrary.simpleMessage( "bookSeries": MessageLookupByLibrary.simpleMessage("Series"),
"No shelves to display", "bookShelveEmpty": MessageLookupByLibrary.simpleMessage("Try again"),
), "bookShelveEmptyText": MessageLookupByLibrary.simpleMessage(
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"), "No shelves to display",
"copyToClipboard": MessageLookupByLibrary.simpleMessage( ),
"Copy to Clipboard", "cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
), "copyToClipboard": MessageLookupByLibrary.simpleMessage(
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage( "Copy to Clipboard",
"Copy the app settings to the clipboard", ),
), "copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
"copyToClipboardToast": MessageLookupByLibrary.simpleMessage( "Copy the app settings to the clipboard",
"Settings copied to clipboard", ),
), "copyToClipboardToast": MessageLookupByLibrary.simpleMessage(
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "Settings copied to clipboard",
"deleteDialog": m0, ),
"deleted": m1, "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"explore": MessageLookupByLibrary.simpleMessage("explore"), "deleteDialog": m0,
"exploreHint": MessageLookupByLibrary.simpleMessage( "deleted": m1,
"Seek and you shall discover...", "explore": MessageLookupByLibrary.simpleMessage("explore"),
), "exploreHint": MessageLookupByLibrary.simpleMessage(
"exploreTooltip": MessageLookupByLibrary.simpleMessage( "Seek and you shall discover...",
"Search and Explore", ),
), "exploreTooltip": MessageLookupByLibrary.simpleMessage(
"general": MessageLookupByLibrary.simpleMessage("General"), "Search and Explore",
"help": MessageLookupByLibrary.simpleMessage("Help"), ),
"home": MessageLookupByLibrary.simpleMessage("Home"), "general": MessageLookupByLibrary.simpleMessage("General"),
"homeBookContinueListening": MessageLookupByLibrary.simpleMessage( "help": MessageLookupByLibrary.simpleMessage("Help"),
"Continue Listening", "home": MessageLookupByLibrary.simpleMessage("Home"),
), "homeBookContinueListening": MessageLookupByLibrary.simpleMessage(
"homeBookContinueSeries": MessageLookupByLibrary.simpleMessage( "Continue Listening",
"Continue Series", ),
), "homeBookContinueSeries": MessageLookupByLibrary.simpleMessage(
"homeBookDiscover": MessageLookupByLibrary.simpleMessage("Discover"), "Continue Series",
"homeBookListenAgain": MessageLookupByLibrary.simpleMessage("Listen Again"), ),
"homeBookNewestAuthors": MessageLookupByLibrary.simpleMessage( "homeBookDiscover": MessageLookupByLibrary.simpleMessage("Discover"),
"Newest Authors", "homeBookListenAgain":
), MessageLookupByLibrary.simpleMessage("Listen Again"),
"homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage( "homeBookNewestAuthors": MessageLookupByLibrary.simpleMessage(
"Recently Added", "Newest Authors",
), ),
"homeBookRecommended": MessageLookupByLibrary.simpleMessage("Recommended"), "homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage(
"homeContinueListening": MessageLookupByLibrary.simpleMessage( "Recently Added",
"Continue Listening", ),
), "homeBookRecommended":
"homeListenAgain": MessageLookupByLibrary.simpleMessage("Listen Again"), MessageLookupByLibrary.simpleMessage("Recommended"),
"homePageSettings": MessageLookupByLibrary.simpleMessage( "homeContinueListening": MessageLookupByLibrary.simpleMessage(
"Home Page Settings", "Continue Listening",
), ),
"homePageSettingsDescription": MessageLookupByLibrary.simpleMessage( "homeListenAgain": MessageLookupByLibrary.simpleMessage("Listen Again"),
"Customize the home page", "homePageSettings": MessageLookupByLibrary.simpleMessage(
), "Home Page Settings",
"homeStartListening": MessageLookupByLibrary.simpleMessage( ),
"Start Listening", "homePageSettingsDescription": MessageLookupByLibrary.simpleMessage(
), "Customize the home page",
"language": MessageLookupByLibrary.simpleMessage("Language"), ),
"languageDescription": MessageLookupByLibrary.simpleMessage( "homeStartListening": MessageLookupByLibrary.simpleMessage(
"Language switch", "Start Listening",
), ),
"library": MessageLookupByLibrary.simpleMessage("Library"), "language": MessageLookupByLibrary.simpleMessage("Language"),
"libraryChange": MessageLookupByLibrary.simpleMessage("Change Library"), "languageDescription": MessageLookupByLibrary.simpleMessage(
"libraryEmpty": MessageLookupByLibrary.simpleMessage( "Language switch",
"No libraries available.", ),
), "library": MessageLookupByLibrary.simpleMessage("Library"),
"libraryLoadError": m2, "libraryChange": MessageLookupByLibrary.simpleMessage("Change Library"),
"librarySelect": MessageLookupByLibrary.simpleMessage("Select Library"), "libraryEmpty": MessageLookupByLibrary.simpleMessage(
"librarySwitchTooltip": MessageLookupByLibrary.simpleMessage( "No libraries available.",
"Switch Library", ),
), "libraryLoadError": m2,
"libraryTooltip": MessageLookupByLibrary.simpleMessage( "librarySelect": MessageLookupByLibrary.simpleMessage("Select Library"),
"Browse your library", "librarySwitchTooltip": MessageLookupByLibrary.simpleMessage(
), "Switch Library",
"loading": MessageLookupByLibrary.simpleMessage("Loading..."), ),
"logs": MessageLookupByLibrary.simpleMessage("Logs"), "libraryTooltip": MessageLookupByLibrary.simpleMessage(
"no": MessageLookupByLibrary.simpleMessage("No"), "Browse your library",
"notImplemented": MessageLookupByLibrary.simpleMessage("Not implemented"), ),
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage( "loading": MessageLookupByLibrary.simpleMessage("Loading..."),
"Notification Media Player", "logs": MessageLookupByLibrary.simpleMessage("Logs"),
), "no": MessageLookupByLibrary.simpleMessage("No"),
"notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage( "notImplemented":
"Customize the media player in notifications", MessageLookupByLibrary.simpleMessage("Not implemented"),
), "notificationMediaPlayer": MessageLookupByLibrary.simpleMessage(
"ok": MessageLookupByLibrary.simpleMessage("OK"), "Notification Media Player",
"pause": MessageLookupByLibrary.simpleMessage("Pause"), ),
"play": MessageLookupByLibrary.simpleMessage("Play"), "notificationMediaPlayerDescription":
"playerSettings": MessageLookupByLibrary.simpleMessage("Player Settings"), MessageLookupByLibrary.simpleMessage(
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage( "Customize the media player in notifications",
"Customize the player settings", ),
), "ok": MessageLookupByLibrary.simpleMessage("OK"),
"playerSettingsPlaybackReporting": MessageLookupByLibrary.simpleMessage( "pause": MessageLookupByLibrary.simpleMessage("Pause"),
"Playback Reporting", "play": MessageLookupByLibrary.simpleMessage("Play"),
), "playerSettings":
"playerSettingsPlaybackReportingIgnore": MessageLookupByLibrary.simpleMessage("Player Settings"),
MessageLookupByLibrary.simpleMessage( "playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Customize the player settings",
),
"playerSettingsPlaybackReporting": MessageLookupByLibrary.simpleMessage(
"Playback Reporting",
),
"playerSettingsPlaybackReportingIgnore":
MessageLookupByLibrary.simpleMessage(
"Ignore Playback Position Less Than", "Ignore Playback Position Less Than",
), ),
"playerSettingsPlaybackReportingMinimum": "playerSettingsPlaybackReportingMinimum":
MessageLookupByLibrary.simpleMessage("Minimum Position to Report"), MessageLookupByLibrary.simpleMessage("Minimum Position to Report"),
"playerSettingsPlaybackReportingMinimumDescriptionHead": "playerSettingsPlaybackReportingMinimumDescriptionHead":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"Do not report playback for the first ", "Do not report playback for the first ",
), ),
"playerSettingsPlaybackReportingMinimumDescriptionTail": "playerSettingsPlaybackReportingMinimumDescriptionTail":
MessageLookupByLibrary.simpleMessage("of the book"), MessageLookupByLibrary.simpleMessage("of the book"),
"playerSettingsRememberForEveryBook": MessageLookupByLibrary.simpleMessage( "playerSettingsRememberForEveryBook":
"Remember Player Settings for Every Book", MessageLookupByLibrary.simpleMessage(
), "Remember Player Settings for Every Book",
"playerSettingsRememberForEveryBookDescription": ),
MessageLookupByLibrary.simpleMessage( "playerSettingsRememberForEveryBookDescription":
MessageLookupByLibrary.simpleMessage(
"Settings like speed, loudness, etc. will be remembered for every book", "Settings like speed, loudness, etc. will be remembered for every book",
), ),
"playerSettingsSpeedDefault": MessageLookupByLibrary.simpleMessage( "playerSettingsSpeedDefault": MessageLookupByLibrary.simpleMessage(
"Default Speed", "Default Speed",
), ),
"playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage( "playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage(
"Speed Options", "Speed Options",
), ),
"playlistsMine": MessageLookupByLibrary.simpleMessage("My Playlists"), "playlistsMine": MessageLookupByLibrary.simpleMessage("My Playlists"),
"readLess": MessageLookupByLibrary.simpleMessage("Read Less"), "readLess": MessageLookupByLibrary.simpleMessage("Read Less"),
"readMore": MessageLookupByLibrary.simpleMessage("Read More"), "readMore": MessageLookupByLibrary.simpleMessage("Read More"),
"refresh": MessageLookupByLibrary.simpleMessage("Refresh"), "refresh": MessageLookupByLibrary.simpleMessage("Refresh"),
"reset": MessageLookupByLibrary.simpleMessage("Reset"), "reset": MessageLookupByLibrary.simpleMessage("Reset"),
"resetAppSettings": MessageLookupByLibrary.simpleMessage( "resetAppSettings": MessageLookupByLibrary.simpleMessage(
"Reset App Settings", "Reset App Settings",
), ),
"resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage( "resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Reset the app settings to the default values", "Reset the app settings to the default values",
), ),
"resetAppSettingsDialog": MessageLookupByLibrary.simpleMessage( "resetAppSettingsDialog": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to reset the app settings?", "Are you sure you want to reset the app settings?",
), ),
"restore": MessageLookupByLibrary.simpleMessage("Restore"), "restore": MessageLookupByLibrary.simpleMessage("Restore"),
"restoreBackup": MessageLookupByLibrary.simpleMessage("Restore Backup"), "restoreBackup": MessageLookupByLibrary.simpleMessage("Restore Backup"),
"restoreBackupHint": MessageLookupByLibrary.simpleMessage( "restoreBackupHint": MessageLookupByLibrary.simpleMessage(
"Paste the backup here", "Paste the backup here",
), ),
"restoreBackupInvalid": MessageLookupByLibrary.simpleMessage( "restoreBackupInvalid": MessageLookupByLibrary.simpleMessage(
"Invalid backup", "Invalid backup",
), ),
"restoreBackupSuccess": MessageLookupByLibrary.simpleMessage( "restoreBackupSuccess": MessageLookupByLibrary.simpleMessage(
"Settings restored", "Settings restored",
), ),
"restoreBackupValidator": MessageLookupByLibrary.simpleMessage( "restoreBackupValidator": MessageLookupByLibrary.simpleMessage(
"Please paste the backup here", "Please paste the backup here",
), ),
"restoreDescription": MessageLookupByLibrary.simpleMessage( "restoreDescription": MessageLookupByLibrary.simpleMessage(
"Restore the app settings from the backup", "Restore the app settings from the backup",
), ),
"resume": MessageLookupByLibrary.simpleMessage("Resume"), "resume": MessageLookupByLibrary.simpleMessage("Resume"),
"retry": MessageLookupByLibrary.simpleMessage("Retry"), "retry": MessageLookupByLibrary.simpleMessage("Retry"),
"settings": MessageLookupByLibrary.simpleMessage("Settings"), "settings": MessageLookupByLibrary.simpleMessage("Settings"),
"shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"), "shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"),
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage( "shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
"Customize the shake detector settings", "Customize the shake detector settings",
), ),
"themeSettings": MessageLookupByLibrary.simpleMessage("Theme Settings"), "themeSettings": MessageLookupByLibrary.simpleMessage("Theme Settings"),
"themeSettingsDescription": MessageLookupByLibrary.simpleMessage( "themeSettingsDescription": MessageLookupByLibrary.simpleMessage(
"Customize the app theme", "Customize the app theme",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"), "unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"webVersion": MessageLookupByLibrary.simpleMessage("Web Version"), "webVersion": MessageLookupByLibrary.simpleMessage("Web Version"),
"yes": MessageLookupByLibrary.simpleMessage("Yes"), "yes": MessageLookupByLibrary.simpleMessage("Yes"),
"you": MessageLookupByLibrary.simpleMessage("You"), "you": MessageLookupByLibrary.simpleMessage("You"),
"youTooltip": MessageLookupByLibrary.simpleMessage( "youTooltip": MessageLookupByLibrary.simpleMessage(
"Your Profile and Settings", "Your Profile and Settings",
), ),
}; };
} }

View file

@ -28,135 +28,144 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages); final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"account": MessageLookupByLibrary.simpleMessage("账户"), "account": MessageLookupByLibrary.simpleMessage("账户"),
"accountSwitch": MessageLookupByLibrary.simpleMessage("切换账户"), "accountSwitch": MessageLookupByLibrary.simpleMessage("切换账户"),
"appSettings": MessageLookupByLibrary.simpleMessage("应用设置"), "appSettings": MessageLookupByLibrary.simpleMessage("应用设置"),
"appearance": MessageLookupByLibrary.simpleMessage("外观"), "appearance": MessageLookupByLibrary.simpleMessage("外观"),
"autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"), "autoTurnOnSleepTimer":
"automaticallyDescription": MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"),
"根据一天中的时间自动打开睡眠定时器", "automaticallyDescription": MessageLookupByLibrary.simpleMessage(
), "根据一天中的时间自动打开睡眠定时器",
"backup": MessageLookupByLibrary.simpleMessage("备份"), ),
"backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"), "backup": MessageLookupByLibrary.simpleMessage("备份"),
"bookAbout": MessageLookupByLibrary.simpleMessage("关于本书"), "backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"),
"bookAboutDefault": MessageLookupByLibrary.simpleMessage("抱歉,找不到描述"), "bookAbout": MessageLookupByLibrary.simpleMessage("关于本书"),
"bookAuthors": MessageLookupByLibrary.simpleMessage("作者"), "bookAboutDefault": MessageLookupByLibrary.simpleMessage("抱歉,找不到描述"),
"bookDownloads": MessageLookupByLibrary.simpleMessage("下载"), "bookAuthors": MessageLookupByLibrary.simpleMessage("作者"),
"bookGenres": MessageLookupByLibrary.simpleMessage("风格"), "bookDownloads": MessageLookupByLibrary.simpleMessage("下载"),
"bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("删节版"), "bookGenres": MessageLookupByLibrary.simpleMessage("风格"),
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("持续时间"), "bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("删节版"),
"bookMetadataPublished": MessageLookupByLibrary.simpleMessage("发布年份"), "bookMetadataLength": MessageLookupByLibrary.simpleMessage("持续时间"),
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage("未删节版"), "bookMetadataPublished": MessageLookupByLibrary.simpleMessage("发布年份"),
"bookSeries": MessageLookupByLibrary.simpleMessage("系列"), "bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage("未删节版"),
"bookShelveEmpty": MessageLookupByLibrary.simpleMessage("重试"), "bookSeries": MessageLookupByLibrary.simpleMessage("系列"),
"bookShelveEmptyText": MessageLookupByLibrary.simpleMessage("未查询到书架"), "bookShelveEmpty": MessageLookupByLibrary.simpleMessage("重试"),
"cancel": MessageLookupByLibrary.simpleMessage("取消"), "bookShelveEmptyText": MessageLookupByLibrary.simpleMessage("未查询到书架"),
"copyToClipboard": MessageLookupByLibrary.simpleMessage("复制到剪贴板"), "cancel": MessageLookupByLibrary.simpleMessage("取消"),
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage( "copyToClipboard": MessageLookupByLibrary.simpleMessage("复制到剪贴板"),
"将应用程序设置复制到剪贴板", "copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
), "将应用程序设置复制到剪贴板",
"copyToClipboardToast": MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"), ),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "copyToClipboardToast":
"deleteDialog": m0, MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"),
"deleted": m1, "delete": MessageLookupByLibrary.simpleMessage("删除"),
"explore": MessageLookupByLibrary.simpleMessage("探索"), "deleteDialog": m0,
"exploreHint": MessageLookupByLibrary.simpleMessage("搜索与探索..."), "deleted": m1,
"exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"), "explore": MessageLookupByLibrary.simpleMessage("探索"),
"general": MessageLookupByLibrary.simpleMessage("通用"), "exploreHint": MessageLookupByLibrary.simpleMessage("搜索与探索..."),
"help": MessageLookupByLibrary.simpleMessage("Help"), "exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"),
"home": MessageLookupByLibrary.simpleMessage("首页"), "general": MessageLookupByLibrary.simpleMessage("通用"),
"homeBookContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"), "help": MessageLookupByLibrary.simpleMessage("Help"),
"homeBookContinueSeries": MessageLookupByLibrary.simpleMessage("继续系列"), "home": MessageLookupByLibrary.simpleMessage("首页"),
"homeBookDiscover": MessageLookupByLibrary.simpleMessage("发现"), "homeBookContinueListening":
"homeBookListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"), MessageLookupByLibrary.simpleMessage("继续收听"),
"homeBookNewestAuthors": MessageLookupByLibrary.simpleMessage("最新作者"), "homeBookContinueSeries": MessageLookupByLibrary.simpleMessage("继续系列"),
"homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage("最近添加"), "homeBookDiscover": MessageLookupByLibrary.simpleMessage("发现"),
"homeBookRecommended": MessageLookupByLibrary.simpleMessage("推荐"), "homeBookListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"),
"homeContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"), "homeBookNewestAuthors": MessageLookupByLibrary.simpleMessage("最新作者"),
"homeListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"), "homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage("最近添加"),
"homePageSettings": MessageLookupByLibrary.simpleMessage("主页设置"), "homeBookRecommended": MessageLookupByLibrary.simpleMessage("推荐"),
"homePageSettingsDescription": MessageLookupByLibrary.simpleMessage( "homeContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"),
"自定义主页", "homeListenAgain": MessageLookupByLibrary.simpleMessage("再听一遍"),
), "homePageSettings": MessageLookupByLibrary.simpleMessage("主页设置"),
"homeStartListening": MessageLookupByLibrary.simpleMessage("开始收听"), "homePageSettingsDescription": MessageLookupByLibrary.simpleMessage(
"language": MessageLookupByLibrary.simpleMessage("语言"), "自定义主页",
"languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"), ),
"library": MessageLookupByLibrary.simpleMessage("媒体库"), "homeStartListening": MessageLookupByLibrary.simpleMessage("开始收听"),
"libraryChange": MessageLookupByLibrary.simpleMessage("更改媒体库"), "language": MessageLookupByLibrary.simpleMessage("语言"),
"libraryEmpty": MessageLookupByLibrary.simpleMessage("没有可用的库。"), "languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"),
"libraryLoadError": m2, "library": MessageLookupByLibrary.simpleMessage("媒体库"),
"librarySelect": MessageLookupByLibrary.simpleMessage("选择媒体库"), "libraryChange": MessageLookupByLibrary.simpleMessage("更改媒体库"),
"librarySwitchTooltip": MessageLookupByLibrary.simpleMessage("切换媒体库"), "libraryEmpty": MessageLookupByLibrary.simpleMessage("没有可用的库。"),
"libraryTooltip": MessageLookupByLibrary.simpleMessage("浏览您的媒体库"), "libraryLoadError": m2,
"loading": MessageLookupByLibrary.simpleMessage("加载中..."), "librarySelect": MessageLookupByLibrary.simpleMessage("选择媒体库"),
"logs": MessageLookupByLibrary.simpleMessage("日志"), "librarySwitchTooltip": MessageLookupByLibrary.simpleMessage("切换媒体库"),
"no": MessageLookupByLibrary.simpleMessage(""), "libraryTooltip": MessageLookupByLibrary.simpleMessage("浏览您的媒体库"),
"notImplemented": MessageLookupByLibrary.simpleMessage("未实现"), "loading": MessageLookupByLibrary.simpleMessage("加载中..."),
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage("通知媒体播放器"), "logs": MessageLookupByLibrary.simpleMessage("日志"),
"notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage( "no": MessageLookupByLibrary.simpleMessage(""),
"在通知中自定义媒体播放器", "notImplemented": MessageLookupByLibrary.simpleMessage("未实现"),
), "notificationMediaPlayer":
"ok": MessageLookupByLibrary.simpleMessage("确定"), MessageLookupByLibrary.simpleMessage("通知媒体播放器"),
"pause": MessageLookupByLibrary.simpleMessage("暂停"), "notificationMediaPlayerDescription":
"play": MessageLookupByLibrary.simpleMessage("播放"), MessageLookupByLibrary.simpleMessage(
"playerSettings": MessageLookupByLibrary.simpleMessage("播放器设置"), "在通知中自定义媒体播放器",
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage( ),
"自定义播放器设置", "ok": MessageLookupByLibrary.simpleMessage("确定"),
), "pause": MessageLookupByLibrary.simpleMessage("暂停"),
"playerSettingsPlaybackReporting": MessageLookupByLibrary.simpleMessage( "play": MessageLookupByLibrary.simpleMessage("播放"),
"回放报告", "playerSettings": MessageLookupByLibrary.simpleMessage("播放器设置"),
), "playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
"playerSettingsPlaybackReportingIgnore": "自定义播放器设置",
MessageLookupByLibrary.simpleMessage("忽略播放位置小于"), ),
"playerSettingsPlaybackReportingMinimum": "playerSettingsPlaybackReporting": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage("回放报告最小位置"), "回放报告",
"playerSettingsPlaybackReportingMinimumDescriptionHead": ),
MessageLookupByLibrary.simpleMessage("不要报告本书前 "), "playerSettingsPlaybackReportingIgnore":
"playerSettingsPlaybackReportingMinimumDescriptionTail": MessageLookupByLibrary.simpleMessage("忽略播放位置小于"),
MessageLookupByLibrary.simpleMessage(" 的播放"), "playerSettingsPlaybackReportingMinimum":
"playerSettingsRememberForEveryBook": MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage("回放报告最小位置"),
"记住每本书的播放器设置", "playerSettingsPlaybackReportingMinimumDescriptionHead":
), MessageLookupByLibrary.simpleMessage("不要报告本书前 "),
"playerSettingsRememberForEveryBookDescription": "playerSettingsPlaybackReportingMinimumDescriptionTail":
MessageLookupByLibrary.simpleMessage("每本书都会记住播放速度、音量等设置"), MessageLookupByLibrary.simpleMessage(" 的播放"),
"playerSettingsSpeedDefault": MessageLookupByLibrary.simpleMessage( "playerSettingsRememberForEveryBook":
"默认播放速度", MessageLookupByLibrary.simpleMessage(
), "记住每本书的播放器设置",
"playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage( ),
"播放速度选项", "playerSettingsRememberForEveryBookDescription":
), MessageLookupByLibrary.simpleMessage("每本书都会记住播放速度、音量等设置"),
"playlistsMine": MessageLookupByLibrary.simpleMessage("播放列表"), "playerSettingsSpeedDefault": MessageLookupByLibrary.simpleMessage(
"readLess": MessageLookupByLibrary.simpleMessage("折叠"), "默认播放速度",
"readMore": MessageLookupByLibrary.simpleMessage("展开"), ),
"refresh": MessageLookupByLibrary.simpleMessage("刷新"), "playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage(
"reset": MessageLookupByLibrary.simpleMessage("重置"), "播放速度选项",
"resetAppSettings": MessageLookupByLibrary.simpleMessage("重置应用程序设置"), ),
"resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage( "playlistsMine": MessageLookupByLibrary.simpleMessage("播放列表"),
"将应用程序设置重置为默认值", "readLess": MessageLookupByLibrary.simpleMessage("折叠"),
), "readMore": MessageLookupByLibrary.simpleMessage("展开"),
"resetAppSettingsDialog": MessageLookupByLibrary.simpleMessage( "refresh": MessageLookupByLibrary.simpleMessage("刷新"),
"您确定要重置应用程序设置吗?", "reset": MessageLookupByLibrary.simpleMessage("重置"),
), "resetAppSettings": MessageLookupByLibrary.simpleMessage("重置应用程序设置"),
"restore": MessageLookupByLibrary.simpleMessage("恢复"), "resetAppSettingsDescription": MessageLookupByLibrary.simpleMessage(
"restoreBackup": MessageLookupByLibrary.simpleMessage("恢复备份"), "将应用程序设置重置为默认值",
"restoreBackupHint": MessageLookupByLibrary.simpleMessage("将备份粘贴到此处"), ),
"restoreBackupInvalid": MessageLookupByLibrary.simpleMessage("无效备份"), "resetAppSettingsDialog": MessageLookupByLibrary.simpleMessage(
"restoreBackupSuccess": MessageLookupByLibrary.simpleMessage("设置已恢复"), "您确定要重置应用程序设置吗?",
"restoreBackupValidator": MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"), ),
"restoreDescription": MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"), "restore": MessageLookupByLibrary.simpleMessage("恢复"),
"resume": MessageLookupByLibrary.simpleMessage("继续"), "restoreBackup": MessageLookupByLibrary.simpleMessage("恢复备份"),
"retry": MessageLookupByLibrary.simpleMessage("重试"), "restoreBackupHint": MessageLookupByLibrary.simpleMessage("将备份粘贴到此处"),
"settings": MessageLookupByLibrary.simpleMessage("设置"), "restoreBackupInvalid": MessageLookupByLibrary.simpleMessage("无效备份"),
"shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"), "restoreBackupSuccess": MessageLookupByLibrary.simpleMessage("设置已恢复"),
"shakeDetectorDescription": MessageLookupByLibrary.simpleMessage( "restoreBackupValidator":
"自定义抖动检测器设置", MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"),
), "restoreDescription":
"themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"), MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"),
"themeSettingsDescription": MessageLookupByLibrary.simpleMessage("自定义应用主题"), "resume": MessageLookupByLibrary.simpleMessage("继续"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "retry": MessageLookupByLibrary.simpleMessage("重试"),
"webVersion": MessageLookupByLibrary.simpleMessage("Web版本"), "settings": MessageLookupByLibrary.simpleMessage("设置"),
"yes": MessageLookupByLibrary.simpleMessage(""), "shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"),
"you": MessageLookupByLibrary.simpleMessage("我的"), "shakeDetectorDescription": MessageLookupByLibrary.simpleMessage(
"youTooltip": MessageLookupByLibrary.simpleMessage("您的个人资料和设置"), "自定义抖动检测器设置",
}; ),
"themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"),
"themeSettingsDescription":
MessageLookupByLibrary.simpleMessage("自定义应用主题"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
"webVersion": MessageLookupByLibrary.simpleMessage("Web版本"),
"yes": MessageLookupByLibrary.simpleMessage(""),
"you": MessageLookupByLibrary.simpleMessage("我的"),
"youTooltip": MessageLookupByLibrary.simpleMessage("您的个人资料和设置"),
};
} }

View file

@ -66,7 +66,8 @@ class MyApp extends ConsumerWidget {
ColorScheme lightColorScheme = brandLightColorScheme; ColorScheme lightColorScheme = brandLightColorScheme;
ColorScheme darkColorScheme = brandDarkColorScheme; ColorScheme darkColorScheme = brandDarkColorScheme;
final shouldUseHighContrast = themeSettings.highContrast || MediaQuery.of(context).highContrast; final shouldUseHighContrast =
themeSettings.highContrast || MediaQuery.of(context).highContrast;
if (shouldUseHighContrast) { if (shouldUseHighContrast) {
lightColorScheme = lightColorScheme.copyWith( lightColorScheme = lightColorScheme.copyWith(
@ -78,7 +79,8 @@ class MyApp extends ConsumerWidget {
} }
if (themeSettings.useMaterialThemeFromSystem) { if (themeSettings.useMaterialThemeFromSystem) {
var themes = ref.watch(systemThemeProvider(highContrast: shouldUseHighContrast)); var themes =
ref.watch(systemThemeProvider(highContrast: shouldUseHighContrast));
if (themes.valueOrNull != null) { if (themes.valueOrNull != null) {
lightColorScheme = themes.valueOrNull!.$1; lightColorScheme = themes.valueOrNull!.$1;
darkColorScheme = themes.valueOrNull!.$2; darkColorScheme = themes.valueOrNull!.$2;

View file

@ -7,7 +7,8 @@ import 'package:vaani/generated/l10n.dart';
import 'package:vaani/main.dart'; import 'package:vaani/main.dart';
import 'package:vaani/router/router.dart'; import 'package:vaani/router/router.dart';
import 'package:vaani/settings/api_settings_provider.dart'; import 'package:vaani/settings/api_settings_provider.dart';
import 'package:vaani/settings/app_settings_provider.dart' show appSettingsProvider; import 'package:vaani/settings/app_settings_provider.dart'
show appSettingsProvider;
import 'package:vaani/settings/constants.dart'; import 'package:vaani/settings/constants.dart';
import '../shared/widgets/shelves/home_shelf.dart'; import '../shared/widgets/shelves/home_shelf.dart';
@ -72,9 +73,12 @@ class HomePage extends HookConsumerWidget {
// check if showPlayButton is enabled for the shelf // check if showPlayButton is enabled for the shelf
// using the id of the shelf // using the id of the shelf
final showPlayButton = switch (shelf.id) { final showPlayButton = switch (shelf.id) {
'continue-listening' => homePageSettings.showPlayButtonOnContinueListeningShelf, 'continue-listening' =>
'continue-series' => homePageSettings.showPlayButtonOnContinueSeriesShelf, homePageSettings.showPlayButtonOnContinueListeningShelf,
'listen-again' => homePageSettings.showPlayButtonOnListenAgainShelf, 'continue-series' =>
homePageSettings.showPlayButtonOnContinueSeriesShelf,
'listen-again' =>
homePageSettings.showPlayButtonOnListenAgainShelf,
_ => homePageSettings.showPlayButtonOnAllRemainingShelves, _ => homePageSettings.showPlayButtonOnAllRemainingShelves,
}; };
final showLabel = switch (shelf.label) { final showLabel = switch (shelf.label) {
@ -111,7 +115,8 @@ class HomePage extends HookConsumerWidget {
}, },
loading: () => const HomePageSkeleton(), loading: () => const HomePageSkeleton(),
error: (error, stack) { error: (error, stack) {
if (apiSettings.activeUser == null || apiSettings.activeServer == null) { if (apiSettings.activeUser == null ||
apiSettings.activeServer == null) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View file

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

View file

@ -116,7 +116,8 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
// extended: false, // extended: false,
destinations: _navigationItems(context).map((item) { destinations: _navigationItems(context).map((item) {
final isDestinationLibrary = item.name == S.of(context).library; final isDestinationLibrary = item.name == S.of(context).library;
var currentLibrary = ref.watch(currentLibraryProvider).valueOrNull; var currentLibrary =
ref.watch(currentLibraryProvider).valueOrNull;
final libraryIcon = AbsIcons.getIconByName( final libraryIcon = AbsIcons.getIconByName(
currentLibrary?.icon, currentLibrary?.icon,
); );
@ -125,9 +126,13 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon, isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
), ),
selectedIcon: Icon( selectedIcon: Icon(
isDestinationLibrary ? libraryIcon ?? item.activeIcon : item.activeIcon, isDestinationLibrary
? libraryIcon ?? item.activeIcon
: item.activeIcon,
), ),
label: Text(isDestinationLibrary ? currentLibrary?.name ?? item.name : item.name), label: Text(isDestinationLibrary
? currentLibrary?.name ?? item.name
: item.name),
// tooltip: item.tooltip, // tooltip: item.tooltip,
); );
// if (isDestinationLibrary) { // if (isDestinationLibrary) {
@ -166,8 +171,8 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
// useValueListenable(ref.watch(playerExpandProgressNotifierProvider)); // useValueListenable(ref.watch(playerExpandProgressNotifierProvider));
final playerProgress = ref.watch(playerHeightProvider); final playerProgress = ref.watch(playerHeightProvider);
final playerMaxHeight = MediaQuery.of(context).size.height; final playerMaxHeight = MediaQuery.of(context).size.height;
var percentExpandedMiniPlayer = var percentExpandedMiniPlayer = (playerProgress - playerMinHeight) /
(playerProgress - playerMinHeight) / (playerMaxHeight - playerMinHeight); (playerMaxHeight - playerMinHeight);
// Clamp the value between 0 and 1 // Clamp the value between 0 and 1
percentExpandedMiniPlayer = percentExpandedMiniPlayer.clamp(0.0, 1.0); percentExpandedMiniPlayer = percentExpandedMiniPlayer.clamp(0.0, 1.0);
return Opacity( return Opacity(
@ -193,9 +198,13 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon, isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
), ),
selectedIcon: Icon( selectedIcon: Icon(
isDestinationLibrary ? libraryIcon ?? item.activeIcon : item.activeIcon, isDestinationLibrary
? libraryIcon ?? item.activeIcon
: item.activeIcon,
), ),
label: isDestinationLibrary ? currentLibrary?.name ?? item.name : item.name, label: isDestinationLibrary
? currentLibrary?.name ?? item.name
: item.name,
tooltip: item.tooltip, tooltip: item.tooltip,
); );
if (isDestinationLibrary) { if (isDestinationLibrary) {

View file

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

View file

@ -128,7 +128,8 @@ class AppSettingsPage extends HookConsumerWidget {
SettingsTile( SettingsTile(
title: Text(S.of(context).notificationMediaPlayer), title: Text(S.of(context).notificationMediaPlayer),
leading: const Icon(Icons.play_lesson), leading: const Icon(Icons.play_lesson),
description: Text(S.of(context).notificationMediaPlayerDescription), description:
Text(S.of(context).notificationMediaPlayerDescription),
onPressed: (context) { onPressed: (context) {
context.pushNamed(Routes.notificationSettings.name); context.pushNamed(Routes.notificationSettings.name);
}, },

View file

@ -51,7 +51,8 @@ class PlayerSettingsPage extends HookConsumerWidget {
title: Text(S.of(context).playerSettingsSpeedDefault), title: Text(S.of(context).playerSettingsSpeedDefault),
trailing: Text( trailing: Text(
'${playerSettings.preferredDefaultSpeed}x', '${playerSettings.preferredDefaultSpeed}x',
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold), style:
TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
), ),
leading: const Icon(Icons.speed), leading: const Icon(Icons.speed),
onPressed: (context) async { onPressed: (context) async {
@ -75,7 +76,8 @@ class PlayerSettingsPage extends HookConsumerWidget {
title: Text(S.of(context).playerSettingsSpeedOptions), title: Text(S.of(context).playerSettingsSpeedOptions),
description: Text( description: Text(
playerSettings.speedOptions.map((e) => '${e}x').join(', '), playerSettings.speedOptions.map((e) => '${e}x').join(', '),
style: TextStyle(fontWeight: FontWeight.bold, color: primaryColor), style:
TextStyle(fontWeight: FontWeight.bold, color: primaryColor),
), ),
leading: const Icon(Icons.speed), leading: const Icon(Icons.speed),
onPressed: (context) async { onPressed: (context) async {
@ -105,17 +107,22 @@ class PlayerSettingsPage extends HookConsumerWidget {
title: Text(S.of(context).playerSettingsPlaybackReportingMinimum), title: Text(S.of(context).playerSettingsPlaybackReportingMinimum),
description: Text.rich( description: Text.rich(
TextSpan( TextSpan(
text: S.of(context).playerSettingsPlaybackReportingMinimumDescriptionHead, text: S
.of(context)
.playerSettingsPlaybackReportingMinimumDescriptionHead,
children: [ children: [
TextSpan( TextSpan(
text: playerSettings.minimumPositionForReporting.smartBinaryFormat, text: playerSettings
.minimumPositionForReporting.smartBinaryFormat,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: primaryColor, color: primaryColor,
), ),
), ),
TextSpan( TextSpan(
text: S.of(context).playerSettingsPlaybackReportingMinimumDescriptionTail), text: S
.of(context)
.playerSettingsPlaybackReportingMinimumDescriptionTail),
], ],
), ),
), ),
@ -125,7 +132,8 @@ class PlayerSettingsPage extends HookConsumerWidget {
context: context, context: context,
builder: (context) { builder: (context) {
return TimeDurationSelector( return TimeDurationSelector(
title: Text(S.of(context).playerSettingsPlaybackReportingIgnore), title: Text(
S.of(context).playerSettingsPlaybackReportingIgnore),
baseUnit: BaseUnit.second, baseUnit: BaseUnit.second,
initialValue: playerSettings.minimumPositionForReporting, initialValue: playerSettings.minimumPositionForReporting,
); );
@ -148,7 +156,8 @@ class PlayerSettingsPage extends HookConsumerWidget {
text: 'Mark complete when less than ', text: 'Mark complete when less than ',
children: [ children: [
TextSpan( TextSpan(
text: playerSettings.markCompleteWhenTimeLeft.smartBinaryFormat, text: playerSettings
.markCompleteWhenTimeLeft.smartBinaryFormat,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: primaryColor, color: primaryColor,
@ -187,7 +196,8 @@ class PlayerSettingsPage extends HookConsumerWidget {
text: 'Report progress every ', text: 'Report progress every ',
children: [ children: [
TextSpan( TextSpan(
text: playerSettings.playbackReportInterval.smartBinaryFormat, text: playerSettings
.playbackReportInterval.smartBinaryFormat,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: primaryColor, color: primaryColor,
@ -231,7 +241,8 @@ class PlayerSettingsPage extends HookConsumerWidget {
description: const Text( description: const Text(
'Show the total progress of the book in the player', 'Show the total progress of the book in the player',
), ),
initialValue: playerSettings.expandedPlayerSettings.showTotalProgress, initialValue:
playerSettings.expandedPlayerSettings.showTotalProgress,
onToggle: (value) { onToggle: (value) {
ref.read(appSettingsProvider.notifier).update( ref.read(appSettingsProvider.notifier).update(
appSettings.copyWith.playerSettings appSettings.copyWith.playerSettings
@ -246,11 +257,13 @@ class PlayerSettingsPage extends HookConsumerWidget {
description: const Text( description: const Text(
'Show the progress of the current chapter in the player', 'Show the progress of the current chapter in the player',
), ),
initialValue: playerSettings.expandedPlayerSettings.showChapterProgress, initialValue:
playerSettings.expandedPlayerSettings.showChapterProgress,
onToggle: (value) { onToggle: (value) {
ref.read(appSettingsProvider.notifier).update( ref.read(appSettingsProvider.notifier).update(
appSettings.copyWith.playerSettings( appSettings.copyWith.playerSettings(
expandedPlayerSettings: playerSettings.expandedPlayerSettings expandedPlayerSettings: playerSettings
.expandedPlayerSettings
.copyWith(showChapterProgress: value), .copyWith(showChapterProgress: value),
), ),
); );
@ -309,7 +322,8 @@ class SpeedPicker extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final speedController = useTextEditingController(text: initialValue.toString()); final speedController =
useTextEditingController(text: initialValue.toString());
final speed = useState<double?>(initialValue); final speed = useState<double?>(initialValue);
return AlertDialog( return AlertDialog(
title: const Text('Select Speed'), title: const Text('Select Speed'),
@ -368,7 +382,8 @@ class SpeedOptionsPicker extends HookConsumerWidget {
onDeleted: speed == 1 onDeleted: speed == 1
? null ? null
: () { : () {
speedOptions.value = speedOptions.value.where((element) { speedOptions.value =
speedOptions.value.where((element) {
// speed option 1 can't be removed // speed option 1 can't be removed
return element != speed; return element != speed;
}).toList(); }).toList();

View file

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