Compare commits

..

No commits in common. "e742338d920e9de2ec7122313ab79ff05b9c7d21" and "c0680a3134a5529e38444ec4b8da7f684c5b7ba2" have entirely different histories.

9 changed files with 183 additions and 137 deletions

View File

@ -47,11 +47,7 @@ class SolianApp extends StatelessWidget {
child: Overlay( child: Overlay(
initialEntries: [ initialEntries: [
OverlayEntry(builder: (context) { OverlayEntry(builder: (context) {
return ScaffoldMessenger( return NotificationNotifier(child: child ?? Container());
child: Scaffold(
body: NotificationNotifier(child: child ?? Container()),
),
);
}), }),
OverlayEntry(builder: (context) => const CallOverlay()), OverlayEntry(builder: (context) => const CallOverlay()),
], ],

View File

@ -26,52 +26,16 @@ import 'package:solian/widgets/layouts/two_column.dart';
abstract class SolianRouter { abstract class SolianRouter {
static final router = GoRouter( static final router = GoRouter(
routes: [ routes: [
GoRoute(
path: '/',
name: 'explore',
builder: (context, state) => const ExploreScreen(),
),
GoRoute( GoRoute(
path: '/notification', path: '/notification',
name: 'notification', name: 'notification',
builder: (context, state) => const NotificationScreen(), builder: (context, state) => const NotificationScreen(),
), ),
ShellRoute(
pageBuilder: (context, state, child) => defaultPageBuilder(
context,
state,
SolianTheme.isLargeScreen(context)
? TwoColumnLayout(
sideChild: const ExplorePostScreen(),
mainChild: child,
)
: child,
),
routes: [
GoRoute(
path: '/',
name: 'explore',
builder: (context, state) =>
!SolianTheme.isLargeScreen(context) ? const ExplorePostScreen() : const PageEmptyWidget(),
),
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,
),
),
],
),
ShellRoute( ShellRoute(
pageBuilder: (context, state, child) => defaultPageBuilder( pageBuilder: (context, state, child) => defaultPageBuilder(
context, context,
@ -117,50 +81,55 @@ abstract class SolianRouter {
), ),
], ],
), ),
ShellRoute( GoRoute(
pageBuilder: (context, state, child) => defaultPageBuilder( path: '/account',
context, name: 'account',
state, builder: (context, state) => const AccountScreen(),
SolianTheme.isLargeScreen(context) ),
? TwoColumnLayout( GoRoute(
sideChild: const AccountScreen(), path: '/posts/publish/moments',
mainChild: child, name: 'posts.moments.editor',
) builder: (context, state) => MomentEditorScreen(editing: state.extra as Post?),
: child, ),
), GoRoute(
routes: [ path: '/posts/publish/comments',
GoRoute( name: 'posts.comments.editor',
path: '/account', builder: (context, state) {
name: 'account', final args = state.extra as CommentPostArguments;
builder: (context, state) => return CommentEditorScreen(editing: args.editing, related: args.related);
!SolianTheme.isLargeScreen(context) ? const AccountScreen() : const PageEmptyWidget(), },
), ),
GoRoute( GoRoute(
path: '/auth/sign-in', path: '/posts/:dataset/:alias',
name: 'auth.sign-in', name: 'posts.screen',
builder: (context, state) => SignInScreen(), builder: (context, state) => PostScreen(
), alias: state.pathParameters['alias'] as String,
GoRoute( dataset: state.pathParameters['dataset'] as String,
path: '/auth/sign-up', ),
name: 'auth.sign-up', ),
builder: (context, state) => SignUpScreen(), GoRoute(
), path: '/auth/sign-in',
GoRoute( name: 'auth.sign-in',
path: '/account/friend', builder: (context, state) => SignInScreen(),
name: 'account.friend', ),
builder: (context, state) => const FriendScreen(), GoRoute(
), path: '/auth/sign-up',
GoRoute( name: 'auth.sign-up',
path: '/account/personalize', builder: (context, state) => SignUpScreen(),
name: 'account.personalize', ),
builder: (context, state) => const PersonalizeScreen(), 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 GoRoute get currentRoute => SolianRouter.router.routerDelegate.currentConfiguration.last.route;
static Page defaultPageBuilder( static Page defaultPageBuilder(
BuildContext context, BuildContext context,
GoRouterState state, GoRouterState state,

View File

@ -2,30 +2,73 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.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/screens/account/friend.dart';
import 'package:solian/screens/account/personalize.dart';
import 'package:solian/widgets/account/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/scaffold.dart'; import 'package:solian/widgets/scaffold.dart';
class AccountScreen extends StatelessWidget { class AccountScreen extends StatefulWidget {
const AccountScreen({super.key}); const AccountScreen({super.key});
@override
State<AccountScreen> createState() => _AccountScreenState();
}
class _AccountScreenState extends State<AccountScreen> {
String? _title;
String? _selectedTab;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600;
Widget renderContent() {
switch (_selectedTab) {
case 'account.friend':
return const FriendScreenWidget();
case 'account.personalize':
return const PersonalizeScreenWidget();
default:
return const PageEmptyWidget();
}
}
return IndentScaffold( return IndentScaffold(
title: AppLocalizations.of(context)!.account, title: _title ?? AppLocalizations.of(context)!.account,
noSafeArea: true, noSafeArea: true,
fixedAppBarColor: true, fixedAppBarColor: true,
child: AccountScreenWidget( child: isLargeScreen
onSelect: (item) { ? Row(
SolianRouter.router.pushNamed(item); children: [
}, Flexible(
), flex: 2,
child: AccountScreenWidget(
onSelect: (item, title) {
setState(() {
_selectedTab = item;
_title = title;
});
},
),
),
const VerticalDivider(thickness: 0.3, width: 0.3),
Flexible(flex: 4, child: renderContent()),
],
)
: AccountScreenWidget(
onSelect: (item, _) {
SolianRouter.router.pushNamed(item);
},
),
); );
} }
} }
class AccountScreenWidget extends StatefulWidget { class AccountScreenWidget extends StatefulWidget {
final Function(String item) onSelect; final Function(String item, String title) onSelect;
const AccountScreenWidget({super.key, required this.onSelect}); const AccountScreenWidget({super.key, required this.onSelect});
@ -62,7 +105,7 @@ class _AccountScreenWidgetState extends State<AccountScreenWidget> {
leading: const Icon(Icons.color_lens), leading: const Icon(Icons.color_lens),
title: Text(AppLocalizations.of(context)!.personalize), title: Text(AppLocalizations.of(context)!.personalize),
onTap: () { onTap: () {
widget.onSelect('account.personalize'); widget.onSelect('account.personalize', AppLocalizations.of(context)!.personalize);
}, },
), ),
ListTile( ListTile(
@ -70,7 +113,7 @@ class _AccountScreenWidgetState extends State<AccountScreenWidget> {
leading: const Icon(Icons.diversity_1), leading: const Icon(Icons.diversity_1),
title: Text(AppLocalizations.of(context)!.friend), title: Text(AppLocalizations.of(context)!.friend),
onTap: () { onTap: () {
widget.onSelect('account.friend'); widget.onSelect('account.friend', AppLocalizations.of(context)!.friend);
}, },
), ),
ListTile( ListTile(

View File

@ -32,6 +32,7 @@ class ChatScreen extends StatelessWidget {
title: chat.focusChannel?.name ?? 'Loading...', title: chat.focusChannel?.name ?? 'Loading...',
hideDrawer: true, hideDrawer: true,
fixedAppBarColor: SolianTheme.isLargeScreen(context), fixedAppBarColor: SolianTheme.isLargeScreen(context),
appBarLeading: IconButton(icon: const Icon(Icons.tag), onPressed: () {}),
appBarActions: chat.focusChannel != null appBarActions: chat.focusChannel != null
? [ ? [
ChannelCallAction( ChannelCallAction(

View File

@ -25,17 +25,10 @@ class ChatListScreen extends StatelessWidget {
fixedAppBarColor: SolianTheme.isLargeScreen(context), fixedAppBarColor: SolianTheme.isLargeScreen(context),
child: ChatListWidget( child: ChatListWidget(
onSelect: (item) { onSelect: (item) {
if (SolianRouter.currentRoute.name == 'chat.channel') { SolianRouter.router.pushReplacementNamed(
SolianRouter.router.pushReplacementNamed( 'chat.channel',
'chat.channel', pathParameters: {'channel': item.alias},
pathParameters: {'channel': item.alias}, );
);
} else {
SolianRouter.router.pushNamed(
'chat.channel',
pathParameters: {'channel': item.alias},
);
}
}, },
), ),
); );

View File

@ -6,50 +6,85 @@ import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.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/screens/posts/screen.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; 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/utils/theme.dart'; import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/scaffold.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/post.dart'; import 'package:solian/widgets/posts/post.dart';
class ExplorePostScreen extends StatelessWidget { class ExploreScreen extends StatefulWidget {
const ExplorePostScreen({super.key}); const ExploreScreen({super.key});
@override
State<ExploreScreen> createState() => _ExploreScreenState();
}
class _ExploreScreenState extends State<ExploreScreen> {
Post? _selectedPost;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600;
return IndentScaffold( return IndentScaffold(
noSafeArea: true, noSafeArea: true,
fixedAppBarColor: SolianTheme.isLargeScreen(context), fixedAppBarColor: isLargeScreen,
appBarActions: const [NotificationButton()], appBarActions: const [NotificationButton()],
title: AppLocalizations.of(context)!.explore, title: AppLocalizations.of(context)!.explore,
child: ExplorePostWidget( child: isLargeScreen
onSelect: (item) { ? Row(
SolianRouter.router.pushNamed( children: [
'posts.screen', Flexible(
pathParameters: { flex: 2,
'alias': item.alias, child: ExploreScreenWidget(
'dataset': item.dataset, onSelect: (item) {
}, setState(() => _selectedPost = item);
); },
}, ),
), ),
const VerticalDivider(thickness: 0.3, width: 0.3),
Flexible(
flex: 4,
child: _selectedPost == null
? const PageEmptyWidget()
: PostScreenWidget(
key: Key('p${_selectedPost!.id}'),
dataset: _selectedPost!.dataset,
alias: _selectedPost!.alias,
),
),
],
)
: ExploreScreenWidget(
onSelect: (item) {
SolianRouter.router.pushNamed(
'posts.screen',
pathParameters: {
'alias': item.alias,
'dataset': item.dataset,
},
);
},
),
); );
} }
} }
class ExplorePostWidget extends StatefulWidget { class ExploreScreenWidget extends StatefulWidget {
final Function(Post item) onSelect; final Function(Post item) onSelect;
const ExplorePostWidget({super.key, required this.onSelect}); const ExploreScreenWidget({super.key, required this.onSelect});
@override @override
State<ExplorePostWidget> createState() => _ExplorePostWidgetState(); State<ExploreScreenWidget> createState() => _ExploreScreenWidgetState();
} }
class _ExplorePostWidgetState extends State<ExplorePostWidget> { class _ExploreScreenWidgetState extends State<ExploreScreenWidget> {
final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0); final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0);
final http.Client _client = http.Client(); final http.Client _client = http.Client();

View File

@ -9,6 +9,9 @@ abstract class SolianTheme {
brightness: brightness, brightness: brightness,
useMaterial3: true, useMaterial3: true,
colorScheme: ColorScheme.fromSeed(brightness: brightness, seedColor: Colors.indigo), colorScheme: ColorScheme.fromSeed(brightness: brightness, seedColor: Colors.indigo),
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
),
); );
} }
} }

View File

@ -13,15 +13,19 @@ class TwoColumnLayout extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return ScaffoldMessenger(
children: [ child: Scaffold(
SizedBox( body: Row(
width: 400, children: [
child: sideChild, SizedBox(
width: 400,
child: sideChild,
),
const VerticalDivider(width: 0.3, thickness: 0.3),
Expanded(child: mainChild ?? const PageEmptyWidget()),
],
), ),
const VerticalDivider(width: 0.3, thickness: 0.3), ),
Expanded(child: mainChild ?? const PageEmptyWidget()),
],
); );
} }
} }

View File

@ -1,7 +1,6 @@
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/utils/theme.dart';
import 'package:solian/widgets/account/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';
@ -88,13 +87,16 @@ class _PostItemState extends State<PostItem> {
Widget renderAttachments() { Widget renderAttachments() {
if (widget.item.modelType == 'article') return Container(); if (widget.item.modelType == 'article') return Container();
final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600;
if (widget.item.attachments != null && widget.item.attachments!.isNotEmpty) { if (widget.item.attachments != null && widget.item.attachments!.isNotEmpty) {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: AttachmentList( child: AttachmentList(
items: widget.item.attachments!, items: widget.item.attachments!,
provider: 'interactive', provider: 'interactive',
noTag: SolianTheme.isLargeScreen(context) && widget.brief, noTag: isLargeScreen && widget.brief,
), ),
); );
} else { } else {