chore: 优化进度条显示

This commit is contained in:
rang 2026-01-13 11:56:49 +08:00
parent 03cec3f4b6
commit d96995a863
27 changed files with 1229 additions and 1311 deletions

View file

@ -11,7 +11,7 @@ import 'package:vaani/shared/extensions/obfuscation.dart';
part 'authenticated_users_provider.g.dart'; part 'authenticated_users_provider.g.dart';
final _box = AvailableHiveBoxes.authenticatedUserBox; final _box = HiveBoxes.authenticatedUserBox;
final _logger = Logger('authenticated_users_provider'); final _logger = Logger('authenticated_users_provider');

View file

@ -9,7 +9,7 @@ import 'package:vaani/shared/extensions/obfuscation.dart';
part 'server_provider.g.dart'; part 'server_provider.g.dart';
final _box = AvailableHiveBoxes.serverBox; final _box = HiveBoxes.serverBox;
final _logger = Logger('AudiobookShelfServerProvider'); final _logger = Logger('AudiobookShelfServerProvider');

View file

@ -4,48 +4,34 @@ import 'package:vaani/features/per_book_settings/models/book_settings.dart';
import 'package:vaani/features/settings/models/models.dart'; import 'package:vaani/features/settings/models/models.dart';
@immutable @immutable
class AvailableHiveBoxes { class HiveBoxes {
const AvailableHiveBoxes._(); const HiveBoxes._();
static late final Box<dynamic> basicBox;
/// Box for storing user preferences as [AppSettings]
static late final Box<AppSettings> userPrefsBox;
/// Box for storing [ApiSettings]
static late final Box<ApiSettings> apiSettingsBox;
/// stores the a list of [AudiobookShelfServer]
static late final Box<AudiobookShelfServer> serverBox;
/// stores the a list of [AuthenticatedUser]
static late final Box<AuthenticatedUser> authenticatedUserBox;
/// stores the a list of [BookSettings]
static late final Box<BookSettings> individualBookSettingsBox;
static Future<void> init() async { static Future<void> init() async {
await Hive.openBox('basicTypes'); basicBox = await Hive.openBox('basicTypes');
userPrefsBox = await Hive.openBox<AppSettings>('userPrefs');
/// Box for storing user preferences as [AppSettings] apiSettingsBox = await Hive.openBox<ApiSettings>('apiSettings');
await Hive.openBox<AppSettings>('userPrefs'); serverBox =
/// Box for storing [ApiSettings]
await Hive.openBox<ApiSettings>('apiSettings');
/// stores the a list of [AudiobookShelfServer]
await Hive.openBox<AudiobookShelfServer>('audiobookShelfServer'); await Hive.openBox<AudiobookShelfServer>('audiobookShelfServer');
authenticatedUserBox =
/// stores the a list of [AuthenticatedUser]
await Hive.openBox<AuthenticatedUser>('authenticatedUser'); await Hive.openBox<AuthenticatedUser>('authenticatedUser');
individualBookSettingsBox =
/// stores the a list of [BookSettings]
await Hive.openBox<BookSettings>('bookSettings'); await Hive.openBox<BookSettings>('bookSettings');
} }
static final basicBox = Hive.box('basicTypes');
/// Box for storing user preferences as [AppSettings]
static final userPrefsBox = Hive.box<AppSettings>('userPrefs');
/// Box for storing [ApiSettings]
static final apiSettingsBox = Hive.box<ApiSettings>('apiSettings');
/// stores the a list of [AudiobookShelfServer]
static final serverBox =
Hive.box<AudiobookShelfServer>('audiobookShelfServer');
/// stores the a list of [AuthenticatedUser]
static final authenticatedUserBox =
Hive.box<AuthenticatedUser>('authenticatedUser');
/// stores the a list of [BookSettings]
static final individualBookSettingsBox =
Hive.box<BookSettings>('bookSettings');
} }

View file

@ -18,6 +18,6 @@ Future initStorage() async {
// Hive.defaultDirectory = appDocumentsDir.path; // Hive.defaultDirectory = appDocumentsDir.path;
// appLogger.config('Hive storage directory init: ${Hive.defaultDirectory}'); // appLogger.config('Hive storage directory init: ${Hive.defaultDirectory}');
await registerModels(); registerModels();
await AvailableHiveBoxes.init(); await HiveBoxes.init();
} }

View file

@ -5,7 +5,7 @@ import 'package:vaani/features/per_book_settings/models/book_settings.dart';
import 'package:vaani/features/settings/models/models.dart'; import 'package:vaani/features/settings/models/models.dart';
// register all models to Hive for serialization // register all models to Hive for serialization
Future registerModels() async { void registerModels() {
Hive.registerAdapter( Hive.registerAdapter(
JsonAdapter<AppSettings>( JsonAdapter<AppSettings>(
id: 1, id: 1,

View file

@ -7,7 +7,7 @@ import 'package:vaani/features/per_book_settings/models/nullable_player_settings
part 'book_settings_provider.g.dart'; part 'book_settings_provider.g.dart';
final _box = AvailableHiveBoxes.individualBookSettingsBox; final _box = HiveBoxes.individualBookSettingsBox;
final _logger = Logger('BookSettingsProvider'); final _logger = Logger('BookSettingsProvider');

View file

@ -2,6 +2,7 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart'; import 'package:just_audio_background/just_audio_background.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -18,8 +19,9 @@ final offset = Duration(milliseconds: 10);
final _logger = Logger('AbsAudioPlayer'); final _logger = Logger('AbsAudioPlayer');
class AbsAudioPlayer { class AbsAudioPlayer {
final Ref ref;
late final AudioPlayer _player; late final AudioPlayer _player;
AbsAudioPlayer(AudioPlayer player) : _player = player { AbsAudioPlayer(AudioPlayer player, this.ref) : _player = player {
_player.positionStream.listen((position) { _player.positionStream.listen((position) {
final chapter = currentChapter; final chapter = currentChapter;
if (positionInBook <= (chapter?.start ?? Duration.zero) || if (positionInBook <= (chapter?.start ?? Duration.zero) ||
@ -53,34 +55,23 @@ class AbsAudioPlayer {
List<Uri>? downloadedUris, List<Uri>? downloadedUris,
Duration? start, Duration? start,
Duration? end, Duration? end,
bool force = false,
}) async { }) async {
if (_bookStreamController.nvalue == book) { if (!force && _bookStreamController.nvalue == book) {
_logger.info('Book is the same, doing nothing'); _logger.info('Book is the same, doing nothing');
return; return;
} }
_bookStreamController.add(book); _bookStreamController.add(book);
final appSettings = loadOrCreateAppSettings();
final currentTrack = book.findTrackAtTime(initialPosition ?? Duration.zero); final currentTrack = book.findTrackAtTime(initialPosition ?? Duration.zero);
final indexTrack = book.tracks.indexOf(currentTrack); final indexTrack = book.tracks.indexOf(currentTrack);
final positionInTrack = initialPosition != null final positionInTrack = initialPosition != null
? initialPosition - currentTrack.startOffset ? initialPosition - currentTrack.startOffset
: null; : null;
final title = appSettings.notificationSettings.primaryTitle
.formatNotificationTitle(book);
final artist = appSettings.notificationSettings.secondaryTitle
.formatNotificationTitle(book);
chapterStreamController chapterStreamController
.add(book.findChapterAtTime(initialPosition ?? Duration.zero)); .add(book.findChapterAtTime(initialPosition ?? Duration.zero));
// final item = MediaItem( final title = primaryTitle();
// id: book.libraryItemId, final artist = secondaryTitle();
// title: title,
// artist: artist,
// duration: currentChapter?.duration ?? book.duration,
// artUri: Uri.parse(
// '$baseUrl/api/items/${book.libraryItemId}/cover?token=$token',
// ),
// );
mediaItem(track) => MediaItem( mediaItem(track) => MediaItem(
id: book.libraryItemId + track.index.toString(), id: book.libraryItemId + track.index.toString(),
@ -91,6 +82,13 @@ class AbsAudioPlayer {
'$baseUrl/api/items/${book.libraryItemId}/cover?token=$token', '$baseUrl/api/items/${book.libraryItemId}/cover?token=$token',
), ),
); );
if (start != null && start > Duration.zero ||
end != null && end > Duration.zero) {
_logger.info(
'Skip the opening ${start?.inSeconds} seconds, end ${end?.inSeconds} seconds',
);
}
List<AudioSource> audioSources = start != null && start > Duration.zero || List<AudioSource> audioSources = start != null && start > Duration.zero ||
end != null && end > Duration.zero end != null && end > Duration.zero
? book.tracks ? book.tracks
@ -270,6 +268,26 @@ class AbsAudioPlayer {
_bookStreamController.close(); _bookStreamController.close();
chapterStreamController.close(); chapterStreamController.close();
} }
String primaryTitle() {
final appSettings = ref.read(appSettingsProvider);
final currentBook = book;
if (currentBook != null) {
return appSettings.notificationSettings.primaryTitle
.formatNotificationTitle(currentBook, chapter: currentChapter);
}
return "";
}
String secondaryTitle() {
final appSettings = ref.read(appSettingsProvider);
final currentBook = book;
if (currentBook != null) {
return appSettings.notificationSettings.secondaryTitle
.formatNotificationTitle(currentBook, chapter: currentChapter);
}
return "";
}
} }
Uri _getUri( Uri _getUri(
@ -328,14 +346,14 @@ extension BookExpandedExtension on BookExpanded {
} }
extension FormatNotificationTitle on String { extension FormatNotificationTitle on String {
String formatNotificationTitle(BookExpanded book) { String formatNotificationTitle(BookExpanded book, {BookChapter? chapter}) {
return replaceAllMapped( return replaceAllMapped(
RegExp(r'\$(\w+)'), RegExp(r'\$(\w+)'),
(match) { (match) {
final type = match.group(1); final type = match.group(1);
return NotificationTitleType.values return NotificationTitleType.values
.firstWhere((element) => element.name == type) .firstWhere((element) => element.name == type)
.extractFrom(book) ?? .extractFrom(book, chapter: chapter) ??
match.group(0) ?? match.group(0) ??
''; '';
}, },
@ -344,13 +362,16 @@ extension FormatNotificationTitle on String {
} }
extension NotificationTitleUtils on NotificationTitleType { extension NotificationTitleUtils on NotificationTitleType {
String? extractFrom(BookExpanded book) { String? extractFrom(BookExpanded book, {BookChapter? chapter}) {
var bookMetadataExpanded = book.metadata.asBookMetadataExpanded; var bookMetadataExpanded = book.metadata.asBookMetadataExpanded;
switch (this) { switch (this) {
case NotificationTitleType.bookTitle: case NotificationTitleType.bookTitle:
return bookMetadataExpanded.title; return bookMetadataExpanded.title;
case NotificationTitleType.chapterTitle: case NotificationTitleType.chapterTitle:
// TODO: implement chapter title; depends on https://github.com/Dr-Blank/Vaani/issues/2 // TODO: implement chapter title; depends on https://github.com/Dr-Blank/Vaani/issues/2
if (chapter != null) {
return chapter.title;
}
return bookMetadataExpanded.title; return bookMetadataExpanded.title;
case NotificationTitleType.author: case NotificationTitleType.author:
return bookMetadataExpanded.authorName; return bookMetadataExpanded.authorName;

View file

@ -2,64 +2,24 @@ import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio/just_audio.dart' as audio; import 'package:just_audio/just_audio.dart' as audio;
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shelfsdk/audiobookshelf_api.dart' as api; import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk;
import 'package:vaani/api/api_provider.dart'; import 'package:vaani/api/api_provider.dart';
import 'package:vaani/api/library_item_provider.dart'; import 'package:vaani/api/library_item_provider.dart';
import 'package:vaani/db/available_boxes.dart'; import 'package:vaani/db/available_boxes.dart';
import 'package:vaani/db/cache/cache_key.dart'; import 'package:vaani/db/cache/cache_key.dart';
import 'package:vaani/features/downloads/providers/download_manager.dart'; import 'package:vaani/features/downloads/providers/download_manager.dart';
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart'; import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
import 'package:vaani/features/player/core/abs_audio_player.dart' import 'package:vaani/features/player/core/abs_audio_player.dart';
show AbsAudioPlayer;
import 'package:vaani/features/settings/app_settings_provider.dart'; import 'package:vaani/features/settings/app_settings_provider.dart';
import 'package:vaani/shared/extensions/box.dart'; import 'package:vaani/shared/extensions/box.dart';
import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/extensions/model_conversions.dart';
part 'abs_provider.g.dart'; part 'abs_provider.g.dart';
///
// @Riverpod(keepAlive: true)
// Future<AudioHandler> configurePlayer(Ref ref) async {
// final player = ref.read(absPlayerProvider);
// // for playing audio on windows, linux
// // for configuring how this app will interact with other audio apps
// final session = await AudioSession.instance;
// await session.configure(const AudioSessionConfiguration.speech());
// final audioService = await AudioService.init(
// builder: () => AbsAudioHandler(player),
// config: const AudioServiceConfig(
// androidNotificationChannelId: 'dr.blank.vaani.channel.audio',
// androidNotificationChannelName: 'ABSPlayback',
// androidNotificationChannelDescription:
// 'Needed to control audio from lock screen',
// androidNotificationOngoing: false,
// androidStopForegroundOnPause: false,
// androidNotificationIcon: 'drawable/ic_stat_logo',
// preloadArtwork: true,
// // fastForwardInterval: Duration(seconds: 20),
// // rewindInterval: Duration(seconds: 20),
// ),
// );
// _logger.finer('created simple player');
// return audioService;
// }
// just_audio
// @Riverpod(keepAlive: true)
// core.AbsAudioPlayer audioPlayer(Ref ref) {
// final player = AbsPlatformAudioPlayer();
// // final player = AbsMpvAudioPlayer();
// ref.onDispose(player.dispose);
// return player;
// }
// //
@riverpod @riverpod
bool playerActive(Ref ref) { bool playerActive(Ref ref) {
return false; return ref.watch(currentBookProvider) != null;
} }
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
@ -76,23 +36,23 @@ class AbsPlayer extends _$AbsPlayer {
@override @override
AbsAudioPlayer build() { AbsAudioPlayer build() {
final audioPlayer = ref.watch(simpleAudioPlayerProvider); final audioPlayer = ref.watch(simpleAudioPlayerProvider);
return AbsAudioPlayer(audioPlayer); return AbsAudioPlayer(audioPlayer, ref);
} }
Future<void> load( Future<void> load(
api.BookExpanded book, { shelfsdk.BookExpanded book, {
Duration? initialPosition, Duration? initialPosition,
bool play = true, bool play = true,
bool force = false,
}) async { }) async {
if (state.book == book || state.book?.libraryItemId == book.libraryItemId) { if (!force &&
(state.book == book ||
state.book?.libraryItemId == book.libraryItemId)) {
state.playOrPause(); state.playOrPause();
return; return;
} }
final api = ref.read(authenticatedApiProvider); final api = ref.read(authenticatedApiProvider);
final downloadManager = ref.read(simpleDownloadManagerProvider); final downloadManager = ref.read(simpleDownloadManagerProvider);
print(downloadManager.basePath);
final libItem = final libItem =
await ref.read(libraryItemProvider(book.libraryItemId).future); await ref.read(libraryItemProvider(book.libraryItemId).future);
final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem); final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
@ -112,6 +72,7 @@ class AbsPlayer extends _$AbsPlayer {
downloadedUris: downloadedUris, downloadedUris: downloadedUris,
start: bookSettings.playerSettings.skipChapterStart, start: bookSettings.playerSettings.skipChapterStart,
end: bookSettings.playerSettings.skipChapterEnd, end: bookSettings.playerSettings.skipChapterEnd,
force: force,
); );
// set the volume // set the volume
await state.setVolume( await state.setVolume(
@ -131,67 +92,6 @@ class AbsPlayer extends _$AbsPlayer {
} }
} }
/// riverpod状态
// @Riverpod(keepAlive: true)
// class AbsPlayer extends _$AbsPlayer {
// @override
// core.AbsAudioPlayer build() {
// final audioPlayer = ref.watch(audioPlayerProvider);
// return audioPlayer;
// }
// Future<void> load(
// api.BookExpanded book, {
// Duration? initialPosition,
// bool play = true,
// }) async {
// if (state.book == book || state.book?.libraryItemId == book.libraryItemId) {
// state.playOrPause();
// return;
// }
// final api = ref.read(authenticatedApiProvider);
// final downloadManager = ref.read(simpleDownloadManagerProvider);
// print(downloadManager.basePath);
// final libItem =
// await ref.read(libraryItemProvider(book.libraryItemId).future);
// final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
// final bookSettings = ref.read(bookSettingsProvider(book.libraryItemId));
// var bookPlayerSettings = bookSettings.playerSettings;
// var appPlayerSettings = ref.read(appSettingsProvider).playerSettings;
// var configurePlayerForEveryBook =
// appPlayerSettings.configurePlayerForEveryBook;
// await state.load(
// book,
// baseUrl: api.baseUrl,
// token: api.token!,
// initialPosition: initialPosition,
// downloadedUris: downloadedUris,
// start: bookSettings.playerSettings.skipChapterStart,
// end: bookSettings.playerSettings.skipChapterEnd,
// );
// // set the volume
// await state.setVolume(
// configurePlayerForEveryBook
// ? bookPlayerSettings.preferredDefaultVolume ??
// appPlayerSettings.preferredDefaultVolume
// : appPlayerSettings.preferredDefaultVolume,
// );
// // set the speed
// await state.setSpeed(
// configurePlayerForEveryBook
// ? bookPlayerSettings.preferredDefaultSpeed ??
// appPlayerSettings.preferredDefaultSpeed
// : appPlayerSettings.preferredDefaultSpeed,
// );
// if (play) await state.play();
// }
// }
@riverpod @riverpod
class PlayerState extends _$PlayerState { class PlayerState extends _$PlayerState {
@override @override
@ -218,21 +118,24 @@ class PlayerState extends _$PlayerState {
} }
@riverpod @riverpod
Future<Duration?> currentTime(Ref ref, String libraryItemId) async { class CurrentTime extends _$CurrentTime {
@override
Future<shelfsdk.MediaProgress?> build(String libraryItemId) async {
final me = await ref.watch(meProvider.future); final me = await ref.watch(meProvider.future);
final userProgress = me.mediaProgress final userProgress = me.mediaProgress
?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId); ?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId);
return userProgress?.currentTime; return userProgress;
}
} }
@riverpod @riverpod
class CurrentBook extends _$CurrentBook { class CurrentBook extends _$CurrentBook {
@override @override
api.BookExpanded? build() { shelfsdk.BookExpanded? build() {
listenSelf((previous, next) { listenSelf((previous, next) {
if (previous == null && next == null) { if (previous == null && next == null) {
final activeLibraryItemId = AvailableHiveBoxes.basicBox final activeLibraryItemId =
.getAs<String>(CacheKey.activeLibraryItemId); HiveBoxes.basicBox.getAs<String>(CacheKey.activeLibraryItemId);
if (activeLibraryItemId != null) { if (activeLibraryItemId != null) {
update(activeLibraryItemId, play: false); update(activeLibraryItemId, play: false);
} }
@ -240,31 +143,29 @@ class CurrentBook extends _$CurrentBook {
}); });
return null; return null;
} }
// @override
// api.BookExpanded? build() {
// final player = ref.read(absPlayerProvider);
// player.bookStream.listen((book) {
// if (book != state) {
// state = book;
// }
// });
// return player.book;
// }
Future<void> update(String libraryItemId, {bool play = true}) async { Future<void> update(
if (state?.libraryItemId == libraryItemId) { String libraryItemId, {
bool play = true,
bool force = false,
Duration? currentTime,
}) async {
if (!force && (state?.libraryItemId == libraryItemId)) {
ref.read(absPlayerProvider).playOrPause(); ref.read(absPlayerProvider).playOrPause();
return; return;
} }
final book = await ref.read(libraryItemProvider(libraryItemId).future); final book = await ref.read(libraryItemProvider(libraryItemId).future);
state = book.media.asBookExpanded; state = book.media.asBookExpanded;
final currentTime = final mediaProgress =
await ref.read(currentTimeProvider(libraryItemId).future); await ref.read(currentTimeProvider(libraryItemId).future);
await ref await ref.read(absPlayerProvider.notifier).load(
.read(absPlayerProvider.notifier) state!,
.load(state!, initialPosition: currentTime, play: play); initialPosition: currentTime ?? mediaProgress?.currentTime,
play: play,
force: force,
);
if (play) { if (play) {
AvailableHiveBoxes.basicBox.put( HiveBoxes.basicBox.put(
CacheKey.activeLibraryItemId, CacheKey.activeLibraryItemId,
libraryItemId, libraryItemId,
); );
@ -275,7 +176,7 @@ class CurrentBook extends _$CurrentBook {
@riverpod @riverpod
class CurrentChapter extends _$CurrentChapter { class CurrentChapter extends _$CurrentChapter {
@override @override
api.BookChapter? build() { shelfsdk.BookChapter? build() {
final player = ref.read(absPlayerProvider); final player = ref.read(absPlayerProvider);
player.chapterStream.listen((chapter) { player.chapterStream.listen((chapter) {
if (chapter != state) { if (chapter != state) {
@ -286,7 +187,48 @@ class CurrentChapter extends _$CurrentChapter {
} }
} }
///
//
@riverpod @riverpod
Stream<Duration> positionChapter(Ref ref) { Duration total(Ref ref) {
return ref.read(absPlayerProvider).positionInChapterStream; final currentBook = ref.watch(currentBookProvider);
final currentChapter = ref.watch(currentChapterProvider);
final playerSettings =
ref.watch(appSettingsProvider.select((v) => v.playerSettings));
final showChapterProgress =
playerSettings.expandedPlayerSettings.showChapterProgress;
return showChapterProgress
? ((currentChapter?.end ?? Duration.zero) -
(currentChapter?.start ?? Duration.zero))
: currentBook?.duration ?? Duration.zero;
}
//
@riverpod
Stream<Duration> progress(Ref ref) {
final player = ref.read(absPlayerProvider);
final playerSettings =
ref.watch(appSettingsProvider.select((v) => v.playerSettings));
final showChapterProgress =
playerSettings.expandedPlayerSettings.showChapterProgress;
return player.positionStream.map((position) {
return showChapterProgress
? player.getPositionInChapter(position)
: player.getPositionInBook(position);
});
}
//
@riverpod
Stream<Duration> progressBuffered(Ref ref) {
final player = ref.read(absPlayerProvider);
final playerSettings =
ref.watch(appSettingsProvider.select((v) => v.playerSettings));
final showChapterProgress =
playerSettings.expandedPlayerSettings.showChapterProgress;
return player.bufferedPositionStream.map((position) {
return showChapterProgress
? player.getPositionInChapter(position)
: player.getPositionInBook(position);
});
} }

View file

@ -6,11 +6,9 @@ part of 'abs_provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$playerActiveHash() => r'86831758035aa69d74f42ebde0a19bf7ef830910'; String _$playerActiveHash() => r'4d3e7181cf66bfdb46d5caaece56cde07f610cc4';
/// /// See also [playerActive].
///
/// Copied from [playerActive].
@ProviderFor(playerActive) @ProviderFor(playerActive)
final playerActiveProvider = AutoDisposeProvider<bool>.internal( final playerActiveProvider = AutoDisposeProvider<bool>.internal(
playerActive, playerActive,
@ -41,7 +39,87 @@ final simpleAudioPlayerProvider = Provider<audio.AudioPlayer>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef SimpleAudioPlayerRef = ProviderRef<audio.AudioPlayer>; typedef SimpleAudioPlayerRef = ProviderRef<audio.AudioPlayer>;
String _$currentTimeHash() => r'3e7f99dbf48242a5fa0a4239a0f696535d0b4ac9'; String _$totalHash() => r'2d01953862a875f6e66fe3af56868e819e33fcc8';
///
///
/// Copied from [total].
@ProviderFor(total)
final totalProvider = AutoDisposeProvider<Duration>.internal(
total,
name: r'totalProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$totalHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef TotalRef = AutoDisposeProviderRef<Duration>;
String _$progressHash() => r'7ed041be2d26a437becc9ab624322b47efbee06e';
/// See also [progress].
@ProviderFor(progress)
final progressProvider = AutoDisposeStreamProvider<Duration>.internal(
progress,
name: r'progressProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$progressHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ProgressRef = AutoDisposeStreamProviderRef<Duration>;
String _$progressBufferedHash() => r'20f886a5ad8bd4eb031eceb845201dc61dfd5fca';
/// See also [progressBuffered].
@ProviderFor(progressBuffered)
final progressBufferedProvider = AutoDisposeStreamProvider<Duration>.internal(
progressBuffered,
name: r'progressBufferedProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$progressBufferedHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ProgressBufferedRef = AutoDisposeStreamProviderRef<Duration>;
String _$absPlayerHash() => r'c43c02b600326c2d47b900cb3977cd9fae201463';
/// See also [AbsPlayer].
@ProviderFor(AbsPlayer)
final absPlayerProvider = NotifierProvider<AbsPlayer, AbsAudioPlayer>.internal(
AbsPlayer.new,
name: r'absPlayerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$absPlayerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AbsPlayer = Notifier<AbsAudioPlayer>;
String _$playerStateHash() => r'eb79bd816714f721da1c4226d4447de5dc55fc5c';
/// See also [PlayerState].
@ProviderFor(PlayerState)
final playerStateProvider =
AutoDisposeNotifierProvider<PlayerState, audio.PlayerState>.internal(
PlayerState.new,
name: r'playerStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$playerStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$PlayerState = AutoDisposeNotifier<audio.PlayerState>;
String _$currentTimeHash() => r'e2cf66f5f04cd51f5ddafd64ace395ec3bf0ede2';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@ -64,16 +142,25 @@ class _SystemHash {
} }
} }
/// See also [currentTime]. abstract class _$CurrentTime
@ProviderFor(currentTime) extends BuildlessAutoDisposeAsyncNotifier<shelfsdk.MediaProgress?> {
late final String libraryItemId;
FutureOr<shelfsdk.MediaProgress?> build(
String libraryItemId,
);
}
/// See also [CurrentTime].
@ProviderFor(CurrentTime)
const currentTimeProvider = CurrentTimeFamily(); const currentTimeProvider = CurrentTimeFamily();
/// See also [currentTime]. /// See also [CurrentTime].
class CurrentTimeFamily extends Family<AsyncValue<Duration?>> { class CurrentTimeFamily extends Family<AsyncValue<shelfsdk.MediaProgress?>> {
/// See also [currentTime]. /// See also [CurrentTime].
const CurrentTimeFamily(); const CurrentTimeFamily();
/// See also [currentTime]. /// See also [CurrentTime].
CurrentTimeProvider call( CurrentTimeProvider call(
String libraryItemId, String libraryItemId,
) { ) {
@ -106,16 +193,14 @@ class CurrentTimeFamily extends Family<AsyncValue<Duration?>> {
String? get name => r'currentTimeProvider'; String? get name => r'currentTimeProvider';
} }
/// See also [currentTime]. /// See also [CurrentTime].
class CurrentTimeProvider extends AutoDisposeFutureProvider<Duration?> { class CurrentTimeProvider extends AutoDisposeAsyncNotifierProviderImpl<
/// See also [currentTime]. CurrentTime, shelfsdk.MediaProgress?> {
/// See also [CurrentTime].
CurrentTimeProvider( CurrentTimeProvider(
String libraryItemId, String libraryItemId,
) : this._internal( ) : this._internal(
(ref) => currentTime( () => CurrentTime()..libraryItemId = libraryItemId,
ref as CurrentTimeRef,
libraryItemId,
),
from: currentTimeProvider, from: currentTimeProvider,
name: r'currentTimeProvider', name: r'currentTimeProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
@ -141,13 +226,20 @@ class CurrentTimeProvider extends AutoDisposeFutureProvider<Duration?> {
final String libraryItemId; final String libraryItemId;
@override @override
Override overrideWith( FutureOr<shelfsdk.MediaProgress?> runNotifierBuild(
FutureOr<Duration?> Function(CurrentTimeRef provider) create, covariant CurrentTime notifier,
) { ) {
return notifier.build(
libraryItemId,
);
}
@override
Override overrideWith(CurrentTime Function() create) {
return ProviderOverride( return ProviderOverride(
origin: this, origin: this,
override: CurrentTimeProvider._internal( override: CurrentTimeProvider._internal(
(ref) => create(ref as CurrentTimeRef), () => create()..libraryItemId = libraryItemId,
from: from, from: from,
name: null, name: null,
dependencies: null, dependencies: null,
@ -159,7 +251,8 @@ class CurrentTimeProvider extends AutoDisposeFutureProvider<Duration?> {
} }
@override @override
AutoDisposeFutureProviderElement<Duration?> createElement() { AutoDisposeAsyncNotifierProviderElement<CurrentTime, shelfsdk.MediaProgress?>
createElement() {
return _CurrentTimeProviderElement(this); return _CurrentTimeProviderElement(this);
} }
@ -179,73 +272,27 @@ class CurrentTimeProvider extends AutoDisposeFutureProvider<Duration?> {
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
mixin CurrentTimeRef on AutoDisposeFutureProviderRef<Duration?> { mixin CurrentTimeRef
on AutoDisposeAsyncNotifierProviderRef<shelfsdk.MediaProgress?> {
/// The parameter `libraryItemId` of this provider. /// The parameter `libraryItemId` of this provider.
String get libraryItemId; String get libraryItemId;
} }
class _CurrentTimeProviderElement class _CurrentTimeProviderElement
extends AutoDisposeFutureProviderElement<Duration?> with CurrentTimeRef { extends AutoDisposeAsyncNotifierProviderElement<CurrentTime,
shelfsdk.MediaProgress?> with CurrentTimeRef {
_CurrentTimeProviderElement(super.provider); _CurrentTimeProviderElement(super.provider);
@override @override
String get libraryItemId => (origin as CurrentTimeProvider).libraryItemId; String get libraryItemId => (origin as CurrentTimeProvider).libraryItemId;
} }
String _$positionChapterHash() => r'ac6148e92363fad849713c07045503653dcaa7e8'; String _$currentBookHash() => r'f13ba110d104be8bae48972d6fd266d461d30898';
/// See also [positionChapter].
@ProviderFor(positionChapter)
final positionChapterProvider = AutoDisposeStreamProvider<Duration>.internal(
positionChapter,
name: r'positionChapterProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$positionChapterHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef PositionChapterRef = AutoDisposeStreamProviderRef<Duration>;
String _$absPlayerHash() => r'370f576d3d3a2196d1a93f2046005c1a3298d994';
/// See also [AbsPlayer].
@ProviderFor(AbsPlayer)
final absPlayerProvider = NotifierProvider<AbsPlayer, AbsAudioPlayer>.internal(
AbsPlayer.new,
name: r'absPlayerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$absPlayerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AbsPlayer = Notifier<AbsAudioPlayer>;
String _$playerStateHash() => r'eb79bd816714f721da1c4226d4447de5dc55fc5c';
/// riverpod状态
///
/// Copied from [PlayerState].
@ProviderFor(PlayerState)
final playerStateProvider =
AutoDisposeNotifierProvider<PlayerState, audio.PlayerState>.internal(
PlayerState.new,
name: r'playerStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$playerStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$PlayerState = AutoDisposeNotifier<audio.PlayerState>;
String _$currentBookHash() => r'85de9041d356e214761b65bd1b7b74321d5a9221';
/// See also [CurrentBook]. /// See also [CurrentBook].
@ProviderFor(CurrentBook) @ProviderFor(CurrentBook)
final currentBookProvider = final currentBookProvider =
AutoDisposeNotifierProvider<CurrentBook, api.BookExpanded?>.internal( AutoDisposeNotifierProvider<CurrentBook, shelfsdk.BookExpanded?>.internal(
CurrentBook.new, CurrentBook.new,
name: r'currentBookProvider', name: r'currentBookProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
@ -254,13 +301,13 @@ final currentBookProvider =
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
typedef _$CurrentBook = AutoDisposeNotifier<api.BookExpanded?>; typedef _$CurrentBook = AutoDisposeNotifier<shelfsdk.BookExpanded?>;
String _$currentChapterHash() => r'aff83aed7d098099805ec7d72ea84fff3a298522'; String _$currentChapterHash() => r'e8d867067f383372afd758186f13950a6746ba85';
/// See also [CurrentChapter]. /// See also [CurrentChapter].
@ProviderFor(CurrentChapter) @ProviderFor(CurrentChapter)
final currentChapterProvider = final currentChapterProvider =
AutoDisposeNotifierProvider<CurrentChapter, api.BookChapter?>.internal( AutoDisposeNotifierProvider<CurrentChapter, shelfsdk.BookChapter?>.internal(
CurrentChapter.new, CurrentChapter.new,
name: r'currentChapterProvider', name: r'currentChapterProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@ -270,6 +317,6 @@ final currentChapterProvider =
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
typedef _$CurrentChapter = AutoDisposeNotifier<api.BookChapter?>; typedef _$CurrentChapter = AutoDisposeNotifier<shelfsdk.BookChapter?>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -105,22 +105,11 @@ class PlayerExpanded extends HookConsumerWidget {
left: AppElementSizes.paddingRegular, left: AppElementSizes.paddingRegular,
right: AppElementSizes.paddingRegular, right: AppElementSizes.paddingRegular,
), ),
child: const AudiobookChapterProgressBar(), child: const AbsDesktopProgressBar(),
), ),
), ),
), ),
SizedBox(
width: imageSize,
child: Padding(
padding: EdgeInsets.only(
left: AppElementSizes.paddingRegular,
right: AppElementSizes.paddingRegular,
),
child: const AudiobookProgressBar(),
),
),
// the chapter skip buttons, seek 30 seconds back and forward, and play/pause button // the chapter skip buttons, seek 30 seconds back and forward, and play/pause button
Expanded( Expanded(
flex: 2, flex: 2,

View file

@ -110,9 +110,9 @@ class PlayerExpandedDesktop extends HookConsumerWidget {
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: AppElementSizes.paddingRegular, horizontal: AppElementSizes.paddingLarge,
), ),
child: const AudiobookChapterProgressBar( child: const AbsDesktopProgressBar(
timeLabelLocation: TimeLabelLocation.sides, timeLabelLocation: TimeLabelLocation.sides,
), ),
), ),
@ -140,10 +140,10 @@ class PlayerExpandedDesktop extends HookConsumerWidget {
// mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
flex: 1,
child: Row(), child: Row(),
), ),
Expanded( Expanded(
flex: 1,
child: Hero( child: Hero(
tag: HeroTagPrefixes.controlsCenter, tag: HeroTagPrefixes.controlsCenter,
child: const PlayerControlsDesktopCenter(), child: const PlayerControlsDesktopCenter(),

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/constants/hero_tag_conventions.dart'; import 'package:vaani/constants/hero_tag_conventions.dart';
@ -8,12 +7,12 @@ import 'package:vaani/features/player/providers/abs_provider.dart';
import 'package:vaani/features/player/view/widgets/audiobook_player_seek_button.dart'; import 'package:vaani/features/player/view/widgets/audiobook_player_seek_button.dart';
import 'package:vaani/features/player/view/widgets/audiobook_player_seek_chapter_button.dart'; import 'package:vaani/features/player/view/widgets/audiobook_player_seek_chapter_button.dart';
import 'package:vaani/features/player/view/widgets/player_player_pause_button.dart'; import 'package:vaani/features/player/view/widgets/player_player_pause_button.dart';
import 'package:vaani/features/player/view/widgets/player_progress_bar.dart';
import 'package:vaani/features/player/view/widgets/player_speed_adjust_button.dart'; import 'package:vaani/features/player/view/widgets/player_speed_adjust_button.dart';
import 'package:vaani/features/skip_start_end/view/skip_start_end_button.dart'; import 'package:vaani/features/skip_start_end/view/skip_start_end_button.dart';
import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart'; import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart';
import 'package:vaani/globals.dart'; import 'package:vaani/globals.dart';
import 'package:vaani/router/router.dart'; import 'package:vaani/router/router.dart';
import 'package:vaani/shared/extensions/chapter.dart';
import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/extensions/model_conversions.dart';
import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart';
@ -29,16 +28,16 @@ class PlayerMinimized extends HookConsumerWidget {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
// //
final isVertical = size.height > size.width; final isVertical = size.height > size.width;
return Container( return SizedBox(
height: playerMinHeight, height: playerMinHeight,
color: Theme.of(context).colorScheme.surface, // color: Theme.of(context).colorScheme.surface,
child: Stack( child: Stack(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
children: [ children: [
isVertical isVertical
? const PlayerMinimizedControls() ? const PlayerMinimizedControls()
: const PlayerMinimizedControlsDesktop(), : const PlayerMinimizedControlsDesktop(),
const PlayerMinimizedProgress(), const AbsMinimizedProgress(),
], ],
), ),
); );
@ -145,36 +144,14 @@ class PlayerMinimizedControls extends HookConsumerWidget {
} }
} }
class PlayerMinimizedProgress extends HookConsumerWidget {
const PlayerMinimizedProgress({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentChapter = ref.watch(currentChapterProvider);
final progress = useStream(
ref.read(absPlayerProvider).positionInChapterStream,
initialData: Duration.zero,
);
return SizedBox(
height: AppElementSizes.barHeight,
child: LinearProgressIndicator(
value: (progress.data ?? Duration.zero).inSeconds /
(currentChapter?.duration.inSeconds ?? 1),
// color: Theme.of(context).colorScheme.onPrimaryContainer,
// backgroundColor: Theme.of(context).colorScheme.primaryContainer,
),
);
}
}
class PlayerMinimizedControlsDesktop extends HookConsumerWidget { class PlayerMinimizedControlsDesktop extends HookConsumerWidget {
const PlayerMinimizedControlsDesktop({super.key}); const PlayerMinimizedControlsDesktop({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final currentBook = ref.watch(currentBookProvider); final currentBook = ref.watch(currentBookProvider);
final currentChapter = ref.watch(currentChapterProvider); final absPlayer = ref.watch(absPlayerProvider);
ref.watch(currentChapterProvider);
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.click, // cursor: SystemMouseCursors.click, //
@ -210,7 +187,8 @@ class PlayerMinimizedControlsDesktop extends HookConsumerWidget {
}, },
child: Hero( child: Hero(
tag: HeroTagPrefixes.bookCoverWith( tag: HeroTagPrefixes.bookCoverWith(
currentBook?.libraryItemId), currentBook?.libraryItemId,
),
child: BookCoverWidget( child: BookCoverWidget(
playerMinHeight, playerMinHeight,
itemId: currentBook?.libraryItemId, itemId: currentBook?.libraryItemId,
@ -231,18 +209,14 @@ class PlayerMinimizedControlsDesktop extends HookConsumerWidget {
children: [ children: [
// AutoScrollText( // AutoScrollText(
Text( Text(
currentChapter?.title ?? absPlayer.primaryTitle(),
currentBook?.metadata.title ??
'',
maxLines: 1, overflow: TextOverflow.ellipsis, maxLines: 1, overflow: TextOverflow.ellipsis,
// velocity: // velocity:
// const Velocity(pixelsPerSecond: Offset(16, 0)), // const Velocity(pixelsPerSecond: Offset(16, 0)),
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
Text( Text(
currentBook?.metadata.asBookMetadataExpanded absPlayer.secondaryTitle(),
.authorName ??
'',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context) style: Theme.of(context)
@ -263,6 +237,7 @@ class PlayerMinimizedControlsDesktop extends HookConsumerWidget {
), ),
), ),
Expanded( Expanded(
flex: 1,
child: Hero( child: Hero(
tag: HeroTagPrefixes.controlsCenter, tag: HeroTagPrefixes.controlsCenter,
child: const PlayerControlsDesktopCenter(), child: const PlayerControlsDesktopCenter(),

View file

@ -1,60 +1,44 @@
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/constants/sizes.dart'; import 'package:vaani/constants/sizes.dart';
import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/features/player/providers/abs_provider.dart';
import 'package:vaani/features/settings/app_settings_provider.dart';
// ///
class AudiobookChapterProgressBar extends HookConsumerWidget { class AbsDesktopProgressBar extends HookConsumerWidget {
final TimeLabelLocation timeLabelLocation; final TimeLabelLocation timeLabelLocation;
const AudiobookChapterProgressBar({ const AbsDesktopProgressBar({
super.key, super.key,
this.timeLabelLocation = TimeLabelLocation.below, this.timeLabelLocation = TimeLabelLocation.below,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final book = ref.watch(currentBookProvider);
final player = ref.watch(absPlayerProvider); final player = ref.watch(absPlayerProvider);
final currentChapter = ref.watch(currentChapterProvider); final currentChapter = ref.watch(currentChapterProvider);
final position = useStream( final playerSettings =
player.positionInBookStream, ref.watch(appSettingsProvider.select((v) => v.playerSettings));
initialData: const Duration(seconds: 0), final showChapterProgress =
); playerSettings.expandedPlayerSettings.showChapterProgress;
final buffered = useStream(
player.bufferedPositionInBookStream,
initialData: const Duration(seconds: 0),
);
// now find the chapter that corresponds to the current time final position = ref.watch(progressProvider);
// and calculate the progress of the current chapter final buffered = ref.watch(progressBufferedProvider);
final currentChapterProgress = currentChapter == null
? null
: (player.positionInBook - currentChapter.start);
final currentChapterBuffered = currentChapter == null final total = ref.watch(totalProvider);
? null
: (player.bufferedPositionInBook - currentChapter.start);
final progress =
currentChapterProgress ?? position.data ?? const Duration(seconds: 0);
final total = currentChapter == null
? book?.duration ?? const Duration(seconds: 0)
: currentChapter.end - currentChapter.start;
return ProgressBar( return ProgressBar(
progress: progress, progress: position.requireValue,
total: total, total: total,
// ! TODO add onSeek
onSeek: (duration) { onSeek: (duration) {
player.seekInBook( player.seekInBook(
duration + (currentChapter?.start ?? const Duration(seconds: 0)), duration +
(showChapterProgress
? (currentChapter?.start ?? Duration.zero)
: Duration.zero),
); );
// player.seek(duration);
}, },
thumbRadius: 8, thumbRadius: 8,
buffered: buffered: buffered.requireValue,
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, timeLabelLocation: timeLabelLocation,
@ -63,26 +47,19 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
} }
// //
class AudiobookProgressBar extends HookConsumerWidget { class AbsMinimizedProgress extends HookConsumerWidget {
const AudiobookProgressBar({ const AbsMinimizedProgress({super.key});
super.key,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final book = ref.watch(currentBookProvider); final position = ref.watch(progressProvider);
final player = ref.read(absPlayerProvider); final total = ref.watch(totalProvider);
final position = useStream(
player.positionInBookStream,
initialData: const Duration(seconds: 0),
);
return SizedBox( return SizedBox(
height: AppElementSizes.barHeightLarge, height: AppElementSizes.barHeight,
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: (position.data ?? const Duration(seconds: 0)).inSeconds / value: total > Duration.zero
(book?.duration ?? const Duration(seconds: 0)).inSeconds, ? ((position.value ?? Duration.zero).inSeconds / total.inSeconds)
borderRadius: BorderRadiusGeometry.all(Radius.circular(10)), : 0,
), ),
); );
} }

View file

@ -8,7 +8,7 @@ import 'package:vaani/shared/extensions/obfuscation.dart';
part 'api_settings_provider.g.dart'; part 'api_settings_provider.g.dart';
final _box = AvailableHiveBoxes.apiSettingsBox; final _box = HiveBoxes.apiSettingsBox;
final _logger = Logger('ApiSettingsProvider'); final _logger = Logger('ApiSettingsProvider');

View file

@ -7,7 +7,7 @@ import 'package:vaani/features/settings/models/app_settings.dart' as model;
part 'app_settings_provider.g.dart'; part 'app_settings_provider.g.dart';
final _box = AvailableHiveBoxes.userPrefsBox; final _box = HiveBoxes.userPrefsBox;
final _logger = Logger('AppSettingsProvider'); final _logger = Logger('AppSettingsProvider');

View file

@ -1,3 +1,5 @@
// ignore_for_file: unreachable_switch_default
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart';

View file

@ -1,9 +1,10 @@
// ignore_for_file: unreachable_switch_default, unused_element
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
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/features/player/providers/abs_provider.dart' import 'package:vaani/features/player/providers/abs_provider.dart';
hide AudioPlayer;
import 'package:vaani/features/settings/app_settings_provider.dart' import 'package:vaani/features/settings/app_settings_provider.dart'
show appSettingsProvider; show appSettingsProvider;
import 'package:vaani/features/settings/models/app_settings.dart'; import 'package:vaani/features/settings/models/app_settings.dart';

View file

@ -73,6 +73,7 @@ class PlayerSkipChapterStartEnd extends HookConsumerWidget {
bookSettings.copyWith bookSettings.copyWith
.playerSettings(skipChapterStart: interval), .playerSettings(skipChapterStart: interval),
); );
reloadPlayer(ref);
}, },
), ),
), ),
@ -97,6 +98,7 @@ class PlayerSkipChapterStartEnd extends HookConsumerWidget {
bookSettings.copyWith bookSettings.copyWith
.playerSettings(skipChapterEnd: interval), .playerSettings(skipChapterEnd: interval),
); );
reloadPlayer(ref);
}, },
), ),
), ),
@ -104,4 +106,15 @@ class PlayerSkipChapterStartEnd extends HookConsumerWidget {
), ),
); );
} }
void reloadPlayer(WidgetRef ref) {
final currentBook = ref.watch(currentBookProvider);
if (currentBook == null) {
return;
}
final absPlayer = ref.read(absPlayerProvider);
final positionInBook = absPlayer.positionInBook;
ref.read(currentBookProvider.notifier).update(currentBook.libraryItemId,
force: true, currentTime: positionInBook);
}
} }

View file

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class LibraryStatisticsPage extends HookConsumerWidget {
const LibraryStatisticsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: Text('统计'),
),
// body: Text('统计'),
);
}
}

View file

@ -54,10 +54,8 @@ class MessageLookup extends MessageLookupByLibrary {
"accountDeleteServer": MessageLookupByLibrary.simpleMessage( "accountDeleteServer": MessageLookupByLibrary.simpleMessage(
"Delete Server", "Delete Server",
), ),
"accountInvalidURL": "accountInvalidURL": MessageLookupByLibrary.simpleMessage("Invalid URL"),
MessageLookupByLibrary.simpleMessage("Invalid URL"), "accountManage": MessageLookupByLibrary.simpleMessage("Manage Accounts"),
"accountManage":
MessageLookupByLibrary.simpleMessage("Manage Accounts"),
"accountRegisteredServers": MessageLookupByLibrary.simpleMessage( "accountRegisteredServers": MessageLookupByLibrary.simpleMessage(
"Registered Servers", "Registered Servers",
), ),
@ -96,8 +94,7 @@ class MessageLookup extends MessageLookupByLibrary {
"autoTurnOnTimerAlways": MessageLookupByLibrary.simpleMessage( "autoTurnOnTimerAlways": MessageLookupByLibrary.simpleMessage(
"Always Auto Turn On Timer", "Always Auto Turn On Timer",
), ),
"autoTurnOnTimerAlwaysDescription": "autoTurnOnTimerAlwaysDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Always turn on the sleep timer, no matter what", "Always turn on the sleep timer, no matter what",
), ),
"autoTurnOnTimerDescription": MessageLookupByLibrary.simpleMessage( "autoTurnOnTimerDescription": MessageLookupByLibrary.simpleMessage(
@ -125,11 +122,9 @@ class MessageLookup extends MessageLookupByLibrary {
"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": "bookMetadataAbridged": MessageLookupByLibrary.simpleMessage("Abridged"),
MessageLookupByLibrary.simpleMessage("Abridged"),
"bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"), "bookMetadataLength": MessageLookupByLibrary.simpleMessage("Length"),
"bookMetadataPublished": "bookMetadataPublished": MessageLookupByLibrary.simpleMessage("Published"),
MessageLookupByLibrary.simpleMessage("Published"),
"bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage( "bookMetadataUnabridged": MessageLookupByLibrary.simpleMessage(
"Unabridged", "Unabridged",
), ),
@ -173,12 +168,10 @@ class MessageLookup extends MessageLookupByLibrary {
"erDragText": MessageLookupByLibrary.simpleMessage("Pull to refresh"), "erDragText": MessageLookupByLibrary.simpleMessage("Pull to refresh"),
"erDragTextUp": MessageLookupByLibrary.simpleMessage("Pull to refresh"), "erDragTextUp": MessageLookupByLibrary.simpleMessage("Pull to refresh"),
"erFailedText": MessageLookupByLibrary.simpleMessage("Failed"), "erFailedText": MessageLookupByLibrary.simpleMessage("Failed"),
"erMessageText": "erMessageText": MessageLookupByLibrary.simpleMessage("Last updated at %T"),
MessageLookupByLibrary.simpleMessage("Last updated at %T"),
"erNoMoreText": MessageLookupByLibrary.simpleMessage("No more"), "erNoMoreText": MessageLookupByLibrary.simpleMessage("No more"),
"erProcessedText": MessageLookupByLibrary.simpleMessage("Succeeded"), "erProcessedText": MessageLookupByLibrary.simpleMessage("Succeeded"),
"erProcessingText": "erProcessingText": MessageLookupByLibrary.simpleMessage("Refreshing..."),
MessageLookupByLibrary.simpleMessage("Refreshing..."),
"erReadyText": MessageLookupByLibrary.simpleMessage("Refreshing..."), "erReadyText": MessageLookupByLibrary.simpleMessage("Refreshing..."),
"explore": MessageLookupByLibrary.simpleMessage("explore"), "explore": MessageLookupByLibrary.simpleMessage("explore"),
"exploreHint": MessageLookupByLibrary.simpleMessage( "exploreHint": MessageLookupByLibrary.simpleMessage(
@ -200,13 +193,11 @@ class MessageLookup extends MessageLookupByLibrary {
"homeBookContinueSeries": MessageLookupByLibrary.simpleMessage( "homeBookContinueSeries": MessageLookupByLibrary.simpleMessage(
"Continue Series", "Continue Series",
), ),
"homeBookContinueSeriesDescription": "homeBookContinueSeriesDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Show play button for books in continue series shelf", "Show play button for books in continue series shelf",
), ),
"homeBookDiscover": MessageLookupByLibrary.simpleMessage("Discover"), "homeBookDiscover": MessageLookupByLibrary.simpleMessage("Discover"),
"homeBookListenAgain": "homeBookListenAgain": MessageLookupByLibrary.simpleMessage("Listen Again"),
MessageLookupByLibrary.simpleMessage("Listen Again"),
"homeBookListenAgainDescription": MessageLookupByLibrary.simpleMessage( "homeBookListenAgainDescription": MessageLookupByLibrary.simpleMessage(
"Show play button for all books in listen again shelf", "Show play button for all books in listen again shelf",
), ),
@ -216,8 +207,7 @@ class MessageLookup extends MessageLookupByLibrary {
"homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage( "homeBookRecentlyAdded": MessageLookupByLibrary.simpleMessage(
"Recently Added", "Recently Added",
), ),
"homeBookRecommended": "homeBookRecommended": MessageLookupByLibrary.simpleMessage("Recommended"),
MessageLookupByLibrary.simpleMessage("Recommended"),
"homeContinueListening": MessageLookupByLibrary.simpleMessage( "homeContinueListening": MessageLookupByLibrary.simpleMessage(
"Continue Listening", "Continue Listening",
), ),
@ -290,8 +280,7 @@ class MessageLookup extends MessageLookupByLibrary {
"nmpSettingsMediaControls": MessageLookupByLibrary.simpleMessage( "nmpSettingsMediaControls": MessageLookupByLibrary.simpleMessage(
"Media Controls", "Media Controls",
), ),
"nmpSettingsMediaControlsDescription": "nmpSettingsMediaControlsDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Select the media controls to display", "Select the media controls to display",
), ),
"nmpSettingsSelectOne": MessageLookupByLibrary.simpleMessage( "nmpSettingsSelectOne": MessageLookupByLibrary.simpleMessage(
@ -310,32 +299,27 @@ class MessageLookup extends MessageLookupByLibrary {
"nmpSettingsSubTitleDescription": MessageLookupByLibrary.simpleMessage( "nmpSettingsSubTitleDescription": MessageLookupByLibrary.simpleMessage(
"The subtitle of the notification\n", "The subtitle of the notification\n",
), ),
"nmpSettingsTitle": "nmpSettingsTitle": MessageLookupByLibrary.simpleMessage("Primary Title"),
MessageLookupByLibrary.simpleMessage("Primary Title"),
"nmpSettingsTitleDescription": MessageLookupByLibrary.simpleMessage( "nmpSettingsTitleDescription": MessageLookupByLibrary.simpleMessage(
"The title of the notification\n", "The title of the notification\n",
), ),
"no": MessageLookupByLibrary.simpleMessage("No"), "no": MessageLookupByLibrary.simpleMessage("No"),
"notImplemented": "notImplemented": MessageLookupByLibrary.simpleMessage("Not implemented"),
MessageLookupByLibrary.simpleMessage("Not implemented"),
"notificationMediaPlayer": MessageLookupByLibrary.simpleMessage( "notificationMediaPlayer": MessageLookupByLibrary.simpleMessage(
"Notification Media Player", "Notification Media Player",
), ),
"notificationMediaPlayerDescription": "notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Customize the media player in notifications", "Customize the media player in notifications",
), ),
"ok": MessageLookupByLibrary.simpleMessage("OK"), "ok": MessageLookupByLibrary.simpleMessage("OK"),
"pause": MessageLookupByLibrary.simpleMessage("Pause"), "pause": MessageLookupByLibrary.simpleMessage("Pause"),
"play": MessageLookupByLibrary.simpleMessage("Play"), "play": MessageLookupByLibrary.simpleMessage("Play"),
"playerSettings": "playerSettings": MessageLookupByLibrary.simpleMessage("Player Settings"),
MessageLookupByLibrary.simpleMessage("Player Settings"),
"playerSettingsCompleteTime": MessageLookupByLibrary.simpleMessage( "playerSettingsCompleteTime": MessageLookupByLibrary.simpleMessage(
"Mark Complete When Time Left", "Mark Complete When Time Left",
), ),
"playerSettingsCompleteTimeDescriptionHead": "playerSettingsCompleteTimeDescriptionHead":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage("Mark complete when less than "),
"Mark complete when less than "),
"playerSettingsCompleteTimeDescriptionTail": "playerSettingsCompleteTimeDescriptionTail":
MessageLookupByLibrary.simpleMessage(" left in the book"), MessageLookupByLibrary.simpleMessage(" left in the book"),
"playerSettingsDescription": MessageLookupByLibrary.simpleMessage( "playerSettingsDescription": MessageLookupByLibrary.simpleMessage(
@ -350,8 +334,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"Show the progress of the current chapter in the player", "Show the progress of the current chapter in the player",
), ),
"playerSettingsDisplayTotalProgress": "playerSettingsDisplayTotalProgress": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Show Total Progress", "Show Total Progress",
), ),
"playerSettingsDisplayTotalProgressDescription": "playerSettingsDisplayTotalProgressDescription":
@ -380,8 +363,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"playerSettingsPlaybackReportingMinimumDescriptionTail": "playerSettingsPlaybackReportingMinimumDescriptionTail":
MessageLookupByLibrary.simpleMessage("of the book"), MessageLookupByLibrary.simpleMessage("of the book"),
"playerSettingsRememberForEveryBook": "playerSettingsRememberForEveryBook": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Remember Player Settings for Every Book", "Remember Player Settings for Every Book",
), ),
"playerSettingsRememberForEveryBookDescription": "playerSettingsRememberForEveryBookDescription":
@ -395,17 +377,14 @@ class MessageLookup extends MessageLookupByLibrary {
"playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage( "playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage(
"Speed Options", "Speed Options",
), ),
"playerSettingsSpeedOptionsSelect": "playerSettingsSpeedOptionsSelect": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Select Speed Options", "Select Speed Options",
), ),
"playerSettingsSpeedOptionsSelectAdd": "playerSettingsSpeedOptionsSelectAdd": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Add Speed Option", "Add Speed Option",
), ),
"playerSettingsSpeedOptionsSelectAddHelper": "playerSettingsSpeedOptionsSelectAddHelper":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage("Enter a new speed option to add"),
"Enter a new speed option to add"),
"playerSettingsSpeedSelect": MessageLookupByLibrary.simpleMessage( "playerSettingsSpeedSelect": MessageLookupByLibrary.simpleMessage(
"Select Speed", "Select Speed",
), ),
@ -453,8 +432,7 @@ class MessageLookup extends MessageLookupByLibrary {
"shakeActivationThreshold": MessageLookupByLibrary.simpleMessage( "shakeActivationThreshold": MessageLookupByLibrary.simpleMessage(
"Shake Activation Threshold", "Shake Activation Threshold",
), ),
"shakeActivationThresholdDescription": "shakeActivationThresholdDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"The higher the threshold, the harder you need to shake", "The higher the threshold, the harder you need to shake",
), ),
"shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"), "shakeDetector": MessageLookupByLibrary.simpleMessage("Shake Detector"),
@ -492,8 +470,7 @@ class MessageLookup extends MessageLookupByLibrary {
"themeModeHighContrast": MessageLookupByLibrary.simpleMessage( "themeModeHighContrast": MessageLookupByLibrary.simpleMessage(
"High Contrast Mode", "High Contrast Mode",
), ),
"themeModeHighContrastDescription": "themeModeHighContrastDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Increase the contrast between the background and the text", "Increase the contrast between the background and the text",
), ),
"themeModeLight": MessageLookupByLibrary.simpleMessage("Light"), "themeModeLight": MessageLookupByLibrary.simpleMessage("Light"),
@ -508,8 +485,7 @@ class MessageLookup extends MessageLookupByLibrary {
"themeSettingsColorsBook": MessageLookupByLibrary.simpleMessage( "themeSettingsColorsBook": MessageLookupByLibrary.simpleMessage(
"Adaptive Theme on Item Page", "Adaptive Theme on Item Page",
), ),
"themeSettingsColorsBookDescription": "themeSettingsColorsBookDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"Get fancy with the colors on the item page at the cost of some performance", "Get fancy with the colors on the item page at the cost of some performance",
), ),
"themeSettingsColorsCurrent": MessageLookupByLibrary.simpleMessage( "themeSettingsColorsCurrent": MessageLookupByLibrary.simpleMessage(

View file

@ -50,8 +50,7 @@ class MessageLookup extends MessageLookupByLibrary {
"accountDeleteServer": MessageLookupByLibrary.simpleMessage("删除服务器"), "accountDeleteServer": MessageLookupByLibrary.simpleMessage("删除服务器"),
"accountInvalidURL": MessageLookupByLibrary.simpleMessage("无效网址"), "accountInvalidURL": MessageLookupByLibrary.simpleMessage("无效网址"),
"accountManage": MessageLookupByLibrary.simpleMessage("帐户管理"), "accountManage": MessageLookupByLibrary.simpleMessage("帐户管理"),
"accountRegisteredServers": "accountRegisteredServers": MessageLookupByLibrary.simpleMessage("已注册服务器"),
MessageLookupByLibrary.simpleMessage("已注册服务器"),
"accountRemoveServerAndUsers": MessageLookupByLibrary.simpleMessage( "accountRemoveServerAndUsers": MessageLookupByLibrary.simpleMessage(
"删除服务器和用户", "删除服务器和用户",
), ),
@ -61,8 +60,7 @@ class MessageLookup extends MessageLookupByLibrary {
"accountRemoveServerAndUsersTail": MessageLookupByLibrary.simpleMessage( "accountRemoveServerAndUsersTail": MessageLookupByLibrary.simpleMessage(
" 以及该应用程序中所有用户的登录信息。", " 以及该应用程序中所有用户的登录信息。",
), ),
"accountRemoveUserLogin": "accountRemoveUserLogin": MessageLookupByLibrary.simpleMessage("删除用户登录"),
MessageLookupByLibrary.simpleMessage("删除用户登录"),
"accountRemoveUserLoginHead": MessageLookupByLibrary.simpleMessage( "accountRemoveUserLoginHead": MessageLookupByLibrary.simpleMessage(
"这将删除用户 ", "这将删除用户 ",
), ),
@ -74,15 +72,11 @@ class MessageLookup extends MessageLookupByLibrary {
"accountUsersCount": m1, "accountUsersCount": m1,
"appSettings": MessageLookupByLibrary.simpleMessage("应用设置"), "appSettings": MessageLookupByLibrary.simpleMessage("应用设置"),
"appearance": MessageLookupByLibrary.simpleMessage("外观"), "appearance": MessageLookupByLibrary.simpleMessage("外观"),
"autoSleepTimerSettings": "autoSleepTimerSettings": MessageLookupByLibrary.simpleMessage("自动睡眠定时器设置"),
MessageLookupByLibrary.simpleMessage("自动睡眠定时器设置"), "autoTurnOnSleepTimer": MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"),
"autoTurnOnSleepTimer":
MessageLookupByLibrary.simpleMessage("自动开启睡眠定时器"),
"autoTurnOnTimer": MessageLookupByLibrary.simpleMessage("自动开启定时器"), "autoTurnOnTimer": MessageLookupByLibrary.simpleMessage("自动开启定时器"),
"autoTurnOnTimerAlways": "autoTurnOnTimerAlways": MessageLookupByLibrary.simpleMessage("始终自动开启定时器"),
MessageLookupByLibrary.simpleMessage("始终自动开启定时器"), "autoTurnOnTimerAlwaysDescription": MessageLookupByLibrary.simpleMessage(
"autoTurnOnTimerAlwaysDescription":
MessageLookupByLibrary.simpleMessage(
"总是打开睡眠定时器", "总是打开睡眠定时器",
), ),
"autoTurnOnTimerDescription": MessageLookupByLibrary.simpleMessage( "autoTurnOnTimerDescription": MessageLookupByLibrary.simpleMessage(
@ -124,8 +118,7 @@ class MessageLookup extends MessageLookupByLibrary {
"copyToClipboardDescription": MessageLookupByLibrary.simpleMessage( "copyToClipboardDescription": MessageLookupByLibrary.simpleMessage(
"将应用程序设置复制到剪贴板", "将应用程序设置复制到剪贴板",
), ),
"copyToClipboardToast": "copyToClipboardToast": MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"),
MessageLookupByLibrary.simpleMessage("设置已复制到剪贴板"),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteDialog": m2, "deleteDialog": m2,
"deleted": m3, "deleted": m3,
@ -148,13 +141,11 @@ class MessageLookup extends MessageLookupByLibrary {
"general": MessageLookupByLibrary.simpleMessage("通用"), "general": MessageLookupByLibrary.simpleMessage("通用"),
"help": MessageLookupByLibrary.simpleMessage("Help"), "help": MessageLookupByLibrary.simpleMessage("Help"),
"home": MessageLookupByLibrary.simpleMessage("首页"), "home": MessageLookupByLibrary.simpleMessage("首页"),
"homeBookContinueListening": "homeBookContinueListening": MessageLookupByLibrary.simpleMessage("继续收听"),
MessageLookupByLibrary.simpleMessage("继续收听"),
"homeBookContinueListeningDescription": "homeBookContinueListeningDescription":
MessageLookupByLibrary.simpleMessage("继续收听书架上显示播放按钮"), MessageLookupByLibrary.simpleMessage("继续收听书架上显示播放按钮"),
"homeBookContinueSeries": MessageLookupByLibrary.simpleMessage("继续系列"), "homeBookContinueSeries": MessageLookupByLibrary.simpleMessage("继续系列"),
"homeBookContinueSeriesDescription": "homeBookContinueSeriesDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"继续系列书架上显示播放按钮", "继续系列书架上显示播放按钮",
), ),
"homeBookDiscover": MessageLookupByLibrary.simpleMessage("发现"), "homeBookDiscover": MessageLookupByLibrary.simpleMessage("发现"),
@ -176,8 +167,7 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"homePageSettingsOtherShelvesDescription": "homePageSettingsOtherShelvesDescription":
MessageLookupByLibrary.simpleMessage("显示所有剩余书架上所有书籍的播放按钮"), MessageLookupByLibrary.simpleMessage("显示所有剩余书架上所有书籍的播放按钮"),
"homePageSettingsQuickPlay": "homePageSettingsQuickPlay": MessageLookupByLibrary.simpleMessage("继续播放"),
MessageLookupByLibrary.simpleMessage("继续播放"),
"homeStartListening": MessageLookupByLibrary.simpleMessage("开始收听"), "homeStartListening": MessageLookupByLibrary.simpleMessage("开始收听"),
"language": MessageLookupByLibrary.simpleMessage("语言"), "language": MessageLookupByLibrary.simpleMessage("语言"),
"languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"), "languageDescription": MessageLookupByLibrary.simpleMessage("语言切换"),
@ -194,8 +184,7 @@ class MessageLookup extends MessageLookupByLibrary {
"loginOpenID": MessageLookupByLibrary.simpleMessage("OpenID"), "loginOpenID": MessageLookupByLibrary.simpleMessage("OpenID"),
"loginPassword": MessageLookupByLibrary.simpleMessage("密码"), "loginPassword": MessageLookupByLibrary.simpleMessage("密码"),
"loginServerClick": MessageLookupByLibrary.simpleMessage("单击此处"), "loginServerClick": MessageLookupByLibrary.simpleMessage("单击此处"),
"loginServerConnected": "loginServerConnected": MessageLookupByLibrary.simpleMessage("服务器已连接,请登录"),
MessageLookupByLibrary.simpleMessage("服务器已连接,请登录"),
"loginServerNo": MessageLookupByLibrary.simpleMessage("没有服务器? "), "loginServerNo": MessageLookupByLibrary.simpleMessage("没有服务器? "),
"loginServerNoConnected": MessageLookupByLibrary.simpleMessage( "loginServerNoConnected": MessageLookupByLibrary.simpleMessage(
"请输入您的AudiobookShelf服务器的URL", "请输入您的AudiobookShelf服务器的URL",
@ -208,10 +197,8 @@ class MessageLookup extends MessageLookupByLibrary {
"logs": MessageLookupByLibrary.simpleMessage("日志"), "logs": MessageLookupByLibrary.simpleMessage("日志"),
"nmpSettingsBackward": MessageLookupByLibrary.simpleMessage("快退间隔"), "nmpSettingsBackward": MessageLookupByLibrary.simpleMessage("快退间隔"),
"nmpSettingsForward": MessageLookupByLibrary.simpleMessage("快进间隔"), "nmpSettingsForward": MessageLookupByLibrary.simpleMessage("快进间隔"),
"nmpSettingsMediaControls": "nmpSettingsMediaControls": MessageLookupByLibrary.simpleMessage("媒体控制"),
MessageLookupByLibrary.simpleMessage("媒体控制"), "nmpSettingsMediaControlsDescription": MessageLookupByLibrary.simpleMessage(
"nmpSettingsMediaControlsDescription":
MessageLookupByLibrary.simpleMessage(
"选择要显示的媒体控件", "选择要显示的媒体控件",
), ),
"nmpSettingsSelectOne": MessageLookupByLibrary.simpleMessage( "nmpSettingsSelectOne": MessageLookupByLibrary.simpleMessage(
@ -232,10 +219,8 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"no": MessageLookupByLibrary.simpleMessage(""), "no": MessageLookupByLibrary.simpleMessage(""),
"notImplemented": MessageLookupByLibrary.simpleMessage("未实现"), "notImplemented": MessageLookupByLibrary.simpleMessage("未实现"),
"notificationMediaPlayer": "notificationMediaPlayer": MessageLookupByLibrary.simpleMessage("通知媒体播放器"),
MessageLookupByLibrary.simpleMessage("通知媒体播放器"), "notificationMediaPlayerDescription": MessageLookupByLibrary.simpleMessage(
"notificationMediaPlayerDescription":
MessageLookupByLibrary.simpleMessage(
"在通知中自定义媒体播放器", "在通知中自定义媒体播放器",
), ),
"ok": MessageLookupByLibrary.simpleMessage("确定"), "ok": MessageLookupByLibrary.simpleMessage("确定"),
@ -257,8 +242,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("显示章节进度"), MessageLookupByLibrary.simpleMessage("显示章节进度"),
"playerSettingsDisplayChapterProgressDescription": "playerSettingsDisplayChapterProgressDescription":
MessageLookupByLibrary.simpleMessage("在播放器中显示当前章节的进度"), MessageLookupByLibrary.simpleMessage("在播放器中显示当前章节的进度"),
"playerSettingsDisplayTotalProgress": "playerSettingsDisplayTotalProgress": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"显示总进度", "显示总进度",
), ),
"playerSettingsDisplayTotalProgressDescription": "playerSettingsDisplayTotalProgressDescription":
@ -281,8 +265,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("不要报告本书前 "), MessageLookupByLibrary.simpleMessage("不要报告本书前 "),
"playerSettingsPlaybackReportingMinimumDescriptionTail": "playerSettingsPlaybackReportingMinimumDescriptionTail":
MessageLookupByLibrary.simpleMessage(" 的播放"), MessageLookupByLibrary.simpleMessage(" 的播放"),
"playerSettingsRememberForEveryBook": "playerSettingsRememberForEveryBook": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"记住每本书的播放器设置", "记住每本书的播放器设置",
), ),
"playerSettingsRememberForEveryBookDescription": "playerSettingsRememberForEveryBookDescription":
@ -294,18 +277,15 @@ class MessageLookup extends MessageLookupByLibrary {
"playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage( "playerSettingsSpeedOptions": MessageLookupByLibrary.simpleMessage(
"播放速度选项", "播放速度选项",
), ),
"playerSettingsSpeedOptionsSelect": "playerSettingsSpeedOptionsSelect": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"播放速度选项", "播放速度选项",
), ),
"playerSettingsSpeedOptionsSelectAdd": "playerSettingsSpeedOptionsSelectAdd": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"添加一个速度选项", "添加一个速度选项",
), ),
"playerSettingsSpeedOptionsSelectAddHelper": "playerSettingsSpeedOptionsSelectAddHelper":
MessageLookupByLibrary.simpleMessage("输入一个新的速度选项"), MessageLookupByLibrary.simpleMessage("输入一个新的速度选项"),
"playerSettingsSpeedSelect": "playerSettingsSpeedSelect": MessageLookupByLibrary.simpleMessage("选择播放速度"),
MessageLookupByLibrary.simpleMessage("选择播放速度"),
"playerSettingsSpeedSelectHelper": MessageLookupByLibrary.simpleMessage( "playerSettingsSpeedSelectHelper": MessageLookupByLibrary.simpleMessage(
"输入默认的播放速度", "输入默认的播放速度",
), ),
@ -326,10 +306,8 @@ class MessageLookup extends MessageLookupByLibrary {
"restoreBackupHint": MessageLookupByLibrary.simpleMessage("将备份粘贴到此处"), "restoreBackupHint": MessageLookupByLibrary.simpleMessage("将备份粘贴到此处"),
"restoreBackupInvalid": MessageLookupByLibrary.simpleMessage("无效备份"), "restoreBackupInvalid": MessageLookupByLibrary.simpleMessage("无效备份"),
"restoreBackupSuccess": MessageLookupByLibrary.simpleMessage("设置已恢复"), "restoreBackupSuccess": MessageLookupByLibrary.simpleMessage("设置已恢复"),
"restoreBackupValidator": "restoreBackupValidator": MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"),
MessageLookupByLibrary.simpleMessage("请将备份粘贴到此处"), "restoreDescription": MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"),
"restoreDescription":
MessageLookupByLibrary.simpleMessage("从备份中还原应用程序设置"),
"resume": MessageLookupByLibrary.simpleMessage("继续"), "resume": MessageLookupByLibrary.simpleMessage("继续"),
"retry": MessageLookupByLibrary.simpleMessage("重试"), "retry": MessageLookupByLibrary.simpleMessage("重试"),
"settings": MessageLookupByLibrary.simpleMessage("设置"), "settings": MessageLookupByLibrary.simpleMessage("设置"),
@ -337,10 +315,8 @@ class MessageLookup extends MessageLookupByLibrary {
"shakeActionDescription": MessageLookupByLibrary.simpleMessage( "shakeActionDescription": MessageLookupByLibrary.simpleMessage(
"检测到抖动时要执行的操作", "检测到抖动时要执行的操作",
), ),
"shakeActivationThreshold": "shakeActivationThreshold": MessageLookupByLibrary.simpleMessage("抖动激活阈值"),
MessageLookupByLibrary.simpleMessage("抖动激活阈值"), "shakeActivationThresholdDescription": MessageLookupByLibrary.simpleMessage(
"shakeActivationThresholdDescription":
MessageLookupByLibrary.simpleMessage(
"门槛越高,你就越难摇晃", "门槛越高,你就越难摇晃",
), ),
"shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"), "shakeDetector": MessageLookupByLibrary.simpleMessage("抖动检测器"),
@ -351,8 +327,7 @@ class MessageLookup extends MessageLookupByLibrary {
"shakeDetectorEnableDescription": MessageLookupByLibrary.simpleMessage( "shakeDetectorEnableDescription": MessageLookupByLibrary.simpleMessage(
"启用抖动检测以执行各种操作", "启用抖动检测以执行各种操作",
), ),
"shakeDetectorSettings": "shakeDetectorSettings": MessageLookupByLibrary.simpleMessage("抖动检测器设置"),
MessageLookupByLibrary.simpleMessage("抖动检测器设置"),
"shakeFeedback": MessageLookupByLibrary.simpleMessage("抖动反馈"), "shakeFeedback": MessageLookupByLibrary.simpleMessage("抖动反馈"),
"shakeFeedbackDescription": MessageLookupByLibrary.simpleMessage( "shakeFeedbackDescription": MessageLookupByLibrary.simpleMessage(
"检测到抖动时给出的反馈", "检测到抖动时给出的反馈",
@ -367,21 +342,18 @@ class MessageLookup extends MessageLookupByLibrary {
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"), "themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
"themeModeDark": MessageLookupByLibrary.simpleMessage("深色"), "themeModeDark": MessageLookupByLibrary.simpleMessage("深色"),
"themeModeHighContrast": MessageLookupByLibrary.simpleMessage("高对比度模式"), "themeModeHighContrast": MessageLookupByLibrary.simpleMessage("高对比度模式"),
"themeModeHighContrastDescription": "themeModeHighContrastDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"增加背景和文本之间的对比度", "增加背景和文本之间的对比度",
), ),
"themeModeLight": MessageLookupByLibrary.simpleMessage("浅色"), "themeModeLight": MessageLookupByLibrary.simpleMessage("浅色"),
"themeModeSystem": MessageLookupByLibrary.simpleMessage("跟随系统"), "themeModeSystem": MessageLookupByLibrary.simpleMessage("跟随系统"),
"themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"), "themeSettings": MessageLookupByLibrary.simpleMessage("主题设置"),
"themeSettingsColors": MessageLookupByLibrary.simpleMessage("主题色"), "themeSettingsColors": MessageLookupByLibrary.simpleMessage("主题色"),
"themeSettingsColorsAndroid": "themeSettingsColorsAndroid": MessageLookupByLibrary.simpleMessage("主题色"),
MessageLookupByLibrary.simpleMessage("主题色"),
"themeSettingsColorsBook": MessageLookupByLibrary.simpleMessage( "themeSettingsColorsBook": MessageLookupByLibrary.simpleMessage(
"书籍详情页自适应主题", "书籍详情页自适应主题",
), ),
"themeSettingsColorsBookDescription": "themeSettingsColorsBookDescription": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage(
"以牺牲一些性能为代价,对书籍详情页的颜色进行美化", "以牺牲一些性能为代价,对书籍详情页的颜色进行美化",
), ),
"themeSettingsColorsCurrent": MessageLookupByLibrary.simpleMessage( "themeSettingsColorsCurrent": MessageLookupByLibrary.simpleMessage(
@ -392,8 +364,7 @@ class MessageLookup extends MessageLookupByLibrary {
"themeSettingsColorsDescription": MessageLookupByLibrary.simpleMessage( "themeSettingsColorsDescription": MessageLookupByLibrary.simpleMessage(
"使用应用程序的系统主题色", "使用应用程序的系统主题色",
), ),
"themeSettingsDescription": "themeSettingsDescription": MessageLookupByLibrary.simpleMessage("自定义应用主题"),
MessageLookupByLibrary.simpleMessage("自定义应用主题"),
"timeSecond": m7, "timeSecond": m7,
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknown": MessageLookupByLibrary.simpleMessage("未知"),
"webVersion": MessageLookupByLibrary.simpleMessage("Web版本"), "webVersion": MessageLookupByLibrary.simpleMessage("Web版本"),

View file

@ -20,7 +20,6 @@ import 'package:vaani/shared/extensions/model_conversions.dart';
import 'package:vaani/shared/extensions/style.dart'; import 'package:vaani/shared/extensions/style.dart';
import 'package:vaani/shared/icons/abs_icons.dart'; import 'package:vaani/shared/icons/abs_icons.dart';
import 'package:vaani/shared/utils/components.dart'; import 'package:vaani/shared/utils/components.dart';
import 'package:vaani/shared/widgets/custom_dropdown.dart';
import 'package:vaani/shared/widgets/skeletons.dart'; import 'package:vaani/shared/widgets/skeletons.dart';
class LibraryPage extends HookConsumerWidget { class LibraryPage extends HookConsumerWidget {
@ -83,21 +82,21 @@ class LibraryPage extends HookConsumerWidget {
// ), // ),
const LibraryItemsSort(), const LibraryItemsSort(),
IconButton( IconButton(
icon: Icon(Icons.next_plan), icon: Icon(Icons.bar_chart),
tooltip: '加载下一页', // Helpful tooltip for users tooltip: '统计',
onPressed: () => ref.read(libraryItemsProvider.notifier).loadMore(), onPressed: () =>
), GoRouter.of(context).pushNamed(Routes.libraryStatistics.name),
IconButton(
icon: Icon(Icons.refresh),
tooltip: '刷新', // Helpful tooltip for users
onPressed: () => ref.read(libraryItemsProvider.notifier).refresh(),
), ),
// IconButton(
// icon: Icon(Icons.refresh),
// tooltip: '刷新', // Helpful tooltip for users
// onPressed: () => ref.read(libraryItemsProvider.notifier).refresh(),
// ),
IconButton( IconButton(
icon: Icon(Icons.download), icon: Icon(Icons.download),
tooltip: S.of(context).bookDownloads, // Helpful tooltip for users tooltip: S.of(context).bookDownloads, // Helpful tooltip for users
onPressed: () { onPressed: () =>
GoRouter.of(context).pushNamed(Routes.downloads.name); GoRouter.of(context).pushNamed(Routes.downloads.name),
},
), ),
], ],
), ),

View file

@ -21,6 +21,11 @@ class Routes {
pathParamName: 'itemId', pathParamName: 'itemId',
name: 'libraryItem', name: 'libraryItem',
); );
static const libraryStatistics = _SimpleRoute(
pathName: 'statistics',
// pathParamName: 'statistics',
name: 'libraryStatistics',
);
// Local settings // Local settings
static const settings = _SimpleRoute( static const settings = _SimpleRoute(

View file

@ -15,6 +15,7 @@ import 'package:vaani/features/settings/view/notification_settings_page.dart';
import 'package:vaani/features/settings/view/player_settings_page.dart'; import 'package:vaani/features/settings/view/player_settings_page.dart';
import 'package:vaani/features/settings/view/shake_detector_settings_page.dart'; import 'package:vaani/features/settings/view/shake_detector_settings_page.dart';
import 'package:vaani/features/settings/view/theme_settings_page.dart'; import 'package:vaani/features/settings/view/theme_settings_page.dart';
import 'package:vaani/features/statistics/abs_statistics.dart';
import 'package:vaani/features/you/view/server_manager.dart'; import 'package:vaani/features/you/view/server_manager.dart';
import 'package:vaani/features/you/view/you_page.dart'; import 'package:vaani/features/you/view/you_page.dart';
import 'package:vaani/globals.dart'; import 'package:vaani/globals.dart';
@ -128,7 +129,15 @@ class MyAppRouter {
path: Routes.library.localPath, path: Routes.library.localPath,
name: Routes.library.name, name: Routes.library.name,
pageBuilder: defaultPageBuilder(LibraryPage()), pageBuilder: defaultPageBuilder(LibraryPage()),
routes: [
GoRoute(
path: Routes.libraryStatistics.pathName,
name: Routes.libraryStatistics.name,
pageBuilder: defaultPageBuilder(
const LibraryStatisticsPage(),
), ),
),
]),
], ],
), ),
// search/explore page // search/explore page

View file

@ -35,12 +35,18 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
// //
final isVertical = size.height > size.width; final isVertical = size.height > size.width;
final currentBook = ref.watch(currentBookProvider);
return Scaffold( return Scaffold(
body: Stack( body: Stack(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
children: [ children: [
isVertical ? navigationShell : buildNavLeft(context, ref), Padding(
padding: EdgeInsets.only(
bottom: currentBook != null ? playerMinHeight : 0,
),
child: isVertical ? navigationShell : buildNavLeft(context, ref),
),
const PlayerMinimized(), const PlayerMinimized(),
], ],
), ),
@ -49,11 +55,7 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
} }
Widget buildNavLeft(BuildContext context, WidgetRef ref) { Widget buildNavLeft(BuildContext context, WidgetRef ref) {
final currentBook = ref.watch(currentBookProvider); return Row(
return Padding(
padding:
EdgeInsets.only(bottom: currentBook != null ? playerMinHeight : 0),
child: Row(
children: [ children: [
SafeArea( SafeArea(
child: NavigationRail( child: NavigationRail(
@ -109,7 +111,6 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
child: navigationShell, child: navigationShell,
), ),
], ],
),
); );
} }

View file

@ -626,14 +626,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.3.3"
hive_ce_generator:
dependency: "direct dev"
description:
name: hive_ce_generator
sha256: "182fb88273055e05ef0e630b5e32e5a30268722ed3bf46fdf5986792862975af"
url: "https://pub.dev"
source: hosted
version: "1.8.1"
hooks_riverpod: hooks_riverpod:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1711,14 +1703,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
yaml_writer:
dependency: transitive
description:
name: yaml_writer
sha256: "69651cd7238411179ac32079937d4aa9a2970150d6b2ae2c6fe6de09402a5dc5"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
sdks: sdks:
dart: ">=3.8.0 <4.0.0" dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.0" flutter: ">=3.32.0"

View file

@ -41,6 +41,8 @@ dependencies:
# 图标 # 图标
icons_plus: ^5.0.0 icons_plus: ^5.0.0
# 自动适配Material和Cupertino
#flutter_platform_widgets: ^9.0.0
# hooks # hooks
flutter_hooks: ^0.21.2 flutter_hooks: ^0.21.2
@ -143,6 +145,8 @@ dependencies:
# 分享插件 # 分享插件
share_plus: ^10.0.2 share_plus: ^10.0.2
# chart
# fl_chart: ^1.1.1
# AudiobookShelf SDK # AudiobookShelf SDK
shelfsdk: shelfsdk:
@ -174,7 +178,7 @@ dev_dependencies:
freezed: ^2.5.2 freezed: ^2.5.2
json_serializable: ^6.8.0 json_serializable: ^6.8.0
hive_ce_generator: ^1.8.1 # hive_ce_generator: ^1.8.1
# 启动图标 # 启动图标
flutter_launcher_icons: "^0.14.4" flutter_launcher_icons: "^0.14.4"