✨ Terms that show up let user accept
This commit is contained in:
		| @@ -432,5 +432,13 @@ | ||||
|   "update": "Update", | ||||
|   "updateCheckStrictly": "Strict mode", | ||||
|   "updateCheckStrictlyDesc": "If enabled, the app will ask for updating once the local version is different from remote one.", | ||||
|   "updateMayAvailable": "App version @version is available, you can update from app store or our website." | ||||
|   "updateMayAvailable": "App version @version is available, you can update from app store or our website.", | ||||
|   "termAccept": "I've read and agree to Solar Network's Terms", | ||||
|   "termAcceptDesc": "Including but not limited to \"User Agreement\" and \"Privacy Policy\"", | ||||
|   "termAcceptLink": "View terms", | ||||
|   "termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates. You should already agreed with them while you sign up.", | ||||
|   "termRelated": "Related Terms", | ||||
|   "appDetails": "App Details", | ||||
|   "projectWebsite": "Project Website", | ||||
|   "iAmNotRobot": "I'm not a Robot" | ||||
| } | ||||
|   | ||||
| @@ -428,5 +428,13 @@ | ||||
|   "update": "更新", | ||||
|   "updateCheckStrictly": "严格模式", | ||||
|   "updateCheckStrictlyDesc": "如果启用,应用程序将会在本地版本与远程版本不同时询问更新,而不会检查版本号大小。", | ||||
|   "updateMayAvailable": "版本 @version 现已可用,你可以前往应用商店或是我们的官网下载更新。" | ||||
|   "updateMayAvailable": "版本 @version 现已可用,你可以前往应用商店或是我们的官网下载更新。", | ||||
|   "termAccept": "我已阅读并同意 Solar Network 各项条款", | ||||
|   "termAcceptDesc": "包括但不限于《用户守则》和《隐私政策》", | ||||
|   "termAcceptLink": "浏览条款", | ||||
|   "termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。你应该在注册时已经同意过了。", | ||||
|   "termRelated": "相关条款", | ||||
|   "projectWebsite": "项目网站", | ||||
|   "appDetails": "应用详情", | ||||
|   "iAmNotRobot": "我不是机器人" | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:package_info_plus/package_info_plus.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| @@ -49,7 +50,7 @@ class AboutScreen extends StatelessWidget { | ||||
|             const Gap(16), | ||||
|             TextButton( | ||||
|               style: denseButtonStyle, | ||||
|               child: const Text('App Details'), | ||||
|               child: Text('appDetails'.tr), | ||||
|               onPressed: () async { | ||||
|                 final info = await PackageInfo.fromPlatform(); | ||||
|  | ||||
| @@ -68,11 +69,18 @@ class AboutScreen extends StatelessWidget { | ||||
|             ), | ||||
|             TextButton( | ||||
|               style: denseButtonStyle, | ||||
|               child: const Text('Project Website'), | ||||
|               child: Text('projectWebsite'.tr), | ||||
|               onPressed: () { | ||||
|                 launchUrlString('https://solsynth.dev/products/solar-network'); | ||||
|               }, | ||||
|             ), | ||||
|             TextButton( | ||||
|               style: denseButtonStyle, | ||||
|               child: Text('termRelated'.tr), | ||||
|               onPressed: () { | ||||
|                 launchUrlString('https://solsynth.dev/terms'); | ||||
|               }, | ||||
|             ), | ||||
|             const Gap(16), | ||||
|             const Text( | ||||
|               'Open-sourced under AGPLv3', | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import 'package:solian/providers/relation.dart'; | ||||
| import 'package:solian/providers/websocket.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| import 'package:solian/widgets/sized_container.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| class SignInScreen extends StatefulWidget { | ||||
|   const SignInScreen({super.key}); | ||||
| @@ -167,7 +168,6 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|  | ||||
|       final result = AuthResult.fromJson(resp.body); | ||||
|       _currentTicket = result.ticket; | ||||
|       _passwordController.clear(); | ||||
|  | ||||
|       // Finish sign in if possible | ||||
|       if (result.isFinished) { | ||||
| @@ -185,11 +185,13 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|           autoStartBackgroundNotificationService(); | ||||
|  | ||||
|           Navigator.pop(context, true); | ||||
|           _passwordController.clear(); | ||||
|         }); | ||||
|       } else { | ||||
|         // Skip the first step | ||||
|         _factorPicked = null; | ||||
|         _factorPickedType = null; | ||||
|         _passwordController.clear(); | ||||
|         setState(() => _period += 2); | ||||
|       } | ||||
|     } catch (e) { | ||||
| @@ -210,9 +212,8 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|       case 2: | ||||
|         _passwordController.clear(); | ||||
|         _factorPickedType = null; | ||||
|       default: | ||||
|         setState(() => _period--); | ||||
|     } | ||||
|     setState(() => _period--); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -235,16 +236,18 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|             ); | ||||
|           }, | ||||
|           child: switch (_period % 3) { | ||||
|             1 => Column( | ||||
|             1 => ListView( | ||||
|                 shrinkWrap: true, | ||||
|                 key: const ValueKey<int>(1), | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   ClipRRect( | ||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                     child: | ||||
|                         Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|                   ).paddingOnly(bottom: 8, left: 4), | ||||
|                   Align( | ||||
|                     alignment: Alignment.centerLeft, | ||||
|                     child: ClipRRect( | ||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                       child: | ||||
|                           Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|                     ).paddingOnly(bottom: 8, left: 4), | ||||
|                   ), | ||||
|                   Text( | ||||
|                     'signinPickFactor'.tr, | ||||
|                     style: const TextStyle( | ||||
| @@ -323,16 +326,18 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             2 => Column( | ||||
|             2 => ListView( | ||||
|                 key: const ValueKey<int>(2), | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 shrinkWrap: true, | ||||
|                 children: [ | ||||
|                   ClipRRect( | ||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                     child: | ||||
|                         Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|                   ).paddingOnly(bottom: 8, left: 4), | ||||
|                   Align( | ||||
|                     alignment: Alignment.centerLeft, | ||||
|                     child: ClipRRect( | ||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                       child: | ||||
|                           Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|                     ).paddingOnly(bottom: 8, left: 4), | ||||
|                   ), | ||||
|                   Text( | ||||
|                     'signinEnterPassword'.tr, | ||||
|                     style: const TextStyle( | ||||
| @@ -396,16 +401,18 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             _ => Column( | ||||
|             _ => ListView( | ||||
|                 key: const ValueKey<int>(0), | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 shrinkWrap: true, | ||||
|                 children: [ | ||||
|                   ClipRRect( | ||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                     child: | ||||
|                         Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|                   ).paddingOnly(bottom: 8, left: 4), | ||||
|                   Align( | ||||
|                     alignment: Alignment.centerLeft, | ||||
|                     child: ClipRRect( | ||||
|                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                       child: | ||||
|                           Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|                     ).paddingOnly(bottom: 8, left: 4), | ||||
|                   ), | ||||
|                   Text( | ||||
|                     'signinGreeting'.tr, | ||||
|                     style: const TextStyle( | ||||
| @@ -451,11 +458,50 @@ class _SignInScreenState extends State<SignInScreen> { | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   const Gap(12), | ||||
|                   Align( | ||||
|                     alignment: Alignment.centerRight, | ||||
|                     child: Container( | ||||
|                       constraints: const BoxConstraints(maxWidth: 290), | ||||
|                       child: Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.end, | ||||
|                         children: [ | ||||
|                           Text( | ||||
|                             'termAcceptNextWithAgree'.tr, | ||||
|                             textAlign: TextAlign.end, | ||||
|                             style: | ||||
|                                 Theme.of(context).textTheme.bodySmall!.copyWith( | ||||
|                                       color: Theme.of(context) | ||||
|                                           .colorScheme | ||||
|                                           .onSurface | ||||
|                                           .withOpacity(0.75), | ||||
|                                     ), | ||||
|                           ), | ||||
|                           Material( | ||||
|                             color: Colors.transparent, | ||||
|                             child: InkWell( | ||||
|                               child: Row( | ||||
|                                 mainAxisSize: MainAxisSize.min, | ||||
|                                 children: [ | ||||
|                                   Text('termAcceptLink'.tr), | ||||
|                                   const Gap(4), | ||||
|                                   const Icon(Icons.launch, size: 14), | ||||
|                                 ], | ||||
|                               ), | ||||
|                               onTap: () { | ||||
|                                 launchUrlString('https://solsynth.dev/terms'); | ||||
|                               }, | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ).paddingSymmetric(horizontal: 16), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|           }, | ||||
|         ), | ||||
|       ), | ||||
|       ).paddingAll(24), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import 'package:get/get.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| import 'package:solian/widgets/sized_container.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| class SignUpScreen extends StatefulWidget { | ||||
|   const SignUpScreen({super.key}); | ||||
| @@ -18,7 +19,7 @@ class _SignUpScreenState extends State<SignUpScreen> { | ||||
|   final _nicknameController = TextEditingController(); | ||||
|   final _passwordController = TextEditingController(); | ||||
|  | ||||
|   void performAction(BuildContext context) async { | ||||
|   void _performAction(BuildContext context) async { | ||||
|     final email = _emailController.value.text; | ||||
|     final username = _usernameController.value.text; | ||||
|     final nickname = _nicknameController.value.text; | ||||
| @@ -60,20 +61,24 @@ class _SignUpScreenState extends State<SignUpScreen> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   bool _isTermAccepted = false; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Material( | ||||
|       color: Theme.of(context).colorScheme.surface, | ||||
|       child: CenteredContainer( | ||||
|         maxWidth: 360, | ||||
|         child: Column( | ||||
|           mainAxisSize: MainAxisSize.min, | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         child: ListView( | ||||
|           shrinkWrap: true, | ||||
|           children: [ | ||||
|             ClipRRect( | ||||
|               borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|               child: Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|             ).paddingOnly(bottom: 8, left: 4), | ||||
|             Align( | ||||
|               alignment: Alignment.centerLeft, | ||||
|               child: ClipRRect( | ||||
|                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|                 child: Image.asset('assets/logo.png', width: 64, height: 64), | ||||
|               ).paddingOnly(bottom: 8, left: 4), | ||||
|             ), | ||||
|             Text( | ||||
|               'signupGreeting'.tr, | ||||
|               style: const TextStyle( | ||||
| @@ -136,12 +141,58 @@ class _SignUpScreenState extends State<SignUpScreen> { | ||||
|               ), | ||||
|               onTapOutside: (_) => | ||||
|                   FocusManager.instance.primaryFocus?.unfocus(), | ||||
|               onSubmitted: (_) => performAction(context), | ||||
|               onSubmitted: (_) => _performAction(context), | ||||
|             ), | ||||
|             const Gap(8), | ||||
|             CheckboxListTile( | ||||
|               value: _isTermAccepted, | ||||
|               title: Text('termAccept'.tr), | ||||
|               shape: const RoundedRectangleBorder( | ||||
|                 borderRadius: BorderRadius.all( | ||||
|                   Radius.circular(8), | ||||
|                 ), | ||||
|               ), | ||||
|               subtitle: RichText( | ||||
|                 text: TextSpan( | ||||
|                   style: Theme.of(context).textTheme.bodySmall!.copyWith( | ||||
|                         color: Theme.of(context) | ||||
|                             .colorScheme | ||||
|                             .onSurface | ||||
|                             .withOpacity(0.75), | ||||
|                       ), | ||||
|                   children: [ | ||||
|                     TextSpan(text: 'termAcceptDesc'.tr), | ||||
|                     WidgetSpan( | ||||
|                       child: Material( | ||||
|                         color: Colors.transparent, | ||||
|                         child: InkWell( | ||||
|                           child: Row( | ||||
|                             mainAxisSize: MainAxisSize.min, | ||||
|                             children: [ | ||||
|                               Text('termAcceptLink'.tr), | ||||
|                               const Gap(4), | ||||
|                               const Icon(Icons.launch, size: 14), | ||||
|                             ], | ||||
|                           ), | ||||
|                           onTap: () { | ||||
|                             launchUrlString('https://solsynth.dev/terms'); | ||||
|                           }, | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|               onChanged: (value) { | ||||
|                 setState(() => _isTermAccepted = value ?? false); | ||||
|               }, | ||||
|             ), | ||||
|             const Gap(16), | ||||
|             Align( | ||||
|               alignment: Alignment.centerRight, | ||||
|               child: TextButton( | ||||
|                 onPressed: | ||||
|                     !_isTermAccepted ? null : () => _performAction(context), | ||||
|                 child: Row( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   children: [ | ||||
| @@ -149,12 +200,11 @@ class _SignUpScreenState extends State<SignUpScreen> { | ||||
|                     const Icon(Icons.chevron_right), | ||||
|                   ], | ||||
|                 ), | ||||
|                 onPressed: () => performAction(context), | ||||
|               ), | ||||
|             ) | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|       ).paddingAll(24), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -35,6 +35,9 @@ abstract class AppTheme { | ||||
|         brightness: brightness, | ||||
|         seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1), | ||||
|       ), | ||||
|       snackBarTheme: const SnackBarThemeData( | ||||
|         behavior: SnackBarBehavior.floating, | ||||
|       ), | ||||
|       fontFamily: 'Comfortaa', | ||||
|       fontFamilyFallback: [ | ||||
|         'NotoSansSC', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user