intercept back button to minimize player first

and also navigate stack of branches like youtube
This commit is contained in:
Dr-Blank 2024-06-20 00:58:12 -04:00
parent ed236ef117
commit 402e264137
No known key found for this signature in database
GPG key ID: 7452CC63F210A266
4 changed files with 106 additions and 68 deletions

View file

@ -58,10 +58,4 @@ double playerHeight(
return playerExpandProgress.value;
}
// a final MiniplayerController controller = MiniplayerController();
@Riverpod(keepAlive: true)
Raw<MiniplayerController> miniplayerController(
MiniplayerControllerRef ref,
) {
return MiniplayerController();
}
final audioBookMiniplayerController = MiniplayerController();

View file

@ -38,22 +38,5 @@ final playerHeightProvider = Provider<double>.internal(
);
typedef PlayerHeightRef = ProviderRef<double>;
String _$miniplayerControllerHash() =>
r'a3677e63a9823881f45a12855e74bf558322e0ec';
/// See also [miniplayerController].
@ProviderFor(miniplayerController)
final miniplayerControllerProvider =
Provider<Raw<MiniplayerController>>.internal(
miniplayerController,
name: r'miniplayerControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$miniplayerControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef MiniplayerControllerRef = ProviderRef<Raw<MiniplayerController>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View file

@ -93,7 +93,7 @@ class AudiobookPlayer extends HookConsumerWidget {
minHeight: playerMinHeight,
// subtract the height of notches and other system UI
maxHeight: playerMaxHeight,
controller: ref.watch(miniplayerControllerProvider),
controller: audioBookMiniplayerController,
elevation: 4,
onDismissed: () {
// add a delay before closing the player

View file

@ -1,10 +1,16 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:miniplayer/miniplayer.dart';
import 'package:whispering_pages/features/explore/providers/search_controller.dart';
import 'package:whispering_pages/features/player/providers/player_form.dart';
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
// stack to track changes in navigationShell.currentIndex
// home is always at index 0 and at the start and should be the last before popping
// if stack is empty, push home, if already contains home, pop it
final Set<int> navigationShellStack = {};
/// Builds the "shell" for the app by building a Scaffold with a
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
class ScaffoldWithNavBar extends HookConsumerWidget {
@ -30,52 +36,100 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
// Clamp the value between 0 and 1
percentExpanded = percentExpanded.clamp(0.0, 1.0);
final SearchController searchController =
ref.watch(globalSearchControllerProvider);
onBackButtonPressed() async {
final isPlayerExpanded = playerProgress != playerMinHeight;
return Scaffold(
body: Stack(
children: [
navigationShell,
const AudiobookPlayer(),
],
),
bottomNavigationBar: Opacity(
// Opacity is interpolated from 1 to 0 when player is expanded
opacity: 1 - percentExpanded,
child: SizedBox(
// height is interpolated from 0 to 56 when player is expanded
height: 56 * (1 - percentExpanded),
debugPrint(
'BackButtonListener: Back button pressed, isPlayerExpanded: $isPlayerExpanded, stack: $navigationShellStack',
);
// close miniplayer if it is open
if (isPlayerExpanded) {
debugPrint(
'BackButtonListener: closing the player',
);
audioBookMiniplayerController.animateToHeight(state: PanelState.MIN);
return true;
}
child: BottomNavigationBar(
elevation: 0.0,
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
selectedFontSize:
Theme.of(context).textTheme.labelMedium!.fontSize!,
unselectedFontSize:
Theme.of(context).textTheme.labelMedium!.fontSize!,
showUnselectedLabels: false,
fixedColor: Theme.of(context).colorScheme.onSurface,
enableFeedback: true,
type: BottomNavigationBarType.fixed,
// do the the following only if the current branch has nothing to pop
final canPop = GoRouter.of(context).canPop();
// Here, the items of BottomNavigationBar are hard coded. In a real
// world scenario, the items would most likely be generated from the
// branches of the shell route, which can be fetched using
// `navigationShell.route.branches`.
items: _navigationItems
.map(
(item) => BottomNavigationBarItem(
icon: Icon(item.icon),
activeIcon: item.activeIcon != null
? Icon(item.activeIcon)
: Icon(item.icon),
label: item.name,
),
)
.toList(),
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index, ref),
if (canPop) {
debugPrint(
'BackButtonListener: passing it to the router as canPop is true',
);
return false;
}
if (navigationShellStack.isNotEmpty) {
// pop the last index from the stack and navigate to it
final index = navigationShellStack.last;
navigationShellStack.remove(index);
debugPrint('BackButtonListener: popping the stack, index: $index');
// if the stack is empty, navigate to home else navigate to the last index
if (navigationShellStack.isNotEmpty) {
navigationShell.goBranch(navigationShellStack.last);
return true;
}
}
if (navigationShell.currentIndex != 0) {
// if the stack is empty and the current branch is not home, navigate to home
debugPrint('BackButtonListener: navigating to home');
navigationShell.goBranch(0);
return true;
}
debugPrint('BackButtonListener: passing it to the router');
return false;
}
return BackButtonListener(
onBackButtonPressed: onBackButtonPressed,
child: Scaffold(
body: Stack(
children: [
navigationShell,
const AudiobookPlayer(),
],
),
bottomNavigationBar: Opacity(
// Opacity is interpolated from 1 to 0 when player is expanded
opacity: 1 - percentExpanded,
child: SizedBox(
// height is interpolated from 0 to 56 when player is expanded
height: 56 * (1 - percentExpanded),
child: BottomNavigationBar(
elevation: 0.0,
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
selectedFontSize:
Theme.of(context).textTheme.labelMedium!.fontSize!,
unselectedFontSize:
Theme.of(context).textTheme.labelMedium!.fontSize!,
showUnselectedLabels: false,
fixedColor: Theme.of(context).colorScheme.onSurface,
enableFeedback: true,
type: BottomNavigationBarType.fixed,
// Here, the items of BottomNavigationBar are hard coded. In a real
// world scenario, the items would most likely be generated from the
// branches of the shell route, which can be fetched using
// `navigationShell.route.branches`.
items: _navigationItems
.map(
(item) => BottomNavigationBarItem(
icon: Icon(item.icon),
activeIcon: item.activeIcon != null
? Icon(item.activeIcon)
: Icon(item.icon),
label: item.name,
),
)
.toList(),
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index, ref),
),
),
),
),
@ -97,6 +151,13 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
initialLocation: index == navigationShell.currentIndex,
);
// add the index to the stack but remove it if it is already there
if (navigationShellStack.contains(index)) {
navigationShellStack.remove(index);
}
navigationShellStack.add(index);
debugPrint('Tapped index: $index, stack: $navigationShellStack');
// Check if the current branch is the same as the branch that was tapped.
// If it is, debugPrint a message to the console.
if (index == navigationShell.currentIndex) {