From f4a8373d9a9752176c43ef16edce0724d8459b96 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Mon, 8 Jan 2024 09:09:56 -0800 Subject: [PATCH 1/2] fixed issue where drawer icon does not trigger the drawer to open on the feed page --- lib/feed/view/feed_page.dart | 18 +- lib/feed/widgets/feed_page_app_bar.dart | 8 +- lib/thunder/pages/thunder_page.dart | 247 +++++++++++++----------- 3 files changed, 149 insertions(+), 124 deletions(-) diff --git a/lib/feed/view/feed_page.dart b/lib/feed/view/feed_page.dart index 9902a3d34..f05c3414e 100644 --- a/lib/feed/view/feed_page.dart +++ b/lib/feed/view/feed_page.dart @@ -51,6 +51,7 @@ class FeedPage extends StatefulWidget { this.communityName, this.userId, this.username, + this.scaffoldStateKey, }); /// The type of feed to display. @@ -80,6 +81,9 @@ class FeedPage extends StatefulWidget { /// This is useful if we want to keep the user on the "same" page final bool useGlobalFeedBloc; + /// The scaffold key which holds the drawer + final GlobalKey? scaffoldStateKey; + @override State createState() => _FeedPageState(); } @@ -123,7 +127,7 @@ class _FeedPageState extends State with AutomaticKeepAliveClientMixin< return BlocProvider.value( value: bloc, - child: const FeedView(), + child: FeedView(scaffoldStateKey: widget.scaffoldStateKey), ); } @@ -139,13 +143,16 @@ class _FeedPageState extends State with AutomaticKeepAliveClientMixin< username: widget.username, reset: true, )), - child: const FeedView(), + child: FeedView(scaffoldStateKey: widget.scaffoldStateKey), ); } } class FeedView extends StatefulWidget { - const FeedView({super.key}); + const FeedView({super.key, this.scaffoldStateKey}); + + /// The scaffold key which holds the drawer + final GlobalKey? scaffoldStateKey; @override State createState() => _FeedViewState(); @@ -291,7 +298,10 @@ class _FeedViewState extends State { physics: showCommunitySidebar ? const NeverScrollableScrollPhysics() : null, // Disable scrolling on the feed page when the community sidebar is open controller: _scrollController, slivers: [ - FeedPageAppBar(showAppBarTitle: (state.feedType == FeedType.general && state.status != FeedStatus.initial) ? true : showAppBarTitle), + FeedPageAppBar( + showAppBarTitle: (state.feedType == FeedType.general && state.status != FeedStatus.initial) ? true : showAppBarTitle, + scaffoldStateKey: widget.scaffoldStateKey, + ), // Display loading indicator until the feed is fetched if (state.status == FeedStatus.initial) const SliverFillRemaining( diff --git a/lib/feed/widgets/feed_page_app_bar.dart b/lib/feed/widgets/feed_page_app_bar.dart index 8dc9cafcb..ab418f6c0 100644 --- a/lib/feed/widgets/feed_page_app_bar.dart +++ b/lib/feed/widgets/feed_page_app_bar.dart @@ -21,10 +21,14 @@ import 'package:thunder/shared/sort_picker.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; class FeedPageAppBar extends StatelessWidget { - const FeedPageAppBar({super.key, this.showAppBarTitle = true}); + const FeedPageAppBar({super.key, this.showAppBarTitle = true, this.scaffoldStateKey}); + /// Whether to show the app bar title final bool showAppBarTitle; + /// The scaffold key of the parent scaffold holding the drawer. + final GlobalKey? scaffoldStateKey; + @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; @@ -50,7 +54,7 @@ class FeedPageAppBar extends StatelessWidget { : Icon(Icons.menu, semanticLabel: MaterialLocalizations.of(context).openAppDrawerTooltip), onPressed: () { HapticFeedback.mediumImpact(); - (Navigator.of(context).canPop() && feedBloc.state.feedType == FeedType.community) ? Navigator.of(context).maybePop() : Scaffold.of(context).openDrawer(); + (Navigator.of(context).canPop() && feedBloc.state.feedType == FeedType.community) ? Navigator.of(context).maybePop() : scaffoldStateKey?.currentState?.openDrawer(); }, ), actions: feedState.status != FeedStatus.failureLoadingCommunity diff --git a/lib/thunder/pages/thunder_page.dart b/lib/thunder/pages/thunder_page.dart index c6fe69701..d60e232b7 100644 --- a/lib/thunder/pages/thunder_page.dart +++ b/lib/thunder/pages/thunder_page.dart @@ -75,6 +75,8 @@ class _ThunderState extends State { bool reduceAnimations = false; + final GlobalKey scaffoldStateKey = GlobalKey(); + final GlobalKey scaffoldMessengerKey = GlobalKey(); late final StreamSubscription mediaIntentDataStreamSubscription; @@ -404,129 +406,138 @@ class _ThunderState extends State { // Update the variable so that it can be used in _handleBackButtonPress _isFabOpen = thunderBlocState.isFabOpen; - return Scaffold( + return ScaffoldMessenger( key: scaffoldMessengerKey, - drawer: selectedPageIndex == 0 - ? CommunityDrawer( - navigateToAccount: () { - Navigator.of(context).pop(); - - if (reduceAnimations) { - pageController.jumpToPage(2); - } else { - pageController.animateToPage(2, duration: const Duration(milliseconds: 500), curve: Curves.ease); - } - }, - ) - : null, - floatingActionButton: thunderBlocState.enableFeedsFab - ? AnimatedOpacity( - opacity: selectedPageIndex == 0 ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - curve: Curves.easeIn, - child: const FeedFAB(), - ) - : null, - floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling, - bottomNavigationBar: CustomBottomNavigationBar( - selectedPageIndex: selectedPageIndex, - onPageChange: (int index) { - setState(() { - selectedPageIndex = index; - - if (reduceAnimations) { - pageController.jumpToPage(index); - } else { - pageController.animateToPage(index, duration: const Duration(milliseconds: 500), curve: Curves.ease); + child: Scaffold( + key: scaffoldStateKey, + drawer: selectedPageIndex == 0 + ? CommunityDrawer( + navigateToAccount: () { + Navigator.of(context).pop(); + + if (reduceAnimations) { + pageController.jumpToPage(2); + } else { + pageController.animateToPage(2, duration: const Duration(milliseconds: 500), curve: Curves.ease); + } + }, + ) + : null, + floatingActionButton: thunderBlocState.enableFeedsFab + ? AnimatedOpacity( + opacity: selectedPageIndex == 0 ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + curve: Curves.easeIn, + child: const FeedFAB(), + ) + : null, + floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling, + bottomNavigationBar: CustomBottomNavigationBar( + selectedPageIndex: selectedPageIndex, + onPageChange: (int index) { + setState(() { + selectedPageIndex = index; + + if (reduceAnimations) { + pageController.jumpToPage(index); + } else { + pageController.animateToPage(index, duration: const Duration(milliseconds: 500), curve: Curves.ease); + } + }); + }, + ), + body: BlocConsumer( + listenWhen: (AuthState previous, AuthState current) { + if (previous.isLoggedIn != current.isLoggedIn || previous.status == AuthStatus.initial) return true; + return false; + }, + buildWhen: (previous, current) => current.status != AuthStatus.failure && current.status != AuthStatus.loading, + listener: (context, state) { + context.read().add(GetAccountInformation()); + + // Add a bit of artificial delay to allow preferences to set the proper active profile + Future.delayed(const Duration(milliseconds: 500), () => context.read().add(const GetInboxEvent(reset: true))); + if (context.read().state.status != FeedStatus.initial) { + context.read().add( + FeedFetchedEvent( + feedType: FeedType.general, + postListingType: thunderBlocState.defaultListingType, + sortType: thunderBlocState.defaultSortType, + reset: true, + ), + ); } - }); - }, - ), - body: BlocConsumer( - listenWhen: (AuthState previous, AuthState current) { - if (previous.isLoggedIn != current.isLoggedIn || previous.status == AuthStatus.initial) return true; - return false; - }, - buildWhen: (previous, current) => current.status != AuthStatus.failure && current.status != AuthStatus.loading, - listener: (context, state) { - context.read().add(GetAccountInformation()); - - // Add a bit of artificial delay to allow preferences to set the proper active profile - Future.delayed(const Duration(milliseconds: 500), () => context.read().add(const GetInboxEvent(reset: true))); - if (context.read().state.status != FeedStatus.initial) { - context.read().add( - FeedFetchedEvent( - feedType: FeedType.general, - postListingType: thunderBlocState.defaultListingType, - sortType: thunderBlocState.defaultSortType, - reset: true, + }, + builder: (context, state) { + switch (state.status) { + case AuthStatus.initial: + context.read().add(CheckAuth()); + return Scaffold( + appBar: AppBar(), + body: Center( + child: Container(), ), ); - } - }, - builder: (context, state) { - switch (state.status) { - case AuthStatus.initial: - context.read().add(CheckAuth()); - return Scaffold( - appBar: AppBar(), - body: Center( - child: Container(), - ), - ); - case AuthStatus.success: - Version? version = thunderBlocState.version; - bool showInAppUpdateNotification = thunderBlocState.showInAppUpdateNotification; - - if (version?.hasUpdate == true && hasShownUpdateDialog == false && showInAppUpdateNotification == true) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showUpdateNotification(context, version); - setState(() => hasShownUpdateDialog = true); - }); - } + case AuthStatus.success: + Version? version = thunderBlocState.version; + bool showInAppUpdateNotification = thunderBlocState.showInAppUpdateNotification; + + if (version?.hasUpdate == true && hasShownUpdateDialog == false && showInAppUpdateNotification == true) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showUpdateNotification(context, version); + setState(() => hasShownUpdateDialog = true); + }); + } - return PageView( - controller: pageController, - onPageChanged: (index) => setState(() => selectedPageIndex = index), - physics: const NeverScrollableScrollPhysics(), - children: [ - Stack( - children: [ - FeedPage(useGlobalFeedBloc: true, feedType: FeedType.general, postListingType: thunderBlocState.defaultListingType, sortType: thunderBlocState.defaultSortType), - AnimatedOpacity( - opacity: _isFabOpen ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: _isFabOpen - ? ModalBarrier( - color: theme.colorScheme.background.withOpacity(0.95), - dismissible: true, - onDismiss: () => context.read().add(const OnFabToggle(false)), - ) - : null, - ), - ], - ), - const SearchPage(), - const AccountPage(), - const InboxPage(), - SettingsPage(), - ], - ); - - // Should never hit these, they're handled by the login page - case AuthStatus.failure: - case AuthStatus.loading: - return Container(); - case AuthStatus.failureCheckingInstance: - showSnackbar(context, state.errorMessage ?? AppLocalizations.of(context)!.missingErrorMessage); - return ErrorMessage( - title: AppLocalizations.of(context)!.unableToLoadInstance(LemmyClient.instance.lemmyApiV3.host), - message: AppLocalizations.of(context)!.internetOrInstanceIssues, - actionText: AppLocalizations.of(context)!.accountSettings, - action: () => showProfileModalSheet(context), - ); - } - }, + return PageView( + controller: pageController, + onPageChanged: (index) => setState(() => selectedPageIndex = index), + physics: const NeverScrollableScrollPhysics(), + children: [ + Stack( + children: [ + FeedPage( + useGlobalFeedBloc: true, + feedType: FeedType.general, + postListingType: thunderBlocState.defaultListingType, + sortType: thunderBlocState.defaultSortType, + scaffoldStateKey: scaffoldStateKey, + ), + AnimatedOpacity( + opacity: _isFabOpen ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: _isFabOpen + ? ModalBarrier( + color: theme.colorScheme.background.withOpacity(0.95), + dismissible: true, + onDismiss: () => context.read().add(const OnFabToggle(false)), + ) + : null, + ), + ], + ), + const SearchPage(), + const AccountPage(), + const InboxPage(), + SettingsPage(), + ], + ); + + // Should never hit these, they're handled by the login page + case AuthStatus.failure: + case AuthStatus.loading: + return Container(); + case AuthStatus.failureCheckingInstance: + showSnackbar(context, state.errorMessage ?? AppLocalizations.of(context)!.missingErrorMessage); + return ErrorMessage( + title: AppLocalizations.of(context)!.unableToLoadInstance(LemmyClient.instance.lemmyApiV3.host), + message: AppLocalizations.of(context)!.internetOrInstanceIssues, + actionText: AppLocalizations.of(context)!.accountSettings, + action: () => showProfileModalSheet(context), + ); + } + }, + ), ), ); case ThunderStatus.failure: From 313ca54e25ac1fd945ec33170cf73566ee17a2de Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Mon, 8 Jan 2024 09:17:42 -0800 Subject: [PATCH 2/2] fixed a visual glitch where navigating to a community shows the drawe icon briefly before showing the back icon --- lib/feed/widgets/feed_page_app_bar.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/feed/widgets/feed_page_app_bar.dart b/lib/feed/widgets/feed_page_app_bar.dart index ab418f6c0..ae474d0a3 100644 --- a/lib/feed/widgets/feed_page_app_bar.dart +++ b/lib/feed/widgets/feed_page_app_bar.dart @@ -44,7 +44,7 @@ class FeedPageAppBar extends StatelessWidget { toolbarHeight: 70.0, title: FeedAppBarTitle(visible: showAppBarTitle), leading: IconButton( - icon: Navigator.of(context).canPop() && feedBloc.state.feedType == FeedType.community + icon: scaffoldStateKey == null ? (!kIsWeb && Platform.isIOS ? Icon( Icons.arrow_back_ios_new_rounded, @@ -54,7 +54,7 @@ class FeedPageAppBar extends StatelessWidget { : Icon(Icons.menu, semanticLabel: MaterialLocalizations.of(context).openAppDrawerTooltip), onPressed: () { HapticFeedback.mediumImpact(); - (Navigator.of(context).canPop() && feedBloc.state.feedType == FeedType.community) ? Navigator.of(context).maybePop() : scaffoldStateKey?.currentState?.openDrawer(); + (scaffoldStateKey == null && feedBloc.state.feedType == FeedType.community) ? Navigator.of(context).maybePop() : scaffoldStateKey?.currentState?.openDrawer(); }, ), actions: feedState.status != FeedStatus.failureLoadingCommunity