This commit is contained in:
rang 2025-10-17 17:43:14 +08:00
parent 07aea41c6e
commit 4304af26dc
4 changed files with 433 additions and 438 deletions

View file

@ -31,8 +31,7 @@ class AudiobookPlayer extends HookConsumerWidget {
if (currentBook == null) { if (currentBook == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final itemBeingPlayed = final itemBeingPlayed = ref.watch(libraryItemProvider(currentBook.libraryItemId));
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(
@ -65,8 +64,7 @@ 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 || highContrast: appSettings.themeSettings.highContrast || MediaQuery.of(context).highContrast,
MediaQuery.of(context).highContrast,
), ),
); );
@ -88,14 +86,14 @@ 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 await player.setVolume(preferredVolume * (1 - percentage.clamp(0, .75)));
.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
maxHeight: playerMaxHeight, maxHeight: playerMaxHeight,
controller: audioBookMiniplayerController, controller: audioBookMiniplayerController,
elevation: 4, elevation: 4,
duration: Duration(seconds: 3),
onDismissed: () { onDismissed: () {
// add a delay before closing the player // add a delay before closing the player
// to allow the user to see the player closing // to allow the user to see the player closing
@ -103,15 +101,13 @@ class AudiobookPlayer extends HookConsumerWidget {
player.setSourceAudiobook(null); player.setSourceAudiobook(null);
}); });
}, },
curve: Curves.easeOut, curve: Curves.linear,
builder: (height, percentage) { builder: (height, percentage) {
// 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) / (maxImgSize - playerMinHeight) / (playerMaxHeight - playerMinHeight);
(playerMaxHeight - playerMinHeight); final bool isFormMiniplayer = percentage < miniplayerPercentageDeclaration;
final bool isFormMiniplayer =
percentage < miniplayerPercentageDeclaration;
if (!isFormMiniplayer) { if (!isFormMiniplayer) {
// this calculation needs a refactor // this calculation needs a refactor
@ -211,17 +207,14 @@ 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 = currentChapter == null final currentChapterProgress =
? null currentChapter == null ? null : (player.positionInBook - currentChapter.start);
: (player.positionInBook - currentChapter.start);
final currentChapterBuffered = currentChapter == null final currentChapterBuffered =
? null currentChapter == null ? null : (player.bufferedPositionInBook - currentChapter.start);
: (player.bufferedPositionInBook - currentChapter.start);
return ProgressBar( return ProgressBar(
progress: progress: currentChapterProgress ?? position.data ?? const Duration(seconds: 0),
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,
@ -232,8 +225,7 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
); );
}, },
thumbRadius: 8, thumbRadius: 8,
buffered: buffered: currentChapterBuffered ?? buffered.data ?? const Duration(seconds: 0),
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

@ -43,9 +43,9 @@ class PlayerWhenExpanded extends HookConsumerWidget {
earlyEnd, earlyEnd,
) )
.clamp(0.0, 1.0); .clamp(0.0, 1.0);
final currentChapter = ref.watch(currentPlayingChapterProvider); final currentChapter = ref.watch(currentPlayingChapterProvider);
final currentBookMetadata = ref.watch(currentBookMetadataProvider); final currentBookMetadata = ref.watch(currentBookMetadataProvider);
return Column( return Column(
children: [ children: [
// sized box for system status bar; not needed as not full screen // sized box for system status bar; not needed as not full screen
@ -104,10 +104,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Theme.of(context) color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
.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),
@ -131,128 +128,130 @@ class PlayerWhenExpanded extends HookConsumerWidget {
), ),
// the chapter title // the chapter title
Opacity( if (earlyPercentage > 0.4)
opacity: earlyPercentage, Opacity(
child: Padding( opacity: earlyPercentage,
padding: EdgeInsets.only(
top: AppElementSizes.paddingRegular * 4 * earlyPercentage,
// horizontal: 16.0,
),
// child: SizedBox(
// same as the image width
// width: imageSize,
child: currentChapter == null
? const SizedBox()
: Text(
currentChapter.title,
style: Theme.of(context).textTheme.titleLarge,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// ),
),
),
// the book name and author
Opacity(
opacity: earlyPercentage,
child: Padding(
padding: EdgeInsets.only(
bottom: AppElementSizes.paddingRegular * earlyPercentage,
// horizontal: 16.0,
),
// child: SizedBox(
// same as the image width
// width: imageSize,
child: Text(
[
currentBookMetadata?.title ?? '',
currentBookMetadata?.authorName ?? '',
].join(' - '),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.7),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// ),
),
),
const Spacer(),
// the progress bar
Opacity(
opacity: earlyPercentage,
child: SizedBox(
width: imageSize,
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: AppElementSizes.paddingRegular * earlyPercentage, top: AppElementSizes.paddingRegular * 4 * earlyPercentage,
left: AppElementSizes.paddingRegular * earlyPercentage, // horizontal: 16.0,
right: AppElementSizes.paddingRegular * earlyPercentage,
), ),
child: const AudiobookChapterProgressBar(), // child: SizedBox(
// same as the image width
// width: imageSize,
child: currentChapter == null
? const SizedBox()
: Text(
currentChapter.title,
style: Theme.of(context).textTheme.titleLarge,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// ),
), ),
), ),
),
const Spacer(), // the book name and author
if (earlyPercentage > 0.5)
Opacity(
opacity: earlyPercentage,
child: Padding(
padding: EdgeInsets.only(
bottom: AppElementSizes.paddingRegular * earlyPercentage,
// horizontal: 16.0,
),
// child: SizedBox(
// same as the image width
// width: imageSize,
child: Text(
[
currentBookMetadata?.title ?? '',
currentBookMetadata?.authorName ?? '',
].join(' - '),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// ),
),
),
if (earlyPercentage > 0.5) const Spacer(),
// the progress bar
if (earlyPercentage > 0.6)
Opacity(
opacity: earlyPercentage,
child: SizedBox(
width: imageSize,
child: Padding(
padding: EdgeInsets.only(
top: AppElementSizes.paddingRegular * earlyPercentage,
left: AppElementSizes.paddingRegular * earlyPercentage,
right: AppElementSizes.paddingRegular * earlyPercentage,
),
child: const AudiobookChapterProgressBar(),
),
),
),
if (earlyPercentage > 0.6) const Spacer(),
// 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
Opacity( if (earlyPercentage > 0.8)
opacity: earlyPercentage, Opacity(
child: SizedBox( opacity: earlyPercentage,
width: imageSize, child: SizedBox(
child: Row( width: imageSize,
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
// previous chapter children: [
const AudiobookPlayerSeekChapterButton(isForward: false), // previous chapter
// buttonSkipBackwards const AudiobookPlayerSeekChapterButton(isForward: false),
const AudiobookPlayerSeekButton(isForward: false), // buttonSkipBackwards
AudiobookPlayerPlayPauseButton( const AudiobookPlayerSeekButton(isForward: false),
playPauseController: playPauseController, AudiobookPlayerPlayPauseButton(
), playPauseController: playPauseController,
// buttonSkipForwards ),
const AudiobookPlayerSeekButton(isForward: true), // buttonSkipForwards
// next chapter const AudiobookPlayerSeekButton(isForward: true),
const AudiobookPlayerSeekChapterButton(isForward: true), // next chapter
], const AudiobookPlayerSeekChapterButton(isForward: true),
],
),
), ),
), ),
), if (earlyPercentage > 0.8) const Spacer(),
const Spacer(),
// speed control, sleep timer, chapter list, and settings // speed control, sleep timer, chapter list, and settings
Opacity( if (earlyPercentage > 0.9)
opacity: earlyPercentage, Opacity(
child: Padding( opacity: earlyPercentage,
padding: EdgeInsets.only( child: Padding(
bottom: AppElementSizes.paddingRegular * 4 * earlyPercentage, padding: EdgeInsets.only(
), bottom: AppElementSizes.paddingRegular * 4 * earlyPercentage,
child: Row( ),
mainAxisAlignment: MainAxisAlignment.spaceEvenly, child: Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// speed control children: [
const PlayerSpeedAdjustButton(), // speed control
// sleep timer const PlayerSpeedAdjustButton(),
const SleepTimerButton(), // sleep timer
// chapter list const SleepTimerButton(),
const ChapterSelectionButton(), // chapter list
// settings const ChapterSelectionButton(),
// IconButton( // settings
// icon: const Icon(Icons.more_horiz), // IconButton(
// onPressed: () { // icon: const Icon(Icons.more_horiz),
// // show toast // onPressed: () {
// showNotImplementedToast(context); // // show toast
// }, // showNotImplementedToast(context);
// ), // },
], // ),
],
),
), ),
), ),
),
], ],
); );
} }

View file

@ -32,8 +32,7 @@ class PlayerWhenMinimized extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
final vanishingPercentage = 1 - percentageMiniplayer; final vanishingPercentage = 1 - percentageMiniplayer;
final progress = final progress = useStream(player.slowPositionStream, initialData: Duration.zero);
useStream(player.slowPositionStream, initialData: Duration.zero);
final bookMetaExpanded = ref.watch(currentBookMetadataProvider); final bookMetaExpanded = ref.watch(currentBookMetadataProvider);
@ -46,8 +45,7 @@ class PlayerWhenMinimized extends HookConsumerWidget {
// image // image
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: horizontal: ((availWidth - maxImgSize) / 2) * percentageMiniplayer,
((availWidth - maxImgSize) / 2) * percentageMiniplayer,
), ),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
@ -55,8 +53,7 @@ class PlayerWhenMinimized extends HookConsumerWidget {
context.pushNamed( context.pushNamed(
Routes.libraryItem.name, Routes.libraryItem.name,
pathParameters: { pathParameters: {
Routes.libraryItem.pathParamName!: Routes.libraryItem.pathParamName!: player.book!.libraryItemId,
player.book!.libraryItemId,
}, },
); );
}, },
@ -90,10 +87,7 @@ 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) color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
.colorScheme
.onSurface
.withValues(alpha: 0.7),
), ),
), ),
], ],
@ -106,37 +100,39 @@ class PlayerWhenMinimized extends HookConsumerWidget {
// controller.animateToHeight(state: PanelState.MAX); // controller.animateToHeight(state: PanelState.MAX);
// }, // },
// ), // ),
// rewind button // rewind button
Opacity( if (vanishingPercentage > 0.6)
opacity: vanishingPercentage, Opacity(
child: Padding( opacity: vanishingPercentage,
padding: const EdgeInsets.only(left: 8), child: Padding(
child: IconButton( padding: const EdgeInsets.only(left: 8),
icon: const Icon( child: IconButton(
Icons.replay_30, icon: const Icon(
size: AppElementSizes.iconSizeSmall, Icons.replay_30,
size: AppElementSizes.iconSizeSmall,
),
onPressed: () {},
), ),
onPressed: () {},
), ),
), ),
),
// play/pause button // play/pause button
Opacity( if (vanishingPercentage > 0.8)
opacity: vanishingPercentage, Opacity(
child: Padding( opacity: vanishingPercentage,
padding: const EdgeInsets.only(right: 8), child: Padding(
child: AudiobookPlayerPlayPauseButton( padding: const EdgeInsets.only(right: 8),
playPauseController: playPauseController, child: AudiobookPlayerPlayPauseButton(
playPauseController: playPauseController,
),
), ),
), ),
),
], ],
), ),
SizedBox( SizedBox(
height: barHeight, height: barHeight,
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: (progress.data ?? Duration.zero).inSeconds / value: (progress.data ?? Duration.zero).inSeconds / player.book!.duration.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,
), ),

File diff suppressed because it is too large Load diff