💫 Animated collapsible sidebar

This commit is contained in:
LittleSheep 2024-08-21 19:11:27 +08:00
parent f834351ce2
commit bc99865ba8
5 changed files with 164 additions and 67 deletions

View File

@ -383,4 +383,6 @@ const i18nEnglish = {
'Since the App has entered the background, there may be a time difference between the message list and the server. Click to Refresh.', 'Since the App has entered the background, there may be a time difference between the message list and the server. Click to Refresh.',
'messageHistoryWipe': 'Wipe local message history', 'messageHistoryWipe': 'Wipe local message history',
'unknown': 'Unknown', 'unknown': 'Unknown',
'collapse': 'Collapse',
'expand': 'Expand',
}; };

View File

@ -353,4 +353,6 @@ const i18nSimplifiedChinese = {
'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。', 'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。',
'messageHistoryWipe': '清除消息记录', 'messageHistoryWipe': '清除消息记录',
'unknown': '未知', 'unknown': '未知',
'collapse': '折叠',
'expand': '展开',
}; };

View File

@ -13,7 +13,7 @@ import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_status_action.dart'; import 'package:solian/widgets/account/account_status_action.dart';
import 'package:solian/widgets/navigation/app_navigation.dart'; import 'package:solian/widgets/navigation/app_navigation.dart';
import 'package:badges/badges.dart' as badges; import 'package:badges/badges.dart' as badges;
import 'package:solian/widgets/navigation/app_navigation_regions.dart'; import 'package:solian/widgets/navigation/app_navigation_region.dart';
class AppNavigationDrawer extends StatefulWidget { class AppNavigationDrawer extends StatefulWidget {
final String? routeName; final String? routeName;
@ -24,8 +24,22 @@ class AppNavigationDrawer extends StatefulWidget {
State<AppNavigationDrawer> createState() => _AppNavigationDrawerState(); State<AppNavigationDrawer> createState() => _AppNavigationDrawerState();
} }
class _AppNavigationDrawerState extends State<AppNavigationDrawer> { class _AppNavigationDrawerState extends State<AppNavigationDrawer>
bool _isCollapsed = true; with TickerProviderStateMixin {
bool _isCollapsed = false;
late final AnimationController _drawerAnimationController =
AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
late final Animation<double> _drawerAnimation = Tween<double>(
begin: 80.0,
end: 304.0,
).animate(CurvedAnimation(
parent: _drawerAnimationController,
curve: Curves.easeInOut,
));
AccountStatus? _accountStatus; AccountStatus? _accountStatus;
@ -42,13 +56,19 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
} }
} }
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
Widget _buildUserInfo() { Widget _buildUserInfo() {
return Obx(() { return Obx(() {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) { if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
if (_isCollapsed) { if (_isCollapsed) {
return InkWell( return InkWell(
child: const Icon(Icons.account_circle).paddingAll(28), child: const Icon(Icons.account_circle).paddingSymmetric(
horizontal: 28,
vertical: 20,
),
onTap: () { onTap: () {
AppRouter.instance.goNamed('account'); AppRouter.instance.goNamed('account');
_closeDrawer(); _closeDrawer();
@ -70,9 +90,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
final leading = Obx(() { final leading = Obx(() {
final statusBadgeColor = _accountStatus != null final statusBadgeColor = _accountStatus != null
? StatusProvider.determineStatus( ? StatusProvider.determineStatus(_accountStatus!).$2
_accountStatus!,
).$2
: Colors.grey; : Colors.grey;
final RelationshipProvider relations = Get.find(); final RelationshipProvider relations = Get.find();
@ -104,31 +122,43 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
return InkWell( return InkWell(
child: !_isCollapsed child: !_isCollapsed
? ListTile( ? Row(
contentPadding: const EdgeInsets.only(left: 20, right: 20), children: [
title: Text( leading,
auth.userProfile.value!['nick'], Expanded(
maxLines: 1, child: Column(
overflow: TextOverflow.fade, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
subtitle: Builder( Text(
builder: (context) { auth.userProfile.value!['nick'],
if (_accountStatus == null) { maxLines: 1,
return Text('loading'.tr); overflow: TextOverflow.fade,
} style: Theme.of(context).textTheme.bodyLarge,
final info = StatusProvider.determineStatus( ).paddingOnly(left: 16),
_accountStatus!, Builder(
); builder: (context) {
return Text( if (_accountStatus == null) {
info.$3, return Text('loading'.tr).paddingOnly(left: 16);
maxLines: 1, }
overflow: TextOverflow.fade, final info = StatusProvider.determineStatus(
); _accountStatus!,
}, );
), return Text(
leading: leading, info.$3,
) maxLines: 1,
: leading.paddingAll(20), overflow: TextOverflow.fade,
style: TextStyle(
color: _unFocusColor,
),
).paddingOnly(left: 16);
},
),
],
),
),
],
).paddingSymmetric(horizontal: 20, vertical: 16)
: leading.paddingSymmetric(horizontal: 20, vertical: 16),
onTap: () { onTap: () {
AppRouter.instance.goNamed('account'); AppRouter.instance.goNamed('account');
_closeDrawer(); _closeDrawer();
@ -148,22 +178,59 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
}); });
} }
void _expandDrawer() {
_drawerAnimationController.animateTo(1);
}
void _collapseDrawer() {
_drawerAnimationController.animateTo(0);
}
void _closeDrawer() { void _closeDrawer() {
_autoResize();
rootScaffoldKey.currentState!.closeDrawer(); rootScaffoldKey.currentState!.closeDrawer();
} }
void _autoResize() {
if (SolianTheme.isExtraLargeScreen(context)) {
_expandDrawer();
} else if (SolianTheme.isLargeScreen(context)) {
_collapseDrawer();
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_getStatus(); _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Drawer( return AnimatedBuilder(
width: _isCollapsed ? 80 : null, animation: _drawerAnimation,
backgroundColor: builder: (context, child) {
SolianTheme.isLargeScreen(context) ? Colors.transparent : null, return Drawer(
width: _drawerAnimation.value,
backgroundColor:
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
child: child,
);
},
child: SafeArea( child: SafeArea(
bottom: false, bottom: false,
child: Column( child: Column(
@ -175,7 +242,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
.map( .map(
(e) => _isCollapsed (e) => _isCollapsed
? InkWell( ? InkWell(
child: Icon(e.icon, size: 22).paddingSymmetric( child: Icon(e.icon, size: 20).paddingSymmetric(
horizontal: 28, horizontal: 28,
vertical: 16, vertical: 16,
), ),
@ -201,7 +268,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
), ),
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),
Expanded( Expanded(
child: AppNavigationRegions( child: AppNavigationRegion(
isCollapsed: _isCollapsed, isCollapsed: _isCollapsed,
onSelected: (item) { onSelected: (item) {
_closeDrawer(); _closeDrawer();
@ -211,31 +278,57 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),
Column( Column(
children: [ children: [
_isCollapsed if (_isCollapsed)
? InkWell( InkWell(
child: const Icon(Icons.settings, size: 22) child: const Icon(
.paddingSymmetric( Icons.settings,
horizontal: 28, size: 20,
vertical: 16, ).paddingSymmetric(
), horizontal: 28,
onTap: () { vertical: 10,
AppRouter.instance.pushNamed('settings'); ),
_closeDrawer(); onTap: () {
}, AppRouter.instance.pushNamed('settings');
) _closeDrawer();
: ListTile( },
minTileHeight: 0, )
contentPadding: const EdgeInsets.symmetric( else
horizontal: 20, ListTile(
), minTileHeight: 0,
leading: contentPadding: const EdgeInsets.symmetric(
const Icon(Icons.settings, size: 20).paddingAll(2), horizontal: 20,
title: Text('settings'.tr), ),
onTap: () { leading: const Icon(Icons.settings, size: 20).paddingAll(2),
AppRouter.instance.pushNamed('settings'); title: Text('settings'.tr),
_closeDrawer(); onTap: () {
}, AppRouter.instance.pushNamed('settings');
), _closeDrawer();
},
),
if (_isCollapsed)
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( ).paddingOnly(
top: 8, top: 8,

View File

@ -5,11 +5,11 @@ import 'package:solian/providers/content/channel.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
class AppNavigationRegions extends StatelessWidget { class AppNavigationRegion extends StatelessWidget {
final bool isCollapsed; final bool isCollapsed;
final Function(Channel item) onSelected; final Function(Channel item) onSelected;
const AppNavigationRegions({ const AppNavigationRegion({
super.key, super.key,
required this.onSelected, required this.onSelected,
this.isCollapsed = false, this.isCollapsed = false,
@ -32,7 +32,7 @@ class AppNavigationRegions extends StatelessWidget {
if (isCollapsed) { if (isCollapsed) {
return InkWell( return InkWell(
child: const Icon(Icons.tag_outlined).paddingSymmetric( child: const Icon(Icons.tag_outlined, size: 20).paddingSymmetric(
horizontal: 20, horizontal: 20,
vertical: 16, vertical: 16,
), ),

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App" description: "The Solar Network App"
publish_to: "none" publish_to: "none"
version: 1.2.1+20 version: 1.2.1+21
environment: environment:
sdk: ">=3.3.4 <4.0.0" sdk: ">=3.3.4 <4.0.0"