From 402e2641372dcdf327a6b897bcc399e7b5a319c0 Mon Sep 17 00:00:00 2001 From: Dr-Blank <64108942+Dr-Blank@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:58:12 -0400 Subject: [PATCH] intercept back button to minimize player first and also navigate stack of branches like youtube --- .../player/providers/player_form.dart | 8 +- .../player/providers/player_form.g.dart | 17 -- .../player/view/audiobook_player.dart | 2 +- lib/router/scaffold_with_nav_bar.dart | 147 +++++++++++++----- 4 files changed, 106 insertions(+), 68 deletions(-) diff --git a/lib/features/player/providers/player_form.dart b/lib/features/player/providers/player_form.dart index 33e52fa..1b0c3f2 100644 --- a/lib/features/player/providers/player_form.dart +++ b/lib/features/player/providers/player_form.dart @@ -58,10 +58,4 @@ double playerHeight( return playerExpandProgress.value; } -// a final MiniplayerController controller = MiniplayerController(); -@Riverpod(keepAlive: true) -Raw miniplayerController( - MiniplayerControllerRef ref, -) { - return MiniplayerController(); -} +final audioBookMiniplayerController = MiniplayerController(); diff --git a/lib/features/player/providers/player_form.g.dart b/lib/features/player/providers/player_form.g.dart index 613c1d1..b3b73d9 100644 --- a/lib/features/player/providers/player_form.g.dart +++ b/lib/features/player/providers/player_form.g.dart @@ -38,22 +38,5 @@ final playerHeightProvider = Provider.internal( ); typedef PlayerHeightRef = ProviderRef; -String _$miniplayerControllerHash() => - r'a3677e63a9823881f45a12855e74bf558322e0ec'; - -/// See also [miniplayerController]. -@ProviderFor(miniplayerController) -final miniplayerControllerProvider = - Provider>.internal( - miniplayerController, - name: r'miniplayerControllerProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$miniplayerControllerHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef MiniplayerControllerRef = ProviderRef>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/features/player/view/audiobook_player.dart b/lib/features/player/view/audiobook_player.dart index 655c2e8..c88220b 100644 --- a/lib/features/player/view/audiobook_player.dart +++ b/lib/features/player/view/audiobook_player.dart @@ -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 diff --git a/lib/router/scaffold_with_nav_bar.dart b/lib/router/scaffold_with_nav_bar.dart index 45a69e6..aee00ed 100644 --- a/lib/router/scaffold_with_nav_bar.dart +++ b/lib/router/scaffold_with_nav_bar.dart @@ -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 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) {