Vaani/lib/features/player/view/player_expanded.dart

212 lines
7.2 KiB
Dart
Raw Normal View History

2025-11-13 17:53:23 +08:00
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/constants/sizes.dart';
2025-11-19 17:43:04 +08:00
import 'package:vaani/features/player/providers/session_provider.dart';
2025-11-13 17:53:23 +08:00
import 'package:vaani/features/player/view/widgets/player_player_pause_button.dart';
import 'package:vaani/features/player/view/widgets/player_progress_bar.dart';
2025-11-22 15:54:29 +08:00
import 'package:vaani/features/player/view/widgets/player_skip_chapter_start_end.dart';
2025-11-13 17:53:23 +08:00
import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart';
import 'package:vaani/shared/widgets/not_implemented.dart';
import 'package:vaani/shared/widgets/shelves/book_shelf.dart';
import 'widgets/audiobook_player_seek_button.dart';
import 'widgets/audiobook_player_seek_chapter_button.dart';
import 'widgets/chapter_selection_button.dart';
import 'widgets/player_speed_adjust_button.dart';
var pendingPlayerModals = 0;
class PlayerExpanded extends HookConsumerWidget {
const PlayerExpanded({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
2025-11-22 15:54:29 +08:00
final session = ref.watch(sessionProvider);
2025-11-19 17:43:04 +08:00
if (session == null) {
return SizedBox.shrink();
}
2025-11-13 17:53:23 +08:00
/// all the properties that help in building the widget are calculated from the [percentageExpandedPlayer]
/// however, some properties need to start later than 0% and end before 100%
2025-11-19 17:43:04 +08:00
final currentChapter = ref.watch(currentChapterProvider);
// final currentBookMetadata = ref.watch(currentBookMetadataProvider);
2025-11-13 17:53:23 +08:00
// max height of the player is the height of the screen
final playerMaxHeight = MediaQuery.of(context).size.height;
final availWidth = MediaQuery.of(context).size.width;
// the image width when the player is expanded
final imageSize = min(playerMaxHeight * 0.5, availWidth * 0.9);
return Scaffold(
appBar: AppBar(
leading: IconButton(
iconSize: 30,
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () => context.pop(),
),
actions: [
IconButton(
icon: const Icon(Icons.cast),
onPressed: () {
showNotImplementedToast(context);
},
),
],
),
body: Column(
children: [
// sized box for system status bar; not needed as not full screen
SizedBox(
height: MediaQuery.of(context).padding.top,
),
// the image
Padding(
padding: EdgeInsets.only(top: AppElementSizes.paddingLarge),
child: Align(
alignment: Alignment.center,
// add a shadow to the image elevation hovering effect
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Theme.of(context)
.colorScheme
.primary
.withValues(alpha: 0.1),
blurRadius: 32,
spreadRadius: 8,
),
],
),
child: SizedBox(
height: imageSize,
child: InkWell(
onTap: () {},
child: ClipRRect(
borderRadius: BorderRadius.circular(
AppElementSizes.borderRadiusRegular,
),
2025-11-19 17:43:04 +08:00
child: BookCoverWidget(),
2025-11-13 17:53:23 +08:00
),
),
),
),
),
),
// the chapter title
Expanded(
child: Padding(
padding: EdgeInsets.only(top: AppElementSizes.paddingRegular),
child: currentChapter == null
? const SizedBox()
: Text(
currentChapter.title,
style: Theme.of(context).textTheme.titleLarge,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
// the book name and author
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: AppElementSizes.paddingRegular),
child: Text(
[
2025-11-19 17:43:04 +08:00
session.displayTitle,
session.displayAuthor,
2025-11-13 17:53:23 +08:00
].join(' - '),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.7),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
// the progress bar
Expanded(
child: SizedBox(
width: imageSize,
child: Padding(
padding: EdgeInsets.only(
left: AppElementSizes.paddingRegular,
right: AppElementSizes.paddingRegular,
),
child: const AudiobookChapterProgressBar(),
),
),
),
2025-11-22 15:54:29 +08:00
SizedBox(
width: imageSize,
child: Padding(
padding: EdgeInsets.only(
left: AppElementSizes.paddingRegular,
right: AppElementSizes.paddingRegular,
2025-11-13 17:53:23 +08:00
),
2025-11-22 15:54:29 +08:00
child: const AudiobookProgressBar(),
2025-11-13 17:53:23 +08:00
),
),
// the chapter skip buttons, seek 30 seconds back and forward, and play/pause button
Expanded(
flex: 2,
child: SizedBox(
width: imageSize,
height: AppElementSizes.iconSizeRegular,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// previous chapter
const AudiobookPlayerSeekChapterButton(isForward: false),
// buttonSkipBackwards
const AudiobookPlayerSeekButton(isForward: false),
AudiobookPlayerPlayPauseButton(),
// buttonSkipForwards
const AudiobookPlayerSeekButton(isForward: true),
// next chapter
const AudiobookPlayerSeekChapterButton(isForward: true),
],
),
),
),
// speed control, sleep timer, chapter list, and settings
Expanded(
child: SizedBox(
width: imageSize,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// speed control
const PlayerSpeedAdjustButton(),
const Spacer(),
// sleep timer
const SleepTimerButton(),
const Spacer(),
// chapter list
const ChapterSelectionButton(),
const Spacer(),
// 跳过片头片尾
SkipChapterStartEndButton(),
],
),
),
),
],
),
);
}
}