Account page two pane

This commit is contained in:
LittleSheep 2024-05-02 12:51:16 +08:00
parent 3089e1f8d2
commit 52c09151a6
4 changed files with 161 additions and 113 deletions

View File

@ -7,7 +7,9 @@ import 'package:solian/utils/http.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
class AuthProvider extends ChangeNotifier { class AuthProvider extends ChangeNotifier {
AuthProvider(); AuthProvider() {
loadClient();
}
final deviceEndpoint = getRequestUri('passport', '/api/notifications/subscribe'); final deviceEndpoint = getRequestUri('passport', '/api/notifications/subscribe');
final tokenEndpoint = getRequestUri('passport', '/api/auth/token'); final tokenEndpoint = getRequestUri('passport', '/api/auth/token');
@ -39,6 +41,7 @@ class AuthProvider extends ChangeNotifier {
return false; return false;
} }
} else { } else {
client = HttpClient(onTokenRefreshed: setToken);
return false; return false;
} }
} }

View File

@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/account/friend.dart';
import 'package:solian/widgets/account/avatar.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:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/indent_wrapper.dart';
class AccountScreen extends StatefulWidget { class AccountScreen extends StatefulWidget {
const AccountScreen({super.key}); const AccountScreen({super.key});
@ -14,13 +16,71 @@ class AccountScreen extends StatefulWidget {
} }
class _AccountScreenState extends State<AccountScreen> { class _AccountScreenState extends State<AccountScreen> {
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<AccountScreenWidget> createState() => _AccountScreenWidgetState();
}
class _AccountScreenWidgetState extends State<AccountScreenWidget> {
bool _isAuthorized = false;
@override @override
void initState() { void initState() {
Future.delayed(Duration.zero, () async { Future.delayed(Duration.zero, () async {
var authorized = await context.read<AuthProvider>().isAuthorized(); var authorized = await context.read<AuthProvider>().isAuthorized();
setState(() => isAuthorized = authorized); setState(() => _isAuthorized = authorized);
}); });
super.initState(); super.initState();
@ -30,64 +90,63 @@ class _AccountScreenState extends State<AccountScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final auth = context.watch<AuthProvider>(); final auth = context.watch<AuthProvider>();
return LayoutWrapper( if (_isAuthorized) {
title: AppLocalizations.of(context)!.account, return Column(
child: isAuthorized children: [
? Column( const Padding(
children: [ padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24),
const Padding( child: NameCard(),
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 24), ),
child: NameCard(), ListTile(
), contentPadding: const EdgeInsets.symmetric(horizontal: 34),
ListTile( leading: const Icon(Icons.diversity_1),
contentPadding: const EdgeInsets.symmetric(horizontal: 34), title: Text(AppLocalizations.of(context)!.friend),
leading: const Icon(Icons.diversity_1), onTap: () {
title: Text(AppLocalizations.of(context)!.friend), widget.onSelect('account.friend', AppLocalizations.of(context)!.friend);
onTap: () { },
router.goNamed('account.friend'); ),
}, ListTile(
), contentPadding: const EdgeInsets.symmetric(horizontal: 34),
ListTile( leading: const Icon(Icons.logout),
contentPadding: const EdgeInsets.symmetric(horizontal: 34), title: Text(AppLocalizations.of(context)!.signOut),
leading: const Icon(Icons.logout), onTap: () {
title: Text(AppLocalizations.of(context)!.signOut), auth.signoff();
onTap: () { setState(() {
auth.signoff(); _isAuthorized = false;
setState(() { });
isAuthorized = false; },
}); ),
}, ],
), );
], } else {
) return Center(
: Center( child: Column(
child: Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ ActionCard(
ActionCard( icon: const Icon(Icons.login, color: Colors.white),
icon: const Icon(Icons.login, color: Colors.white), title: AppLocalizations.of(context)!.signIn,
title: AppLocalizations.of(context)!.signIn, caption: AppLocalizations.of(context)!.signInCaption,
caption: AppLocalizations.of(context)!.signInCaption, onTap: () {
onTap: () { router.pushNamed('auth.sign-in').then((did) {
router.pushNamed('auth.sign-in').then((did) { auth.isAuthorized().then((value) {
auth.isAuthorized().then((value) { setState(() => _isAuthorized = 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');
},
),
],
),
), ),
); 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: [ children: [
FutureBuilder( FutureBuilder(
future: renderAvatar(context), future: renderAvatar(context),
builder: builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
(BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return snapshot.data!; return snapshot.data!;
} else { } else {
@ -141,8 +199,7 @@ class NameCard extends StatelessWidget {
const SizedBox(width: 20), const SizedBox(width: 20),
FutureBuilder( FutureBuilder(
future: renderLabel(context), future: renderLabel(context),
builder: builder: (BuildContext context, AsyncSnapshot<Column> snapshot) {
(BuildContext context, AsyncSnapshot<Column> snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return snapshot.data!; return snapshot.data!;
} else { } else {
@ -164,12 +221,7 @@ class ActionCard extends StatelessWidget {
final String caption; final String caption;
final Function onTap; final Function onTap;
const ActionCard( const ActionCard({super.key, required this.onTap, required this.title, required this.caption, required this.icon});
{super.key,
required this.onTap,
required this.title,
required this.caption,
required this.icon});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -11,17 +11,32 @@ import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class FriendScreen extends StatefulWidget { class FriendScreen extends StatelessWidget {
const FriendScreen({super.key}); const FriendScreen({super.key});
@override @override
State<FriendScreen> createState() => _FriendScreenState(); Widget build(BuildContext context) {
return IndentWrapper(
title: AppLocalizations.of(context)!.friend,
noSafeArea: true,
hideDrawer: true,
child: const FriendScreenWidget(),
);
}
} }
class _FriendScreenState extends State<FriendScreen> { class FriendScreenWidget extends StatefulWidget {
const FriendScreenWidget({super.key});
@override
State<FriendScreenWidget> createState() => _FriendScreenWidgetState();
}
class _FriendScreenWidgetState extends State<FriendScreenWidget> {
bool _isSubmitting = false; bool _isSubmitting = false;
int _selfId = 0; int _selfId = 0;
List<Friendship> _friendships = List.empty(); List<Friendship> _friendships = List.empty();
Future<void> fetchFriendships() async { Future<void> fetchFriendships() async {
@ -120,16 +135,14 @@ class _FriendScreenState extends State<FriendScreen> {
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: AppLocalizations.of(context)!.username, labelText: AppLocalizations.of(context)!.username,
), ),
onTapOutside: (_) => onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
FocusManager.instance.primaryFocus?.unfocus(),
), ),
], ],
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: foregroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
), ),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel), child: Text(AppLocalizations.of(context)!.cancel),
@ -211,31 +224,22 @@ class _FriendScreenState extends State<FriendScreen> {
); );
} }
return IndentWrapper( return Scaffold(
title: AppLocalizations.of(context)!.friend, floatingActionButton: FloatingActionButton(
appBarActions: [ child: const Icon(Icons.add),
IconButton( onPressed: () => promptAddFriend(),
icon: const Icon(Icons.add), ),
onPressed: () => promptAddFriend(), body: RefreshIndicator(
),
],
child: RefreshIndicator(
onRefresh: () => fetchFriendships(), onRefresh: () => fetchFriendships(),
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
child: _isSubmitting child: _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
? const LinearProgressIndicator().animate().scaleX()
: Container(),
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
padding: padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
const EdgeInsets.symmetric(horizontal: 18, vertical: 12), color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.8),
child: Text(AppLocalizations.of(context)!.friendPending), child: Text(AppLocalizations.of(context)!.friendPending),
), ),
), ),
@ -245,12 +249,8 @@ class _FriendScreenState extends State<FriendScreen> {
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
padding: padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
const EdgeInsets.symmetric(horizontal: 18, vertical: 12), color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.8),
child: Text(AppLocalizations.of(context)!.friendActive), child: Text(AppLocalizations.of(context)!.friendActive),
), ),
), ),
@ -260,12 +260,8 @@ class _FriendScreenState extends State<FriendScreen> {
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
padding: padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
const EdgeInsets.symmetric(horizontal: 18, vertical: 12), color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.8),
child: Text(AppLocalizations.of(context)!.friendBlocked), child: Text(AppLocalizations.of(context)!.friendBlocked),
), ),
), ),
@ -278,10 +274,7 @@ class _FriendScreenState extends State<FriendScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide( top: BorderSide(
color: Theme.of(context) color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
.colorScheme
.surfaceVariant
.withOpacity(0.8),
width: 0.3, width: 0.3,
)), )),
), ),

View File

@ -48,7 +48,7 @@ class HttpClient extends http.BaseClient {
request.headers['Authorization'] = 'Bearer $currentToken'; request.headers['Authorization'] = 'Bearer $currentToken';
final res = await _client.send(request); final res = await _client.send(request);
if (res.statusCode == 401 && isUnauthorizedRetry) { if (res.statusCode == 401 && currentToken != null && isUnauthorizedRetry) {
if (onUnauthorizedRetry != null) { if (onUnauthorizedRetry != null) {
currentToken = await onUnauthorizedRetry!(); currentToken = await onUnauthorizedRetry!();
} else if (currentRefreshToken != null) { } else if (currentRefreshToken != null) {