💄 Better bottom navigation
This commit is contained in:
parent
a304b26c96
commit
66ddfea68d
@ -19,7 +19,6 @@ import 'package:solian/providers/content/realm.dart';
|
||||
import 'package:solian/providers/friend.dart';
|
||||
import 'package:solian/providers/account_status.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/shells/listener_shell.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/translations.dart';
|
||||
|
||||
@ -83,15 +82,14 @@ class SolianApp extends StatelessWidget {
|
||||
routerDelegate: AppRouter.instance.routerDelegate,
|
||||
routeInformationParser: AppRouter.instance.routeInformationParser,
|
||||
routeInformationProvider: AppRouter.instance.routeInformationProvider,
|
||||
backButtonDispatcher: AppRouter.instance.backButtonDispatcher,
|
||||
translations: SolianMessages(),
|
||||
locale: Get.deviceLocale,
|
||||
fallbackLocale: const Locale('en', 'US'),
|
||||
onInit: () => _initializeProviders(context),
|
||||
builder: (context, child) {
|
||||
return ListenerShell(
|
||||
child: ScaffoldMessenger(
|
||||
child: child ?? Container(),
|
||||
),
|
||||
return ScaffoldMessenger(
|
||||
child: child ?? const SizedBox(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -13,10 +13,10 @@ import 'package:solian/screens/realms.dart';
|
||||
import 'package:solian/screens/realms/realm_detail.dart';
|
||||
import 'package:solian/screens/realms/realm_organize.dart';
|
||||
import 'package:solian/screens/realms/realm_view.dart';
|
||||
import 'package:solian/screens/social.dart';
|
||||
import 'package:solian/screens/feed.dart';
|
||||
import 'package:solian/screens/posts/post_publish.dart';
|
||||
import 'package:solian/shells/basic_shell.dart';
|
||||
import 'package:solian/shells/nav_shell.dart';
|
||||
import 'package:solian/shells/root_shell.dart';
|
||||
import 'package:solian/shells/title_shell.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/sidebar/empty_placeholder.dart';
|
||||
@ -25,29 +25,35 @@ abstract class AppRouter {
|
||||
static GoRouter instance = GoRouter(
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => NavShell(
|
||||
builder: (context, state, child) => RootShell(
|
||||
state: state,
|
||||
showAppBar: false,
|
||||
showSidebar: false,
|
||||
child: child,
|
||||
),
|
||||
routes: [
|
||||
ShellRoute(
|
||||
_feedRoute,
|
||||
_chatRoute,
|
||||
_realmRoute,
|
||||
_accountRoute,
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
static final ShellRoute _feedRoute = ShellRoute(
|
||||
builder: (context, state, child) => BasicShell(
|
||||
state: state,
|
||||
sidebarFirst: true,
|
||||
showAppBar: false,
|
||||
sidebar: const SocialScreen(),
|
||||
sidebar: const FeedScreen(),
|
||||
child: child,
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
name: 'social',
|
||||
builder: (context, state) =>
|
||||
SolianTheme.isExtraLargeScreen(context)
|
||||
name: 'feed',
|
||||
builder: (context, state) => SolianTheme.isExtraLargeScreen(context)
|
||||
? const EmptyPagePlaceholder()
|
||||
: const SocialScreen(),
|
||||
: const FeedScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/posts/view/:alias',
|
||||
@ -73,8 +79,9 @@ abstract class AppRouter {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
);
|
||||
|
||||
static final ShellRoute _chatRoute = ShellRoute(
|
||||
builder: (context, state, child) => BasicShell(
|
||||
state: state,
|
||||
sidebarFirst: true,
|
||||
@ -86,8 +93,7 @@ abstract class AppRouter {
|
||||
GoRoute(
|
||||
path: '/chat',
|
||||
name: 'chat',
|
||||
builder: (context, state) =>
|
||||
SolianTheme.isExtraLargeScreen(context)
|
||||
builder: (context, state) => SolianTheme.isExtraLargeScreen(context)
|
||||
? const EmptyPagePlaceholder()
|
||||
: const ChatScreen(),
|
||||
),
|
||||
@ -128,8 +134,9 @@ abstract class AppRouter {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
);
|
||||
|
||||
static final ShellRoute _realmRoute = ShellRoute(
|
||||
builder: (context, state, child) => BasicShell(
|
||||
state: state,
|
||||
sidebarFirst: true,
|
||||
@ -141,8 +148,7 @@ abstract class AppRouter {
|
||||
GoRoute(
|
||||
path: '/realms',
|
||||
name: 'realms',
|
||||
builder: (context, state) =>
|
||||
SolianTheme.isExtraLargeScreen(context)
|
||||
builder: (context, state) => SolianTheme.isExtraLargeScreen(context)
|
||||
? const EmptyPagePlaceholder()
|
||||
: const RealmListScreen(),
|
||||
),
|
||||
@ -177,8 +183,9 @@ abstract class AppRouter {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
);
|
||||
|
||||
static final ShellRoute _accountRoute = ShellRoute(
|
||||
builder: (context, state, child) => BasicShell(
|
||||
state: state,
|
||||
sidebarFirst: true,
|
||||
@ -190,8 +197,7 @@ abstract class AppRouter {
|
||||
GoRoute(
|
||||
path: '/account',
|
||||
name: 'account',
|
||||
builder: (context, state) =>
|
||||
SolianTheme.isExtraLargeScreen(context)
|
||||
builder: (context, state) => SolianTheme.isExtraLargeScreen(context)
|
||||
? const EmptyPagePlaceholder()
|
||||
: const AccountScreen(),
|
||||
),
|
||||
@ -220,9 +226,5 @@ abstract class AppRouter {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:protocol_handler/protocol_handler.dart';
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/providers/account.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SignInPopup extends StatefulWidget {
|
||||
@ -13,13 +15,13 @@ class SignInPopup extends StatefulWidget {
|
||||
State<SignInPopup> createState() => _SignInPopupState();
|
||||
}
|
||||
|
||||
class _SignInPopupState extends State<SignInPopup> {
|
||||
class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
|
||||
bool _isBusy = false;
|
||||
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
void requestResetPassword(BuildContext context) async {
|
||||
void requestResetPassword() async {
|
||||
final username = _usernameController.value.text;
|
||||
if (username.isEmpty) {
|
||||
context.showErrorDialog('signinResetPasswordHint'.tr);
|
||||
@ -49,7 +51,7 @@ class _SignInPopupState extends State<SignInPopup> {
|
||||
context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr);
|
||||
}
|
||||
|
||||
void performAction(BuildContext context) async {
|
||||
void performAction() async {
|
||||
final AuthProvider provider = Get.find();
|
||||
|
||||
final username = _usernameController.value.text;
|
||||
@ -96,6 +98,27 @@ class _SignInPopupState extends State<SignInPopup> {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
protocolHandler.addListener(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
protocolHandler.removeListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void onProtocolUrlReceived(String url) {
|
||||
final uri = url.replaceFirst('solink://', '');
|
||||
if (uri == 'auth?status=done') {
|
||||
closeInAppWebView();
|
||||
performAction();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
@ -144,20 +167,19 @@ class _SignInPopupState extends State<SignInPopup> {
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onSubmitted: (_) => performAction(context),
|
||||
onSubmitted: (_) => performAction(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
_isBusy ? null : () => requestResetPassword(context),
|
||||
onPressed: _isBusy ? null : () => requestResetPassword(),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.grey),
|
||||
child: Text('forgotPassword'.tr),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => performAction(context),
|
||||
onPressed: _isBusy ? null : () => performAction(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
@ -12,14 +12,14 @@ import 'package:solian/widgets/app_bar_title.dart';
|
||||
import 'package:solian/widgets/current_state_action.dart';
|
||||
import 'package:solian/widgets/posts/post_list.dart';
|
||||
|
||||
class SocialScreen extends StatefulWidget {
|
||||
const SocialScreen({super.key});
|
||||
class FeedScreen extends StatefulWidget {
|
||||
const FeedScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SocialScreen> createState() => _SocialScreenState();
|
||||
State<FeedScreen> createState() => _FeedScreenState();
|
||||
}
|
||||
|
||||
class _SocialScreenState extends State<SocialScreen> {
|
||||
class _FeedScreenState extends State<FeedScreen> {
|
||||
final PagingController<int, Post> _pagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
|
||||
@ -52,26 +52,7 @@ class _SocialScreenState extends State<SocialScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AuthProvider auth = Get.find();
|
||||
|
||||
return Scaffold(
|
||||
floatingActionButton: FutureBuilder(
|
||||
future: auth.isAuthorized,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data == true) {
|
||||
return FloatingActionButton(
|
||||
child: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
final value =
|
||||
await AppRouter.instance.pushNamed('postPublishing');
|
||||
if (value != null) {
|
||||
_pagingController.refresh();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
body: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: RefreshIndicator(
|
||||
@ -79,7 +60,7 @@ class _SocialScreenState extends State<SocialScreen> {
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
title: AppBarTitle('social'.tr),
|
||||
title: AppBarTitle('feed'.tr),
|
||||
centerTitle: false,
|
||||
floating: true,
|
||||
titleSpacing: SolianTheme.titleSpacing(context),
|
||||
@ -87,6 +68,7 @@ class _SocialScreenState extends State<SocialScreen> {
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
const NotificationButton(),
|
||||
const FeedCreationButton(),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
@ -100,3 +82,26 @@ class _SocialScreenState extends State<SocialScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FeedCreationButton extends StatelessWidget {
|
||||
const FeedCreationButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AuthProvider auth = Get.find();
|
||||
|
||||
return FutureBuilder(
|
||||
future: auth.isAuthorized,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data == true) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.add_circle),
|
||||
onPressed: () {
|
||||
AppRouter.instance.pushNamed('postPublishing');
|
||||
},
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:protocol_handler/protocol_handler.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ListenerShell extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const ListenerShell({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<ListenerShell> createState() => _ListenerShellState();
|
||||
}
|
||||
|
||||
class _ListenerShellState extends State<ListenerShell> with ProtocolListener {
|
||||
@override
|
||||
void initState() {
|
||||
protocolHandler.addListener(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
protocolHandler.removeListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
@override
|
||||
void onProtocolUrlReceived(String url) {
|
||||
final uri = url.replaceFirst('solink://', '');
|
||||
if (uri == 'auth?status=done') {
|
||||
closeInAppWebView();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation.dart';
|
||||
@ -14,7 +13,6 @@ class NavShell extends StatelessWidget {
|
||||
final bool showSidebar;
|
||||
final bool showNavigation;
|
||||
final bool? showBottomNavigation;
|
||||
final GoRouterState state;
|
||||
final Widget child;
|
||||
|
||||
final bool sidebarFirst;
|
||||
@ -23,7 +21,6 @@ class NavShell extends StatelessWidget {
|
||||
const NavShell({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.state,
|
||||
this.showAppBar = true,
|
||||
this.showSidebar = true,
|
||||
this.showNavigation = true,
|
||||
@ -60,7 +57,7 @@ class NavShell extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: showAppBar
|
||||
? AppBar(
|
||||
title: Text(state.topRoute?.name?.tr ?? 'page'.tr),
|
||||
title: Text(routeName ?? 'page'.tr),
|
||||
centerTitle: false,
|
||||
titleSpacing: canPop ? null : 24,
|
||||
elevation: SolianTheme.isLargeScreen(context) ? 1 : 0,
|
||||
|
63
lib/shells/root_shell.dart
Normal file
63
lib/shells/root_shell.dart
Normal file
@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation_bottom_bar.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation_rail.dart';
|
||||
|
||||
class RootShell extends StatelessWidget {
|
||||
final bool showSidebar;
|
||||
final bool showNavigation;
|
||||
final bool? showBottomNavigation;
|
||||
final GoRouterState state;
|
||||
final Widget child;
|
||||
|
||||
const RootShell({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.child,
|
||||
this.showSidebar = true,
|
||||
this.showNavigation = true,
|
||||
this.showBottomNavigation,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final routeName = AppRouter
|
||||
.instance.routerDelegate.currentConfiguration.lastOrNull?.route.name;
|
||||
final showBottom = showBottomNavigation ??
|
||||
AppNavigation.destinationPages.contains(routeName);
|
||||
|
||||
return Scaffold(
|
||||
body: SolianTheme.isLargeScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
if (showNavigation) const AppNavigationRail(),
|
||||
if (showNavigation)
|
||||
const VerticalDivider(thickness: 0.3, width: 1),
|
||||
Expanded(child: child),
|
||||
],
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
child,
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: const AppNavigationBottomBar()
|
||||
.animate(target: showBottom ? 0 : 1)
|
||||
.slideY(
|
||||
duration: 250.ms,
|
||||
begin: 0,
|
||||
end: 1,
|
||||
curve: Curves.easeInToLinear,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ class SolianMessages extends Translations {
|
||||
'next': 'Next',
|
||||
'reset': 'Reset',
|
||||
'page': 'Page',
|
||||
'social': 'Social',
|
||||
'feed': 'Feed',
|
||||
'chat': 'Chat',
|
||||
'apply': 'Apply',
|
||||
'cancel': 'Cancel',
|
||||
@ -263,7 +263,7 @@ class SolianMessages extends Translations {
|
||||
'edit': '编辑',
|
||||
'delete': '删除',
|
||||
'page': '页面',
|
||||
'social': '社交',
|
||||
'feed': '资讯',
|
||||
'chat': '聊天',
|
||||
'apply': '应用',
|
||||
'search': '搜索',
|
||||
|
@ -4,9 +4,9 @@ import 'package:get/utils.dart';
|
||||
abstract class AppNavigation {
|
||||
static List<AppNavigationDestination> destinations = [
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.public),
|
||||
label: 'social'.tr,
|
||||
page: 'social',
|
||||
icon: const Icon(Icons.feed),
|
||||
label: 'feed'.tr,
|
||||
page: 'feed',
|
||||
),
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.forum),
|
||||
|
Loading…
Reference in New Issue
Block a user