Make theme switcher easier to use

This commit is contained in:
LittleSheep 2024-07-31 22:48:22 +08:00
parent 31d50bfb1f
commit 52e58fce3d
9 changed files with 149 additions and 111 deletions

View File

@ -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(),
),
),
);
},
); );
}, }),
); );
} }

View 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();
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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.',
}; };

View File

@ -289,5 +289,5 @@ const i18nSimplifiedChinese = {
'themeColorMiku': '未来色', 'themeColorMiku': '未来色',
'themeColorKagamine': '镜音黄', 'themeColorKagamine': '镜音黄',
'themeColorLuka': '流音粉', 'themeColorLuka': '流音粉',
'themeColorApplied': '全局主题颜色已应用,深色模式中主题需要重启生效', 'themeColorApplied': '全局主题颜色已应用',
}; };

View File

@ -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:

View File

@ -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:

View File

@ -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>