Vaani/lib/features/item_viewer/view/library_item_page.dart

168 lines
5.7 KiB
Dart
Raw Normal View History

2024-05-09 23:23:50 -04:00
import 'dart:math';
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
2024-05-09 00:41:19 -04:00
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
2024-05-09 00:41:19 -04:00
import 'package:hooks_riverpod/hooks_riverpod.dart';
2024-08-23 04:21:46 -04:00
import 'package:vaani/api/library_item_provider.dart';
import 'package:vaani/features/item_viewer/view/library_item_sliver_app_bar.dart';
import 'package:vaani/features/player/view/mini_player_bottom_padding.dart';
2024-08-23 04:21:46 -04:00
import 'package:vaani/router/models/library_item_extras.dart';
import 'package:vaani/shared/widgets/expandable_description.dart';
2024-05-09 00:41:19 -04:00
2024-06-16 22:24:32 -04:00
import 'library_item_actions.dart';
import 'library_item_hero_section.dart';
import 'library_item_metadata.dart';
2024-05-11 04:06:25 -04:00
2024-05-09 00:41:19 -04:00
class LibraryItemPage extends HookConsumerWidget {
2026-01-10 16:51:05 +05:30
const LibraryItemPage({super.key, required this.itemId, this.extra});
2024-05-09 00:41:19 -04:00
final String itemId;
final Object? extra;
static const double _showFabThreshold = 300.0;
2024-05-09 00:41:19 -04:00
@override
Widget build(BuildContext context, WidgetRef ref) {
2026-01-10 16:51:05 +05:30
final additionalItemData = extra is LibraryItemExtras
? extra as LibraryItemExtras
: null;
final scrollController = useScrollController();
final showFab = useState(false);
// Effect to listen to scroll changes and update FAB visibility
2026-01-10 16:51:05 +05:30
useEffect(() {
void listener() {
if (!scrollController.hasClients) {
return; // Ensure controller is attached
}
2026-01-10 16:51:05 +05:30
final shouldShow = scrollController.offset > _showFabThreshold;
// Update state only if it changes and widget is still mounted
if (showFab.value != shouldShow && context.mounted) {
showFab.value = shouldShow;
}
}
2026-01-10 16:51:05 +05:30
scrollController.addListener(listener);
// Initial check in case the view starts scrolled (less likely but safe)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (scrollController.hasClients && context.mounted) {
listener();
}
});
2026-01-10 16:51:05 +05:30
// Cleanup: remove the listener when the widget is disposed
return () => scrollController.removeListener(listener);
}, [scrollController]); // Re-run effect if scrollController changes
// --- FAB Scroll-to-Top Logic ---
void scrollToTop() {
if (scrollController.hasClients) {
scrollController.animateTo(
0.0, // Target offset (top)
duration: 300.ms,
curve: Curves.easeInOut,
);
}
}
2024-05-09 00:41:19 -04:00
return ThemeProvider(
initTheme: Theme.of(context),
duration: 200.ms,
child: ThemeSwitchingArea(
2024-06-16 22:24:32 -04:00
child: Scaffold(
floatingActionButton: AnimatedSwitcher(
duration: 250.ms,
// A common transition for FABs (fade + scale)
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation,
2026-01-10 16:51:05 +05:30
child: FadeTransition(opacity: animation, child: child),
);
},
child: showFab.value
? FloatingActionButton(
// Key is important for AnimatedSwitcher to differentiate
key: const ValueKey('fab-scroll-top'),
onPressed: scrollToTop,
tooltip: 'Scroll to top',
child: const Icon(Icons.arrow_upward),
)
2026-01-10 16:51:05 +05:30
: const SizedBox.shrink(key: ValueKey('fab-empty')),
),
2024-06-16 22:24:32 -04:00
body: CustomScrollView(
controller: scrollController,
2024-06-16 22:24:32 -04:00
slivers: [
LibraryItemSliverAppBar(
id: itemId,
scrollController: scrollController,
),
2024-06-16 22:24:32 -04:00
SliverPadding(
padding: const EdgeInsets.all(8),
sliver: LibraryItemHeroSection(
itemId: itemId,
extraMap: additionalItemData,
2024-06-16 22:24:32 -04:00
),
),
// a horizontal display with dividers of metadata
2026-01-10 16:51:05 +05:30
SliverToBoxAdapter(child: LibraryItemMetadata(id: itemId)),
2024-06-16 22:24:32 -04:00
// a row of actions like play, download, share, etc
2026-01-10 16:51:05 +05:30
SliverToBoxAdapter(child: LibraryItemActions(id: itemId)),
2024-06-16 22:24:32 -04:00
// a expandable section for book description
2026-01-10 16:51:05 +05:30
SliverToBoxAdapter(child: LibraryItemDescription(id: itemId)),
2024-08-20 08:36:39 -04:00
// a padding at the bottom to make sure the last item is not hidden by mini player
const SliverToBoxAdapter(child: MiniPlayerBottomPadding()),
2024-06-16 22:24:32 -04:00
],
2024-05-12 05:38:30 -04:00
),
),
),
);
}
}
class LibraryItemDescription extends HookConsumerWidget {
2026-01-10 16:51:05 +05:30
const LibraryItemDescription({super.key, required this.id});
final String id;
@override
Widget build(BuildContext context, WidgetRef ref) {
2026-01-10 16:46:06 +05:30
final item = ref.watch(libraryItemProvider(id)).value;
if (item == null) {
return const SizedBox();
}
return ExpandableDescription(
title: 'About the Book',
content: item.media.metadata.description ?? 'Sorry, no description found',
);
}
}
2024-05-11 04:06:25 -04:00
/// Calculate the width of the book cover based on the screen size
2024-05-09 23:23:50 -04:00
double calculateWidth(
BuildContext context,
BoxConstraints constraints, {
2026-01-10 16:51:05 +05:30
2024-05-11 04:06:25 -04:00
/// width ratio of the cover image to the available width
double widthRatio = 0.4,
/// height ratio of the cover image to the available height
2024-05-12 05:38:30 -04:00
double maxHeightToUse = 0.25,
2024-05-09 23:23:50 -04:00
}) {
2026-01-10 16:51:05 +05:30
final availHeight = min(
constraints.maxHeight,
MediaQuery.of(context).size.height,
);
final availWidth = min(
constraints.maxWidth,
MediaQuery.of(context).size.width,
);
2024-05-09 23:23:50 -04:00
2024-05-11 04:06:25 -04:00
// make the width widthRatio of the available width
2024-05-09 23:23:50 -04:00
var width = availWidth * widthRatio;
2024-05-11 04:06:25 -04:00
// but never exceed more than heightRatio of height
if (width > availHeight * maxHeightToUse) {
width = availHeight * maxHeightToUse;
2024-05-09 23:23:50 -04:00
}
return width;
}