diff --git a/lib/router.dart b/lib/router.dart index dd7755c..ed0c727 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -8,6 +8,8 @@ import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/personalize.dart'; import 'package:solian/screens/account/profile_page.dart'; import 'package:solian/screens/account/stickers.dart'; +import 'package:solian/screens/auth/signin.dart'; +import 'package:solian/screens/auth/signup.dart'; import 'package:solian/screens/channel/channel_chat.dart'; import 'package:solian/screens/channel/channel_detail.dart'; import 'package:solian/screens/channel/channel_organize.dart'; @@ -259,6 +261,24 @@ abstract class AppRouter { name: state.pathParameters['name']!, ), ), + GoRoute( + path: '/auth/sign-in', + name: 'signin', + builder: (context, state) => TitleShell( + state: state, + isCenteredTitle: true, + child: const SignInScreen(), + ), + ), + GoRoute( + path: '/auth/sign-up', + name: 'signup', + builder: (context, state) => TitleShell( + state: state, + isCenteredTitle: true, + child: const SignUpScreen(), + ), + ), ], ); } diff --git a/lib/screens/account.dart b/lib/screens/account.dart index f4d680d..496344b 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -6,8 +6,6 @@ import 'package:solian/providers/auth.dart'; import 'package:solian/providers/account_status.dart'; import 'package:solian/providers/relation.dart'; import 'package:solian/router.dart'; -import 'package:solian/screens/auth/signin.dart'; -import 'package:solian/screens/auth/signup.dart'; import 'package:solian/widgets/account/account_heading.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:badges/badges.dart' as badges; @@ -73,13 +71,7 @@ class _AccountScreenState extends State { title: 'signin'.tr, caption: 'signinCaption'.tr, onTap: () { - showModalBottomSheet( - useRootNavigator: true, - isDismissible: false, - isScrollControlled: true, - context: context, - builder: (context) => const SignInPopup(), - ).then((val) async { + AppRouter.instance.pushNamed('signin').then((val) async { if (val == true) { await auth.refreshUserProfile(); } @@ -94,13 +86,7 @@ class _AccountScreenState extends State { title: 'signup'.tr, caption: 'signupCaption'.tr, onTap: () { - showModalBottomSheet( - useRootNavigator: true, - isDismissible: false, - isScrollControlled: true, - context: context, - builder: (context) => const SignUpPopup(), - ).then((_) { + AppRouter.instance.pushNamed('signup').then((_) { setState(() {}); }); }, diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index ebf10ea..ab9c20e 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -7,17 +7,18 @@ import 'package:solian/exts.dart'; import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/services.dart'; +import 'package:solian/widgets/sized_container.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; -class SignInPopup extends StatefulWidget { - const SignInPopup({super.key}); +class SignInScreen extends StatefulWidget { + const SignInScreen({super.key}); @override - State createState() => _SignInPopupState(); + State createState() => _SignInScreenState(); } -class _SignInPopupState extends State with ProtocolListener { +class _SignInScreenState extends State with ProtocolListener { bool _isBusy = false; final _usernameController = TextEditingController(); @@ -130,79 +131,76 @@ class _SignInPopupState extends State with ProtocolListener { @override Widget build(BuildContext context) { - return SizedBox( - height: MediaQuery.of(context).size.height * 0.9, - child: Center( - child: Container( - width: MediaQuery.of(context).size.width * 0.6, - constraints: const BoxConstraints(maxWidth: 360), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Image.asset('assets/logo.png', width: 64, height: 64), - ).paddingOnly(bottom: 4), - Text( - 'signinGreeting'.tr, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w900, - ), - ).paddingOnly(left: 4, bottom: 16), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _usernameController, - autofillHints: const [AutofillHints.username], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'username'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + return Material( + color: Theme.of(context).colorScheme.surface, + child: CenteredContainer( + maxWidth: 360, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Image.asset('assets/logo.png', width: 64, height: 64), + ).paddingOnly(bottom: 4), + Text( + 'signinGreeting'.tr, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, ), - const Gap(12), - TextField( - obscureText: true, - autocorrect: false, - enableSuggestions: false, - autofillHints: const [AutofillHints.password], - controller: _passwordController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'password'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: (_) => _performAction(), + ).paddingOnly(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'username'.tr, ), - const Gap(12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: _isBusy ? null : () => _requestResetPassword(), - style: TextButton.styleFrom(foregroundColor: Colors.grey), - child: Text('forgotPassword'.tr), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + obscureText: true, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + controller: _passwordController, + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'password'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: (_) => _performAction(), + ), + const Gap(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: _isBusy ? null : () => _requestResetPassword(), + style: TextButton.styleFrom(foregroundColor: Colors.grey), + child: Text('forgotPassword'.tr), + ), + TextButton( + onPressed: _isBusy ? null : () => _performAction(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr), + const Icon(Icons.chevron_right), + ], ), - TextButton( - onPressed: _isBusy ? null : () => _performAction(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('next'.tr), - const Icon(Icons.chevron_right), - ], - ), - ), - ], - ), - ], - ), + ), + ], + ), + ], ), ), ); diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart index 491f1e3..61fae98 100644 --- a/lib/screens/auth/signup.dart +++ b/lib/screens/auth/signup.dart @@ -3,15 +3,16 @@ import 'package:gap/gap.dart'; import 'package:get/get.dart'; import 'package:solian/exts.dart'; import 'package:solian/services.dart'; +import 'package:solian/widgets/sized_container.dart'; -class SignUpPopup extends StatefulWidget { - const SignUpPopup({super.key}); +class SignUpScreen extends StatefulWidget { + const SignUpScreen({super.key}); @override - State createState() => _SignUpPopupState(); + State createState() => _SignUpScreenState(); } -class _SignUpPopupState extends State { +class _SignUpScreenState extends State { final _emailController = TextEditingController(); final _usernameController = TextEditingController(); final _nicknameController = TextEditingController(); @@ -61,100 +62,97 @@ class _SignUpPopupState extends State { @override Widget build(BuildContext context) { - return SizedBox( - height: MediaQuery.of(context).size.height * 0.9, - child: Center( - child: Container( - width: MediaQuery.of(context).size.width * 0.6, - constraints: const BoxConstraints(maxWidth: 360), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Image.asset('assets/logo.png', width: 64, height: 64), - ).paddingOnly(bottom: 4), - Text( - 'signupGreeting'.tr, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w900, - ), - ).paddingOnly(left: 4, bottom: 16), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _usernameController, - autofillHints: const [AutofillHints.username], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'username'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + return Material( + color: Theme.of(context).colorScheme.surface, + child: CenteredContainer( + maxWidth: 360, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Image.asset('assets/logo.png', width: 64, height: 64), + ).paddingOnly(bottom: 4), + Text( + 'signupGreeting'.tr, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w900, ), - const Gap(12), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _nicknameController, - autofillHints: const [AutofillHints.nickname], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'nickname'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + ).paddingOnly(left: 4, bottom: 16), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _usernameController, + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'username'.tr, ), - const Gap(12), - TextField( - autocorrect: false, - enableSuggestions: false, - controller: _emailController, - autofillHints: const [AutofillHints.email], - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'email'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _nicknameController, + autofillHints: const [AutofillHints.nickname], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'nickname'.tr, ), - const Gap(12), - TextField( - obscureText: true, - autocorrect: false, - enableSuggestions: false, - autofillHints: const [AutofillHints.password], - controller: _passwordController, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - labelText: 'password'.tr, - ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), - onSubmitted: (_) => performAction(context), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + autocorrect: false, + enableSuggestions: false, + controller: _emailController, + autofillHints: const [AutofillHints.email], + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'email'.tr, ), - const Gap(16), - Align( - alignment: Alignment.centerRight, - child: TextButton( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('next'.tr), - const Icon(Icons.chevron_right), - ], - ), - onPressed: () => performAction(context), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + TextField( + obscureText: true, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + controller: _passwordController, + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + labelText: 'password'.tr, + ), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + onSubmitted: (_) => performAction(context), + ), + const Gap(16), + Align( + alignment: Alignment.centerRight, + child: TextButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('next'.tr), + const Icon(Icons.chevron_right), + ], ), - ) - ], - ), + onPressed: () => performAction(context), + ), + ) + ], ), ), ); diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index ad7b94a..e41517c 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -102,7 +102,7 @@ class _ChatScreenState extends State { body: Obx(() { if (auth.isAuthorized.isFalse) { return SigninRequiredOverlay( - onSignedIn: () => _channels.refreshAvailableChannel(), + onDone: () => _channels.refreshAvailableChannel(), ); } diff --git a/lib/screens/feed.dart b/lib/screens/feed.dart index 4479844..f80bbae 100644 --- a/lib/screens/feed.dart +++ b/lib/screens/feed.dart @@ -151,7 +151,7 @@ class _FeedScreenState extends State ); } else { return SigninRequiredOverlay( - onSignedIn: () => _postController.reloadAllOver(), + onDone: () => _postController.reloadAllOver(), ); } }), diff --git a/lib/screens/realms.dart b/lib/screens/realms.dart index 51e2500..96a5e75 100644 --- a/lib/screens/realms.dart +++ b/lib/screens/realms.dart @@ -84,7 +84,7 @@ class _RealmListScreenState extends State { body: Obx(() { if (auth.isAuthorized.isFalse) { return SigninRequiredOverlay( - onSignedIn: () => _getRealms(), + onDone: () => _getRealms(), ); } diff --git a/lib/shells/title_shell.dart b/lib/shells/title_shell.dart index 3fabdc9..5e7cbc0 100644 --- a/lib/shells/title_shell.dart +++ b/lib/shells/title_shell.dart @@ -24,6 +24,8 @@ class TitleShell extends StatelessWidget { @override Widget build(BuildContext context) { + assert(state != null || title != null); + return Scaffold( appBar: showAppBar ? AppBar( diff --git a/lib/widgets/account/signin_required_overlay.dart b/lib/widgets/account/signin_required_overlay.dart index eb77864..d650661 100644 --- a/lib/widgets/account/signin_required_overlay.dart +++ b/lib/widgets/account/signin_required_overlay.dart @@ -1,49 +1,43 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart'; -import 'package:solian/screens/auth/signin.dart'; +import 'package:solian/router.dart'; +import 'package:solian/widgets/sized_container.dart'; class SigninRequiredOverlay extends StatelessWidget { - final Function onSignedIn; + final Function onDone; - const SigninRequiredOverlay({super.key, required this.onSignedIn}); + const SigninRequiredOverlay({super.key, required this.onDone}); @override Widget build(BuildContext context) { return GestureDetector( - child: Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 280), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.login, - size: 48, - ), - const Gap(8), - Text( - 'signinRequired'.tr, - style: Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - Text( - 'signinRequiredHint'.tr, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - ], - ), + child: CenteredContainer( + maxWidth: 280, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.login, + size: 48, + ), + const Gap(8), + Text( + 'signinRequired'.tr, + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, + ), + Text( + 'signinRequiredHint'.tr, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ], ), ), onTap: () { - showModalBottomSheet( - useRootNavigator: true, - isScrollControlled: true, - context: context, - builder: (context) => const SignInPopup(), - ).then((value) { - if (value != null) onSignedIn(); + AppRouter.instance.pushNamed('signin').then((value) { + if (value != null) onDone(); }); }, );