♿ Make theme switcher easier to use
This commit is contained in:
		| @@ -5,11 +5,12 @@ import 'package:get/get.dart'; | |||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:media_kit/media_kit.dart'; | import 'package:media_kit/media_kit.dart'; | ||||||
| import 'package:protocol_handler/protocol_handler.dart'; | import 'package:protocol_handler/protocol_handler.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
| import 'package:sentry_flutter/sentry_flutter.dart'; | import 'package:sentry_flutter/sentry_flutter.dart'; | ||||||
| import 'package:shared_preferences/shared_preferences.dart'; |  | ||||||
| import 'package:solian/bootstrapper.dart'; | import 'package:solian/bootstrapper.dart'; | ||||||
| import 'package:solian/firebase_options.dart'; | import 'package:solian/firebase_options.dart'; | ||||||
| import 'package:solian/platform.dart'; | import 'package:solian/platform.dart'; | ||||||
|  | import 'package:solian/providers/theme_switcher.dart'; | ||||||
| import 'package:solian/providers/websocket.dart'; | import 'package:solian/providers/websocket.dart'; | ||||||
| import 'package:solian/providers/auth.dart'; | import 'package:solian/providers/auth.dart'; | ||||||
| import 'package:solian/providers/content/attachment.dart'; | import 'package:solian/providers/content/attachment.dart'; | ||||||
| @@ -38,7 +39,6 @@ void main() async { | |||||||
|       MediaKit.ensureInitialized(); |       MediaKit.ensureInitialized(); | ||||||
|  |  | ||||||
|       await Future.wait([ |       await Future.wait([ | ||||||
|         _initializeTheme(), |  | ||||||
|         _initializeFirebase(), |         _initializeFirebase(), | ||||||
|         _initializePlatformComponents(), |         _initializePlatformComponents(), | ||||||
|       ]); |       ]); | ||||||
| @@ -51,16 +51,6 @@ void main() async { | |||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| Future<void> _initializeTheme() 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); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Future<void> _initializeFirebase() async { | Future<void> _initializeFirebase() async { | ||||||
|   await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); |   await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); | ||||||
| } | } | ||||||
| @@ -83,33 +73,45 @@ Future<void> _initializePlatformComponents() async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | final themeSwitcher = ThemeSwitcher( | ||||||
|  |   lightThemeData: SolianTheme.build(Brightness.light), | ||||||
|  |   darkThemeData: SolianTheme.build(Brightness.dark), | ||||||
|  | )..restoreTheme(); | ||||||
|  |  | ||||||
| class SolianApp extends StatelessWidget { | class SolianApp extends StatelessWidget { | ||||||
|   const SolianApp({super.key}); |   const SolianApp({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return GetMaterialApp.router( |     return ChangeNotifierProvider.value( | ||||||
|       title: 'Solian', |       value: themeSwitcher, | ||||||
|       theme: currentLightTheme, |       child: Builder(builder: (context) { | ||||||
|       darkTheme: currentDarkTheme, |         final theme = Provider.of<ThemeSwitcher>(context); | ||||||
|       themeMode: ThemeMode.system, |  | ||||||
|       routerDelegate: AppRouter.instance.routerDelegate, |         return GetMaterialApp.router( | ||||||
|       routeInformationParser: AppRouter.instance.routeInformationParser, |           title: 'Solian', | ||||||
|       routeInformationProvider: AppRouter.instance.routeInformationProvider, |           theme: theme.lightThemeData, | ||||||
|       backButtonDispatcher: AppRouter.instance.backButtonDispatcher, |           darkTheme: theme.darkThemeData, | ||||||
|       translations: SolianMessages(), |           themeMode: ThemeMode.system, | ||||||
|       locale: Get.deviceLocale, |           routerDelegate: AppRouter.instance.routerDelegate, | ||||||
|       fallbackLocale: const Locale('en', 'US'), |           routeInformationParser: AppRouter.instance.routeInformationParser, | ||||||
|       onInit: () => _initializeProviders(context), |           routeInformationProvider: AppRouter.instance.routeInformationProvider, | ||||||
|       builder: (context, child) { |           backButtonDispatcher: AppRouter.instance.backButtonDispatcher, | ||||||
|         return SystemShell( |           translations: SolianMessages(), | ||||||
|           child: ScaffoldMessenger( |           locale: Get.deviceLocale, | ||||||
|             child: BootstrapperShell( |           fallbackLocale: const Locale('en', 'US'), | ||||||
|               child: child ?? const SizedBox(), |           onInit: () => _initializeProviders(context), | ||||||
|             ), |           builder: (context, child) { | ||||||
|           ), |             return SystemShell( | ||||||
|  |               child: ScaffoldMessenger( | ||||||
|  |                 child: BootstrapperShell( | ||||||
|  |                   child: child ?? const SizedBox(), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ); | ||||||
|  |           }, | ||||||
|         ); |         ); | ||||||
|       }, |       }), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								lib/providers/theme_switcher.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/providers/theme_switcher.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
|  | import 'package:solian/theme.dart'; | ||||||
|  |  | ||||||
|  | class ThemeSwitcher extends ChangeNotifier { | ||||||
|  |   ThemeData lightThemeData; | ||||||
|  |   ThemeData darkThemeData; | ||||||
|  |  | ||||||
|  |   ThemeSwitcher({ | ||||||
|  |     required this.lightThemeData, | ||||||
|  |     required this.darkThemeData, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   Future<void> restoreTheme() async { | ||||||
|  |     final prefs = await SharedPreferences.getInstance(); | ||||||
|  |     if (prefs.containsKey('global_theme_color')) { | ||||||
|  |       final value = prefs.getInt('global_theme_color')!; | ||||||
|  |       final color = Color(value); | ||||||
|  |       lightThemeData = SolianTheme.build(Brightness.light, seedColor: color); | ||||||
|  |       darkThemeData = SolianTheme.build(Brightness.dark, seedColor: color); | ||||||
|  |       notifyListeners(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void setTheme(ThemeData light, dark) { | ||||||
|  |     lightThemeData = light; | ||||||
|  |     darkThemeData = dark; | ||||||
|  |     notifyListeners(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -2,8 +2,10 @@ import 'dart:ui'; | |||||||
|  |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||||
|  | import 'package:provider/provider.dart'; | ||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:solian/exts.dart'; | import 'package:solian/exts.dart'; | ||||||
|  | import 'package:solian/providers/theme_switcher.dart'; | ||||||
| import 'package:solian/theme.dart'; | import 'package:solian/theme.dart'; | ||||||
|  |  | ||||||
| class SettingScreen extends StatefulWidget { | class SettingScreen extends StatefulWidget { | ||||||
| @@ -30,22 +32,16 @@ class _SettingScreenState extends State<SettingScreen> { | |||||||
|       icon: Icon(Icons.circle, color: color), |       icon: Icon(Icons.circle, color: color), | ||||||
|       tooltip: label, |       tooltip: label, | ||||||
|       onPressed: () { |       onPressed: () { | ||||||
|         currentLightTheme = SolianTheme.build( |         context.read<ThemeSwitcher>().setTheme( | ||||||
|           Brightness.light, |               SolianTheme.build( | ||||||
|           seedColor: color, |                 Brightness.light, | ||||||
|         ); |                 seedColor: color, | ||||||
|         currentDarkTheme = SolianTheme.build( |               ), | ||||||
|           Brightness.dark, |               SolianTheme.build( | ||||||
|           seedColor: color, |                 Brightness.dark, | ||||||
|         ); |                 seedColor: color, | ||||||
|         if (!Get.isDarkMode) { |               ), | ||||||
|           Get.changeTheme( |             ); | ||||||
|             SolianTheme.build(Brightness.light, seedColor: color), |  | ||||||
|           ); |  | ||||||
|         } else { |  | ||||||
|           // Dark mode cannot be hot reload |  | ||||||
|           // https://github.com/jonataslaw/getx/issues/1411 |  | ||||||
|         } |  | ||||||
|         _prefs.setInt('global_theme_color', color.value); |         _prefs.setInt('global_theme_color', color.value); | ||||||
|         context.clearSnackbar(); |         context.clearSnackbar(); | ||||||
|         context.showSnackbar('themeColorApplied'.tr); |         context.showSnackbar('themeColorApplied'.tr); | ||||||
|   | |||||||
| @@ -1,9 +1,6 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:solian/platform.dart'; | import 'package:solian/platform.dart'; | ||||||
|  |  | ||||||
| ThemeData? currentLightTheme = SolianTheme.build(Brightness.light); |  | ||||||
| ThemeData? currentDarkTheme = SolianTheme.build(Brightness.dark); |  | ||||||
|  |  | ||||||
| abstract class SolianTheme { | abstract class SolianTheme { | ||||||
|   static bool isLargeScreen(BuildContext context) => |   static bool isLargeScreen(BuildContext context) => | ||||||
|       MediaQuery.of(context).size.width > 640; |       MediaQuery.of(context).size.width > 640; | ||||||
|   | |||||||
| @@ -312,6 +312,5 @@ const i18nEnglish = { | |||||||
|   'themeColorMiku': 'Miku Blue', |   'themeColorMiku': 'Miku Blue', | ||||||
|   'themeColorKagamine': 'Kagamine Yellow', |   'themeColorKagamine': 'Kagamine Yellow', | ||||||
|   'themeColorLuka': 'Luka Pink', |   'themeColorLuka': 'Luka Pink', | ||||||
|   'themeColorApplied': |   'themeColorApplied': 'Global theme color has been applied.', | ||||||
|       'Global theme color has been applied, dark mode theme need restart to get applied.', |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -289,5 +289,5 @@ const i18nSimplifiedChinese = { | |||||||
|   'themeColorMiku': '未来色', |   'themeColorMiku': '未来色', | ||||||
|   'themeColorKagamine': '镜音黄', |   'themeColorKagamine': '镜音黄', | ||||||
|   'themeColorLuka': '流音粉', |   'themeColorLuka': '流音粉', | ||||||
|   'themeColorApplied': '全局主题颜色已应用,深色模式中主题需要重启生效', |   'themeColorApplied': '全局主题颜色已应用', | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -1016,6 +1016,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.5" |     version: "1.0.5" | ||||||
|  |   nested: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: nested | ||||||
|  |       sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.0.0" | ||||||
|   nm: |   nm: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1272,6 +1280,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.2.0" |     version: "0.2.0" | ||||||
|  |   provider: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: provider | ||||||
|  |       sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "6.1.2" | ||||||
|   pub_semver: |   pub_semver: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ name: solian | |||||||
| description: "The Solar Network App" | description: "The Solar Network App" | ||||||
| publish_to: "none" | publish_to: "none" | ||||||
|  |  | ||||||
| version: 1.2.0+3 | version: 1.2.0+4 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ">=3.3.4 <4.0.0" |   sdk: ">=3.3.4 <4.0.0" | ||||||
| @@ -61,6 +61,7 @@ dependencies: | |||||||
|   flutter_markdown_selectionarea: ^0.6.17+1 |   flutter_markdown_selectionarea: ^0.6.17+1 | ||||||
|   shared_preferences: ^2.2.3 |   shared_preferences: ^2.2.3 | ||||||
|   easy_debounce: ^2.0.3 |   easy_debounce: ^2.0.3 | ||||||
|  |   provider: ^6.1.2 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
							
								
								
									
										107
									
								
								web/index.html
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								web/index.html
									
									
									
									
									
								
							| @@ -1,8 +1,7 @@ | |||||||
| <!DOCTYPE html> | <!doctype html> | ||||||
| <html> | <html> | ||||||
|  |   <head> | ||||||
| <head> |     <!-- | ||||||
|   <!-- |  | ||||||
|     If you are serving your web app in a path other than the root, change the |     If you are serving your web app in a path other than the root, change the | ||||||
|     href value below to reflect the base path you are serving from. |     href value below to reflect the base path you are serving from. | ||||||
|  |  | ||||||
| @@ -15,66 +14,64 @@ | |||||||
|     This is a placeholder for base href that will be replaced by the value of |     This is a placeholder for base href that will be replaced by the value of | ||||||
|     the `--base-href` argument provided to `flutter build`. |     the `--base-href` argument provided to `flutter build`. | ||||||
|   --> |   --> | ||||||
|   <!-- <base href="$FLUTTER_BASE_HREF"> --> |     <base href="$FLUTTER_BASE_HREF" /> | ||||||
|   <base href="/" /> |  | ||||||
|  |  | ||||||
|   <meta charset="UTF-8"> |     <meta charset="UTF-8" /> | ||||||
|   <meta content="IE=Edge" http-equiv="X-UA-Compatible"> |     <meta content="IE=Edge" http-equiv="X-UA-Compatible" /> | ||||||
|   <meta name="description" content="A new Flutter project."> |     <meta name="description" content="A new Flutter project." /> | ||||||
|  |  | ||||||
|   <!-- iOS meta tags & icons --> |     <!-- iOS meta tags & icons --> | ||||||
|   <meta name="apple-mobile-web-app-capable" content="yes"> |     <meta name="apple-mobile-web-app-capable" content="yes" /> | ||||||
|   <meta name="apple-mobile-web-app-status-bar-style" content="black"> |     <meta name="apple-mobile-web-app-status-bar-style" content="black" /> | ||||||
|   <meta name="apple-mobile-web-app-title" content="solian"> |     <meta name="apple-mobile-web-app-title" content="solian" /> | ||||||
|   <link rel="apple-touch-icon" href="icons/Icon-192.png"> |     <link rel="apple-touch-icon" href="icons/Icon-192.png" /> | ||||||
|  |  | ||||||
|   <!-- Favicon --> |     <!-- Favicon --> | ||||||
|   <link rel="icon" type="image/png" href="favicon.png" /> |     <link rel="icon" type="image/png" href="favicon.png" /> | ||||||
|  |  | ||||||
|   <!-- Loading styles --> |     <!-- Loading styles --> | ||||||
|   <style> |     <style> | ||||||
|     .loader-container { |       .loader-container { | ||||||
|       display: flex; |         display: flex; | ||||||
|       justify-content: center; |         justify-content: center; | ||||||
|       align-items: center; |         align-items: center; | ||||||
|       margin: 0; |         margin: 0; | ||||||
|       position: absolute; |         position: absolute; | ||||||
|       top: 50%; |         top: 50%; | ||||||
|       left: 50%; |         left: 50%; | ||||||
|       -ms-transform: translate(-50%, -50%); |         -ms-transform: translate(-50%, -50%); | ||||||
|       transform: translate(-50%, -50%); |         transform: translate(-50%, -50%); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .loader { |  | ||||||
|       border: 10px solid #f3f3f3; |  | ||||||
|       border-top: 10px solid #8f94ca; |  | ||||||
|       border-radius: 50%; |  | ||||||
|       width: 80px; |  | ||||||
|       height: 80px; |  | ||||||
|       animation: spin .35s linear infinite; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @keyframes spin { |  | ||||||
|       0% { |  | ||||||
|         transform: rotate(0deg); |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       100% { |       .loader { | ||||||
|         transform: rotate(360deg); |         border: 10px solid #f3f3f3; | ||||||
|  |         border-top: 10px solid #8f94ca; | ||||||
|  |         border-radius: 50%; | ||||||
|  |         width: 80px; | ||||||
|  |         height: 80px; | ||||||
|  |         animation: spin 0.35s linear infinite; | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|   </style> |  | ||||||
|  |  | ||||||
|   <title>Solian</title> |       @keyframes spin { | ||||||
|   <link rel="manifest" href="manifest.json"> |         0% { | ||||||
| </head> |           transform: rotate(0deg); | ||||||
|  |         } | ||||||
|  |  | ||||||
| <body> |         100% { | ||||||
|   <div class="loader-container"> |           transform: rotate(360deg); | ||||||
|     <div class="loader"></div> |         } | ||||||
|   </div> |       } | ||||||
|  |     </style> | ||||||
|  |  | ||||||
|   <script src="flutter_bootstrap.js" async></script> |     <title>Solian</title> | ||||||
| </body> |     <link rel="manifest" href="manifest.json" /> | ||||||
|  |   </head> | ||||||
|  |  | ||||||
|  |   <body> | ||||||
|  |     <div class="loader-container"> | ||||||
|  |       <div class="loader"></div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <script src="flutter_bootstrap.js" async></script> | ||||||
|  |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user