♻️ Refactored drawer nav

This commit is contained in:
2025-03-22 18:14:36 +08:00
parent 71b41d470a
commit 237abe564d
11 changed files with 360 additions and 136 deletions

View File

@ -39,6 +39,7 @@ import 'package:surface/screens/post/post_shuffle.dart';
import 'package:surface/screens/post/publisher_page.dart';
import 'package:surface/screens/post/post_search.dart';
import 'package:surface/screens/realm.dart';
import 'package:surface/screens/realm/community.dart';
import 'package:surface/screens/realm/manage.dart';
import 'package:surface/screens/realm/realm_detail.dart';
import 'package:surface/screens/realm/realm_discovery.dart';
@ -259,6 +260,13 @@ final _appRoutes = [
child: const RealmScreen(),
),
routes: [
GoRoute(
path: '/:alias/community',
name: 'realmCommunity',
builder: (context, state) => RealmCommunityScreen(
alias: state.pathParameters['alias']!,
),
),
GoRoute(
path: '/manage',
name: 'realmManage',

View File

@ -45,12 +45,14 @@ class PostEditorExtra {
final String? title;
final String? description;
final List<PostWriteMedia>? attachments;
final SnRealm? realm;
const PostEditorExtra({
this.text,
this.title,
this.description,
this.attachments,
this.realm,
});
}
@ -263,6 +265,7 @@ class _PostEditorScreenState extends State<PostEditorScreen>
_writeController.descriptionController.text =
widget.extraProps!.description ?? '';
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
_writeController.setRealm(widget.extraProps!.realm);
}
}

View File

@ -0,0 +1,149 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/post/post_editor.dart';
import 'package:surface/types/post.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class RealmCommunityScreen extends StatefulWidget {
final String alias;
const RealmCommunityScreen({super.key, required this.alias});
@override
State<RealmCommunityScreen> createState() => _RealmCommunityScreenState();
}
class _RealmCommunityScreenState extends State<RealmCommunityScreen> {
SnRealm? _realm;
Future<void> _fetchRealm() async {
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/realms/${widget.alias}');
_realm = SnRealm.fromJson(resp.data);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
rethrow;
} finally {
setState(() {});
}
}
bool _isBusy = false;
int? _totalCount;
final List<SnPost> _posts = List.empty(growable: true);
Future<void> _fetchPosts() async {
setState(() => _isBusy = true);
try {
final pt = context.read<SnPostContentProvider>();
final out = await pt.listPosts(
take: 10,
offset: _posts.length,
realm: _realm?.id.toString(),
);
_totalCount = out.$2;
_posts.addAll(out.$1);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchRealm();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
title: Text(_realm?.name ?? 'loading'.tr()),
),
floatingActionButton: _realm != null
? FloatingActionButton(
child: const Icon(Symbols.edit),
onPressed: () {
GoRouter.of(context).pushNamed(
'postEditor',
extra: PostEditorExtra(realm: _realm!),
);
},
)
: null,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_realm == null)
Expanded(
child: Center(
child: CircularProgressIndicator().center(),
),
),
if (_realm != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('realmCommunity'.tr(args: [_realm!.name]))
.fontSize(17)
.padding(horizontal: 20, bottom: 4),
Text('postTotalCount'.plural(_totalCount ?? 0))
.fontSize(13)
.opacity(0.8)
.padding(horizontal: 20, bottom: 4),
],
).padding(horizontal: 20, vertical: 16),
const Divider(height: 1),
if (_realm != null)
Expanded(
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: _fetchPosts,
child: InfiniteList(
padding: const EdgeInsets.only(top: 8),
itemCount: _posts.length,
isLoading: _isBusy,
hasReachedMax:
_totalCount != null && _posts.length >= _totalCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
final post = _posts[idx];
return OpenablePostItem(
data: post,
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
setState(() => _posts.removeAt(idx));
},
);
},
separatorBuilder: (_, __) =>
const Divider().padding(vertical: 2),
),
),
),
),
],
),
);
}
}

View File

@ -318,7 +318,7 @@ class _RealmPostListWidgetState extends State<_RealmPostListWidget> {
},
);
},
separatorBuilder: (_, __) => const Gap(8),
separatorBuilder: (_, __) => const Divider().padding(vertical: 2),
),
),
).padding(top: 8);

View File

@ -89,6 +89,7 @@ Future<ThemeData> createAppTheme(
},
),
progressIndicatorTheme: ProgressIndicatorThemeData(year2023: false),
sliderTheme: SliderThemeData(year2023: false),
);
}

View File

@ -76,7 +76,10 @@ class _LoadingIndicatorState extends State<LoadingIndicator>
const SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(strokeWidth: 2.5),
child: CircularProgressIndicator(
strokeWidth: 2.5,
padding: EdgeInsets.zero,
),
),
const Gap(16),
Text('loading').tr(),

View File

@ -10,6 +10,7 @@ import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/channel.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/navigation.dart';
import 'package:surface/providers/sn_network.dart';
@ -42,10 +43,8 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
@override
Widget build(BuildContext context) {
final ua = context.read<UserProvider>();
final sn = context.read<SnNetworkProvider>();
final nav = context.watch<NavigationProvider>();
final cfg = context.watch<ConfigProvider>();
final rel = context.read<SnRealmProvider>();
final backgroundColor = cfg.drawerIsExpanded ? Colors.transparent : null;
@ -77,111 +76,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
),
Gap(MediaQuery.of(context).padding.top),
Expanded(
child: PageTransitionSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
fillColor: Colors.transparent,
transitionType: SharedAxisTransitionType.horizontal,
child: child,
);
},
child: nav.focusedRealm == null
? ListView(
key: const Key('realm-list-view'),
padding: EdgeInsets.zero,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Solar Network').bold(),
AppVersionLabel(),
],
).padding(
horizontal: 32,
vertical: 12,
),
...rel.availableRealms.map((ele) {
return ListTile(
minTileHeight: 48,
contentPadding:
EdgeInsets.symmetric(horizontal: 24),
leading: AccountImage(
content: ele.avatar,
radius: 16,
),
title: Text(ele.name),
onTap: () {
if (Scaffold.of(context).isDrawerOpen) {
GoRouter.of(context).goNamed(
'realmDetail',
pathParameters: {
'alias': ele.alias,
},
);
Scaffold.of(context).closeDrawer();
}
nav.setFocusedRealm(ele);
},
);
}),
],
)
: ListView(
key: ValueKey(nav.focusedRealm),
padding: EdgeInsets.zero,
children: [
if (nav.focusedRealm!.banner != null)
AspectRatio(
aspectRatio: 16 / 9,
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(
nav.focusedRealm!.banner!,
),
fit: BoxFit.cover,
),
),
ListTile(
minTileHeight: 48,
tileColor: Theme.of(context)
.colorScheme
.surfaceContainer,
contentPadding: EdgeInsets.only(
left: 24,
right: 16,
),
leading: AccountImage(
content: nav.focusedRealm!.avatar,
radius: 16,
),
trailing: IconButton(
icon: const Icon(Symbols.close),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
onPressed: () {
nav.setFocusedRealm(null);
},
),
title: Text(nav.focusedRealm!.name),
onTap: () {
GoRouter.of(context).pushNamed(
'realmDetail',
pathParameters: {
'alias': nav.focusedRealm!.alias,
},
);
Scaffold.of(context).closeDrawer();
},
),
],
),
),
child: _DrawerContentList(),
),
Row(
spacing: 8,
@ -249,3 +144,153 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
);
}
}
class _DrawerContentList extends StatelessWidget {
const _DrawerContentList();
@override
Widget build(BuildContext context) {
final ct = context.read<ChatChannelProvider>();
final sn = context.read<SnNetworkProvider>();
final nav = context.watch<NavigationProvider>();
final rel = context.read<SnRealmProvider>();
return PageTransitionSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> primaryAnimation,
Animation<double> secondaryAnimation) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
fillColor: Colors.transparent,
transitionType: SharedAxisTransitionType.horizontal,
child: child,
);
},
child: nav.focusedRealm == null
? ListView(
key: const Key('realm-list-view'),
padding: EdgeInsets.zero,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Solar Network').bold(),
AppVersionLabel(),
],
).padding(
horizontal: 32,
vertical: 12,
),
...rel.availableRealms.map((ele) {
return ListTile(
minTileHeight: 48,
contentPadding: EdgeInsets.symmetric(horizontal: 24),
leading: AccountImage(
content: ele.avatar,
radius: 16,
),
title: Text(ele.name),
onTap: () {
nav.setFocusedRealm(ele);
},
);
}),
],
)
: ListView(
key: ValueKey(nav.focusedRealm),
padding: EdgeInsets.zero,
children: [
if (nav.focusedRealm!.banner != null)
AspectRatio(
aspectRatio: 16 / 9,
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(
nav.focusedRealm!.banner!,
),
fit: BoxFit.cover,
),
),
ListTile(
minTileHeight: 48,
tileColor: Theme.of(context).colorScheme.surfaceContainer,
contentPadding: EdgeInsets.only(
left: 24,
right: 16,
),
leading: AccountImage(
content: nav.focusedRealm!.avatar,
radius: 16,
),
trailing: IconButton(
icon: const Icon(Symbols.close),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
onPressed: () {
nav.setFocusedRealm(null);
},
),
title: Text(nav.focusedRealm!.name),
onTap: () {
GoRouter.of(context).pushNamed(
'realmDetail',
pathParameters: {
'alias': nav.focusedRealm!.alias,
},
);
Scaffold.of(context).closeDrawer();
},
),
ListTile(
minTileHeight: 48,
contentPadding: EdgeInsets.only(
left: 28,
right: 8,
),
leading: const Icon(Symbols.globe),
title: Text('community').tr(),
onTap: () {
GoRouter.of(context).pushNamed(
'realmCommunity',
pathParameters: {
'alias': nav.focusedRealm!.alias,
},
);
Scaffold.of(context).closeDrawer();
},
),
if (ct.availableChannels
.where((ele) => ele.realmId == nav.focusedRealm?.id)
.isNotEmpty)
const Divider(height: 1),
...(ct.availableChannels
.where((ele) => ele.realmId == nav.focusedRealm?.id)
.map((ele) {
return ListTile(
minTileHeight: 48,
contentPadding: EdgeInsets.only(
left: 28,
right: 8,
),
leading: const Icon(Symbols.tag),
title: Text(ele.name),
onTap: () {
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': ele.realm?.alias ?? 'global',
'alias': ele.alias,
},
);
Scaffold.of(context).closeDrawer();
},
);
}))
],
),
);
}
}