diff --git a/lib/main.dart b/lib/main.dart index 391e5a7..e9eaa35 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,6 @@ import 'package:solian/providers/content/realm.dart'; import 'package:solian/providers/friend.dart'; import 'package:solian/providers/account_status.dart'; import 'package:solian/router.dart'; -import 'package:solian/shells/root_shell.dart'; import 'package:solian/shells/system_shell.dart'; import 'package:solian/theme.dart'; import 'package:solian/translations.dart'; @@ -88,10 +87,8 @@ class SolianApp extends StatelessWidget { onInit: () => _initializeProviders(context), builder: (context, child) { return SystemShell( - child: RootShell( - child: ScaffoldMessenger( - child: child ?? const SizedBox(), - ), + child: ScaffoldMessenger( + child: child ?? const SizedBox(), ), ); }, diff --git a/lib/providers/account_status.dart b/lib/providers/account_status.dart index ed02f10..3cccefd 100644 --- a/lib/providers/account_status.dart +++ b/lib/providers/account_status.dart @@ -94,9 +94,10 @@ class StatusProvider extends GetConnect { return resp; } - static (Widget, String) determineStatus(AccountStatus status, + static (Widget, Color, String) determineStatus(AccountStatus status, {double size = 14}) { Widget icon; + Color color; String? text; if (!presetStatuses.keys.contains(status.status?.type)) { @@ -104,15 +105,18 @@ class StatusProvider extends GetConnect { } if (status.isDisturbable && status.isOnline) { - icon = Icon(Icons.circle, color: Colors.green, size: size); + color = Colors.green; + icon = Icon(Icons.circle, color: color, size: size); text ??= 'accountStatusOnline'.tr; } else if (!status.isDisturbable && status.isOnline) { - icon = Icon(Icons.do_not_disturb_on, color: Colors.red, size: size); + color = Colors.red; + icon = Icon(Icons.do_not_disturb_on, color: color, size: size); text ??= 'accountStatusSilent'.tr; } else { - icon = Icon(Icons.circle, color: Colors.grey, size: size); + color = Colors.grey; + icon = Icon(Icons.circle, color: color, size: size); text ??= 'accountStatusOffline'.tr; } - return (icon, text); + return (icon, color, text); } } diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index f493b4f..b48e846 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -218,4 +218,10 @@ class AuthProvider extends GetConnect { return resp; } + + Future getProfileWithCheck({noCache = false}) async { + if (!await isAuthorized) return null; + + return await getProfile(noCache: noCache); + } } diff --git a/lib/router.dart b/lib/router.dart index 2a48dd8..ea29901 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -20,6 +20,7 @@ import 'package:solian/screens/realms/realm_view.dart'; import 'package:solian/screens/feed.dart'; import 'package:solian/screens/posts/post_editor.dart'; import 'package:solian/shells/basic_shell.dart'; +import 'package:solian/shells/root_shell.dart'; import 'package:solian/shells/title_shell.dart'; import 'package:solian/theme.dart'; import 'package:solian/widgets/sidebar/empty_placeholder.dart'; @@ -27,10 +28,18 @@ import 'package:solian/widgets/sidebar/empty_placeholder.dart'; abstract class AppRouter { static GoRouter instance = GoRouter( routes: [ - _feedRoute, - _chatRoute, - _realmRoute, - _accountRoute, + ShellRoute( + builder: (context, state, child) => RootShell( + state: state, + child: child, + ), + routes: [ + _feedRoute, + _chatRoute, + _realmRoute, + _accountRoute, + ], + ), ], ); diff --git a/lib/shells/root_shell.dart b/lib/shells/root_shell.dart index 2d497d9..914284a 100644 --- a/lib/shells/root_shell.dart +++ b/lib/shells/root_shell.dart @@ -1,4 +1,5 @@ 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_rail.dart'; @@ -9,10 +10,12 @@ class RootShell extends StatelessWidget { final bool showSidebar; final bool showNavigation; final bool? showBottomNavigation; + final GoRouterState state; final Widget child; const RootShell({ super.key, + required this.state, required this.child, this.showSidebar = true, this.showNavigation = true, @@ -25,9 +28,14 @@ class RootShell extends StatelessWidget { @override Widget build(BuildContext context) { + final routeName = state.topRoute?.name; + return Scaffold( key: rootScaffoldKey, - drawer: const AppNavigationDrawer(), + drawer: AppNavigationDrawer( + key: const ValueKey('navigation-drawer'), + routeName: routeName, + ), body: SolianTheme.isLargeScreen(context) ? Row( children: [ diff --git a/lib/widgets/account/account_heading.dart b/lib/widgets/account/account_heading.dart index 765085d..4b70d1b 100644 --- a/lib/widgets/account/account_heading.dart +++ b/lib/widgets/account/account_heading.dart @@ -124,7 +124,7 @@ class AccountHeadingWidget extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text(info.$2), + Text(info.$3), if (!status.isOnline && status.lastSeenAt != null) Opacity( opacity: 0.75, diff --git a/lib/widgets/navigation/app_navigation_bottom_bar.dart b/lib/widgets/navigation/app_navigation_bottom_bar.dart deleted file mode 100644 index 5282901..0000000 --- a/lib/widgets/navigation/app_navigation_bottom_bar.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:solian/router.dart'; -import 'package:solian/widgets/navigation/app_navigation.dart'; - -class AppNavigationBottomBar extends StatefulWidget { - const AppNavigationBottomBar({super.key}); - - @override - State createState() => _AppNavigationBottomBarState(); -} - -class _AppNavigationBottomBarState extends State { - int _selectedIndex = 0; - - @override - Widget build(BuildContext context) { - return BottomNavigationBar( - items: AppNavigation.destinations - .map( - (e) => BottomNavigationBarItem( - icon: e.icon, - label: e.label, - ), - ) - .toList(), - type: BottomNavigationBarType.fixed, - landscapeLayout: BottomNavigationBarLandscapeLayout.centered, - currentIndex: _selectedIndex, - showUnselectedLabels: false, - onTap: (idx) { - setState(() => _selectedIndex = 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 index b8e2ffc..f11b774 100644 --- a/lib/widgets/navigation/app_navigation_drawer.dart +++ b/lib/widgets/navigation/app_navigation_drawer.dart @@ -1,10 +1,19 @@ 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/router.dart'; import 'package:solian/shells/root_shell.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; class AppNavigationDrawer extends StatefulWidget { - const AppNavigationDrawer({super.key}); + final String? routeName; + + const AppNavigationDrawer({super.key, this.routeName}); @override State createState() => _AppNavigationDrawerState(); @@ -12,9 +21,45 @@ class AppNavigationDrawer extends StatefulWidget { class _AppNavigationDrawerState extends State { int _selectedIndex = 0; + AccountStatus? _accountStatus; + + void getStatus() async { + final StatusProvider provider = Get.find(); + + final resp = await provider.getCurrentStatus(); + final status = AccountStatus.fromJson(resp.body); + + setState(() { + _accountStatus = status; + }); + } + + void detectSelectedIndex() { + if (widget.routeName == null) return; + + final nameList = AppNavigation.destinations.map((x) => x.page).toList(); + final idx = nameList.indexOf(widget.routeName!); + + _selectedIndex = idx != -1 ? idx : 0; + } + + @override + void initState() { + super.initState(); + detectSelectedIndex(); + getStatus(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + detectSelectedIndex(); + } @override Widget build(BuildContext context) { + final AuthProvider auth = Get.find(); + return NavigationDrawer( selectedIndex: _selectedIndex, onDestinationSelected: (idx) { @@ -23,6 +68,68 @@ class _AppNavigationDrawerState extends State { rootScaffoldKey.currentState!.closeDrawer(); }, children: [ + FutureBuilder( + future: auth.getProfileWithCheck(), + builder: (context, snapshot) { + if (!snapshot.hasData || snapshot.data == null) { + return const SizedBox(); + } + + return Column( + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + title: Text(snapshot.data!.body['nick']), + subtitle: Builder( + builder: (context) { + if (_accountStatus == null) { + return Text('loading'.tr); + } + final info = StatusProvider.determineStatus( + _accountStatus!, + ); + return Text(info.$3); + }, + ), + leading: Builder(builder: (context) { + final badgeColor = _accountStatus != null + ? StatusProvider.determineStatus( + _accountStatus!, + ).$2 + : Colors.grey; + + return badges.Badge( + showBadge: _accountStatus != null, + badgeStyle: badges.BadgeStyle(badgeColor: badgeColor), + position: badges.BadgePosition.bottomEnd( + bottom: 0, + end: -2, + ), + child: AccountAvatar( + content: snapshot.data!.body['avatar'], + ), + ); + }), + onTap: () { + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) => AccountStatusAction( + currentStatus: _accountStatus!.status, + ), + ).then((val) { + if (val == true) getStatus(); + }); + }, + ), + const Divider(thickness: 0.3, height: 1).paddingOnly( + bottom: 16, + top: 8, + ), + ], + ); + }, + ), ...AppNavigation.destinations.map( (e) => NavigationDrawerDestination( icon: e.icon,