diff --git a/lib/bootstrapper.dart b/lib/bootstrapper.dart index e1dac38..ea382bb 100644 --- a/lib/bootstrapper.dart +++ b/lib/bootstrapper.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:solian/exts.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/providers/content/channel.dart'; import 'package:solian/providers/relation.dart'; import 'package:solian/providers/websocket.dart'; import 'package:solian/services.dart'; +import 'package:solian/theme.dart'; import 'package:solian/widgets/sized_container.dart'; class BootstrapperShell extends StatefulWidget { @@ -29,6 +31,18 @@ class _BootstrapperShellState extends State { int _periodCursor = 0; late final List<({String label, Future Function() action})> _periods = [ + ( + label: 'bsLoadingTheme', + action: () async { + final prefs = await SharedPreferences.getInstance(); + if (prefs.containsKey('global_theme_color')) { + final value = prefs.getInt('global_theme_color')!; + final color = Color(value); + currentLightTheme = SolianTheme.build(Brightness.light, seedColor: color); + currentDarkTheme = SolianTheme.build(Brightness.dark, seedColor: color); + } + } + ), ( label: 'bsCheckingServer', action: () async { diff --git a/lib/main.dart b/lib/main.dart index ff378f0..8f8c525 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -78,8 +78,8 @@ class SolianApp extends StatelessWidget { Widget build(BuildContext context) { return GetMaterialApp.router( title: 'Solian', - theme: SolianTheme.build(Brightness.light), - darkTheme: SolianTheme.build(Brightness.dark), + theme: currentLightTheme, + darkTheme: currentDarkTheme, themeMode: ThemeMode.system, routerDelegate: AppRouter.instance.routerDelegate, routeInformationParser: AppRouter.instance.routeInformationParser, diff --git a/lib/router.dart b/lib/router.dart index ea9c570..8cc259e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -18,6 +18,7 @@ import 'package:solian/screens/realms/realm_organize.dart'; import 'package:solian/screens/realms/realm_view.dart'; import 'package:solian/screens/home.dart'; import 'package:solian/screens/posts/post_editor.dart'; +import 'package:solian/screens/settings.dart'; import 'package:solian/shells/root_shell.dart'; import 'package:solian/shells/title_shell.dart'; @@ -34,6 +35,22 @@ abstract class AppRouter { _chatRoute, _realmRoute, _accountRoute, + GoRoute( + path: '/about', + name: 'about', + builder: (context, state) => TitleShell( + state: state, + child: const AboutScreen(), + ), + ), + GoRoute( + path: '/settings', + name: 'settings', + builder: (context, state) => TitleShell( + state: state, + child: const SettingScreen(), + ), + ), ], ), ], @@ -210,14 +227,6 @@ abstract class AppRouter { name: state.pathParameters['name']!, ), ), - GoRoute( - path: '/about', - name: 'about', - builder: (context, state) => TitleShell( - state: state, - child: const AboutScreen(), - ), - ), ], ); } diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart new file mode 100644 index 0000000..89ed471 --- /dev/null +++ b/lib/screens/settings.dart @@ -0,0 +1,81 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:solian/exts.dart'; +import 'package:solian/theme.dart'; + +class SettingScreen extends StatefulWidget { + const SettingScreen({super.key}); + + @override + State createState() => _SettingScreenState(); +} + +class _SettingScreenState extends State { + late final SharedPreferences _prefs; + + Widget _buildCaptionHeader(String title) { + return Container( + width: MediaQuery.of(context).size.width, + color: Theme.of(context).colorScheme.surfaceContainer, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Text(title), + ); + } + + Widget _buildThemeColorButton(String label, Color color) { + return IconButton( + icon: Icon(Icons.circle, color: color), + tooltip: label, + onPressed: () { + currentLightTheme = + SolianTheme.build(Brightness.light, seedColor: color); + currentDarkTheme = SolianTheme.build(Brightness.dark, seedColor: color); + _prefs.setInt('global_theme_color', color.value); + context.showSnackbar('themeColorApplied'.tr); + }, + ); + } + + @override + void initState() { + super.initState(); + SharedPreferences.getInstance().then((inst) { + _prefs = inst; + }); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.surface, + child: ListView( + children: [ + _buildCaptionHeader('themeColor'.tr), + SizedBox( + height: 56, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + _buildThemeColorButton( + 'themeColorRed'.tr, + const Color.fromRGBO(154, 98, 91, 1), + ), + _buildThemeColorButton( + 'themeColorBlue'.tr, + const Color.fromRGBO(103, 96, 193, 1), + ), + _buildThemeColorButton( + 'themeColorMiku'.tr, + const Color.fromRGBO(56, 120, 126, 1), + ), + ], + ).paddingSymmetric(horizontal: 12, vertical: 8), + ), + ], + ), + ); + } +} diff --git a/lib/theme.dart b/lib/theme.dart index 552d761..3819919 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:solian/platform.dart'; +ThemeData? currentLightTheme = SolianTheme.build(Brightness.light); +ThemeData? currentDarkTheme = SolianTheme.build(Brightness.dark); + abstract class SolianTheme { static bool isLargeScreen(BuildContext context) => MediaQuery.of(context).size.width > 640; @@ -27,13 +30,13 @@ abstract class SolianTheme { } } - static ThemeData build(Brightness brightness) { + static ThemeData build(Brightness brightness, {Color? seedColor}) { return ThemeData( brightness: brightness, useMaterial3: true, colorScheme: ColorScheme.fromSeed( brightness: brightness, - seedColor: const Color.fromRGBO(154, 98, 91, 1), + seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1), ), ); } diff --git a/lib/translations/en_us.dart b/lib/translations/en_us.dart index 143b33e..e5766fb 100644 --- a/lib/translations/en_us.dart +++ b/lib/translations/en_us.dart @@ -27,6 +27,7 @@ const i18nEnglish = { 'about': 'About', 'edit': 'Edit', 'delete': 'Delete', + 'settings': 'Settings', 'search': 'Search', 'post': 'Post', 'article': 'Article', @@ -293,6 +294,7 @@ const i18nEnglish = { 'accountStatusNegative': 'Negative', 'accountStatusNeutral': 'Neutral', 'accountStatusPositive': 'Positive', + 'bsLoadingTheme': 'Loading Theme', 'bsCheckingServer': 'Checking Server Status', 'bsCheckingServerFail': 'Unable connect to server, check your network connection', @@ -304,4 +306,9 @@ const i18nEnglish = { 'postShareContent': '@content\n\n@username on the Solar Network\nCheck it out: @link', 'postShareSubject': '@username posted a post on the Solar Network', + 'themeColor': 'Global Theme Color', + 'themeColorRed': 'Modern Red', + 'themeColorBlue': 'Classic Blue', + 'themeColorMiku': 'Miku Blue', + 'themeColorApplied': 'Global theme color has been saved, restart to get applied.', }; diff --git a/lib/translations/zh_cn.dart b/lib/translations/zh_cn.dart index b1a999d..baaabc2 100644 --- a/lib/translations/zh_cn.dart +++ b/lib/translations/zh_cn.dart @@ -13,6 +13,7 @@ const i18nSimplifiedChinese = { 'about': '关于', 'edit': '编辑', 'delete': '删除', + 'settings': '设置', 'page': '页面', 'draft': '草稿', 'draftSave': '存为草稿', @@ -272,6 +273,7 @@ const i18nSimplifiedChinese = { 'accountStatusNegative': '负面', 'accountStatusNeutral': '中性', 'accountStatusPositive': '积极', + 'bsLoadingTheme': '正在装载主题', 'bsCheckingServer': '检查服务器状态中', 'bsCheckingServerFail': '无法连接至服务器,请检查你的网络连接状态', 'bsCheckingServerDown': '当前服务器不可用,请稍后重试', @@ -281,4 +283,9 @@ const i18nSimplifiedChinese = { 'bsRegisteringPushNotify': '正在启用推送通知', 'postShareContent': '@content\n\n@username 在 Solar Network\n原帖地址:@link', 'postShareSubject': '@username 在 Solar Network 上发布了一篇帖子', + 'themeColor': '全局主题色', + 'themeColorRed': '现代红', + 'themeColorBlue': '经典蓝', + 'themeColorMiku': '未来色', + 'themeColorApplied': '全局主题颜色已保存,重启后生效。', }; diff --git a/lib/widgets/navigation/app_navigation_drawer.dart b/lib/widgets/navigation/app_navigation_drawer.dart index 1e26ee3..06368cf 100644 --- a/lib/widgets/navigation/app_navigation_drawer.dart +++ b/lib/widgets/navigation/app_navigation_drawer.dart @@ -138,17 +138,11 @@ class _AppNavigationDrawerState extends State { ); }), trailing: IconButton( - icon: const Icon(Icons.face_retouching_natural), + icon: const Icon(Icons.settings), onPressed: () { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => AccountStatusAction( - currentStatus: _accountStatus!.status, - ), - ).then((val) { - if (val == true) getStatus(); - }); + AppRouter.instance.pushNamed('settings'); + setState(() => _selectedIndex = null); + closeDrawer(); }, ), onTap: () { @@ -156,6 +150,17 @@ class _AppNavigationDrawerState extends State { setState(() => _selectedIndex = null); closeDrawer(); }, + onLongPress: () { + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) => AccountStatusAction( + currentStatus: _accountStatus!.status, + ), + ).then((val) { + if (val == true) getStatus(); + }); + }, ); }).paddingOnly(top: 8), const Divider(thickness: 0.3, height: 1).paddingOnly(