From ad4e4071fa28e0fe493f746adb9d82eb3b1733a3 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 5 Oct 2024 03:12:47 +0800 Subject: [PATCH] :recycle: Use bottom navigation bar instead --- lib/screens/explore.dart | 2 +- lib/shells/root_shell.dart | 9 +- .../navigation/app_account_widget.dart | 80 +++++ lib/widgets/navigation/app_navigation.dart | 16 +- .../navigation/app_navigation_bottom.dart | 37 ++ .../navigation/app_navigation_drawer.dart | 330 ------------------ 6 files changed, 131 insertions(+), 343 deletions(-) create mode 100644 lib/widgets/navigation/app_account_widget.dart create mode 100644 lib/widgets/navigation/app_navigation_bottom.dart delete mode 100644 lib/widgets/navigation/app_navigation_drawer.dart diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 586c22f..1e58c50 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -83,7 +83,7 @@ class _ExploreScreenState extends State return [ SliverAppBar( title: AppBarTitle('explore'.tr), - centerTitle: false, + centerTitle: true, floating: true, toolbarHeight: AppTheme.toolbarHeight(context), leading: AppBarLeadingButton.adaptive(context), diff --git a/lib/shells/root_shell.dart b/lib/shells/root_shell.dart index 3652301..e0b361d 100644 --- a/lib/shells/root_shell.dart +++ b/lib/shells/root_shell.dart @@ -2,7 +2,7 @@ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:solian/theme.dart'; -import 'package:solian/widgets/navigation/app_navigation_drawer.dart'; +import 'package:solian/widgets/navigation/app_navigation_bottom.dart'; final GlobalKey rootScaffoldKey = GlobalKey(); @@ -41,15 +41,10 @@ class RootShell extends StatelessWidget { return Scaffold( key: rootScaffoldKey, - drawer: AppTheme.isLargeScreen(context) - ? null - : AppNavigationDrawer(routeName: routeName), + bottomNavigationBar: const AppNavigationBottom(), body: AppTheme.isLargeScreen(context) ? Row( children: [ - if (showNavigation) AppNavigationDrawer(routeName: routeName), - if (showNavigation) - const VerticalDivider(thickness: 0.3, width: 1), Expanded(child: child), ], ) diff --git a/lib/widgets/navigation/app_account_widget.dart b/lib/widgets/navigation/app_account_widget.dart new file mode 100644 index 0000000..77defdd --- /dev/null +++ b/lib/widgets/navigation/app_account_widget.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:solian/models/account_status.dart'; +import 'package:solian/providers/account_status.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/providers/relation.dart'; +import 'package:badges/badges.dart' as badges; +import 'package:solian/widgets/account/account_avatar.dart'; + +class AppAccountWidget extends StatefulWidget { + const AppAccountWidget({super.key}); + + @override + State createState() => _AppAccountWidgetState(); +} + +class _AppAccountWidgetState extends State { + AccountStatus? _accountStatus; + + Future _getStatus() async { + final StatusProvider provider = Get.find(); + + final resp = await provider.getCurrentStatus(); + final status = AccountStatus.fromJson(resp.body); + + if (mounted) { + setState(() { + _accountStatus = status; + }); + } + } + + @override + void initState() { + super.initState(); + _getStatus(); + } + + @override + Widget build(BuildContext context) { + final AuthProvider auth = Get.find(); + + return Obx(() { + if (auth.isAuthorized.isFalse || auth.userProfile.value == null) { + return const Icon(Icons.account_circle); + } + + final statusBadgeColor = _accountStatus != null + ? StatusProvider.determineStatus(_accountStatus!).$2 + : Colors.grey; + + final RelationshipProvider relations = Get.find(); + final accountNotifications = relations.friendRequestCount.value; + + return badges.Badge( + badgeContent: Text( + accountNotifications.toString(), + style: const TextStyle(color: Colors.white), + ), + showBadge: accountNotifications > 0, + position: badges.BadgePosition.topEnd( + top: -10, + end: -6, + ), + child: badges.Badge( + showBadge: _accountStatus != null, + badgeStyle: badges.BadgeStyle(badgeColor: statusBadgeColor), + position: badges.BadgePosition.bottomEnd( + bottom: 0, + end: -2, + ), + child: AccountAvatar( + radius: 14, + content: auth.userProfile.value!['avatar'], + ), + ), + ); + }); + } +} diff --git a/lib/widgets/navigation/app_navigation.dart b/lib/widgets/navigation/app_navigation.dart index f56a627..740fb1b 100644 --- a/lib/widgets/navigation/app_navigation.dart +++ b/lib/widgets/navigation/app_navigation.dart @@ -1,28 +1,34 @@ import 'package:flutter/material.dart'; import 'package:get/utils.dart'; +import 'package:solian/widgets/navigation/app_account_widget.dart'; abstract class AppNavigation { static List destinations = [ AppNavigationDestination( - icon: Icons.dashboard, + icon: const Icon(Icons.dashboard), label: 'dashboard'.tr, page: 'dashboard', ), AppNavigationDestination( - icon: Icons.explore, + icon: const Icon(Icons.explore), label: 'explore'.tr, page: 'explore', ), AppNavigationDestination( - icon: Icons.workspaces, + icon: const Icon(Icons.workspaces), label: 'realms'.tr, page: 'realms', ), AppNavigationDestination( - icon: Icons.forum, + icon: const Icon(Icons.forum), label: 'chat'.tr, page: 'chat', ), + AppNavigationDestination( + icon: const AppAccountWidget(), + label: 'account'.tr, + page: 'account', + ), ]; static List get destinationPages => @@ -30,7 +36,7 @@ abstract class AppNavigation { } class AppNavigationDestination { - final IconData icon; + final Widget icon; final String label; final String page; diff --git a/lib/widgets/navigation/app_navigation_bottom.dart b/lib/widgets/navigation/app_navigation_bottom.dart new file mode 100644 index 0000000..163ac3e --- /dev/null +++ b/lib/widgets/navigation/app_navigation_bottom.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:solian/router.dart'; +import 'package:solian/widgets/navigation/app_navigation.dart'; + +class AppNavigationBottom extends StatefulWidget { + const AppNavigationBottom({super.key}); + + @override + State createState() => _AppNavigationBottomState(); +} + +class _AppNavigationBottomState extends State { + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + currentIndex: _currentIndex, + type: BottomNavigationBarType.fixed, + showUnselectedLabels: false, + showSelectedLabels: false, + landscapeLayout: BottomNavigationBarLandscapeLayout.centered, + items: AppNavigation.destinations + .map( + (x) => BottomNavigationBarItem( + icon: x.icon, + label: x.label, + ), + ) + .toList(), + onTap: (idx) { + setState(() => _currentIndex = idx); + AppRouter.instance.goNamed(AppNavigation.destinations[idx].page); + }, + ); + } +} diff --git a/lib/widgets/navigation/app_navigation_drawer.dart b/lib/widgets/navigation/app_navigation_drawer.dart deleted file mode 100644 index 413ac7f..0000000 --- a/lib/widgets/navigation/app_navigation_drawer.dart +++ /dev/null @@ -1,330 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:solian/models/account_status.dart'; -import 'package:solian/providers/account_status.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/providers/relation.dart'; -import 'package:solian/router.dart'; -import 'package:solian/shells/root_shell.dart'; -import 'package:solian/theme.dart'; -import 'package:solian/widgets/account/account_avatar.dart'; -import 'package:solian/widgets/account/account_status_action.dart'; -import 'package:solian/widgets/navigation/app_navigation.dart'; -import 'package:badges/badges.dart' as badges; -import 'package:solian/widgets/navigation/app_navigation_region.dart'; - -class AppNavigationDrawer extends StatefulWidget { - final String? routeName; - - const AppNavigationDrawer({super.key, this.routeName}); - - @override - State createState() => _AppNavigationDrawerState(); -} - -class _AppNavigationDrawerState extends State - with TickerProviderStateMixin { - bool _isCollapsed = true; - - late final AnimationController _drawerAnimationController = - AnimationController( - duration: const Duration(milliseconds: 500), - vsync: this, - ); - late final Animation _drawerAnimation = Tween( - begin: 80.0, - end: 304.0, - ).animate(CurvedAnimation( - parent: _drawerAnimationController, - curve: Curves.easeInOut, - )); - - AccountStatus? _accountStatus; - - Future _getStatus() async { - final StatusProvider provider = Get.find(); - - final resp = await provider.getCurrentStatus(); - final status = AccountStatus.fromJson(resp.body); - - if (mounted) { - setState(() { - _accountStatus = status; - }); - } - } - - Color get _unFocusColor => - Theme.of(context).colorScheme.onSurface.withOpacity(0.75); - - Widget _buildUserInfo() { - return Obx(() { - final AuthProvider auth = Get.find(); - if (auth.isAuthorized.isFalse || auth.userProfile.value == null) { - if (_isCollapsed) { - return InkWell( - child: const Icon(Icons.account_circle).paddingSymmetric( - horizontal: 28, - vertical: 20, - ), - onTap: () { - AppRouter.instance.goNamed('account'); - _closeDrawer(); - }, - ); - } - - return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 28), - leading: const Icon(Icons.account_circle), - title: !_isCollapsed ? Text('guest'.tr) : null, - subtitle: !_isCollapsed ? Text('unsignedIn'.tr) : null, - onTap: () { - AppRouter.instance.goNamed('account'); - _closeDrawer(); - }, - ); - } - - final leading = Obx(() { - final statusBadgeColor = _accountStatus != null - ? StatusProvider.determineStatus(_accountStatus!).$2 - : Colors.grey; - - final RelationshipProvider relations = Get.find(); - final accountNotifications = relations.friendRequestCount.value; - - return badges.Badge( - badgeContent: Text( - accountNotifications.toString(), - style: const TextStyle(color: Colors.white), - ), - showBadge: accountNotifications > 0, - position: badges.BadgePosition.topEnd( - top: -10, - end: -6, - ), - child: badges.Badge( - showBadge: _accountStatus != null, - badgeStyle: badges.BadgeStyle(badgeColor: statusBadgeColor), - position: badges.BadgePosition.bottomEnd( - bottom: 0, - end: -2, - ), - child: AccountAvatar( - content: auth.userProfile.value!['avatar'], - ), - ), - ); - }); - - return InkWell( - child: !_isCollapsed - ? Row( - children: [ - leading, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - auth.userProfile.value!['nick'], - maxLines: 1, - overflow: TextOverflow.fade, - style: Theme.of(context).textTheme.bodyLarge, - ).paddingOnly(left: 16), - Builder( - builder: (context) { - if (_accountStatus == null) { - return Text( - 'loading'.tr, - maxLines: 1, - overflow: TextOverflow.fade, - style: TextStyle( - color: _unFocusColor, - ), - ).paddingOnly(left: 16); - } - final info = StatusProvider.determineStatus( - _accountStatus!, - ); - return Text( - info.$3, - maxLines: 1, - overflow: TextOverflow.fade, - style: TextStyle( - color: _unFocusColor, - ), - ).paddingOnly(left: 16); - }, - ), - ], - ), - ), - ], - ).paddingSymmetric(horizontal: 20, vertical: 16) - : leading.paddingSymmetric(horizontal: 20, vertical: 16), - onTap: () { - AppRouter.instance.goNamed('account'); - _closeDrawer(); - }, - onLongPress: () { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => AccountStatusAction( - currentStatus: _accountStatus!.status, - ), - ).then((val) { - if (val == true) _getStatus(); - }); - }, - ); - }); - } - - void _expandDrawer() { - _drawerAnimationController.animateTo(1); - } - - void _collapseDrawer() { - _drawerAnimationController.animateTo(0); - } - - void _closeDrawer() { - _autoResize(); - rootScaffoldKey.currentState!.closeDrawer(); - } - - void _autoResize() { - if (AppTheme.isExtraLargeScreen(context)) { - _expandDrawer(); - } else if (AppTheme.isLargeScreen(context)) { - _collapseDrawer(); - } else { - _drawerAnimationController.value = 1; - } - } - - @override - void initState() { - super.initState(); - final AuthProvider auth = Get.find(); - if (auth.isAuthorized.value) _getStatus(); - Future.delayed(Duration.zero, () => _autoResize()); - _drawerAnimationController.addListener(() { - if (_drawerAnimation.value > 180 && _isCollapsed) { - setState(() => _isCollapsed = false); - } else if (_drawerAnimation.value < 180 && !_isCollapsed) { - setState(() => _isCollapsed = true); - } - }); - } - - @override - void dispose() { - _drawerAnimationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _drawerAnimation, - builder: (context, child) { - return Drawer( - width: _drawerAnimation.value, - backgroundColor: - AppTheme.isLargeScreen(context) ? Colors.transparent : null, - child: child, - ); - }, - child: SafeArea( - bottom: false, - child: Column( - children: [ - _buildUserInfo().paddingSymmetric(vertical: 8), - const Divider(thickness: 0.3, height: 1), - SizedBox( - width: double.infinity, - child: Wrap( - runSpacing: 8, - spacing: 8, - alignment: WrapAlignment.spaceAround, - children: AppNavigation.destinations - .map( - (e) => Tooltip( - message: e.label, - child: InkWell( - borderRadius: - const BorderRadius.all(Radius.circular(8)), - child: Icon( - e.icon, - size: 22, - color: Theme.of(context).colorScheme.onSurface, - ).paddingAll(16), - onTap: () { - AppRouter.instance.goNamed(e.page); - _closeDrawer(); - }, - ), - ), - ) - .toList(), - ).paddingSymmetric(vertical: 8, horizontal: 12), - ), - const Divider(thickness: 0.3, height: 1), - Expanded( - child: Material( - color: Theme.of(context).colorScheme.surface, - child: AppNavigationRegion( - isCollapsed: _isCollapsed, - onSelected: () { - _closeDrawer(); - }, - ), - ), - ), - const Divider(thickness: 0.3, height: 1), - Column( - children: [ - if (_isCollapsed) - Tooltip( - message: 'expand'.tr, - child: InkWell( - child: const Icon(Icons.chevron_right, size: 20) - .paddingSymmetric( - horizontal: 28, - vertical: 10, - ), - onTap: () { - _expandDrawer(); - }, - ), - ) - else - ListTile( - minTileHeight: 0, - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - ), - leading: - const Icon(Icons.chevron_left, size: 20).paddingAll(2), - title: Text('collapse'.tr), - onTap: () { - _collapseDrawer(); - }, - ), - ], - ).paddingOnly( - top: 8, - bottom: math.max(8, MediaQuery.of(context).padding.bottom), - ), - ], - ), - ), - ); - } -}