✨ Terms that show up let user accept
This commit is contained in:
		| @@ -432,5 +432,13 @@ | |||||||
|   "update": "Update", |   "update": "Update", | ||||||
|   "updateCheckStrictly": "Strict mode", |   "updateCheckStrictly": "Strict mode", | ||||||
|   "updateCheckStrictlyDesc": "If enabled, the app will ask for updating once the local version is different from remote one.", |   "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": "更新", |   "update": "更新", | ||||||
|   "updateCheckStrictly": "严格模式", |   "updateCheckStrictly": "严格模式", | ||||||
|   "updateCheckStrictlyDesc": "如果启用,应用程序将会在本地版本与远程版本不同时询问更新,而不会检查版本号大小。", |   "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:flutter/material.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:get/get.dart'; | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| @@ -49,7 +50,7 @@ class AboutScreen extends StatelessWidget { | |||||||
|             const Gap(16), |             const Gap(16), | ||||||
|             TextButton( |             TextButton( | ||||||
|               style: denseButtonStyle, |               style: denseButtonStyle, | ||||||
|               child: const Text('App Details'), |               child: Text('appDetails'.tr), | ||||||
|               onPressed: () async { |               onPressed: () async { | ||||||
|                 final info = await PackageInfo.fromPlatform(); |                 final info = await PackageInfo.fromPlatform(); | ||||||
|  |  | ||||||
| @@ -68,11 +69,18 @@ class AboutScreen extends StatelessWidget { | |||||||
|             ), |             ), | ||||||
|             TextButton( |             TextButton( | ||||||
|               style: denseButtonStyle, |               style: denseButtonStyle, | ||||||
|               child: const Text('Project Website'), |               child: Text('projectWebsite'.tr), | ||||||
|               onPressed: () { |               onPressed: () { | ||||||
|                 launchUrlString('https://solsynth.dev/products/solar-network'); |                 launchUrlString('https://solsynth.dev/products/solar-network'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|  |             TextButton( | ||||||
|  |               style: denseButtonStyle, | ||||||
|  |               child: Text('termRelated'.tr), | ||||||
|  |               onPressed: () { | ||||||
|  |                 launchUrlString('https://solsynth.dev/terms'); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|             const Gap(16), |             const Gap(16), | ||||||
|             const Text( |             const Text( | ||||||
|               'Open-sourced under AGPLv3', |               'Open-sourced under AGPLv3', | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import 'package:solian/providers/relation.dart'; | |||||||
| import 'package:solian/providers/websocket.dart'; | import 'package:solian/providers/websocket.dart'; | ||||||
| import 'package:solian/services.dart'; | import 'package:solian/services.dart'; | ||||||
| import 'package:solian/widgets/sized_container.dart'; | import 'package:solian/widgets/sized_container.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| class SignInScreen extends StatefulWidget { | class SignInScreen extends StatefulWidget { | ||||||
|   const SignInScreen({super.key}); |   const SignInScreen({super.key}); | ||||||
| @@ -167,7 +168,6 @@ class _SignInScreenState extends State<SignInScreen> { | |||||||
|  |  | ||||||
|       final result = AuthResult.fromJson(resp.body); |       final result = AuthResult.fromJson(resp.body); | ||||||
|       _currentTicket = result.ticket; |       _currentTicket = result.ticket; | ||||||
|       _passwordController.clear(); |  | ||||||
|  |  | ||||||
|       // Finish sign in if possible |       // Finish sign in if possible | ||||||
|       if (result.isFinished) { |       if (result.isFinished) { | ||||||
| @@ -185,11 +185,13 @@ class _SignInScreenState extends State<SignInScreen> { | |||||||
|           autoStartBackgroundNotificationService(); |           autoStartBackgroundNotificationService(); | ||||||
|  |  | ||||||
|           Navigator.pop(context, true); |           Navigator.pop(context, true); | ||||||
|  |           _passwordController.clear(); | ||||||
|         }); |         }); | ||||||
|       } else { |       } else { | ||||||
|         // Skip the first step |         // Skip the first step | ||||||
|         _factorPicked = null; |         _factorPicked = null; | ||||||
|         _factorPickedType = null; |         _factorPickedType = null; | ||||||
|  |         _passwordController.clear(); | ||||||
|         setState(() => _period += 2); |         setState(() => _period += 2); | ||||||
|       } |       } | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
| @@ -210,9 +212,8 @@ class _SignInScreenState extends State<SignInScreen> { | |||||||
|       case 2: |       case 2: | ||||||
|         _passwordController.clear(); |         _passwordController.clear(); | ||||||
|         _factorPickedType = null; |         _factorPickedType = null; | ||||||
|       default: |  | ||||||
|         setState(() => _period--); |  | ||||||
|     } |     } | ||||||
|  |     setState(() => _period--); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -235,16 +236,18 @@ class _SignInScreenState extends State<SignInScreen> { | |||||||
|             ); |             ); | ||||||
|           }, |           }, | ||||||
|           child: switch (_period % 3) { |           child: switch (_period % 3) { | ||||||
|             1 => Column( |             1 => ListView( | ||||||
|  |                 shrinkWrap: true, | ||||||
|                 key: const ValueKey<int>(1), |                 key: const ValueKey<int>(1), | ||||||
|                 mainAxisAlignment: MainAxisAlignment.center, |  | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                 children: [ |                 children: [ | ||||||
|                   ClipRRect( |                   Align( | ||||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), |                     alignment: Alignment.centerLeft, | ||||||
|                     child: |                     child: ClipRRect( | ||||||
|                         Image.asset('assets/logo.png', width: 64, height: 64), |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|                   ).paddingOnly(bottom: 8, left: 4), |                       child: | ||||||
|  |                           Image.asset('assets/logo.png', width: 64, height: 64), | ||||||
|  |                     ).paddingOnly(bottom: 8, left: 4), | ||||||
|  |                   ), | ||||||
|                   Text( |                   Text( | ||||||
|                     'signinPickFactor'.tr, |                     'signinPickFactor'.tr, | ||||||
|                     style: const TextStyle( |                     style: const TextStyle( | ||||||
| @@ -323,16 +326,18 @@ class _SignInScreenState extends State<SignInScreen> { | |||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|             2 => Column( |             2 => ListView( | ||||||
|                 key: const ValueKey<int>(2), |                 key: const ValueKey<int>(2), | ||||||
|                 mainAxisAlignment: MainAxisAlignment.center, |                 shrinkWrap: true, | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                 children: [ |                 children: [ | ||||||
|                   ClipRRect( |                   Align( | ||||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), |                     alignment: Alignment.centerLeft, | ||||||
|                     child: |                     child: ClipRRect( | ||||||
|                         Image.asset('assets/logo.png', width: 64, height: 64), |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|                   ).paddingOnly(bottom: 8, left: 4), |                       child: | ||||||
|  |                           Image.asset('assets/logo.png', width: 64, height: 64), | ||||||
|  |                     ).paddingOnly(bottom: 8, left: 4), | ||||||
|  |                   ), | ||||||
|                   Text( |                   Text( | ||||||
|                     'signinEnterPassword'.tr, |                     'signinEnterPassword'.tr, | ||||||
|                     style: const TextStyle( |                     style: const TextStyle( | ||||||
| @@ -396,16 +401,18 @@ class _SignInScreenState extends State<SignInScreen> { | |||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|             _ => Column( |             _ => ListView( | ||||||
|                 key: const ValueKey<int>(0), |                 key: const ValueKey<int>(0), | ||||||
|                 mainAxisAlignment: MainAxisAlignment.center, |                 shrinkWrap: true, | ||||||
|                 crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                 children: [ |                 children: [ | ||||||
|                   ClipRRect( |                   Align( | ||||||
|                     borderRadius: const BorderRadius.all(Radius.circular(8)), |                     alignment: Alignment.centerLeft, | ||||||
|                     child: |                     child: ClipRRect( | ||||||
|                         Image.asset('assets/logo.png', width: 64, height: 64), |                       borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|                   ).paddingOnly(bottom: 8, left: 4), |                       child: | ||||||
|  |                           Image.asset('assets/logo.png', width: 64, height: 64), | ||||||
|  |                     ).paddingOnly(bottom: 8, left: 4), | ||||||
|  |                   ), | ||||||
|                   Text( |                   Text( | ||||||
|                     'signinGreeting'.tr, |                     'signinGreeting'.tr, | ||||||
|                     style: const TextStyle( |                     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/exts.dart'; | ||||||
| import 'package:solian/services.dart'; | import 'package:solian/services.dart'; | ||||||
| import 'package:solian/widgets/sized_container.dart'; | import 'package:solian/widgets/sized_container.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| class SignUpScreen extends StatefulWidget { | class SignUpScreen extends StatefulWidget { | ||||||
|   const SignUpScreen({super.key}); |   const SignUpScreen({super.key}); | ||||||
| @@ -18,7 +19,7 @@ class _SignUpScreenState extends State<SignUpScreen> { | |||||||
|   final _nicknameController = TextEditingController(); |   final _nicknameController = TextEditingController(); | ||||||
|   final _passwordController = TextEditingController(); |   final _passwordController = TextEditingController(); | ||||||
|  |  | ||||||
|   void performAction(BuildContext context) async { |   void _performAction(BuildContext context) async { | ||||||
|     final email = _emailController.value.text; |     final email = _emailController.value.text; | ||||||
|     final username = _usernameController.value.text; |     final username = _usernameController.value.text; | ||||||
|     final nickname = _nicknameController.value.text; |     final nickname = _nicknameController.value.text; | ||||||
| @@ -60,20 +61,24 @@ class _SignUpScreenState extends State<SignUpScreen> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool _isTermAccepted = false; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Material( |     return Material( | ||||||
|       color: Theme.of(context).colorScheme.surface, |       color: Theme.of(context).colorScheme.surface, | ||||||
|       child: CenteredContainer( |       child: CenteredContainer( | ||||||
|         maxWidth: 360, |         maxWidth: 360, | ||||||
|         child: Column( |         child: ListView( | ||||||
|           mainAxisSize: MainAxisSize.min, |           shrinkWrap: true, | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|           children: [ |           children: [ | ||||||
|             ClipRRect( |             Align( | ||||||
|               borderRadius: const BorderRadius.all(Radius.circular(8)), |               alignment: Alignment.centerLeft, | ||||||
|               child: Image.asset('assets/logo.png', width: 64, height: 64), |               child: ClipRRect( | ||||||
|             ).paddingOnly(bottom: 8, left: 4), |                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||||
|  |                 child: Image.asset('assets/logo.png', width: 64, height: 64), | ||||||
|  |               ).paddingOnly(bottom: 8, left: 4), | ||||||
|  |             ), | ||||||
|             Text( |             Text( | ||||||
|               'signupGreeting'.tr, |               'signupGreeting'.tr, | ||||||
|               style: const TextStyle( |               style: const TextStyle( | ||||||
| @@ -136,12 +141,58 @@ class _SignUpScreenState extends State<SignUpScreen> { | |||||||
|               ), |               ), | ||||||
|               onTapOutside: (_) => |               onTapOutside: (_) => | ||||||
|                   FocusManager.instance.primaryFocus?.unfocus(), |                   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), |             const Gap(16), | ||||||
|             Align( |             Align( | ||||||
|               alignment: Alignment.centerRight, |               alignment: Alignment.centerRight, | ||||||
|               child: TextButton( |               child: TextButton( | ||||||
|  |                 onPressed: | ||||||
|  |                     !_isTermAccepted ? null : () => _performAction(context), | ||||||
|                 child: Row( |                 child: Row( | ||||||
|                   mainAxisSize: MainAxisSize.min, |                   mainAxisSize: MainAxisSize.min, | ||||||
|                   children: [ |                   children: [ | ||||||
| @@ -149,12 +200,11 @@ class _SignUpScreenState extends State<SignUpScreen> { | |||||||
|                     const Icon(Icons.chevron_right), |                     const Icon(Icons.chevron_right), | ||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|                 onPressed: () => performAction(context), |  | ||||||
|               ), |               ), | ||||||
|             ) |             ) | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ).paddingAll(24), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -35,6 +35,9 @@ abstract class AppTheme { | |||||||
|         brightness: brightness, |         brightness: brightness, | ||||||
|         seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1), |         seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1), | ||||||
|       ), |       ), | ||||||
|  |       snackBarTheme: const SnackBarThemeData( | ||||||
|  |         behavior: SnackBarBehavior.floating, | ||||||
|  |       ), | ||||||
|       fontFamily: 'Comfortaa', |       fontFamily: 'Comfortaa', | ||||||
|       fontFamilyFallback: [ |       fontFamilyFallback: [ | ||||||
|         'NotoSansSC', |         'NotoSansSC', | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user