🎨 Optimized code structure, rename a lot of widgets
💄 Better large screen support in chat
			
			
This commit is contained in:
		| @@ -34,7 +34,7 @@ class SolianApp extends StatelessWidget { | ||||
|       themeMode: ThemeMode.system, | ||||
|       localizationsDelegates: AppLocalizations.localizationsDelegates, | ||||
|       supportedLocales: AppLocalizations.supportedLocales, | ||||
|       routerConfig: router, | ||||
|       routerConfig: SolianRouter.router, | ||||
|       builder: (context, child) { | ||||
|         return MultiProvider( | ||||
|           providers: [ | ||||
|   | ||||
							
								
								
									
										224
									
								
								lib/router.dart
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								lib/router.dart
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:solian/models/call.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| @@ -8,110 +9,135 @@ import 'package:solian/screens/account/personalize.dart'; | ||||
| import 'package:solian/screens/auth/signup.dart'; | ||||
| import 'package:solian/screens/chat/call.dart'; | ||||
| import 'package:solian/screens/chat/chat.dart'; | ||||
| import 'package:solian/screens/chat/index.dart'; | ||||
| import 'package:solian/screens/chat/manage.dart'; | ||||
| import 'package:solian/screens/chat/channel/editor.dart'; | ||||
| import 'package:solian/screens/chat/channel/member.dart'; | ||||
| import 'package:solian/screens/chat/chat_list.dart'; | ||||
| import 'package:solian/screens/chat/chat_detail.dart'; | ||||
| import 'package:solian/screens/chat/channel/channel_editor.dart'; | ||||
| import 'package:solian/screens/chat/channel/channel_member.dart'; | ||||
| import 'package:solian/screens/explore.dart'; | ||||
| import 'package:solian/screens/notification.dart'; | ||||
| import 'package:solian/screens/posts/comment_editor.dart'; | ||||
| import 'package:solian/screens/posts/moment_editor.dart'; | ||||
| import 'package:solian/screens/posts/screen.dart'; | ||||
| import 'package:solian/screens/auth/signin.dart'; | ||||
| import 'package:solian/utils/theme.dart'; | ||||
| import 'package:solian/widgets/empty.dart'; | ||||
| import 'package:solian/widgets/layouts/two_column.dart'; | ||||
|  | ||||
| final router = GoRouter( | ||||
|   routes: [ | ||||
|     GoRoute( | ||||
|       path: '/', | ||||
|       name: 'explore', | ||||
|       builder: (context, state) => const ExploreScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/notification', | ||||
|       name: 'notification', | ||||
|       builder: (context, state) => const NotificationScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat', | ||||
|       name: 'chat', | ||||
|       builder: (context, state) => const ChatIndexScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/create', | ||||
|       name: 'chat.channel.editor', | ||||
|       builder: (context, state) => | ||||
|           ChannelEditorScreen(editing: state.extra as Channel?), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/c/:channel', | ||||
|       name: 'chat.channel', | ||||
|       builder: (context, state) => | ||||
|           ChatScreen(alias: state.pathParameters['channel'] as String), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/c/:channel/call', | ||||
|       name: 'chat.channel.call', | ||||
|       builder: (context, state) => ChatCall(call: state.extra as Call), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/c/:channel/manage', | ||||
|       name: 'chat.channel.manage', | ||||
|       builder: (context, state) => | ||||
|           ChatManageScreen(channel: state.extra as Channel), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/chat/c/:channel/member', | ||||
|       name: 'chat.channel.member', | ||||
|       builder: (context, state) => | ||||
|           ChatMemberScreen(channel: state.extra as Channel), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/account', | ||||
|       name: 'account', | ||||
|       builder: (context, state) => const AccountScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/posts/publish/moments', | ||||
|       name: 'posts.moments.editor', | ||||
|       builder: (context, state) => | ||||
|           MomentEditorScreen(editing: state.extra as Post?), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/posts/publish/comments', | ||||
|       name: 'posts.comments.editor', | ||||
|       builder: (context, state) { | ||||
|         final args = state.extra as CommentPostArguments; | ||||
|         return CommentEditorScreen( | ||||
|             editing: args.editing, related: args.related); | ||||
|       }, | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/posts/:dataset/:alias', | ||||
|       name: 'posts.screen', | ||||
|       builder: (context, state) => PostScreen( | ||||
|         alias: state.pathParameters['alias'] as String, | ||||
|         dataset: state.pathParameters['dataset'] as String, | ||||
| abstract class SolianRouter { | ||||
|   static final router = GoRouter( | ||||
|     routes: [ | ||||
|       GoRoute( | ||||
|         path: '/', | ||||
|         name: 'explore', | ||||
|         builder: (context, state) => const ExploreScreen(), | ||||
|       ), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/auth/sign-in', | ||||
|       name: 'auth.sign-in', | ||||
|       builder: (context, state) => SignInScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/auth/sign-up', | ||||
|       name: 'auth.sign-up', | ||||
|       builder: (context, state) => SignUpScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/account/friend', | ||||
|       name: 'account.friend', | ||||
|       builder: (context, state) => const FriendScreen(), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/account/personalize', | ||||
|       name: 'account.personalize', | ||||
|       builder: (context, state) => const PersonalizeScreen(), | ||||
|     ), | ||||
|   ], | ||||
| ); | ||||
|       GoRoute( | ||||
|         path: '/notification', | ||||
|         name: 'notification', | ||||
|         builder: (context, state) => const NotificationScreen(), | ||||
|       ), | ||||
|       ShellRoute( | ||||
|         pageBuilder: (context, state, child) => defaultPageBuilder( | ||||
|           context, | ||||
|           state, | ||||
|           SolianTheme.isLargeScreen(context) | ||||
|               ? TwoColumnLayout( | ||||
|                   sideChild: const ChatListScreen(), | ||||
|                   mainChild: child, | ||||
|                 ) | ||||
|               : child, | ||||
|         ), | ||||
|         routes: [ | ||||
|           GoRoute( | ||||
|             path: '/chat', | ||||
|             name: 'chat', | ||||
|             builder: (context, state) => | ||||
|                 !SolianTheme.isLargeScreen(context) ? const ChatListScreen() : const PageEmptyWidget(), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/chat/create', | ||||
|             name: 'chat.channel.editor', | ||||
|             builder: (context, state) => ChannelEditorScreen(editing: state.extra as Channel?), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/chat/c/:channel', | ||||
|             name: 'chat.channel', | ||||
|             builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/chat/c/:channel/call', | ||||
|             name: 'chat.channel.call', | ||||
|             builder: (context, state) => ChatCall(call: state.extra as Call), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/chat/c/:channel/manage', | ||||
|             name: 'chat.channel.manage', | ||||
|             builder: (context, state) => ChatDetailScreen(channel: state.extra as Channel), | ||||
|           ), | ||||
|           GoRoute( | ||||
|             path: '/chat/c/:channel/member', | ||||
|             name: 'chat.channel.member', | ||||
|             builder: (context, state) => ChatMemberScreen(channel: state.extra as Channel), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account', | ||||
|         name: 'account', | ||||
|         builder: (context, state) => const AccountScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/posts/publish/moments', | ||||
|         name: 'posts.moments.editor', | ||||
|         builder: (context, state) => MomentEditorScreen(editing: state.extra as Post?), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/posts/publish/comments', | ||||
|         name: 'posts.comments.editor', | ||||
|         builder: (context, state) { | ||||
|           final args = state.extra as CommentPostArguments; | ||||
|           return CommentEditorScreen(editing: args.editing, related: args.related); | ||||
|         }, | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/posts/:dataset/:alias', | ||||
|         name: 'posts.screen', | ||||
|         builder: (context, state) => PostScreen( | ||||
|           alias: state.pathParameters['alias'] as String, | ||||
|           dataset: state.pathParameters['dataset'] as String, | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/auth/sign-in', | ||||
|         name: 'auth.sign-in', | ||||
|         builder: (context, state) => SignInScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/auth/sign-up', | ||||
|         name: 'auth.sign-up', | ||||
|         builder: (context, state) => SignUpScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/friend', | ||||
|         name: 'account.friend', | ||||
|         builder: (context, state) => const FriendScreen(), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/personalize', | ||||
|         name: 'account.personalize', | ||||
|         builder: (context, state) => const PersonalizeScreen(), | ||||
|       ), | ||||
|     ], | ||||
|   ); | ||||
|  | ||||
|   static Page defaultPageBuilder( | ||||
|     BuildContext context, | ||||
|     GoRouterState state, | ||||
|     Widget child, | ||||
|   ) => | ||||
|       MaterialPage( | ||||
|         key: state.pageKey, | ||||
|         restorationId: state.pageKey.value, | ||||
|         child: child, | ||||
|       ); | ||||
| } | ||||
|   | ||||
| @@ -4,10 +4,10 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/account/friend.dart'; | ||||
| import 'package:solian/screens/account/personalize.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/widgets/empty.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
|  | ||||
| class AccountScreen extends StatefulWidget { | ||||
|   const AccountScreen({super.key}); | ||||
| @@ -32,11 +32,11 @@ class _AccountScreenState extends State<AccountScreen> { | ||||
|         case 'account.personalize': | ||||
|           return const PersonalizeScreenWidget(); | ||||
|         default: | ||||
|           return const SelectionEmptyWidget(); | ||||
|           return const PageEmptyWidget(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: _title ?? AppLocalizations.of(context)!.account, | ||||
|       noSafeArea: true, | ||||
|       fixedAppBarColor: true, | ||||
| @@ -60,7 +60,7 @@ class _AccountScreenState extends State<AccountScreen> { | ||||
|             ) | ||||
|           : AccountScreenWidget( | ||||
|               onSelect: (item, _) { | ||||
|                 router.pushNamed(item); | ||||
|                 SolianRouter.router.pushNamed(item); | ||||
|               }, | ||||
|             ), | ||||
|     ); | ||||
| @@ -139,7 +139,7 @@ class _AccountScreenWidgetState extends State<AccountScreenWidget> { | ||||
|               title: AppLocalizations.of(context)!.signIn, | ||||
|               caption: AppLocalizations.of(context)!.signInCaption, | ||||
|               onTap: () { | ||||
|                 router.pushNamed('auth.sign-in').then((did) { | ||||
|                 SolianRouter.router.pushNamed('auth.sign-in').then((did) { | ||||
|                   auth.isAuthorized().then((value) { | ||||
|                     setState(() => _isAuthorized = value); | ||||
|                   }); | ||||
| @@ -151,7 +151,7 @@ class _AccountScreenWidgetState extends State<AccountScreenWidget> { | ||||
|               title: AppLocalizations.of(context)!.signUp, | ||||
|               caption: AppLocalizations.of(context)!.signUpCaption, | ||||
|               onTap: () { | ||||
|                 router.pushNamed('auth.sign-up'); | ||||
|                 SolianRouter.router.pushNamed('auth.sign-up'); | ||||
|               }, | ||||
|             ), | ||||
|           ], | ||||
|   | ||||
| @@ -6,9 +6,9 @@ import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/friendship.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class FriendScreen extends StatelessWidget { | ||||
| @@ -16,7 +16,7 @@ class FriendScreen extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.friend, | ||||
|       noSafeArea: true, | ||||
|       hideDrawer: true, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import 'package:provider/provider.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class PersonalizeScreen extends StatelessWidget { | ||||
| @@ -15,7 +15,7 @@ class PersonalizeScreen extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.personalize, | ||||
|       hideDrawer: true, | ||||
|       child: const PersonalizeScreenWidget(), | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| class SignInScreen extends StatelessWidget { | ||||
| @@ -21,7 +21,7 @@ class SignInScreen extends StatelessWidget { | ||||
|     final password = _passwordController.value.text; | ||||
|     if (username.isEmpty || password.isEmpty) return; | ||||
|     auth.signin(context, username, password).then((_) { | ||||
|       router.pop(true); | ||||
|       SolianRouter.router.pop(true); | ||||
|     }).catchError((e) { | ||||
|       List<String> messages = e.toString().split('\n'); | ||||
|       if (messages.last.contains('risk')) { | ||||
| @@ -61,7 +61,7 @@ class SignInScreen extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.signIn, | ||||
|       hideDrawer: true, | ||||
|       child: Center( | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:http/http.dart' as http; | ||||
|  | ||||
| class SignUpScreen extends StatelessWidget { | ||||
| @@ -58,7 +58,7 @@ class SignUpScreen extends StatelessWidget { | ||||
|           ); | ||||
|         }, | ||||
|       ).then((_) { | ||||
|         router.replaceNamed('auth.sign-in'); | ||||
|         SolianRouter.router.replaceNamed('auth.sign-in'); | ||||
|       }); | ||||
|     } else { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
| @@ -68,7 +68,7 @@ class SignUpScreen extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.signUp, | ||||
|       hideDrawer: true, | ||||
|       child: Center( | ||||
|   | ||||
| @@ -3,10 +3,11 @@ import 'package:livekit_client/livekit_client.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/call.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| import 'package:solian/widgets/chat/call/controls.dart'; | ||||
| import 'package:solian/utils/theme.dart'; | ||||
| import 'package:solian/widgets/chat/call/call_controls.dart'; | ||||
| import 'package:solian/widgets/chat/call/participant.dart'; | ||||
| import 'package:solian/widgets/chat/call/participant_menu.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'dart:math' as math; | ||||
|  | ||||
| @@ -127,8 +128,9 @@ class _ChatCallState extends State<ChatCall> { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.chatCall, | ||||
|       fixedAppBarColor: SolianTheme.isLargeScreen(context), | ||||
|       hideDrawer: true, | ||||
|       child: content, | ||||
|     ); | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
| 
 | ||||
| @@ -55,8 +55,8 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
|       context.showErrorDialog(message); | ||||
|     } else { | ||||
|       if (router.canPop()) { | ||||
|         router.pop(true); | ||||
|       if (SolianRouter.router.canPop()) { | ||||
|         SolianRouter.router.pop(true); | ||||
|       } | ||||
|     } | ||||
|     setState(() => _isSubmitting = false); | ||||
| @@ -67,8 +67,8 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> { | ||||
|   } | ||||
| 
 | ||||
|   void cancelEditing() { | ||||
|     if (router.canPop()) { | ||||
|       router.pop(false); | ||||
|     if (SolianRouter.router.canPop()) { | ||||
|       SolianRouter.router.pop(false); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @@ -99,7 +99,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> { | ||||
|       ], | ||||
|     ); | ||||
| 
 | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       hideDrawer: true, | ||||
|       title: AppLocalizations.of(context)!.chatChannelOrganize, | ||||
|       appBarActions: <Widget>[ | ||||
| @@ -7,10 +7,10 @@ import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/account/friend_picker.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| 
 | ||||
| class ChatMemberScreen extends StatefulWidget { | ||||
| @@ -141,7 +141,7 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> { | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.chatMember, | ||||
|       noSafeArea: true, | ||||
|       hideDrawer: true, | ||||
| @@ -10,12 +10,13 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/utils/theme.dart'; | ||||
| import 'package:solian/widgets/chat/channel_action.dart'; | ||||
| import 'package:solian/widgets/chat/maintainer.dart'; | ||||
| import 'package:solian/widgets/chat/chat_maintainer.dart'; | ||||
| import 'package:solian/widgets/chat/message.dart'; | ||||
| import 'package:solian/widgets/chat/message_action.dart'; | ||||
| import 'package:solian/widgets/chat/message_editor.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class ChatScreen extends StatelessWidget { | ||||
| @@ -27,9 +28,11 @@ class ChatScreen extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     final chat = context.watch<ChatProvider>(); | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: chat.focusChannel?.name ?? 'Loading...', | ||||
|       hideDrawer: true, | ||||
|       fixedAppBarColor: SolianTheme.isLargeScreen(context), | ||||
|       appBarLeading: IconButton(icon: const Icon(Icons.tag), onPressed: () {}), | ||||
|       appBarActions: chat.focusChannel != null | ||||
|           ? [ | ||||
|               ChannelCallAction( | ||||
| @@ -197,7 +200,7 @@ class _ChatScreenWidgetState extends State<ChatScreenWidget> { | ||||
|         TextButton( | ||||
|           child: Text(AppLocalizations.of(context)!.chatCallJoin), | ||||
|           onPressed: () { | ||||
|             router.pushNamed( | ||||
|             SolianRouter.router.pushNamed( | ||||
|               'chat.channel.call', | ||||
|               extra: _chat.ongoingCall, | ||||
|               pathParameters: {'channel': widget.alias}, | ||||
|   | ||||
| @@ -4,19 +4,19 @@ import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/widgets/chat/channel_deletion.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| 
 | ||||
| class ChatManageScreen extends StatefulWidget { | ||||
| class ChatDetailScreen extends StatefulWidget { | ||||
|   final Channel channel; | ||||
| 
 | ||||
|   const ChatManageScreen({super.key, required this.channel}); | ||||
|   const ChatDetailScreen({super.key, required this.channel}); | ||||
| 
 | ||||
|   @override | ||||
|   State<ChatManageScreen> createState() => _ChatManageScreenState(); | ||||
|   State<ChatDetailScreen> createState() => _ChatDetailScreenState(); | ||||
| } | ||||
| 
 | ||||
| class _ChatManageScreenState extends State<ChatManageScreen> { | ||||
| class _ChatDetailScreenState extends State<ChatDetailScreen> { | ||||
|   bool _isOwned = false; | ||||
| 
 | ||||
|   void promptLeaveChannel() async { | ||||
| @@ -27,8 +27,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | ||||
|         isOwned: _isOwned, | ||||
|       ), | ||||
|     ); | ||||
|     if (did == true && router.canPop()) { | ||||
|       router.pop('disposed'); | ||||
|     if (did == true && SolianRouter.router.canPop()) { | ||||
|       SolianRouter.router.pop('disposed'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @@ -53,16 +53,16 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | ||||
|         leading: const Icon(Icons.settings), | ||||
|         title: Text(AppLocalizations.of(context)!.settings), | ||||
|         onTap: () async { | ||||
|           router.pushNamed('chat.channel.editor', extra: widget.channel).then((did) { | ||||
|           SolianRouter.router.pushNamed('chat.channel.editor', extra: widget.channel).then((did) { | ||||
|             if (did == true) { | ||||
|               if (router.canPop()) router.pop('refresh'); | ||||
|               if (SolianRouter.router.canPop()) SolianRouter.router.pop('refresh'); | ||||
|             } | ||||
|           }); | ||||
|         }, | ||||
|       ), | ||||
|     ]; | ||||
| 
 | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.chatManage, | ||||
|       hideDrawer: true, | ||||
|       noSafeArea: true, | ||||
| @@ -100,7 +100,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> { | ||||
|                   leading: const Icon(Icons.supervisor_account), | ||||
|                   title: Text(AppLocalizations.of(context)!.chatMember), | ||||
|                   onTap: () { | ||||
|                     router.pushNamed( | ||||
|                     SolianRouter.router.pushNamed( | ||||
|                       'chat.channel.member', | ||||
|                       extra: widget.channel, | ||||
|                       pathParameters: {'channel': widget.channel.alias}, | ||||
| @@ -4,90 +4,47 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/chat/chat.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/chat/channel_action.dart'; | ||||
| import 'package:solian/utils/theme.dart'; | ||||
| import 'package:solian/widgets/chat/chat_new.dart'; | ||||
| import 'package:solian/widgets/empty.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/widgets/notification_notifier.dart'; | ||||
| import 'package:solian/widgets/signin_required.dart'; | ||||
| 
 | ||||
| class ChatIndexScreen extends StatefulWidget { | ||||
|   const ChatIndexScreen({super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   State<ChatIndexScreen> createState() => _ChatIndexScreenState(); | ||||
| } | ||||
| 
 | ||||
| class _ChatIndexScreenState extends State<ChatIndexScreen> { | ||||
|   Channel? _selectedChannel; | ||||
| class ChatListScreen extends StatelessWidget { | ||||
|   const ChatListScreen({super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final chat = context.watch<ChatProvider>(); | ||||
| 
 | ||||
|     final screenWidth = MediaQuery.of(context).size.width; | ||||
|     final isLargeScreen = screenWidth >= 600; | ||||
| 
 | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.chat, | ||||
|       appBarActions: isLargeScreen && chat.focusChannel != null | ||||
|           ? [ | ||||
|               ChannelCallAction( | ||||
|                 call: chat.ongoingCall, | ||||
|                 channel: chat.focusChannel!, | ||||
|                 onUpdate: () => chat.fetchChannel(chat.focusChannel!.alias), | ||||
|               ), | ||||
|               ChannelManageAction( | ||||
|                 channel: chat.focusChannel!, | ||||
|                 onUpdate: () => chat.fetchChannel(chat.focusChannel!.alias), | ||||
|               ), | ||||
|             ] | ||||
|           : [const NotificationButton()], | ||||
|       fixedAppBarColor: isLargeScreen, | ||||
|       child: isLargeScreen | ||||
|           ? Row( | ||||
|               children: [ | ||||
|                 Flexible( | ||||
|                   flex: 2, | ||||
|                   child: ChatIndexScreenWidget( | ||||
|                     onSelect: (item) { | ||||
|                       setState(() => _selectedChannel = item); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ), | ||||
|                 const VerticalDivider(thickness: 0.3, width: 0.3), | ||||
|                 Flexible( | ||||
|                   flex: 4, | ||||
|                   child: _selectedChannel == null | ||||
|                       ? const SelectionEmptyWidget() | ||||
|                       : ChatScreenWidget( | ||||
|                           key: Key('c${_selectedChannel!.id}'), | ||||
|                           alias: _selectedChannel!.alias, | ||||
|                         ), | ||||
|                 ), | ||||
|               ], | ||||
|             ) | ||||
|           : const ChatIndexScreenWidget(), | ||||
|       appBarActions: const [NotificationButton()], | ||||
|       fixedAppBarColor: SolianTheme.isLargeScreen(context), | ||||
|       child: ChatListWidget( | ||||
|         onSelect: (item) { | ||||
|           SolianRouter.router.pushReplacementNamed( | ||||
|             'chat.channel', | ||||
|             pathParameters: {'channel': item.alias}, | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class ChatIndexScreenWidget extends StatefulWidget { | ||||
| class ChatListWidget extends StatefulWidget { | ||||
|   final Function(Channel item)? onSelect; | ||||
| 
 | ||||
|   const ChatIndexScreenWidget({super.key, this.onSelect}); | ||||
|   const ChatListWidget({super.key, this.onSelect}); | ||||
| 
 | ||||
|   @override | ||||
|   State<ChatIndexScreenWidget> createState() => _ChatIndexScreenWidgetState(); | ||||
|   State<ChatListWidget> createState() => _ChatListWidgetState(); | ||||
| } | ||||
| 
 | ||||
| class _ChatIndexScreenWidgetState extends State<ChatIndexScreenWidget> { | ||||
| class _ChatListWidgetState extends State<ChatListWidget> { | ||||
|   List<Channel> _channels = List.empty(); | ||||
| 
 | ||||
|   Future<void> fetchChannels() async { | ||||
| @@ -169,7 +126,7 @@ class _ChatIndexScreenWidgetState extends State<ChatIndexScreenWidget> { | ||||
|                       return; | ||||
|                     } | ||||
| 
 | ||||
|                     final result = await router.pushNamed( | ||||
|                     final result = await SolianRouter.router.pushNamed( | ||||
|                       'chat.channel', | ||||
|                       pathParameters: { | ||||
|                         'channel': element.alias, | ||||
| @@ -12,9 +12,9 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:http/http.dart' as http; | ||||
| import 'package:solian/widgets/empty.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:solian/widgets/notification_notifier.dart'; | ||||
| import 'package:solian/widgets/posts/item.dart'; | ||||
| import 'package:solian/widgets/posts/post.dart'; | ||||
|  | ||||
| class ExploreScreen extends StatefulWidget { | ||||
|   const ExploreScreen({super.key}); | ||||
| @@ -31,7 +31,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|     final screenWidth = MediaQuery.of(context).size.width; | ||||
|     final isLargeScreen = screenWidth >= 600; | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       noSafeArea: true, | ||||
|       fixedAppBarColor: isLargeScreen, | ||||
|       appBarActions: const [NotificationButton()], | ||||
| @@ -51,7 +51,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|                 Flexible( | ||||
|                   flex: 4, | ||||
|                   child: _selectedPost == null | ||||
|                       ? const SelectionEmptyWidget() | ||||
|                       ? const PageEmptyWidget() | ||||
|                       : PostScreenWidget( | ||||
|                           key: Key('p${_selectedPost!.id}'), | ||||
|                           dataset: _selectedPost!.dataset, | ||||
| @@ -62,7 +62,7 @@ class _ExploreScreenState extends State<ExploreScreen> { | ||||
|             ) | ||||
|           : ExploreScreenWidget( | ||||
|               onSelect: (item) { | ||||
|                 router.pushNamed( | ||||
|                 SolianRouter.router.pushNamed( | ||||
|                   'posts.screen', | ||||
|                   pathParameters: { | ||||
|                     'alias': item.alias, | ||||
| @@ -130,7 +130,7 @@ class _ExploreScreenWidgetState extends State<ExploreScreenWidget> { | ||||
|             return FloatingActionButton( | ||||
|               child: const Icon(Icons.edit), | ||||
|               onPressed: () async { | ||||
|                 final did = await router.pushNamed('posts.moments.editor'); | ||||
|                 final did = await SolianRouter.router.pushNamed('posts.moments.editor'); | ||||
|                 if (did == true) _pagingController.refresh(); | ||||
|               }, | ||||
|             ); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import 'package:provider/provider.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/notify.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
| import 'package:solian/models/notification.dart' as model; | ||||
| @@ -25,7 +25,7 @@ class _NotificationScreenState extends State<NotificationScreen> { | ||||
|       nty.allRead(); | ||||
|     }); | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       noSafeArea: true, | ||||
|       hideDrawer: true, | ||||
|       title: AppLocalizations.of(context)!.notification, | ||||
| @@ -82,10 +82,10 @@ class NotificationItem extends StatelessWidget { | ||||
|   const NotificationItem( | ||||
|       {super.key, required this.index, required this.item, this.onDismiss}); | ||||
|  | ||||
|   bool hasLinks() => item.links != null && item.links!.isNotEmpty; | ||||
|   bool get hasLinks => item.links != null && item.links!.isNotEmpty; | ||||
|  | ||||
|   void showLinks(BuildContext context) { | ||||
|     if (!hasLinks()) return; | ||||
|     if (!hasLinks) return; | ||||
|  | ||||
|     showModalBottomSheet<void>( | ||||
|       context: context, | ||||
| @@ -170,7 +170,7 @@ class NotificationItem extends StatelessWidget { | ||||
|         child: ListTile( | ||||
|           title: Text(item.subject), | ||||
|           subtitle: Text(item.content), | ||||
|           trailing: hasLinks() | ||||
|           trailing: hasLinks | ||||
|               ? TextButton( | ||||
|                   onPressed: () => showLinks(context), | ||||
|                   style: TextButton.styleFrom(shape: const CircleBorder()), | ||||
|   | ||||
| @@ -9,9 +9,9 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:solian/widgets/posts/attachment_editor.dart'; | ||||
|  | ||||
| class CommentPostArguments { | ||||
| @@ -78,16 +78,16 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
|       context.showErrorDialog(message); | ||||
|     } else { | ||||
|       if (router.canPop()) { | ||||
|         router.pop(true); | ||||
|       if (SolianRouter.router.canPop()) { | ||||
|         SolianRouter.router.pop(true); | ||||
|       } | ||||
|     } | ||||
|     setState(() => _isSubmitting = false); | ||||
|   } | ||||
|  | ||||
|   void cancelEditing() { | ||||
|     if (router.canPop()) { | ||||
|       router.pop(false); | ||||
|     if (SolianRouter.router.canPop()) { | ||||
|       SolianRouter.router.pop(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -120,7 +120,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> { | ||||
|       ], | ||||
|     ); | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       hideDrawer: true, | ||||
|       title: AppLocalizations.of(context)!.newComment, | ||||
|       appBarActions: <Widget>[ | ||||
|   | ||||
| @@ -9,9 +9,9 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:solian/widgets/posts/attachment_editor.dart'; | ||||
|  | ||||
| class MomentEditorScreen extends StatefulWidget { | ||||
| @@ -68,16 +68,16 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|       var message = utf8.decode(res.bodyBytes); | ||||
|       context.showErrorDialog(message); | ||||
|     } else { | ||||
|       if (router.canPop()) { | ||||
|         router.pop(true); | ||||
|       if (SolianRouter.router.canPop()) { | ||||
|         SolianRouter.router.pop(true); | ||||
|       } | ||||
|     } | ||||
|     setState(() => _isSubmitting = false); | ||||
|   } | ||||
|  | ||||
|   void cancelEditing() { | ||||
|     if (router.canPop()) { | ||||
|       router.pop(false); | ||||
|     if (SolianRouter.router.canPop()) { | ||||
|       SolianRouter.router.pop(false); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -110,7 +110,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> { | ||||
|       ], | ||||
|     ); | ||||
|  | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       hideDrawer: true, | ||||
|       title: AppLocalizations.of(context)!.newMoment, | ||||
|       appBarActions: <Widget>[ | ||||
|   | ||||
| @@ -6,9 +6,9 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/exts.dart'; | ||||
| import 'package:solian/widgets/indent_wrapper.dart'; | ||||
| import 'package:solian/widgets/scaffold.dart'; | ||||
| import 'package:solian/widgets/posts/comment_list.dart'; | ||||
| import 'package:solian/widgets/posts/item.dart'; | ||||
| import 'package:solian/widgets/posts/post.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class PostScreen extends StatelessWidget { | ||||
| @@ -19,7 +19,7 @@ class PostScreen extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return IndentWrapper( | ||||
|     return IndentScaffold( | ||||
|       title: AppLocalizations.of(context)!.post, | ||||
|       noSafeArea: true, | ||||
|       hideDrawer: true, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| abstract class SolianTheme { | ||||
|   static bool isColumnMode(BuildContext context) => | ||||
|   static bool isLargeScreen(BuildContext context) => | ||||
|       MediaQuery.of(context).size.width > 640; | ||||
|  | ||||
|   static ThemeData build(Brightness brightness) { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import 'package:provider/provider.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/friend.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
|  | ||||
| class FriendPicker extends StatefulWidget { | ||||
|   const FriendPicker({super.key}); | ||||
|   | ||||
| @@ -76,7 +76,7 @@ class _ControlsWidgetState extends State<ControlsWidget> { | ||||
|     if (chat.currentCall != null) { | ||||
|       chat.currentCall!.deactivate(); | ||||
|       chat.currentCall!.dispose(); | ||||
|       router.pop(); | ||||
|       SolianRouter.router.pop(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @@ -52,7 +52,7 @@ class CallOverlay extends StatelessWidget { | ||||
|         ), | ||||
|       ), | ||||
|       onTap: () { | ||||
|         router.pushNamed( | ||||
|         SolianRouter.router.pushNamed( | ||||
|           'chat.channel.call', | ||||
|           extra: chat.currentCall!.info, | ||||
|           pathParameters: {'channel': chat.currentCall!.channel.alias}, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart'; | ||||
| import 'package:livekit_client/livekit_client.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/call.dart'; | ||||
| import 'package:solian/widgets/chat/call/no_content.dart'; | ||||
| import 'package:solian/widgets/chat/call/participant_no_content.dart'; | ||||
| import 'package:solian/widgets/chat/call/participant_info.dart'; | ||||
| import 'package:solian/widgets/chat/call/participant_stats.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'dart:math' as math; | ||||
| 
 | ||||
| class NoContentWidget extends StatefulWidget { | ||||
| @@ -97,14 +97,14 @@ class ChannelManageAction extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     return IconButton( | ||||
|       onPressed: () async { | ||||
|         final result = await router.pushNamed( | ||||
|         final result = await SolianRouter.router.pushNamed( | ||||
|           'chat.channel.manage', | ||||
|           extra: channel, | ||||
|           pathParameters: {'channel': channel.alias}, | ||||
|         ); | ||||
|         switch (result) { | ||||
|           case 'disposed': | ||||
|             if (router.canPop()) router.pop('refresh'); | ||||
|             if (SolianRouter.router.canPop()) SolianRouter.router.pop('refresh'); | ||||
|           case 'refresh': | ||||
|             onUpdate(); | ||||
|         } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class ChatNewAction extends StatelessWidget { | ||||
|                   leading: const Icon(Icons.add), | ||||
|                   title: Text(AppLocalizations.of(context)!.chatNewCreate), | ||||
|                   onTap: () { | ||||
|                     router.pushNamed('chat.channel.editor').then((did) { | ||||
|                     SolianRouter.router.pushNamed('chat.channel.editor').then((did) { | ||||
|                       if (did == true) { | ||||
|                         onUpdate(); | ||||
|                         if (Navigator.canPop(context)) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/chat/content.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/chat/message_content.dart'; | ||||
| import 'package:solian/widgets/posts/content/attachment.dart'; | ||||
| import 'package:timeago/timeago.dart' as timeago; | ||||
| import 'dart:math' as math; | ||||
|   | ||||
| @@ -1,22 +1,24 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class SelectionEmptyWidget extends StatelessWidget { | ||||
|   const SelectionEmptyWidget({super.key}); | ||||
| class PageEmptyWidget extends StatelessWidget { | ||||
|   const PageEmptyWidget({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Center( | ||||
|       child: Column( | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         children: [ | ||||
|           Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|           const SizedBox(height: 2), | ||||
|           Text( | ||||
|             AppLocalizations.of(context)!.appName, | ||||
|             style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900), | ||||
|           ), | ||||
|         ], | ||||
|     return Material( | ||||
|       child: Center( | ||||
|         child: Column( | ||||
|           mainAxisSize: MainAxisSize.min, | ||||
|           children: [ | ||||
|             Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|             const SizedBox(height: 2), | ||||
|             Text( | ||||
|               AppLocalizations.of(context)!.appName, | ||||
|               style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -1,43 +0,0 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/widgets/common_wrapper.dart'; | ||||
| import 'package:solian/widgets/navigation_drawer.dart'; | ||||
|  | ||||
| class IndentWrapper extends LayoutWrapper { | ||||
|   final bool hideDrawer; | ||||
|   final bool fixedAppBarColor; | ||||
|  | ||||
|   const IndentWrapper({ | ||||
|     super.key, | ||||
|     super.child, | ||||
|     required super.title, | ||||
|     super.floatingActionButton, | ||||
|     super.appBarActions, | ||||
|     this.hideDrawer = false, | ||||
|     this.fixedAppBarColor = false, | ||||
|     super.noSafeArea = false, | ||||
|   }) : super(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final content = child ?? Container(); | ||||
|  | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         leading: hideDrawer | ||||
|             ? IconButton( | ||||
|                 icon: const Icon(Icons.arrow_back), | ||||
|                 onPressed: () => router.pop(), | ||||
|               ) | ||||
|             : null, | ||||
|         title: Text(title), | ||||
|         actions: appBarActions, | ||||
|         centerTitle: false, | ||||
|         elevation: fixedAppBarColor ? 4 : null, | ||||
|       ), | ||||
|       floatingActionButton: floatingActionButton, | ||||
|       drawer: const SolianNavigationDrawer(), | ||||
|       body: noSafeArea ? content : SafeArea(child: content), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										31
									
								
								lib/widgets/layouts/two_column.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/widgets/layouts/two_column.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:solian/widgets/empty.dart'; | ||||
|  | ||||
| class TwoColumnLayout extends StatelessWidget { | ||||
|   final Widget sideChild; | ||||
|   final Widget? mainChild; | ||||
|  | ||||
|   const TwoColumnLayout({ | ||||
|     super.key, | ||||
|     required this.sideChild, | ||||
|     required this.mainChild, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ScaffoldMessenger( | ||||
|       child: Scaffold( | ||||
|         body: Row( | ||||
|           children: [ | ||||
|             SizedBox( | ||||
|               width: 400, | ||||
|               child: sideChild, | ||||
|             ), | ||||
|             const VerticalDivider(width: 0.3, thickness: 0.3), | ||||
|             Expanded(child: mainChild ?? const PageEmptyWidget()), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; | ||||
| import 'package:solian/providers/navigation.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/utils/theme.dart'; | ||||
|  | ||||
| class SolianNavigationDrawer extends StatefulWidget { | ||||
|   const SolianNavigationDrawer({super.key}); | ||||
| @@ -17,7 +18,7 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> { | ||||
|   void _onSelect(String name, int idx) { | ||||
|     setState(() => _selectedIndex = idx); | ||||
|     context.read<NavigationProvider>().selectedIndex = idx; | ||||
|     router.goNamed(name); | ||||
|     SolianRouter.router.goNamed(name); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -59,6 +60,9 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> { | ||||
|  | ||||
|     return NavigationDrawer( | ||||
|       selectedIndex: _selectedIndex, | ||||
|       elevation: SolianTheme.isLargeScreen(context) ? 20 : 0, | ||||
|       shadowColor: SolianTheme.isLargeScreen(context) ? Theme.of(context).shadowColor : null, | ||||
|       surfaceTintColor: Theme.of(context).colorScheme.background, | ||||
|       onDestinationSelected: (int idx) { | ||||
|         final element = navigationItems[idx]; | ||||
|         _onSelect(element.$2, idx); | ||||
|   | ||||
| @@ -85,7 +85,7 @@ class _NotificationButtonState extends State<NotificationButton> { | ||||
|       child: IconButton( | ||||
|         icon: const Icon(Icons.notifications), | ||||
|         onPressed: () { | ||||
|           router.pushNamed('notification'); | ||||
|           SolianRouter.router.pushNamed('notification'); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/posts/comment_editor.dart'; | ||||
| import 'package:solian/utils/service_url.dart'; | ||||
| import 'package:solian/widgets/posts/item.dart'; | ||||
| import 'package:solian/widgets/posts/post.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| class CommentList extends StatefulWidget { | ||||
| @@ -120,7 +120,7 @@ class CommentListHeader extends StatelessWidget { | ||||
|               if (snapshot.hasData && snapshot.data == true) { | ||||
|                 return TextButton( | ||||
|                   onPressed: () async { | ||||
|                     final did = await router.pushNamed( | ||||
|                     final did = await SolianRouter.router.pushNamed( | ||||
|                       'posts.comments.editor', | ||||
|                       extra: CommentPostArguments(related: related), | ||||
|                     ); | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/widgets/account/avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/posts/comment_list.dart'; | ||||
| import 'package:solian/widgets/posts/content/article.dart'; | ||||
| import 'package:solian/widgets/posts/content/attachment.dart'; | ||||
| import 'package:solian/widgets/posts/content/moment.dart'; | ||||
| import 'package:solian/widgets/posts/item_action.dart'; | ||||
| import 'package:solian/widgets/posts/post_action.dart'; | ||||
| import 'package:solian/widgets/posts/reaction_list.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:timeago/timeago.dart' as timeago; | ||||
| @@ -5,7 +5,7 @@ import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
| import 'package:solian/screens/posts/comment_editor.dart'; | ||||
| import 'package:solian/widgets/posts/item_deletion.dart'; | ||||
| import 'package:solian/widgets/posts/post_deletion.dart'; | ||||
| 
 | ||||
| class PostItemAction extends StatelessWidget { | ||||
|   final Post item; | ||||
| @@ -23,17 +23,17 @@ class PostItemAction extends StatelessWidget { | ||||
|     bool ok = false; | ||||
|     switch (item.modelType) { | ||||
|       case 'article': | ||||
|         ok = await router.pushNamed( | ||||
|         ok = await SolianRouter.router.pushNamed( | ||||
|           'posts.articles.editor', | ||||
|           extra: item, | ||||
|         ) as bool; | ||||
|       case 'moment': | ||||
|         ok = await router.pushNamed( | ||||
|         ok = await SolianRouter.router.pushNamed( | ||||
|           'posts.moments.editor', | ||||
|           extra: item, | ||||
|         ) as bool; | ||||
|       case 'comment': | ||||
|         ok = await router.pushNamed( | ||||
|         ok = await SolianRouter.router.pushNamed( | ||||
|           'posts.comments.editor', | ||||
|           extra: CommentPostArguments(editing: item), | ||||
|         ) as bool; | ||||
| @@ -1,19 +1,26 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:solian/utils/theme.dart'; | ||||
| import 'package:solian/widgets/navigation_drawer.dart'; | ||||
| 
 | ||||
| class LayoutWrapper extends StatelessWidget { | ||||
| class IndentScaffold extends StatelessWidget { | ||||
|   final Widget? child; | ||||
|   final Widget? floatingActionButton; | ||||
|   final Widget? appBarLeading; | ||||
|   final List<Widget>? appBarActions; | ||||
|   final bool noSafeArea; | ||||
|   final bool hideDrawer; | ||||
|   final bool fixedAppBarColor; | ||||
|   final String title; | ||||
| 
 | ||||
|   const LayoutWrapper({ | ||||
|   const IndentScaffold({ | ||||
|     super.key, | ||||
|     this.child, | ||||
|     required this.title, | ||||
|     this.floatingActionButton, | ||||
|     this.appBarLeading, | ||||
|     this.appBarActions, | ||||
|     this.hideDrawer = false, | ||||
|     this.fixedAppBarColor = false, | ||||
|     this.noSafeArea = false, | ||||
|   }); | ||||
| 
 | ||||
| @@ -24,11 +31,14 @@ class LayoutWrapper extends StatelessWidget { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: Text(title), | ||||
|         leading: appBarLeading, | ||||
|         actions: appBarActions, | ||||
|         centerTitle: false, | ||||
|         elevation: fixedAppBarColor ? 4 : null, | ||||
|       ), | ||||
|       floatingActionButton: floatingActionButton, | ||||
|       drawer: const SolianNavigationDrawer(), | ||||
|       drawer: !hideDrawer ? const SolianNavigationDrawer() : null, | ||||
|       drawerScrimColor: SolianTheme.isLargeScreen(context) ? Colors.transparent : null, | ||||
|       body: noSafeArea ? content : SafeArea(child: content), | ||||
|     ); | ||||
|   } | ||||
| @@ -32,7 +32,7 @@ class SignInRequiredScreen extends StatelessWidget { | ||||
|         ), | ||||
|       ), | ||||
|       onTap: () { | ||||
|         router.goNamed('account'); | ||||
|         SolianRouter.router.goNamed('account'); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| #include "generated_plugin_registrant.h" | ||||
|  | ||||
| #include <desktop_drop/desktop_drop_plugin.h> | ||||
| #include <file_selector_linux/file_selector_plugin.h> | ||||
| #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> | ||||
| #include <flutter_webrtc/flutter_web_r_t_c_plugin.h> | ||||
| @@ -14,6 +15,9 @@ | ||||
| #include <url_launcher_linux/url_launcher_plugin.h> | ||||
|  | ||||
| void fl_register_plugins(FlPluginRegistry* registry) { | ||||
|   g_autoptr(FlPluginRegistrar) desktop_drop_registrar = | ||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); | ||||
|   desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); | ||||
|   g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = | ||||
|       fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); | ||||
|   file_selector_plugin_register_with_registrar(file_selector_linux_registrar); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| # | ||||
|  | ||||
| list(APPEND FLUTTER_PLUGIN_LIST | ||||
|   desktop_drop | ||||
|   file_selector_linux | ||||
|   flutter_secure_storage_linux | ||||
|   flutter_webrtc | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import FlutterMacOS | ||||
| import Foundation | ||||
|  | ||||
| import connectivity_plus | ||||
| import desktop_drop | ||||
| import device_info_plus | ||||
| import file_selector_macos | ||||
| import flutter_local_notifications | ||||
| @@ -23,6 +24,7 @@ import wakelock_plus | ||||
|  | ||||
| func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||
|   ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) | ||||
|   DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) | ||||
|   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) | ||||
|   FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) | ||||
|   FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) | ||||
|   | ||||
| @@ -177,6 +177,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.10" | ||||
|   desktop_drop: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: desktop_drop | ||||
|       sha256: d55a010fe46c8e8fcff4ea4b451a9ff84a162217bdb3b2a0aa1479776205e15d | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.4.4" | ||||
|   device_info_plus: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -70,6 +70,7 @@ dependencies: | ||||
|   file_picker: ^8.0.3 | ||||
|   package_info_plus: ^7.0.0 | ||||
|   cached_network_image: ^3.3.1 | ||||
|   desktop_drop: ^0.4.4 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include "generated_plugin_registrant.h" | ||||
|  | ||||
| #include <connectivity_plus/connectivity_plus_windows_plugin.h> | ||||
| #include <desktop_drop/desktop_drop_plugin.h> | ||||
| #include <file_selector_windows/file_selector_windows.h> | ||||
| #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> | ||||
| #include <flutter_webrtc/flutter_web_r_t_c_plugin.h> | ||||
| @@ -20,6 +21,8 @@ | ||||
| void RegisterPlugins(flutter::PluginRegistry* registry) { | ||||
|   ConnectivityPlusWindowsPluginRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); | ||||
|   DesktopDropPluginRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("DesktopDropPlugin")); | ||||
|   FileSelectorWindowsRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("FileSelectorWindows")); | ||||
|   FlutterSecureStorageWindowsPluginRegisterWithRegistrar( | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| list(APPEND FLUTTER_PLUGIN_LIST | ||||
|   connectivity_plus | ||||
|   desktop_drop | ||||
|   file_selector_windows | ||||
|   flutter_secure_storage_windows | ||||
|   flutter_webrtc | ||||
|   | ||||
		Reference in New Issue
	
	Block a user