diff --git a/lib/auth.dart b/lib/auth.dart index 4fa1a83..2f5c07d 100644 --- a/lib/auth.dart +++ b/lib/auth.dart @@ -14,6 +14,8 @@ class AuthInfo { bool get isAuthed => (_user != null); bool _checked = false; bool get checked => _checked; + bool _isChecking = false; + bool get isChecking => _isChecking; void clear() { _user = null; @@ -26,20 +28,25 @@ class AuthInfo { } Future checkAuth() async { - final re = await api.getUser(); - if (re.ok) { - _user = re.data!; - final u = _user!; - _log.info( - "Logged in as ${u.username} (${u.id}). isAdmin: ${u.isAdmin}. permissions: ${u.permissions}"); - } else if (re.status == 401 || re.status == 1 || re.status == 404) { - _user = null; - } else { - _user = null; - throw re.unwrapErr(); + _isChecking = true; + try { + final re = await api.getUser(); + if (re.ok) { + _user = re.data!; + final u = _user!; + _log.info( + "Logged in as ${u.username} (${u.id}). isAdmin: ${u.isAdmin}. permissions: ${u.permissions}"); + } else if (re.status == 401 || re.status == 1 || re.status == 404) { + _user = null; + } else { + _user = null; + throw re.unwrapErr(); + } + _checked = true; + await getServerStatus(); + return re.ok; + } finally { + _isChecking = false; } - _checked = true; - await getServerStatus(); - return re.ok; } } diff --git a/lib/globals.dart b/lib/globals.dart index 24dbd76..ce5f2cb 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -115,6 +116,7 @@ final GlobalKey rootScaffoldMessengerKey = enum MoreVertSettings { setServerUrl, createRootUser, + settings, } void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) { @@ -125,6 +127,9 @@ void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) { case MoreVertSettings.createRootUser: context.push("/create_root_user"); break; + case MoreVertSettings.settings: + context.push("/settings"); + break; default: break; } @@ -148,6 +153,11 @@ List> buildMoreVertSettings( value: MoreVertSettings.createRootUser, child: Text(AppLocalizations.of(context)!.createRootUser))); } + if (path != "/settings") { + list.add(PopupMenuItem( + value: MoreVertSettings.settings, + child: Text(AppLocalizations.of(context)!.settings))); + } return list; } @@ -184,3 +194,22 @@ mixin ThemeModeWidget on State { : Icons.light_mode)); } } + +enum Lang { + system("System"), + english("English"), + simplifiedChinese("简体中文"); + + const Lang(String lang) : langName = lang; + final String langName; + Locale toLocale() { + switch (this) { + case Lang.english: + return const Locale("en"); + case Lang.simplifiedChinese: + return const Locale("zh"); + default: + return PlatformDispatcher.instance.locale; + } + } +} diff --git a/lib/home.dart b/lib/home.dart index 77e24d4..1cbf499 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -15,7 +15,10 @@ class HomePage extends HookWidget { @override Widget build(BuildContext context) { - if (tryInitApi(context) && !auth.isAuthed && !auth.checked) { + if (tryInitApi(context) && + !auth.isAuthed && + !auth.checked && + !auth.isChecking) { auth.checkAuth().then((re) { if (!re) { if (auth.status!.noUser && diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c784915..14a9080 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -13,5 +13,9 @@ "setServerUrl": "Server URL Settings", "createRootUser": "Create Root User", "skip": "Skip", - "create": "Create" + "create": "Create", + "settings": "Settings", + "lang": "Language", + "systemLang": "System Language", + "reset": "Reset" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 06a9948..a061543 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -13,5 +13,9 @@ "setServerUrl": "服务器地址设置", "createRootUser": "创建根用户", "skip": "跳过", - "create": "创建" + "create": "创建", + "settings": "设置", + "lang": "语言", + "systemLang": "系统语言", + "reset": "重置" } diff --git a/lib/main.dart b/lib/main.dart index efc7e4f..4f539ee 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'home.dart'; import 'login.dart'; import 'logs/file.dart'; import 'set_server.dart'; +import 'settings.dart'; import 'utils.dart'; final _router = GoRouter( @@ -33,6 +34,10 @@ final _router = GoRouter( GoRoute( path: CreateRootUserPage.routeName, builder: (context, state) => const CreateRootUserPage(), + ), + GoRoute( + path: SettingsPage.routeName, + builder: (context, state) => const SettingsPage(), ) ], ); @@ -99,6 +104,8 @@ class _MainApp extends State { ThemeData _themeData = ThemeData(); ThemeData _darkThemeData = ThemeData.dark(); ThemeMode get themeMode => _themeMode; + Lang _lang = Lang.system; + Lang get lang => lang; @override void initState() { @@ -108,6 +115,11 @@ class _MainApp extends State { } catch (e) { _log.warning("Failed to read themeMode from prefs:", e); } + try { + _lang = Lang.values[prefs.getInt("lang") ?? 0]; + } catch (e) { + _log.warning("Failed to read lang from prefs:", e); + } if (kIsWeb || isWindows) { _themeData = _themeData.useSystemChineseFont(); _darkThemeData = _darkThemeData.useSystemChineseFont(); @@ -125,6 +137,7 @@ class _MainApp extends State { } return title; }, + locale: _lang.toLocale(), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: _themeData, @@ -141,4 +154,10 @@ class _MainApp extends State { _themeMode = mode; }); } + + void changeLang(Lang lang) { + setState(() { + _lang = lang; + }); + } } diff --git a/lib/settings.dart b/lib/settings.dart new file mode 100644 index 0000000..6ff3037 --- /dev/null +++ b/lib/settings.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; +import 'globals.dart'; +import 'main.dart'; + +final _log = Logger("SettingsPage"); + +class SettingsPage extends StatefulWidget { + const SettingsPage({Key? key}) : super(key: key); + + static const String routeName = '/settings'; + + @override + State createState() => _SettingsPage(); +} + +class _SettingsPage extends State with ThemeModeWidget { + Lang _ori_lang = Lang.system; + Lang _lang = Lang.system; + @override + void initState() { + super.initState(); + try { + _ori_lang = Lang.values[prefs.getInt("lang") ?? 0]; + _lang = _ori_lang; + } catch (e) { + _log.warning("Failed to get lang:", e); + _ori_lang = Lang.system; + _lang = Lang.system; + } + } + + void fallback(BuildContext context) { + if (_ori_lang != _lang) { + MainApp.of(context).changeLang(_ori_lang); + } + } + + void reset(BuildContext context) { + if (_lang != Lang.system) MainApp.of(context).changeLang(Lang.system); + setState(() { + _lang = Lang.system; + }); + } + + Future save() async { + bool re = true; + if (!await prefs.setInt("lang", _lang.index)) { + re = false; + _log.warning("Failed to save lang."); + } else { + _ori_lang = _lang; + } + return re; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () { + fallback(context); + context.canPop() ? context.pop() : context.go("/"); + }, + icon: const Icon(Icons.arrow_back), + ), + title: Text(AppLocalizations.of(context)!.settings), + actions: [ + buildThemeModeIcon(context), + buildMoreVertSettingsButon(context), + ], + ), + body: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: constraints.maxHeight), + child: Container( + padding: MediaQuery.of(context).size.width > 810 + ? const EdgeInsets.symmetric(horizontal: 100) + : null, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: DropdownMenu( + initialSelection: _lang, + onSelected: (value) { + if (value != null) { + setState(() { + _lang = value; + }); + MainApp.of(context).changeLang(_lang); + } + }, + label: + Text(AppLocalizations.of(context)!.lang), + dropdownMenuEntries: Lang.values + .map((e) => DropdownMenuEntry( + value: e, + label: e == Lang.system + ? AppLocalizations.of(context)! + .systemLang + : e.langName)) + .toList(), + leadingIcon: const Icon(Icons.language), + )), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8), + child: ElevatedButton( + onPressed: () { + reset(context); + }, + child: Text( + AppLocalizations.of(context)! + .reset))), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8), + child: ElevatedButton( + onPressed: () { + save(); + }, + child: Text( + AppLocalizations.of(context)!.save), + )) + ], + ), + ], + )))); + }, + )); + } +}