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';
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;
}
}

View File

@ -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<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
void initState() {
Future.delayed(Duration.zero, () async {
var authorized = await context.read<AuthProvider>().isAuthorized();
setState(() => isAuthorized = authorized);
setState(() => _isAuthorized = authorized);
});
super.initState();
@ -30,64 +90,63 @@ class _AccountScreenState extends State<AccountScreen> {
Widget build(BuildContext context) {
final auth = context.watch<AuthProvider>();
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<Widget> snapshot) {
builder: (BuildContext context, AsyncSnapshot<Widget> 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<Column> snapshot) {
builder: (BuildContext context, AsyncSnapshot<Column> 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) {

View File

@ -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<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;
int _selfId = 0;
List<Friendship> _friendships = List.empty();
Future<void> fetchFriendships() async {
@ -120,16 +135,14 @@ class _FriendScreenState extends State<FriendScreen> {
border: const OutlineInputBorder(),
labelText: AppLocalizations.of(context)!.username,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
),
actions: <Widget>[
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<FriendScreen> {
);
}
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<FriendScreen> {
),
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<FriendScreen> {
),
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<FriendScreen> {
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,
)),
),

View File

@ -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) {