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; return playerExpandProgress.value;
} }
// a final MiniplayerController controller = MiniplayerController(); final audioBookMiniplayerController = MiniplayerController();
@Riverpod(keepAlive: true)
Raw<MiniplayerController> miniplayerController(
MiniplayerControllerRef ref,
) {
return MiniplayerController();
}

View file

@ -38,22 +38,5 @@ final playerHeightProvider = Provider<double>.internal(
); );
typedef PlayerHeightRef = ProviderRef<double>; 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: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member // 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, 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: ref.watch(miniplayerControllerProvider), controller: audioBookMiniplayerController,
elevation: 4, elevation: 4,
onDismissed: () { onDismissed: () {
// add a delay before closing the player // add a delay before closing the player

View file

@ -1,10 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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:miniplayer/miniplayer.dart';
import 'package:whispering_pages/features/explore/providers/search_controller.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/providers/player_form.dart';
import 'package:whispering_pages/features/player/view/audiobook_player.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 /// Builds the "shell" for the app by building a Scaffold with a
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold. /// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
class ScaffoldWithNavBar extends HookConsumerWidget { class ScaffoldWithNavBar extends HookConsumerWidget {
@ -30,52 +36,100 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
// Clamp the value between 0 and 1 // Clamp the value between 0 and 1
percentExpanded = percentExpanded.clamp(0.0, 1.0); percentExpanded = percentExpanded.clamp(0.0, 1.0);
final SearchController searchController = onBackButtonPressed() async {
ref.watch(globalSearchControllerProvider); final isPlayerExpanded = playerProgress != playerMinHeight;
return Scaffold( debugPrint(
body: Stack( 'BackButtonListener: Back button pressed, isPlayerExpanded: $isPlayerExpanded, stack: $navigationShellStack',
children: [ );
navigationShell, // close miniplayer if it is open
const AudiobookPlayer(), if (isPlayerExpanded) {
], debugPrint(
), 'BackButtonListener: closing the player',
bottomNavigationBar: Opacity( );
// Opacity is interpolated from 1 to 0 when player is expanded audioBookMiniplayerController.animateToHeight(state: PanelState.MIN);
opacity: 1 - percentExpanded, return true;
child: SizedBox( }
// height is interpolated from 0 to 56 when player is expanded
height: 56 * (1 - percentExpanded),
child: BottomNavigationBar( // do the the following only if the current branch has nothing to pop
elevation: 0.0, final canPop = GoRouter.of(context).canPop();
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 if (canPop) {
// world scenario, the items would most likely be generated from the debugPrint(
// branches of the shell route, which can be fetched using 'BackButtonListener: passing it to the router as canPop is true',
// `navigationShell.route.branches`. );
items: _navigationItems return false;
.map( }
(item) => BottomNavigationBarItem(
icon: Icon(item.icon), if (navigationShellStack.isNotEmpty) {
activeIcon: item.activeIcon != null // pop the last index from the stack and navigate to it
? Icon(item.activeIcon) final index = navigationShellStack.last;
: Icon(item.icon), navigationShellStack.remove(index);
label: item.name, debugPrint('BackButtonListener: popping the stack, index: $index');
),
) // if the stack is empty, navigate to home else navigate to the last index
.toList(), if (navigationShellStack.isNotEmpty) {
currentIndex: navigationShell.currentIndex, navigationShell.goBranch(navigationShellStack.last);
onTap: (int index) => _onTap(context, index, ref), 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, 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. // Check if the current branch is the same as the branch that was tapped.
// If it is, debugPrint a message to the console. // If it is, debugPrint a message to the console.
if (index == navigationShell.currentIndex) { if (index == navigationShell.currentIndex) {