From 52c09151a6094619e9a1f92849598192c5ff6d8e Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 2 May 2024 12:51:16 +0800 Subject: [PATCH] :sparkles: Account page two pane --- lib/providers/auth.dart | 5 +- lib/screens/account.dart | 192 ++++++++++++++++++++------------ lib/screens/account/friend.dart | 75 ++++++------- lib/utils/http.dart | 2 +- 4 files changed, 161 insertions(+), 113 deletions(-) diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 4830767..8b9765c 100755 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -7,7 +7,9 @@ import 'package:solian/utils/http.dart'; import 'package:solian/utils/service_url.dart'; class AuthProvider extends ChangeNotifier { - AuthProvider(); + AuthProvider() { + loadClient(); + } final deviceEndpoint = getRequestUri('passport', '/api/notifications/subscribe'); final tokenEndpoint = getRequestUri('passport', '/api/auth/token'); @@ -39,6 +41,7 @@ class AuthProvider extends ChangeNotifier { return false; } } else { + client = HttpClient(onTokenRefreshed: setToken); return false; } } diff --git a/lib/screens/account.dart b/lib/screens/account.dart index d41737d..0fc0913 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; +import 'package:solian/screens/account/friend.dart'; import 'package:solian/widgets/account/avatar.dart'; -import 'package:solian/widgets/common_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/empty.dart'; +import 'package:solian/widgets/indent_wrapper.dart'; class AccountScreen extends StatefulWidget { const AccountScreen({super.key}); @@ -14,13 +16,71 @@ class AccountScreen extends StatefulWidget { } class _AccountScreenState extends State { - bool isAuthorized = false; + String? _title; + String? _selectedTab; + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final isLargeScreen = screenWidth >= 600; + + Widget renderContent() { + switch (_selectedTab) { + case 'account.friend': + return const FriendScreenWidget(); + default: + return const SelectionEmptyWidget(); + } + } + + return IndentWrapper( + title: _title ?? AppLocalizations.of(context)!.account, + noSafeArea: true, + fixedAppBarColor: true, + child: isLargeScreen + ? Row( + children: [ + Flexible( + flex: 2, + child: AccountScreenWidget( + onSelect: (item, title) { + setState(() { + _selectedTab = item; + _title = title; + }); + }, + ), + ), + const VerticalDivider(thickness: 0.3, width: 0.3), + Flexible(flex: 4, child: renderContent()), + ], + ) + : AccountScreenWidget( + onSelect: (item, _) { + router.pushNamed(item); + }, + ), + ); + } +} + +class AccountScreenWidget extends StatefulWidget { + final Function(String item, String title) onSelect; + + const AccountScreenWidget({super.key, required this.onSelect}); + + @override + State createState() => _AccountScreenWidgetState(); +} + +class _AccountScreenWidgetState extends State { + bool _isAuthorized = false; @override void initState() { Future.delayed(Duration.zero, () async { var authorized = await context.read().isAuthorized(); - setState(() => isAuthorized = authorized); + setState(() => _isAuthorized = authorized); }); super.initState(); @@ -30,64 +90,63 @@ class _AccountScreenState extends State { Widget build(BuildContext context) { final auth = context.watch(); - return LayoutWrapper( - title: AppLocalizations.of(context)!.account, - child: isAuthorized - ? Column( - children: [ - const Padding( - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24), - child: NameCard(), - ), - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 34), - leading: const Icon(Icons.diversity_1), - title: Text(AppLocalizations.of(context)!.friend), - onTap: () { - router.goNamed('account.friend'); - }, - ), - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 34), - leading: const Icon(Icons.logout), - title: Text(AppLocalizations.of(context)!.signOut), - onTap: () { - auth.signoff(); - setState(() { - isAuthorized = false; - }); - }, - ), - ], - ) - : Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ActionCard( - icon: const Icon(Icons.login, color: Colors.white), - title: AppLocalizations.of(context)!.signIn, - caption: AppLocalizations.of(context)!.signInCaption, - onTap: () { - router.pushNamed('auth.sign-in').then((did) { - auth.isAuthorized().then((value) { - setState(() => isAuthorized = value); - }); - }); - }, - ), - ActionCard( - icon: const Icon(Icons.add, color: Colors.white), - title: AppLocalizations.of(context)!.signUp, - caption: AppLocalizations.of(context)!.signUpCaption, - onTap: () { - router.pushNamed('auth.sign-up'); - }, - ), - ], - ), + if (_isAuthorized) { + return Column( + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24), + child: NameCard(), + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: const Icon(Icons.diversity_1), + title: Text(AppLocalizations.of(context)!.friend), + onTap: () { + widget.onSelect('account.friend', AppLocalizations.of(context)!.friend); + }, + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: const Icon(Icons.logout), + title: Text(AppLocalizations.of(context)!.signOut), + onTap: () { + auth.signoff(); + setState(() { + _isAuthorized = false; + }); + }, + ), + ], + ); + } else { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ActionCard( + icon: const Icon(Icons.login, color: Colors.white), + title: AppLocalizations.of(context)!.signIn, + caption: AppLocalizations.of(context)!.signInCaption, + onTap: () { + router.pushNamed('auth.sign-in').then((did) { + auth.isAuthorized().then((value) { + setState(() => _isAuthorized = value); + }); + }); + }, ), - ); + ActionCard( + icon: const Icon(Icons.add, color: Colors.white), + title: AppLocalizations.of(context)!.signUp, + caption: AppLocalizations.of(context)!.signUpCaption, + onTap: () { + router.pushNamed('auth.sign-up'); + }, + ), + ], + ), + ); + } } } @@ -129,8 +188,7 @@ class NameCard extends StatelessWidget { children: [ FutureBuilder( future: renderAvatar(context), - builder: - (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return snapshot.data!; } else { @@ -141,8 +199,7 @@ class NameCard extends StatelessWidget { const SizedBox(width: 20), FutureBuilder( future: renderLabel(context), - builder: - (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return snapshot.data!; } else { @@ -164,12 +221,7 @@ class ActionCard extends StatelessWidget { final String caption; final Function onTap; - const ActionCard( - {super.key, - required this.onTap, - required this.title, - required this.caption, - required this.icon}); + const ActionCard({super.key, required this.onTap, required this.title, required this.caption, required this.icon}); @override Widget build(BuildContext context) { diff --git a/lib/screens/account/friend.dart b/lib/screens/account/friend.dart index b2390af..8567985 100644 --- a/lib/screens/account/friend.dart +++ b/lib/screens/account/friend.dart @@ -11,17 +11,32 @@ import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class FriendScreen extends StatefulWidget { +class FriendScreen extends StatelessWidget { const FriendScreen({super.key}); @override - State createState() => _FriendScreenState(); + Widget build(BuildContext context) { + return IndentWrapper( + title: AppLocalizations.of(context)!.friend, + noSafeArea: true, + hideDrawer: true, + child: const FriendScreenWidget(), + ); + } } -class _FriendScreenState extends State { +class FriendScreenWidget extends StatefulWidget { + const FriendScreenWidget({super.key}); + + @override + State createState() => _FriendScreenWidgetState(); +} + +class _FriendScreenWidgetState extends State { bool _isSubmitting = false; int _selfId = 0; + List _friendships = List.empty(); Future fetchFriendships() async { @@ -120,16 +135,14 @@ class _FriendScreenState extends State { border: const OutlineInputBorder(), labelText: AppLocalizations.of(context)!.username, ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), ], ), actions: [ TextButton( style: TextButton.styleFrom( - foregroundColor: - Theme.of(context).colorScheme.onSurface.withOpacity(0.8), + foregroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.8), ), onPressed: () => Navigator.pop(context), child: Text(AppLocalizations.of(context)!.cancel), @@ -211,31 +224,22 @@ class _FriendScreenState extends State { ); } - return IndentWrapper( - title: AppLocalizations.of(context)!.friend, - appBarActions: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: () => promptAddFriend(), - ), - ], - child: RefreshIndicator( + return Scaffold( + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () => promptAddFriend(), + ), + body: RefreshIndicator( onRefresh: () => fetchFriendships(), child: CustomScrollView( slivers: [ SliverToBoxAdapter( - child: _isSubmitting - ? const LinearProgressIndicator().animate().scaleX() - : Container(), + child: _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), ), SliverToBoxAdapter( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 12), - color: Theme.of(context) - .colorScheme - .surfaceVariant - .withOpacity(0.8), + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), child: Text(AppLocalizations.of(context)!.friendPending), ), ), @@ -245,12 +249,8 @@ class _FriendScreenState extends State { ), SliverToBoxAdapter( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 12), - color: Theme.of(context) - .colorScheme - .surfaceVariant - .withOpacity(0.8), + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), child: Text(AppLocalizations.of(context)!.friendActive), ), ), @@ -260,12 +260,8 @@ class _FriendScreenState extends State { ), SliverToBoxAdapter( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 12), - color: Theme.of(context) - .colorScheme - .surfaceVariant - .withOpacity(0.8), + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), child: Text(AppLocalizations.of(context)!.friendBlocked), ), ), @@ -278,10 +274,7 @@ class _FriendScreenState extends State { decoration: BoxDecoration( border: Border( top: BorderSide( - color: Theme.of(context) - .colorScheme - .surfaceVariant - .withOpacity(0.8), + color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8), width: 0.3, )), ), diff --git a/lib/utils/http.dart b/lib/utils/http.dart index 531e544..052e745 100644 --- a/lib/utils/http.dart +++ b/lib/utils/http.dart @@ -48,7 +48,7 @@ class HttpClient extends http.BaseClient { request.headers['Authorization'] = 'Bearer $currentToken'; final res = await _client.send(request); - if (res.statusCode == 401 && isUnauthorizedRetry) { + if (res.statusCode == 401 && currentToken != null && isUnauthorizedRetry) { if (onUnauthorizedRetry != null) { currentToken = await onUnauthorizedRetry!(); } else if (currentRefreshToken != null) {