🎨 Optimized code structure, rename a lot of widgets

💄 Better large screen support in chat
This commit is contained in:
LittleSheep 2024-05-03 13:39:52 +08:00
parent e080f49935
commit c0680a3134
48 changed files with 329 additions and 317 deletions

View File

@ -34,7 +34,7 @@ class SolianApp extends StatelessWidget {
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
routerConfig: router, routerConfig: SolianRouter.router,
builder: (context, child) { builder: (context, child) {
return MultiProvider( return MultiProvider(
providers: [ providers: [

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:solian/models/call.dart'; import 'package:solian/models/call.dart';
import 'package:solian/models/channel.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/auth/signup.dart';
import 'package:solian/screens/chat/call.dart'; import 'package:solian/screens/chat/call.dart';
import 'package:solian/screens/chat/chat.dart'; import 'package:solian/screens/chat/chat.dart';
import 'package:solian/screens/chat/index.dart'; import 'package:solian/screens/chat/chat_list.dart';
import 'package:solian/screens/chat/manage.dart'; import 'package:solian/screens/chat/chat_detail.dart';
import 'package:solian/screens/chat/channel/editor.dart'; import 'package:solian/screens/chat/channel/channel_editor.dart';
import 'package:solian/screens/chat/channel/member.dart'; import 'package:solian/screens/chat/channel/channel_member.dart';
import 'package:solian/screens/explore.dart'; import 'package:solian/screens/explore.dart';
import 'package:solian/screens/notification.dart'; import 'package:solian/screens/notification.dart';
import 'package:solian/screens/posts/comment_editor.dart'; import 'package:solian/screens/posts/comment_editor.dart';
import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/moment_editor.dart';
import 'package:solian/screens/posts/screen.dart'; import 'package:solian/screens/posts/screen.dart';
import 'package:solian/screens/auth/signin.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( abstract class SolianRouter {
routes: [ static final router = GoRouter(
GoRoute( routes: [
path: '/', GoRoute(
name: 'explore', path: '/',
builder: (context, state) => const ExploreScreen(), 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,
), ),
), GoRoute(
GoRoute( path: '/notification',
path: '/auth/sign-in', name: 'notification',
name: 'auth.sign-in', builder: (context, state) => const NotificationScreen(),
builder: (context, state) => SignInScreen(), ),
), ShellRoute(
GoRoute( pageBuilder: (context, state, child) => defaultPageBuilder(
path: '/auth/sign-up', context,
name: 'auth.sign-up', state,
builder: (context, state) => SignUpScreen(), SolianTheme.isLargeScreen(context)
), ? TwoColumnLayout(
GoRoute( sideChild: const ChatListScreen(),
path: '/account/friend', mainChild: child,
name: 'account.friend', )
builder: (context, state) => const FriendScreen(), : child,
), ),
GoRoute( routes: [
path: '/account/personalize', GoRoute(
name: 'account.personalize', path: '/chat',
builder: (context, state) => const PersonalizeScreen(), 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,
);
}

View File

@ -4,10 +4,10 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/friend.dart';
import 'package:solian/screens/account/personalize.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:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/empty.dart'; import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/scaffold.dart';
class AccountScreen extends StatefulWidget { class AccountScreen extends StatefulWidget {
const AccountScreen({super.key}); const AccountScreen({super.key});
@ -32,11 +32,11 @@ class _AccountScreenState extends State<AccountScreen> {
case 'account.personalize': case 'account.personalize':
return const PersonalizeScreenWidget(); return const PersonalizeScreenWidget();
default: default:
return const SelectionEmptyWidget(); return const PageEmptyWidget();
} }
} }
return IndentWrapper( return IndentScaffold(
title: _title ?? AppLocalizations.of(context)!.account, title: _title ?? AppLocalizations.of(context)!.account,
noSafeArea: true, noSafeArea: true,
fixedAppBarColor: true, fixedAppBarColor: true,
@ -60,7 +60,7 @@ class _AccountScreenState extends State<AccountScreen> {
) )
: AccountScreenWidget( : AccountScreenWidget(
onSelect: (item, _) { onSelect: (item, _) {
router.pushNamed(item); SolianRouter.router.pushNamed(item);
}, },
), ),
); );
@ -139,7 +139,7 @@ class _AccountScreenWidgetState extends State<AccountScreenWidget> {
title: AppLocalizations.of(context)!.signIn, title: AppLocalizations.of(context)!.signIn,
caption: AppLocalizations.of(context)!.signInCaption, caption: AppLocalizations.of(context)!.signInCaption,
onTap: () { onTap: () {
router.pushNamed('auth.sign-in').then((did) { SolianRouter.router.pushNamed('auth.sign-in').then((did) {
auth.isAuthorized().then((value) { auth.isAuthorized().then((value) {
setState(() => _isAuthorized = value); setState(() => _isAuthorized = value);
}); });
@ -151,7 +151,7 @@ class _AccountScreenWidgetState extends State<AccountScreenWidget> {
title: AppLocalizations.of(context)!.signUp, title: AppLocalizations.of(context)!.signUp,
caption: AppLocalizations.of(context)!.signUpCaption, caption: AppLocalizations.of(context)!.signUpCaption,
onTap: () { onTap: () {
router.pushNamed('auth.sign-up'); SolianRouter.router.pushNamed('auth.sign-up');
}, },
), ),
], ],

View File

@ -6,9 +6,9 @@ import 'package:provider/provider.dart';
import 'package:solian/models/friendship.dart'; import 'package:solian/models/friendship.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.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/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:flutter_gen/gen_l10n/app_localizations.dart';
class FriendScreen extends StatelessWidget { class FriendScreen extends StatelessWidget {
@ -16,7 +16,7 @@ class FriendScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.friend, title: AppLocalizations.of(context)!.friend,
noSafeArea: true, noSafeArea: true,
hideDrawer: true, hideDrawer: true,

View File

@ -7,7 +7,7 @@ import 'package:provider/provider.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.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:flutter_gen/gen_l10n/app_localizations.dart';
class PersonalizeScreen extends StatelessWidget { class PersonalizeScreen extends StatelessWidget {
@ -15,7 +15,7 @@ class PersonalizeScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.personalize, title: AppLocalizations.of(context)!.personalize,
hideDrawer: true, hideDrawer: true,
child: const PersonalizeScreenWidget(), child: const PersonalizeScreenWidget(),

View File

@ -5,7 +5,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.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'; import 'package:url_launcher/url_launcher_string.dart';
class SignInScreen extends StatelessWidget { class SignInScreen extends StatelessWidget {
@ -21,7 +21,7 @@ class SignInScreen extends StatelessWidget {
final password = _passwordController.value.text; final password = _passwordController.value.text;
if (username.isEmpty || password.isEmpty) return; if (username.isEmpty || password.isEmpty) return;
auth.signin(context, username, password).then((_) { auth.signin(context, username, password).then((_) {
router.pop(true); SolianRouter.router.pop(true);
}).catchError((e) { }).catchError((e) {
List<String> messages = e.toString().split('\n'); List<String> messages = e.toString().split('\n');
if (messages.last.contains('risk')) { if (messages.last.contains('risk')) {
@ -61,7 +61,7 @@ class SignInScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.signIn, title: AppLocalizations.of(context)!.signIn,
hideDrawer: true, hideDrawer: true,
child: Center( child: Center(

View File

@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.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; import 'package:http/http.dart' as http;
class SignUpScreen extends StatelessWidget { class SignUpScreen extends StatelessWidget {
@ -58,7 +58,7 @@ class SignUpScreen extends StatelessWidget {
); );
}, },
).then((_) { ).then((_) {
router.replaceNamed('auth.sign-in'); SolianRouter.router.replaceNamed('auth.sign-in');
}); });
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
@ -68,7 +68,7 @@ class SignUpScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.signUp, title: AppLocalizations.of(context)!.signUp,
hideDrawer: true, hideDrawer: true,
child: Center( child: Center(

View File

@ -3,10 +3,11 @@ import 'package:livekit_client/livekit_client.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:solian/models/call.dart'; import 'package:solian/models/call.dart';
import 'package:solian/providers/chat.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.dart';
import 'package:solian/widgets/chat/call/participant_menu.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 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'dart:math' as math; import 'dart:math' as math;
@ -127,8 +128,9 @@ class _ChatCallState extends State<ChatCall> {
); );
} }
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.chatCall, title: AppLocalizations.of(context)!.chatCall,
fixedAppBarColor: SolianTheme.isLargeScreen(context),
hideDrawer: true, hideDrawer: true,
child: content, child: content,
); );

View File

@ -9,7 +9,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.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:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -55,8 +55,8 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
context.showErrorDialog(message); context.showErrorDialog(message);
} else { } else {
if (router.canPop()) { if (SolianRouter.router.canPop()) {
router.pop(true); SolianRouter.router.pop(true);
} }
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
@ -67,8 +67,8 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
} }
void cancelEditing() { void cancelEditing() {
if (router.canPop()) { if (SolianRouter.router.canPop()) {
router.pop(false); SolianRouter.router.pop(false);
} }
} }
@ -99,7 +99,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
], ],
); );
return IndentWrapper( return IndentScaffold(
hideDrawer: true, hideDrawer: true,
title: AppLocalizations.of(context)!.chatChannelOrganize, title: AppLocalizations.of(context)!.chatChannelOrganize,
appBarActions: <Widget>[ appBarActions: <Widget>[

View File

@ -7,10 +7,10 @@ import 'package:solian/models/account.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.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/account/friend_picker.dart';
import 'package:solian/widgets/exts.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:flutter_gen/gen_l10n/app_localizations.dart';
class ChatMemberScreen extends StatefulWidget { class ChatMemberScreen extends StatefulWidget {
@ -141,7 +141,7 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.chatMember, title: AppLocalizations.of(context)!.chatMember,
noSafeArea: true, noSafeArea: true,
hideDrawer: true, hideDrawer: true,

View File

@ -10,12 +10,13 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/providers/chat.dart'; import 'package:solian/providers/chat.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.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/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.dart';
import 'package:solian/widgets/chat/message_action.dart'; import 'package:solian/widgets/chat/message_action.dart';
import 'package:solian/widgets/chat/message_editor.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'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ChatScreen extends StatelessWidget { class ChatScreen extends StatelessWidget {
@ -27,9 +28,11 @@ class ChatScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final chat = context.watch<ChatProvider>(); final chat = context.watch<ChatProvider>();
return IndentWrapper( return IndentScaffold(
title: chat.focusChannel?.name ?? 'Loading...', title: chat.focusChannel?.name ?? 'Loading...',
hideDrawer: true, hideDrawer: true,
fixedAppBarColor: SolianTheme.isLargeScreen(context),
appBarLeading: IconButton(icon: const Icon(Icons.tag), onPressed: () {}),
appBarActions: chat.focusChannel != null appBarActions: chat.focusChannel != null
? [ ? [
ChannelCallAction( ChannelCallAction(
@ -197,7 +200,7 @@ class _ChatScreenWidgetState extends State<ChatScreenWidget> {
TextButton( TextButton(
child: Text(AppLocalizations.of(context)!.chatCallJoin), child: Text(AppLocalizations.of(context)!.chatCallJoin),
onPressed: () { onPressed: () {
router.pushNamed( SolianRouter.router.pushNamed(
'chat.channel.call', 'chat.channel.call',
extra: _chat.ongoingCall, extra: _chat.ongoingCall,
pathParameters: {'channel': widget.alias}, pathParameters: {'channel': widget.alias},

View File

@ -4,19 +4,19 @@ import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/widgets/chat/channel_deletion.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'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ChatManageScreen extends StatefulWidget { class ChatDetailScreen extends StatefulWidget {
final Channel channel; final Channel channel;
const ChatManageScreen({super.key, required this.channel}); const ChatDetailScreen({super.key, required this.channel});
@override @override
State<ChatManageScreen> createState() => _ChatManageScreenState(); State<ChatDetailScreen> createState() => _ChatDetailScreenState();
} }
class _ChatManageScreenState extends State<ChatManageScreen> { class _ChatDetailScreenState extends State<ChatDetailScreen> {
bool _isOwned = false; bool _isOwned = false;
void promptLeaveChannel() async { void promptLeaveChannel() async {
@ -27,8 +27,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
isOwned: _isOwned, isOwned: _isOwned,
), ),
); );
if (did == true && router.canPop()) { if (did == true && SolianRouter.router.canPop()) {
router.pop('disposed'); SolianRouter.router.pop('disposed');
} }
} }
@ -53,16 +53,16 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
leading: const Icon(Icons.settings), leading: const Icon(Icons.settings),
title: Text(AppLocalizations.of(context)!.settings), title: Text(AppLocalizations.of(context)!.settings),
onTap: () async { 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 (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, title: AppLocalizations.of(context)!.chatManage,
hideDrawer: true, hideDrawer: true,
noSafeArea: true, noSafeArea: true,
@ -100,7 +100,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
leading: const Icon(Icons.supervisor_account), leading: const Icon(Icons.supervisor_account),
title: Text(AppLocalizations.of(context)!.chatMember), title: Text(AppLocalizations.of(context)!.chatMember),
onTap: () { onTap: () {
router.pushNamed( SolianRouter.router.pushNamed(
'chat.channel.member', 'chat.channel.member',
extra: widget.channel, extra: widget.channel,
pathParameters: {'channel': widget.channel.alias}, pathParameters: {'channel': widget.channel.alias},

View File

@ -4,90 +4,47 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/chat.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/chat/chat.dart';
import 'package:solian/utils/service_url.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/chat/chat_new.dart';
import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/exts.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:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/notification_notifier.dart'; import 'package:solian/widgets/notification_notifier.dart';
import 'package:solian/widgets/signin_required.dart'; import 'package:solian/widgets/signin_required.dart';
class ChatIndexScreen extends StatefulWidget { class ChatListScreen extends StatelessWidget {
const ChatIndexScreen({super.key}); const ChatListScreen({super.key});
@override
State<ChatIndexScreen> createState() => _ChatIndexScreenState();
}
class _ChatIndexScreenState extends State<ChatIndexScreen> {
Channel? _selectedChannel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final chat = context.watch<ChatProvider>(); return IndentScaffold(
final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600;
return IndentWrapper(
title: AppLocalizations.of(context)!.chat, title: AppLocalizations.of(context)!.chat,
appBarActions: isLargeScreen && chat.focusChannel != null appBarActions: const [NotificationButton()],
? [ fixedAppBarColor: SolianTheme.isLargeScreen(context),
ChannelCallAction( child: ChatListWidget(
call: chat.ongoingCall, onSelect: (item) {
channel: chat.focusChannel!, SolianRouter.router.pushReplacementNamed(
onUpdate: () => chat.fetchChannel(chat.focusChannel!.alias), 'chat.channel',
), pathParameters: {'channel': item.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(),
); );
} }
} }
class ChatIndexScreenWidget extends StatefulWidget { class ChatListWidget extends StatefulWidget {
final Function(Channel item)? onSelect; final Function(Channel item)? onSelect;
const ChatIndexScreenWidget({super.key, this.onSelect}); const ChatListWidget({super.key, this.onSelect});
@override @override
State<ChatIndexScreenWidget> createState() => _ChatIndexScreenWidgetState(); State<ChatListWidget> createState() => _ChatListWidgetState();
} }
class _ChatIndexScreenWidgetState extends State<ChatIndexScreenWidget> { class _ChatListWidgetState extends State<ChatListWidget> {
List<Channel> _channels = List.empty(); List<Channel> _channels = List.empty();
Future<void> fetchChannels() async { Future<void> fetchChannels() async {
@ -169,7 +126,7 @@ class _ChatIndexScreenWidgetState extends State<ChatIndexScreenWidget> {
return; return;
} }
final result = await router.pushNamed( final result = await SolianRouter.router.pushNamed(
'chat.channel', 'chat.channel',
pathParameters: { pathParameters: {
'channel': element.alias, 'channel': element.alias,

View File

@ -12,9 +12,9 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:solian/widgets/empty.dart'; 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/notification_notifier.dart';
import 'package:solian/widgets/posts/item.dart'; import 'package:solian/widgets/posts/post.dart';
class ExploreScreen extends StatefulWidget { class ExploreScreen extends StatefulWidget {
const ExploreScreen({super.key}); const ExploreScreen({super.key});
@ -31,7 +31,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600; final isLargeScreen = screenWidth >= 600;
return IndentWrapper( return IndentScaffold(
noSafeArea: true, noSafeArea: true,
fixedAppBarColor: isLargeScreen, fixedAppBarColor: isLargeScreen,
appBarActions: const [NotificationButton()], appBarActions: const [NotificationButton()],
@ -51,7 +51,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
Flexible( Flexible(
flex: 4, flex: 4,
child: _selectedPost == null child: _selectedPost == null
? const SelectionEmptyWidget() ? const PageEmptyWidget()
: PostScreenWidget( : PostScreenWidget(
key: Key('p${_selectedPost!.id}'), key: Key('p${_selectedPost!.id}'),
dataset: _selectedPost!.dataset, dataset: _selectedPost!.dataset,
@ -62,7 +62,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
) )
: ExploreScreenWidget( : ExploreScreenWidget(
onSelect: (item) { onSelect: (item) {
router.pushNamed( SolianRouter.router.pushNamed(
'posts.screen', 'posts.screen',
pathParameters: { pathParameters: {
'alias': item.alias, 'alias': item.alias,
@ -130,7 +130,7 @@ class _ExploreScreenWidgetState extends State<ExploreScreenWidget> {
return FloatingActionButton( return FloatingActionButton(
child: const Icon(Icons.edit), child: const Icon(Icons.edit),
onPressed: () async { onPressed: () async {
final did = await router.pushNamed('posts.moments.editor'); final did = await SolianRouter.router.pushNamed('posts.moments.editor');
if (did == true) _pagingController.refresh(); if (did == true) _pagingController.refresh();
}, },
); );

View File

@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/notify.dart'; import 'package:solian/providers/notify.dart';
import 'package:solian/utils/service_url.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:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:solian/models/notification.dart' as model; import 'package:solian/models/notification.dart' as model;
@ -25,7 +25,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
nty.allRead(); nty.allRead();
}); });
return IndentWrapper( return IndentScaffold(
noSafeArea: true, noSafeArea: true,
hideDrawer: true, hideDrawer: true,
title: AppLocalizations.of(context)!.notification, title: AppLocalizations.of(context)!.notification,
@ -82,10 +82,10 @@ class NotificationItem extends StatelessWidget {
const NotificationItem( const NotificationItem(
{super.key, required this.index, required this.item, this.onDismiss}); {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) { void showLinks(BuildContext context) {
if (!hasLinks()) return; if (!hasLinks) return;
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: context, context: context,
@ -170,7 +170,7 @@ class NotificationItem extends StatelessWidget {
child: ListTile( child: ListTile(
title: Text(item.subject), title: Text(item.subject),
subtitle: Text(item.content), subtitle: Text(item.content),
trailing: hasLinks() trailing: hasLinks
? TextButton( ? TextButton(
onPressed: () => showLinks(context), onPressed: () => showLinks(context),
style: TextButton.styleFrom(shape: const CircleBorder()), style: TextButton.styleFrom(shape: const CircleBorder()),

View File

@ -9,9 +9,9 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/scaffold.dart';
import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:solian/widgets/posts/attachment_editor.dart';
class CommentPostArguments { class CommentPostArguments {
@ -78,16 +78,16 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
context.showErrorDialog(message); context.showErrorDialog(message);
} else { } else {
if (router.canPop()) { if (SolianRouter.router.canPop()) {
router.pop(true); SolianRouter.router.pop(true);
} }
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} }
void cancelEditing() { void cancelEditing() {
if (router.canPop()) { if (SolianRouter.router.canPop()) {
router.pop(false); SolianRouter.router.pop(false);
} }
} }
@ -120,7 +120,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
], ],
); );
return IndentWrapper( return IndentScaffold(
hideDrawer: true, hideDrawer: true,
title: AppLocalizations.of(context)!.newComment, title: AppLocalizations.of(context)!.newComment,
appBarActions: <Widget>[ appBarActions: <Widget>[

View File

@ -9,9 +9,9 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/scaffold.dart';
import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:solian/widgets/posts/attachment_editor.dart';
class MomentEditorScreen extends StatefulWidget { class MomentEditorScreen extends StatefulWidget {
@ -68,16 +68,16 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
context.showErrorDialog(message); context.showErrorDialog(message);
} else { } else {
if (router.canPop()) { if (SolianRouter.router.canPop()) {
router.pop(true); SolianRouter.router.pop(true);
} }
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} }
void cancelEditing() { void cancelEditing() {
if (router.canPop()) { if (SolianRouter.router.canPop()) {
router.pop(false); SolianRouter.router.pop(false);
} }
} }
@ -110,7 +110,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
], ],
); );
return IndentWrapper( return IndentScaffold(
hideDrawer: true, hideDrawer: true,
title: AppLocalizations.of(context)!.newMoment, title: AppLocalizations.of(context)!.newMoment,
appBarActions: <Widget>[ appBarActions: <Widget>[

View File

@ -6,9 +6,9 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.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/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'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class PostScreen extends StatelessWidget { class PostScreen extends StatelessWidget {
@ -19,7 +19,7 @@ class PostScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IndentWrapper( return IndentScaffold(
title: AppLocalizations.of(context)!.post, title: AppLocalizations.of(context)!.post,
noSafeArea: true, noSafeArea: true,
hideDrawer: true, hideDrawer: true,

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
abstract class SolianTheme { abstract class SolianTheme {
static bool isColumnMode(BuildContext context) => static bool isLargeScreen(BuildContext context) =>
MediaQuery.of(context).size.width > 640; MediaQuery.of(context).size.width > 640;
static ThemeData build(Brightness brightness) { static ThemeData build(Brightness brightness) {

View File

@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/friend.dart'; import 'package:solian/providers/friend.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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 { class FriendPicker extends StatefulWidget {
const FriendPicker({super.key}); const FriendPicker({super.key});

View File

@ -76,7 +76,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
if (chat.currentCall != null) { if (chat.currentCall != null) {
chat.currentCall!.deactivate(); chat.currentCall!.deactivate();
chat.currentCall!.dispose(); chat.currentCall!.dispose();
router.pop(); SolianRouter.router.pop();
} }
} }

View File

@ -52,7 +52,7 @@ class CallOverlay extends StatelessWidget {
), ),
), ),
onTap: () { onTap: () {
router.pushNamed( SolianRouter.router.pushNamed(
'chat.channel.call', 'chat.channel.call',
extra: chat.currentCall!.info, extra: chat.currentCall!.info,
pathParameters: {'channel': chat.currentCall!.channel.alias}, pathParameters: {'channel': chat.currentCall!.channel.alias},

View File

@ -5,7 +5,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/livekit_client.dart'; import 'package:livekit_client/livekit_client.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
import 'package:solian/models/call.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_info.dart';
import 'package:solian/widgets/chat/call/participant_stats.dart'; import 'package:solian/widgets/chat/call/participant_stats.dart';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:solian/models/account.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; import 'dart:math' as math;
class NoContentWidget extends StatefulWidget { class NoContentWidget extends StatefulWidget {

View File

@ -97,14 +97,14 @@ class ChannelManageAction extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IconButton( return IconButton(
onPressed: () async { onPressed: () async {
final result = await router.pushNamed( final result = await SolianRouter.router.pushNamed(
'chat.channel.manage', 'chat.channel.manage',
extra: channel, extra: channel,
pathParameters: {'channel': channel.alias}, pathParameters: {'channel': channel.alias},
); );
switch (result) { switch (result) {
case 'disposed': case 'disposed':
if (router.canPop()) router.pop('refresh'); if (SolianRouter.router.canPop()) SolianRouter.router.pop('refresh');
case 'refresh': case 'refresh':
onUpdate(); onUpdate();
} }

View File

@ -29,7 +29,7 @@ class ChatNewAction extends StatelessWidget {
leading: const Icon(Icons.add), leading: const Icon(Icons.add),
title: Text(AppLocalizations.of(context)!.chatNewCreate), title: Text(AppLocalizations.of(context)!.chatNewCreate),
onTap: () { onTap: () {
router.pushNamed('chat.channel.editor').then((did) { SolianRouter.router.pushNamed('chat.channel.editor').then((did) {
if (did == true) { if (did == true) {
onUpdate(); onUpdate();
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/models/message.dart'; import 'package:solian/models/message.dart';
import 'package:solian/widgets/account/avatar.dart'; import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/chat/content.dart'; import 'package:solian/widgets/chat/message_content.dart';
import 'package:solian/widgets/posts/content/attachment.dart'; import 'package:solian/widgets/posts/content/attachment.dart';
import 'package:timeago/timeago.dart' as timeago; import 'package:timeago/timeago.dart' as timeago;
import 'dart:math' as math; import 'dart:math' as math;

View File

@ -1,22 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class SelectionEmptyWidget extends StatelessWidget { class PageEmptyWidget extends StatelessWidget {
const SelectionEmptyWidget({super.key}); const PageEmptyWidget({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return Material(
child: Column( child: Center(
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
Image.asset('assets/logo.png', width: 64, height: 64), children: [
const SizedBox(height: 2), Image.asset('assets/logo.png', width: 64, height: 64),
Text( const SizedBox(height: 2),
AppLocalizations.of(context)!.appName, Text(
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900), AppLocalizations.of(context)!.appName,
), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900),
], ),
],
),
), ),
); );
} }

View File

@ -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),
);
}
}

View 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()),
],
),
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:solian/providers/navigation.dart'; import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/utils/theme.dart';
class SolianNavigationDrawer extends StatefulWidget { class SolianNavigationDrawer extends StatefulWidget {
const SolianNavigationDrawer({super.key}); const SolianNavigationDrawer({super.key});
@ -17,7 +18,7 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> {
void _onSelect(String name, int idx) { void _onSelect(String name, int idx) {
setState(() => _selectedIndex = idx); setState(() => _selectedIndex = idx);
context.read<NavigationProvider>().selectedIndex = idx; context.read<NavigationProvider>().selectedIndex = idx;
router.goNamed(name); SolianRouter.router.goNamed(name);
} }
@override @override
@ -59,6 +60,9 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> {
return NavigationDrawer( return NavigationDrawer(
selectedIndex: _selectedIndex, 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) { onDestinationSelected: (int idx) {
final element = navigationItems[idx]; final element = navigationItems[idx];
_onSelect(element.$2, idx); _onSelect(element.$2, idx);

View File

@ -85,7 +85,7 @@ class _NotificationButtonState extends State<NotificationButton> {
child: IconButton( child: IconButton(
icon: const Icon(Icons.notifications), icon: const Icon(Icons.notifications),
onPressed: () { onPressed: () {
router.pushNamed('notification'); SolianRouter.router.pushNamed('notification');
}, },
), ),
); );

View File

@ -10,7 +10,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/posts/comment_editor.dart'; import 'package:solian/screens/posts/comment_editor.dart';
import 'package:solian/utils/service_url.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'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class CommentList extends StatefulWidget { class CommentList extends StatefulWidget {
@ -120,7 +120,7 @@ class CommentListHeader extends StatelessWidget {
if (snapshot.hasData && snapshot.data == true) { if (snapshot.hasData && snapshot.data == true) {
return TextButton( return TextButton(
onPressed: () async { onPressed: () async {
final did = await router.pushNamed( final did = await SolianRouter.router.pushNamed(
'posts.comments.editor', 'posts.comments.editor',
extra: CommentPostArguments(related: related), extra: CommentPostArguments(related: related),
); );

View File

@ -1,12 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/post.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/comment_list.dart';
import 'package:solian/widgets/posts/content/article.dart'; import 'package:solian/widgets/posts/content/article.dart';
import 'package:solian/widgets/posts/content/attachment.dart'; import 'package:solian/widgets/posts/content/attachment.dart';
import 'package:solian/widgets/posts/content/moment.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:solian/widgets/posts/reaction_list.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:timeago/timeago.dart' as timeago; import 'package:timeago/timeago.dart' as timeago;

View File

@ -5,7 +5,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/screens/posts/comment_editor.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 { class PostItemAction extends StatelessWidget {
final Post item; final Post item;
@ -23,17 +23,17 @@ class PostItemAction extends StatelessWidget {
bool ok = false; bool ok = false;
switch (item.modelType) { switch (item.modelType) {
case 'article': case 'article':
ok = await router.pushNamed( ok = await SolianRouter.router.pushNamed(
'posts.articles.editor', 'posts.articles.editor',
extra: item, extra: item,
) as bool; ) as bool;
case 'moment': case 'moment':
ok = await router.pushNamed( ok = await SolianRouter.router.pushNamed(
'posts.moments.editor', 'posts.moments.editor',
extra: item, extra: item,
) as bool; ) as bool;
case 'comment': case 'comment':
ok = await router.pushNamed( ok = await SolianRouter.router.pushNamed(
'posts.comments.editor', 'posts.comments.editor',
extra: CommentPostArguments(editing: item), extra: CommentPostArguments(editing: item),
) as bool; ) as bool;

View File

@ -1,19 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/utils/theme.dart';
import 'package:solian/widgets/navigation_drawer.dart'; import 'package:solian/widgets/navigation_drawer.dart';
class LayoutWrapper extends StatelessWidget { class IndentScaffold extends StatelessWidget {
final Widget? child; final Widget? child;
final Widget? floatingActionButton; final Widget? floatingActionButton;
final Widget? appBarLeading;
final List<Widget>? appBarActions; final List<Widget>? appBarActions;
final bool noSafeArea; final bool noSafeArea;
final bool hideDrawer;
final bool fixedAppBarColor;
final String title; final String title;
const LayoutWrapper({ const IndentScaffold({
super.key, super.key,
this.child, this.child,
required this.title, required this.title,
this.floatingActionButton, this.floatingActionButton,
this.appBarLeading,
this.appBarActions, this.appBarActions,
this.hideDrawer = false,
this.fixedAppBarColor = false,
this.noSafeArea = false, this.noSafeArea = false,
}); });
@ -24,11 +31,14 @@ class LayoutWrapper extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(title), title: Text(title),
leading: appBarLeading,
actions: appBarActions, actions: appBarActions,
centerTitle: false, centerTitle: false,
elevation: fixedAppBarColor ? 4 : null,
), ),
floatingActionButton: floatingActionButton, floatingActionButton: floatingActionButton,
drawer: const SolianNavigationDrawer(), drawer: !hideDrawer ? const SolianNavigationDrawer() : null,
drawerScrimColor: SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
body: noSafeArea ? content : SafeArea(child: content), body: noSafeArea ? content : SafeArea(child: content),
); );
} }

View File

@ -32,7 +32,7 @@ class SignInRequiredScreen extends StatelessWidget {
), ),
), ),
onTap: () { onTap: () {
router.goNamed('account'); SolianRouter.router.goNamed('account');
}, },
); );
} }

View File

@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h> #include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
@ -14,6 +15,9 @@
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { 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 = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_selector_linux file_selector_linux
flutter_secure_storage_linux flutter_secure_storage_linux
flutter_webrtc flutter_webrtc

View File

@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation import Foundation
import connectivity_plus import connectivity_plus
import desktop_drop
import device_info_plus import device_info_plus
import file_selector_macos import file_selector_macos
import flutter_local_notifications import flutter_local_notifications
@ -23,6 +24,7 @@ import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))

View File

@ -177,6 +177,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.10" 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: device_info_plus:
dependency: transitive dependency: transitive
description: description:

View File

@ -70,6 +70,7 @@ dependencies:
file_picker: ^8.0.3 file_picker: ^8.0.3
package_info_plus: ^7.0.0 package_info_plus: ^7.0.0
cached_network_image: ^3.3.1 cached_network_image: ^3.3.1
desktop_drop: ^0.4.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.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 <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h> #include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
@ -20,6 +21,8 @@
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar( ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(

View File

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus connectivity_plus
desktop_drop
file_selector_windows file_selector_windows
flutter_secure_storage_windows flutter_secure_storage_windows
flutter_webrtc flutter_webrtc