diff --git a/lib/main.dart b/lib/main.dart index e26472e..d02b36a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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: [ diff --git a/lib/router.dart b/lib/router.dart index 1ea1c0e..bce7ee0 100644 --- a/lib/router.dart +++ b/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, + ); +} diff --git a/lib/screens/account.dart b/lib/screens/account.dart index ff6bc97..552475c 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -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 { 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 { ) : AccountScreenWidget( onSelect: (item, _) { - router.pushNamed(item); + SolianRouter.router.pushNamed(item); }, ), ); @@ -139,7 +139,7 @@ class _AccountScreenWidgetState extends State { 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 { title: AppLocalizations.of(context)!.signUp, caption: AppLocalizations.of(context)!.signUpCaption, onTap: () { - router.pushNamed('auth.sign-up'); + SolianRouter.router.pushNamed('auth.sign-up'); }, ), ], diff --git a/lib/screens/account/friend.dart b/lib/screens/account/friend.dart index 8567985..c6af012 100644 --- a/lib/screens/account/friend.dart +++ b/lib/screens/account/friend.dart @@ -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, diff --git a/lib/screens/account/personalize.dart b/lib/screens/account/personalize.dart index 445d9dc..2ccc162 100644 --- a/lib/screens/account/personalize.dart +++ b/lib/screens/account/personalize.dart @@ -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(), diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index 86736b4..d2e12ac 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -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 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( diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart index cf08d4d..9fbce17 100644 --- a/lib/screens/auth/signup.dart +++ b/lib/screens/auth/signup.dart @@ -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( diff --git a/lib/screens/chat/call.dart b/lib/screens/chat/call.dart index 3b37dc8..2d8f387 100644 --- a/lib/screens/chat/call.dart +++ b/lib/screens/chat/call.dart @@ -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 { ); } - return IndentWrapper( + return IndentScaffold( title: AppLocalizations.of(context)!.chatCall, + fixedAppBarColor: SolianTheme.isLargeScreen(context), hideDrawer: true, child: content, ); diff --git a/lib/screens/chat/channel/editor.dart b/lib/screens/chat/channel/channel_editor.dart similarity index 96% rename from lib/screens/chat/channel/editor.dart rename to lib/screens/chat/channel/channel_editor.dart index 34c1fc4..9b8b508 100644 --- a/lib/screens/chat/channel/editor.dart +++ b/lib/screens/chat/channel/channel_editor.dart @@ -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 { 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 { } void cancelEditing() { - if (router.canPop()) { - router.pop(false); + if (SolianRouter.router.canPop()) { + SolianRouter.router.pop(false); } } @@ -99,7 +99,7 @@ class _ChannelEditorScreenState extends State { ], ); - return IndentWrapper( + return IndentScaffold( hideDrawer: true, title: AppLocalizations.of(context)!.chatChannelOrganize, appBarActions: [ diff --git a/lib/screens/chat/channel/member.dart b/lib/screens/chat/channel/channel_member.dart similarity index 97% rename from lib/screens/chat/channel/member.dart rename to lib/screens/chat/channel/channel_member.dart index 86f2b5e..66a12f6 100644 --- a/lib/screens/chat/channel/member.dart +++ b/lib/screens/chat/channel/channel_member.dart @@ -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 { @override Widget build(BuildContext context) { - return IndentWrapper( + return IndentScaffold( title: AppLocalizations.of(context)!.chatMember, noSafeArea: true, hideDrawer: true, diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 0a53a36..ef70e79 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -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(); - 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 { TextButton( child: Text(AppLocalizations.of(context)!.chatCallJoin), onPressed: () { - router.pushNamed( + SolianRouter.router.pushNamed( 'chat.channel.call', extra: _chat.ongoingCall, pathParameters: {'channel': widget.alias}, diff --git a/lib/screens/chat/manage.dart b/lib/screens/chat/chat_detail.dart similarity index 84% rename from lib/screens/chat/manage.dart rename to lib/screens/chat/chat_detail.dart index 3abff55..c273b1d 100644 --- a/lib/screens/chat/manage.dart +++ b/lib/screens/chat/chat_detail.dart @@ -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 createState() => _ChatManageScreenState(); + State createState() => _ChatDetailScreenState(); } -class _ChatManageScreenState extends State { +class _ChatDetailScreenState extends State { bool _isOwned = false; void promptLeaveChannel() async { @@ -27,8 +27,8 @@ class _ChatManageScreenState extends State { 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 { 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 { 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}, diff --git a/lib/screens/chat/index.dart b/lib/screens/chat/chat_list.dart similarity index 60% rename from lib/screens/chat/index.dart rename to lib/screens/chat/chat_list.dart index 2d90656..6ce7b01 100644 --- a/lib/screens/chat/index.dart +++ b/lib/screens/chat/chat_list.dart @@ -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 createState() => _ChatIndexScreenState(); -} - -class _ChatIndexScreenState extends State { - Channel? _selectedChannel; +class ChatListScreen extends StatelessWidget { + const ChatListScreen({super.key}); @override Widget build(BuildContext context) { - final chat = context.watch(); - - 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 createState() => _ChatIndexScreenWidgetState(); + State createState() => _ChatListWidgetState(); } -class _ChatIndexScreenWidgetState extends State { +class _ChatListWidgetState extends State { List _channels = List.empty(); Future fetchChannels() async { @@ -169,7 +126,7 @@ class _ChatIndexScreenWidgetState extends State { return; } - final result = await router.pushNamed( + final result = await SolianRouter.router.pushNamed( 'chat.channel', pathParameters: { 'channel': element.alias, diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index b0c0cbd..e1f8d49 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -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 { 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 { 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 { ) : ExploreScreenWidget( onSelect: (item) { - router.pushNamed( + SolianRouter.router.pushNamed( 'posts.screen', pathParameters: { 'alias': item.alias, @@ -130,7 +130,7 @@ class _ExploreScreenWidgetState extends State { 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(); }, ); diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index 11004a9..259dcfa 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -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 { 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( 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()), diff --git a/lib/screens/posts/comment_editor.dart b/lib/screens/posts/comment_editor.dart index 8ae661a..a52a508 100644 --- a/lib/screens/posts/comment_editor.dart +++ b/lib/screens/posts/comment_editor.dart @@ -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 { 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 { ], ); - return IndentWrapper( + return IndentScaffold( hideDrawer: true, title: AppLocalizations.of(context)!.newComment, appBarActions: [ diff --git a/lib/screens/posts/moment_editor.dart b/lib/screens/posts/moment_editor.dart index 6884d14..fcd417f 100644 --- a/lib/screens/posts/moment_editor.dart +++ b/lib/screens/posts/moment_editor.dart @@ -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 { 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 { ], ); - return IndentWrapper( + return IndentScaffold( hideDrawer: true, title: AppLocalizations.of(context)!.newMoment, appBarActions: [ diff --git a/lib/screens/posts/screen.dart b/lib/screens/posts/screen.dart index 07afa97..831ea34 100644 --- a/lib/screens/posts/screen.dart +++ b/lib/screens/posts/screen.dart @@ -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, diff --git a/lib/utils/theme.dart b/lib/utils/theme.dart index 415f184..a9c618a 100644 --- a/lib/utils/theme.dart +++ b/lib/utils/theme.dart @@ -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) { diff --git a/lib/widgets/account/avatar.dart b/lib/widgets/account/account_avatar.dart similarity index 100% rename from lib/widgets/account/avatar.dart rename to lib/widgets/account/account_avatar.dart diff --git a/lib/widgets/account/friend_picker.dart b/lib/widgets/account/friend_picker.dart index e959c92..810903f 100644 --- a/lib/widgets/account/friend_picker.dart +++ b/lib/widgets/account/friend_picker.dart @@ -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}); diff --git a/lib/widgets/chat/call/controls.dart b/lib/widgets/chat/call/call_controls.dart similarity index 99% rename from lib/widgets/chat/call/controls.dart rename to lib/widgets/chat/call/call_controls.dart index 089578e..c2e4b4e 100644 --- a/lib/widgets/chat/call/controls.dart +++ b/lib/widgets/chat/call/call_controls.dart @@ -76,7 +76,7 @@ class _ControlsWidgetState extends State { if (chat.currentCall != null) { chat.currentCall!.deactivate(); chat.currentCall!.dispose(); - router.pop(); + SolianRouter.router.pop(); } } diff --git a/lib/widgets/chat/call/call_overlay.dart b/lib/widgets/chat/call/call_overlay.dart index 1a23689..4a83310 100644 --- a/lib/widgets/chat/call/call_overlay.dart +++ b/lib/widgets/chat/call/call_overlay.dart @@ -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}, diff --git a/lib/widgets/chat/call/participant.dart b/lib/widgets/chat/call/participant.dart index f2155a1..0002b06 100644 --- a/lib/widgets/chat/call/participant.dart +++ b/lib/widgets/chat/call/participant.dart @@ -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'; diff --git a/lib/widgets/chat/call/no_content.dart b/lib/widgets/chat/call/participant_no_content.dart similarity index 97% rename from lib/widgets/chat/call/no_content.dart rename to lib/widgets/chat/call/participant_no_content.dart index 80caf82..ba7188e 100644 --- a/lib/widgets/chat/call/no_content.dart +++ b/lib/widgets/chat/call/participant_no_content.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 { diff --git a/lib/widgets/chat/channel_action.dart b/lib/widgets/chat/channel_action.dart index d0846cc..fd6e6e2 100644 --- a/lib/widgets/chat/channel_action.dart +++ b/lib/widgets/chat/channel_action.dart @@ -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(); } diff --git a/lib/widgets/chat/maintainer.dart b/lib/widgets/chat/chat_maintainer.dart similarity index 100% rename from lib/widgets/chat/maintainer.dart rename to lib/widgets/chat/chat_maintainer.dart diff --git a/lib/widgets/chat/chat_new.dart b/lib/widgets/chat/chat_new.dart index 3df1562..e1d4bb5 100644 --- a/lib/widgets/chat/chat_new.dart +++ b/lib/widgets/chat/chat_new.dart @@ -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)) { diff --git a/lib/widgets/chat/message.dart b/lib/widgets/chat/message.dart index e4f2e89..6f295b2 100644 --- a/lib/widgets/chat/message.dart +++ b/lib/widgets/chat/message.dart @@ -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; diff --git a/lib/widgets/chat/content.dart b/lib/widgets/chat/message_content.dart similarity index 100% rename from lib/widgets/chat/content.dart rename to lib/widgets/chat/message_content.dart diff --git a/lib/widgets/empty.dart b/lib/widgets/empty.dart index 668648e..2e265fa 100644 --- a/lib/widgets/empty.dart +++ b/lib/widgets/empty.dart @@ -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), + ), + ], + ), ), ); } diff --git a/lib/widgets/indent_wrapper.dart b/lib/widgets/indent_wrapper.dart deleted file mode 100644 index bd2de73..0000000 --- a/lib/widgets/indent_wrapper.dart +++ /dev/null @@ -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), - ); - } -} diff --git a/lib/widgets/layouts/two_column.dart b/lib/widgets/layouts/two_column.dart new file mode 100644 index 0000000..d83e642 --- /dev/null +++ b/lib/widgets/layouts/two_column.dart @@ -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()), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/navigation_drawer.dart b/lib/widgets/navigation_drawer.dart index 79d7a0e..1a856ac 100644 --- a/lib/widgets/navigation_drawer.dart +++ b/lib/widgets/navigation_drawer.dart @@ -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 { void _onSelect(String name, int idx) { setState(() => _selectedIndex = idx); context.read().selectedIndex = idx; - router.goNamed(name); + SolianRouter.router.goNamed(name); } @override @@ -59,6 +60,9 @@ class _SolianNavigationDrawerState extends State { 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); diff --git a/lib/widgets/notification_notifier.dart b/lib/widgets/notification_notifier.dart index 0fab09f..6f33452 100644 --- a/lib/widgets/notification_notifier.dart +++ b/lib/widgets/notification_notifier.dart @@ -85,7 +85,7 @@ class _NotificationButtonState extends State { child: IconButton( icon: const Icon(Icons.notifications), onPressed: () { - router.pushNamed('notification'); + SolianRouter.router.pushNamed('notification'); }, ), ); diff --git a/lib/widgets/posts/comment_list.dart b/lib/widgets/posts/comment_list.dart index 2c5cb53..9c81ae0 100644 --- a/lib/widgets/posts/comment_list.dart +++ b/lib/widgets/posts/comment_list.dart @@ -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), ); diff --git a/lib/widgets/posts/item.dart b/lib/widgets/posts/post.dart similarity index 98% rename from lib/widgets/posts/item.dart rename to lib/widgets/posts/post.dart index 5ea114a..b0797dc 100644 --- a/lib/widgets/posts/item.dart +++ b/lib/widgets/posts/post.dart @@ -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; diff --git a/lib/widgets/posts/item_action.dart b/lib/widgets/posts/post_action.dart similarity index 94% rename from lib/widgets/posts/item_action.dart rename to lib/widgets/posts/post_action.dart index 2bb58b7..386c095 100644 --- a/lib/widgets/posts/item_action.dart +++ b/lib/widgets/posts/post_action.dart @@ -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; diff --git a/lib/widgets/posts/item_deletion.dart b/lib/widgets/posts/post_deletion.dart similarity index 100% rename from lib/widgets/posts/item_deletion.dart rename to lib/widgets/posts/post_deletion.dart diff --git a/lib/widgets/common_wrapper.dart b/lib/widgets/scaffold.dart similarity index 58% rename from lib/widgets/common_wrapper.dart rename to lib/widgets/scaffold.dart index b42ac0c..ed6a224 100644 --- a/lib/widgets/common_wrapper.dart +++ b/lib/widgets/scaffold.dart @@ -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? 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), ); } diff --git a/lib/widgets/signin_required.dart b/lib/widgets/signin_required.dart index bda890e..d133c56 100644 --- a/lib/widgets/signin_required.dart +++ b/lib/widgets/signin_required.dart @@ -32,7 +32,7 @@ class SignInRequiredScreen extends StatelessWidget { ), ), onTap: () { - router.goNamed('account'); + SolianRouter.router.goNamed('account'); }, ); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d41d77e..257ee1c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -14,6 +15,9 @@ #include 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); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 6ad5e50..36c0481 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop file_selector_linux flutter_secure_storage_linux flutter_webrtc diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f36e9b8..e6c4e58 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) diff --git a/pubspec.lock b/pubspec.lock index f81f119..1245e47 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 20df4d0..87bca59 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 53e4f6c..74ac34a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -20,6 +21,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + DesktopDropPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopDropPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index c88aaf1..2ca7e64 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus + desktop_drop file_selector_windows flutter_secure_storage_windows flutter_webrtc