From e334b862dfdc05d439fe0533ba294ab13a0fea10 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 13 Oct 2024 14:13:16 +0800 Subject: [PATCH] :sparkles: Auth preferences --- assets/locales/en_us.json | 6 +- assets/locales/zh_cn.json | 6 +- lib/router.dart | 9 ++ lib/screens/account.dart | 9 ++ .../account/preferences/notifications.dart | 4 +- lib/screens/account/preferences/security.dart | 118 ++++++++++++++++++ lib/screens/dashboard.dart | 6 +- 7 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 lib/screens/account/preferences/security.dart diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index 2c846a2..f0849c7 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -477,5 +477,9 @@ "agedTheme": "Old school style theme", "agedThemeDesc": "Downgrade the global theme to Material Design 2. Unexpected issues may occur. For experimental use only.", "appBackgroundImage": "Global background image", - "appBackgroundImageDesc": "The global background image will be displayed on all pages" + "appBackgroundImageDesc": "The global background image will be displayed on all pages", + "authPreferences": "Auth preferences", + "authPreferencesDesc": "Set the security behavior of your account", + "authMaximumAuthSteps": "Maximum authentication steps", + "authMaximumAuthStepsDesc": "The maximum number of authentication steps when logging in, higher value is more secure, lower value is more convenient; default is 2" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index 884fe0a..71ad5de 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -473,5 +473,9 @@ "agedTheme": "过时主题", "agedThemeDesc": "将全局主题降级为 Material Design 2,可能发生意料之外的问题,仅供实验使用", "appBackgroundImage": "全局背景图片", - "appBackgroundImageDesc": "全局背景图片将会在所有页面中展示" + "appBackgroundImageDesc": "全局背景图片将会在所有页面中展示", + "authPreferences": "安全偏好设置", + "authPreferencesDesc": "调整账号的安全行为模式", + "authMaximumAuthSteps": "最大认证步数", + "authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2" } diff --git a/lib/router.dart b/lib/router.dart index 3812a6c..0d8ea79 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -7,6 +7,7 @@ import 'package:solian/screens/about.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/preferences/notifications.dart'; +import 'package:solian/screens/account/preferences/security.dart'; import 'package:solian/screens/account/profile_edit.dart'; import 'package:solian/screens/account/profile_page.dart'; import 'package:solian/screens/auth/signin.dart'; @@ -264,6 +265,14 @@ abstract class AppRouter { child: const NotificationPreferencesScreen(), ), ), + GoRoute( + path: '/account/preferences/auth', + name: 'authPreferences', + builder: (context, state) => TitleShell( + state: state, + child: const AuthPreferencesScreen(), + ), + ), GoRoute( path: '/account/view/:name', name: 'accountProfilePage', diff --git a/lib/screens/account.dart b/lib/screens/account.dart index e293680..525c7eb 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -129,6 +129,15 @@ class _AccountScreenState extends State { AppRouter.instance.pushNamed('settings'); }, ), + if (auth.isAuthorized.value) + ListTile( + leading: const Icon(Icons.lock), + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + title: Text('authPreferences'.tr), + onTap: () { + AppRouter.instance.pushNamed('authPreferences'); + }, + ), if (auth.isAuthorized.value) ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 34), diff --git a/lib/screens/account/preferences/notifications.dart b/lib/screens/account/preferences/notifications.dart index 43e143c..0c62d49 100644 --- a/lib/screens/account/preferences/notifications.dart +++ b/lib/screens/account/preferences/notifications.dart @@ -59,10 +59,10 @@ class _NotificationPreferencesScreenState }); if (resp.statusCode != 200) { context.showErrorDialog(RequestException(resp)); + } else { + context.showSnackbar('preferencesApplied'.tr); } - context.showSnackbar('preferencesApplied'.tr); - setState(() => _isBusy = false); } diff --git a/lib/screens/account/preferences/security.dart b/lib/screens/account/preferences/security.dart new file mode 100644 index 0000000..6ac3b05 --- /dev/null +++ b/lib/screens/account/preferences/security.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:get/get.dart'; +import 'package:get/get_connect/http/src/exceptions/exceptions.dart'; +import 'package:solian/exceptions/request.dart'; +import 'package:solian/exts.dart'; +import 'package:solian/providers/auth.dart'; + +class AuthPreferencesScreen extends StatefulWidget { + const AuthPreferencesScreen({super.key}); + + @override + State createState() => _AuthPreferencesScreenState(); +} + +class _AuthPreferencesScreenState extends State { + bool _isBusy = true; + + Map _config = { + 'maximum_auth_steps': 2, + }; + + Future _getPreferences() async { + setState(() => _isBusy = true); + + final auth = Get.find(); + if (!auth.isAuthorized.value) throw UnauthorizedException(); + + final client = await auth.configureClient('id'); + final resp = await client.get('/preferences/auth'); + if (resp.statusCode != 200 && resp.statusCode != 404) { + context.showErrorDialog(RequestException(resp)); + } + + if (resp.statusCode == 200) { + _config = resp.body; + } + + setState(() => _isBusy = false); + } + + Future _savePreferences() async { + setState(() => _isBusy = true); + + final auth = Get.find(); + if (!auth.isAuthorized.value) throw UnauthorizedException(); + + final client = await auth.configureClient('id'); + final resp = await client.put('/preferences/auth', _config); + if (resp.statusCode != 200) { + context.showErrorDialog(RequestException(resp)); + } else { + context.showSnackbar('preferencesApplied'.tr); + } + + setState(() => _isBusy = false); + } + + @override + void initState() { + super.initState(); + _getPreferences(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + if (_isBusy) const LinearProgressIndicator().animate().scaleX(), + ListTile( + tileColor: Theme.of(context).colorScheme.surfaceContainer, + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: const Icon(Icons.save), + title: Text('save'.tr), + enabled: !_isBusy, + onTap: () { + _savePreferences(); + }, + ), + Expanded( + child: ListView( + children: [ + ListTile( + title: Text('authMaximumAuthSteps'.tr), + subtitle: Text('authMaximumAuthStepsDesc'.tr), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + trailing: SizedBox( + width: 60, + child: _isBusy + ? null + : TextFormField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + isDense: true, + ), + initialValue: + _config['maximum_auth_steps']?.toString() ?? '2', + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + onChanged: (value) { + _config['maximum_auth_steps'] = + int.tryParse(value) ?? 2; + }, + ), + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index f293d40..ca10da0 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -389,10 +389,14 @@ class _DashboardScreenState extends State { onUpdate: (_) { _pullPosts(); }, + padding: EdgeInsets.symmetric( + vertical: 8, + horizontal: 4, + ), backgroundColor: Theme.of(context) .colorScheme .surfaceContainerLow, - ).paddingAll(8), + ), ), ), ).paddingSymmetric(horizontal: 8),