diff --git a/lib/components/color_dialog/color_dialog_logic.dart b/lib/components/color_dialog/color_dialog_logic.dart deleted file mode 100644 index 6f65e31..0000000 --- a/lib/components/color_dialog/color_dialog_logic.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:ui'; - -import 'package:moodiary/pages/home/setting/setting_logic.dart'; -import 'package:moodiary/presentation/pref.dart'; -import 'package:moodiary/utils/theme_util.dart'; -import 'package:refreshed/refreshed.dart'; - -import 'color_dialog_state.dart'; - -class ColorDialogLogic extends GetxController { - final ColorDialogState state = ColorDialogState(); - - late SettingLogic settingLogic = Bind.find(); - - @override - void onReady() { - //如果支持系统颜色,获取系统颜色 - if (state.supportDynamic) { - state.systemColor = Color(PrefUtil.getValue('systemColor')!); - update(); - } - super.onReady(); - } - - //更改主题色 - Future changeSeedColor(index) async { - await PrefUtil.setValue('color', index); - state.currentColor = index; - settingLogic.state.color = index; - update(); - Get.changeTheme(await ThemeUtil.buildTheme(Brightness.light)); - Get.changeTheme(await ThemeUtil.buildTheme(Brightness.dark)); - } -} diff --git a/lib/components/color_dialog/color_dialog_state.dart b/lib/components/color_dialog/color_dialog_state.dart deleted file mode 100644 index aad1e0b..0000000 --- a/lib/components/color_dialog/color_dialog_state.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:moodiary/presentation/pref.dart'; - -class ColorDialogState { - bool supportDynamic = PrefUtil.getValue('supportDynamicColor')!; - int currentColor = PrefUtil.getValue('color')!; - - Color? systemColor; - - ColorDialogState(); -} diff --git a/lib/components/color_dialog/color_dialog_view.dart b/lib/components/color_dialog/color_dialog_view.dart deleted file mode 100644 index 469b6e1..0000000 --- a/lib/components/color_dialog/color_dialog_view.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:moodiary/common/values/colors.dart'; -import 'package:moodiary/main.dart'; -import 'package:refreshed/refreshed.dart'; - -import 'color_dialog_logic.dart'; - -class ColorDialogComponent extends StatelessWidget { - const ColorDialogComponent({super.key}); - - @override - Widget build(BuildContext context) { - final logic = Get.put(ColorDialogLogic()); - final state = Bind.find().state; - - List buildColorList() { - return List.generate(AppColor.themeColorList.length, (index) { - return SimpleDialogOption( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 10.0, - children: [ - Icon( - Icons.circle, - color: AppColor.themeColorList[index], - ), - Text(AppColor.colorName(index)), - if (state.currentColor == index) ...[const Icon(Icons.check)] - ], - ), - onPressed: () { - logic.changeSeedColor(index); - }, - ); - }); - } - - Widget buildSystemColor() { - return SimpleDialogOption( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 10.0, - children: [ - Icon( - Icons.circle, - color: state.systemColor, - ), - Text(l10n.colorNameSystem), - if (state.currentColor == -1) ...[const Icon(Icons.check)] - ], - ), - onPressed: () { - logic.changeSeedColor(-1); - }, - ); - } - - return GetBuilder( - assignId: true, - builder: (_) { - return SimpleDialog( - title: Text(l10n.settingColor), - children: [ - if (state.supportDynamic) ...[buildSystemColor()], - ...buildColorList() - ], - ); - }, - ); - } -} diff --git a/lib/components/color_sheet/color_sheet_logic.dart b/lib/components/color_sheet/color_sheet_logic.dart index 24b6370..b54ae4f 100644 --- a/lib/components/color_sheet/color_sheet_logic.dart +++ b/lib/components/color_sheet/color_sheet_logic.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:moodiary/pages/home/setting/setting_logic.dart'; import 'package:moodiary/presentation/pref.dart'; import 'package:moodiary/utils/theme_util.dart'; @@ -12,29 +10,12 @@ class ColorSheetLogic extends GetxController { late SettingLogic settingLogic = Bind.find(); - @override - void onInit() { - if (state.supportDynamic) { - state.systemColor = Color(PrefUtil.getValue('systemColor')!); - } - super.onInit(); - } - - int _counter = 0; - //更改主题色 Future changeSeedColor(index) async { await PrefUtil.setValue('color', index); state.currentColor = index; settingLogic.state.color = index; - _counter++; + await ThemeUtil.forceUpdateTheme(); update(); } - - void changeTheme() async { - _counter--; - if (_counter > 0) return; - Get.changeTheme(await ThemeUtil.buildTheme(Brightness.light)); - Get.changeTheme(await ThemeUtil.buildTheme(Brightness.dark)); - } } diff --git a/lib/components/color_sheet/color_sheet_state.dart b/lib/components/color_sheet/color_sheet_state.dart index 748e2c3..3df1ba5 100644 --- a/lib/components/color_sheet/color_sheet_state.dart +++ b/lib/components/color_sheet/color_sheet_state.dart @@ -1,12 +1,7 @@ -import 'dart:ui'; - import 'package:moodiary/presentation/pref.dart'; class ColorSheetState { - bool supportDynamic = PrefUtil.getValue('supportDynamicColor')!; int currentColor = PrefUtil.getValue('color')!; - Color? systemColor; - ColorSheetState(); } diff --git a/lib/components/color_sheet/color_sheet_view.dart b/lib/components/color_sheet/color_sheet_view.dart index 27b2ced..4b49bb8 100644 --- a/lib/components/color_sheet/color_sheet_view.dart +++ b/lib/components/color_sheet/color_sheet_view.dart @@ -5,6 +5,7 @@ import 'package:moodiary/common/values/colors.dart'; import 'package:moodiary/components/base/marquee.dart'; import 'package:moodiary/main.dart'; import 'package:moodiary/presentation/pref.dart'; +import 'package:moodiary/utils/theme_util.dart'; import 'package:refreshed/refreshed.dart'; import 'color_sheet_logic.dart'; @@ -19,27 +20,28 @@ class ColorSheetComponent extends StatelessWidget { required Brightness brightness, required TextStyle? textStyle, required VoidCallback onTap, - required VoidCallback onEnd, required BoxConstraints constraints, bool isSystemColor = false, - Color? systemColor, }) { - if (isSystemColor) assert(systemColor != null); - - final customColorScheme = ColorScheme.fromSeed( - seedColor: (isSystemColor && systemColor != null) - ? systemColor - : AppColor.themeColorList[realIndex], - dynamicSchemeVariant: (realIndex == 0) - ? DynamicSchemeVariant.monochrome - : DynamicSchemeVariant.tonalSpot, - brightness: brightness); + final customColorScheme = + isSystemColor + ? (brightness == Brightness.light + ? ThemeUtil().lightDynamic! + : ThemeUtil().darkDynamic!) + : ColorScheme.fromSeed( + seedColor: AppColor.themeColorList[realIndex], + dynamicSchemeVariant: + (realIndex == 0) + ? DynamicSchemeVariant.monochrome + : DynamicSchemeVariant.tonalSpot, + brightness: brightness, + ); final textPainter = TextPainter( - text: TextSpan(text: AppColor.colorName(realIndex), style: textStyle), - textDirection: TextDirection.ltr, - textScaler: TextScaler.linear(PrefUtil.getValue('fontScale')!)) - ..layout(); + text: TextSpan(text: AppColor.colorName(realIndex), style: textStyle), + textDirection: TextDirection.ltr, + textScaler: TextScaler.linear(PrefUtil.getValue('fontScale')!), + )..layout(); final showMarquee = textPainter.width > constraints.maxWidth - 8.0; return Padding( padding: const EdgeInsets.all(4.0), @@ -57,52 +59,60 @@ class ColorSheetComponent extends StatelessWidget { child: Stack( children: [ AnimatedPositioned( - left: currentColor == realIndex - ? 6 - : (constraints.maxWidth / 2 - textPainter.width / 2 - 4.0), - bottom: currentColor == realIndex - ? 6 - : (constraints.maxHeight / 2 - - textPainter.height / 2 - - 4.0), + left: + currentColor == realIndex + ? 6 + : (constraints.maxWidth / 2 - + textPainter.width / 2 - + 4.0), + bottom: + currentColor == realIndex + ? 6 + : (constraints.maxHeight / 2 - + textPainter.height / 2 - + 4.0), duration: const Duration(milliseconds: 300), - onEnd: onEnd, - child: showMarquee - ? SizedBox( - height: textPainter.height, - width: constraints.maxWidth - 8.0, - child: Marquee( - text: AppColor.colorName(realIndex), - velocity: 20, - blankSpace: 20, - pauseAfterRound: const Duration(seconds: 1), - accelerationDuration: const Duration(seconds: 1), - accelerationCurve: Curves.linear, - decelerationDuration: - const Duration(milliseconds: 500), - decelerationCurve: Curves.easeOut, + child: + showMarquee + ? SizedBox( + height: textPainter.height, + width: constraints.maxWidth - 8.0, + child: Marquee( + text: AppColor.colorName(realIndex), + velocity: 20, + blankSpace: 20, + pauseAfterRound: const Duration(seconds: 1), + accelerationDuration: const Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: const Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + style: textStyle?.copyWith( + color: customColorScheme.onPrimary, + ), + ), + ) + : Text( + AppColor.colorName(realIndex), style: textStyle?.copyWith( - color: customColorScheme.onPrimary), + color: customColorScheme.onPrimary, + ), ), - ) - : Text( - AppColor.colorName(realIndex), - style: textStyle?.copyWith( - color: customColorScheme.onPrimary), - ), ), Positioned( top: 6, right: 6, child: AnimatedOpacity( - opacity: currentColor == realIndex ? 1 : 0, - duration: const Duration(milliseconds: 300), - child: Icon( - Icons.check_circle, - size: 12, - color: customColorScheme.onPrimary, - )), - ) + opacity: currentColor == realIndex ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: Icon( + Icons.check_circle, + size: 12, + color: customColorScheme.onPrimary, + ), + ), + ), ], ), ), @@ -119,20 +129,18 @@ class ColorSheetComponent extends StatelessWidget { // 绘制普通配色 Widget buildCommonColor() { - final hasSystemColor = state.supportDynamic && state.systemColor != null; - final itemCount = hasSystemColor - ? AppColor.themeColorList.length + 1 - : AppColor.themeColorList.length; + final hasSystemColor = ThemeUtil().supportDynamic; + final itemCount = + hasSystemColor + ? AppColor.themeColorList.length + 1 + : AppColor.themeColorList.length; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(8.0), - child: Text( - l10n.colorCommon, - style: textStyle.titleMedium, - ), + child: Text(l10n.colorCommon, style: textStyle.titleMedium), ), GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( @@ -144,20 +152,21 @@ class ColorSheetComponent extends StatelessWidget { shrinkWrap: true, itemBuilder: (context, index) { final realIndex = hasSystemColor ? index - 1 : index; - return LayoutBuilder(builder: (context, constraints) { - return buildColorOption( + return LayoutBuilder( + builder: (context, constraints) { + return buildColorOption( currentColor: state.currentColor, realIndex: realIndex, brightness: colorScheme.brightness, textStyle: textStyle.labelMedium, isSystemColor: realIndex == -1, - systemColor: state.systemColor, constraints: constraints, onTap: () { logic.changeSeedColor(realIndex); }, - onEnd: logic.changeTheme); - }); + ); + }, + ); }, itemCount: itemCount, ), diff --git a/lib/main.dart b/lib/main.dart index 54fd376..49d5975 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,7 +23,6 @@ import 'package:moodiary/router/app_routes.dart'; import 'package:moodiary/src/rust/frb_generated.dart'; import 'package:moodiary/utils/log_util.dart'; import 'package:moodiary/utils/media_util.dart'; -import 'package:moodiary/utils/notice_util.dart'; import 'package:moodiary/utils/theme_util.dart'; import 'package:moodiary/utils/webdav_util.dart'; import 'package:refreshed/refreshed.dart'; @@ -39,16 +38,23 @@ Future _initSystem() async { await RustLib.init(); await PrefUtil.initPref(); await IsarUtil.initIsar(); + await ThemeUtil().buildTheme(); await WebDavUtil().initWebDav(); VideoPlayerMediaKit.ensureInitialized( - android: true, iOS: true, macOS: true, windows: true); + android: true, + iOS: true, + macOS: true, + windows: true, + ); await FMTCObjectBoxBackend().initialise(); await const FMTCStore('mapStore').manage.create(); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( - systemNavigationBarColor: Colors.transparent, - systemNavigationBarContrastEnforced: false, - )); + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarContrastEnforced: false, + ), + ); await _findLanguage(); await _platFormOption(); } @@ -60,9 +66,10 @@ Future _findLanguage() async { ); if (language == Language.system) { final systemLocale = await findSystemLocale(); - final systemLanguageCode = systemLocale.contains('_') - ? systemLocale.split('_').first - : systemLocale; + final systemLanguageCode = + systemLocale.contains('_') + ? systemLocale.split('_').first + : systemLocale; language = Language.values.firstWhere( (e) => e.languageCode == systemLanguageCode, orElse: () => Language.english, @@ -96,71 +103,61 @@ String _getInitialRoute() { void main() async { await _initSystem(); FlutterError.onError = (details) { - LogUtil.printError('Flutter error', - error: details.exception, stackTrace: details.stack); - if (details.exceptionAsString().contains('Render')) { - // NoticeUtil.showBug( - // message: - // Env.debugMode ? details.exceptionAsString() : l10n.layoutErrorToast, - // ); - } else { - // NoticeUtil.showBug( - // message: Env.debugMode ? details.exceptionAsString() : l10n.errorToast, - // ); - } + LogUtil.printError( + 'Flutter error', + error: details.exception, + stackTrace: details.stack, + ); }; PlatformDispatcher.instance.onError = (error, stack) { LogUtil.printWTF('Error', error: error, stackTrace: stack); - NoticeUtil.showBug( - message: Env.debugMode ? error.toString() : l10n.errorToast, - ); return true; }; - runApp(GetMaterialApp.router( - routeInformationParser: GetInformationParser.createInformationParser( - initialRoute: _getInitialRoute(), + final themeData = ThemeUtil().getThemeData(); + runApp( + GetMaterialApp.router( + routeInformationParser: GetInformationParser.createInformationParser( + initialRoute: _getInitialRoute(), + ), + onGenerateTitle: (context) => AppLocalizations.of(context)!.appName, + backButtonDispatcher: GetRootBackButtonDispatcher(), + builder: (context, child) { + l10n = AppLocalizations.of(context)!; + final mediaQuery = MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear( + PrefUtil.getValue('fontScale')!, + ), + ), + child: FToastBuilder()(context, child!), + ); + return Stack( + children: [ + mediaQuery, + const FrostedGlassOverlayComponent(), + if (Env.debugMode) + const Positioned( + top: -15, + right: -15, + child: EnvBadge(envMode: '测试版'), + ), + if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) + const Positioned(top: 0, left: 0, right: 0, child: MoveTitle()), + ], + ); + }, + theme: themeData.$1, + darkTheme: themeData.$2, + locale: locale, + themeMode: ThemeMode.values[PrefUtil.getValue('themeMode')!], + getPages: AppPages.routes, + localizationsDelegates: const [ + ...AppLocalizations.localizationsDelegates, + FlutterQuillLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, ), - onGenerateTitle: (context) => AppLocalizations.of(context)!.appName, - backButtonDispatcher: GetRootBackButtonDispatcher(), - builder: (context, child) { - l10n = AppLocalizations.of(context)!; - final mediaQuery = MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaler: - TextScaler.linear(PrefUtil.getValue('fontScale')!)), - child: FToastBuilder()(context, child!), - ); - return Stack( - children: [ - mediaQuery, - const FrostedGlassOverlayComponent(), - if (Env.debugMode) - const Positioned( - top: -15, - right: -15, - child: EnvBadge(envMode: '测试版'), - ), - if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) - const Positioned( - top: 0, - left: 0, - right: 0, - child: MoveTitle(), - ), - ], - ); - }, - theme: await ThemeUtil.buildTheme(Brightness.light), - darkTheme: await ThemeUtil.buildTheme(Brightness.dark), - locale: locale, - themeMode: ThemeMode.values[PrefUtil.getValue('themeMode')!], - getPages: AppPages.routes, - localizationsDelegates: const [ - ...AppLocalizations.localizationsDelegates, - FlutterQuillLocalizations.delegate - ], - supportedLocales: AppLocalizations.supportedLocales, - )); + ); } class GetRootBackButtonDispatcher extends BackButtonDispatcher diff --git a/lib/pages/font/font_logic.dart b/lib/pages/font/font_logic.dart index 46c6100..cd70991 100644 --- a/lib/pages/font/font_logic.dart +++ b/lib/pages/font/font_logic.dart @@ -27,8 +27,10 @@ class FontLogic extends GetxController with GetSingleTickerProviderStateMixin { void onVerticalDragStart(DragUpdateDetails details) { state.bottomSheetHeight.value -= details.delta.dy; - state.bottomSheetHeight.value = - state.bottomSheetHeight.value.clamp(state.minHeight, state.maxHeight); + state.bottomSheetHeight.value = state.bottomSheetHeight.value.clamp( + state.minHeight, + state.maxHeight, + ); } void changeSelectedFont({Font? font}) async { @@ -45,10 +47,12 @@ class FontLogic extends GetxController with GetSingleTickerProviderStateMixin { state.fontList.value = await IsarUtil.getAllFonts(); final loadList = []; for (final font in state.fontList) { - loadList.add(FontUtil.loadFont( - fontName: font.fontFamily, - fontPath: FileUtil.getRealPath('font', font.fontFileName), - )); + loadList.add( + FontUtil.loadFont( + fontName: font.fontFamily, + fontPath: FileUtil.getRealPath('font', font.fontFileName), + ), + ); } await Future.wait(loadList); state.isFetching.value = false; @@ -90,9 +94,11 @@ class FontLogic extends GetxController with GetSingleTickerProviderStateMixin { //更改字体 Future changeFontTheme() async { await PrefUtil.setValue( - 'customFont', state.currentFontFamily.value); - Get.changeTheme(await ThemeUtil.buildTheme(Brightness.light)); - Get.changeTheme(await ThemeUtil.buildTheme(Brightness.dark)); + 'customFont', + state.currentFontFamily.value, + ); + await ThemeUtil.forceUpdateTheme(); + EllipsisText.clearCache(); } diff --git a/lib/presentation/pref.dart b/lib/presentation/pref.dart index 874359f..3331196 100644 --- a/lib/presentation/pref.dart +++ b/lib/presentation/pref.dart @@ -5,7 +5,6 @@ import 'package:moodiary/merge/merge.dart'; import 'package:moodiary/utils/auth_util.dart'; import 'package:moodiary/utils/file_util.dart'; import 'package:moodiary/utils/package_util.dart'; -import 'package:moodiary/utils/theme_util.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -19,10 +18,6 @@ class PrefUtil { 'firstStart', //自动同步 'autoSync', - //动态配色支持 - 'supportDynamicColor', - //当前系统颜色 - 'systemColor', //主题颜色 'color', //主题颜色类型 @@ -100,8 +95,10 @@ class PrefUtil { static Future initPref() async { _prefs = await SharedPreferencesWithCache.create( - cacheOptions: - const SharedPreferencesWithCacheOptions(allowList: allowList)); + cacheOptions: const SharedPreferencesWithCacheOptions( + allowList: allowList, + ), + ); // 首次启动 final firstStart = _prefs.getBool('firstStart') ?? true; await _prefs.setBool('firstStart', firstStart); @@ -129,30 +126,19 @@ class PrefUtil { /// 支持相关,每次都重新获取 await _prefs.setBool( - 'supportBiometrics', await AuthUtil.canCheckBiometrics()); - - final supportDynamicColor = await ThemeUtil.supportDynamicColor(); - await _prefs.setBool('supportDynamicColor', supportDynamicColor); - - if (supportDynamicColor) { - final color = await ThemeUtil.getDynamicColor(); - await _prefs.setInt( - 'systemColor', - ((color.a * 255).toInt() << 24) | - ((color.r * 255).toInt() << 16) | - ((color.g * 255).toInt() << 8) | - (color.b * 255).toInt()); - } + 'supportBiometrics', + await AuthUtil.canCheckBiometrics(), + ); await _prefs.setInt( - 'color', - _prefs.getInt('color') ?? - (await ThemeUtil.supportDynamicColor() ? -1 : 0)); - await _prefs.setInt( - 'colorType', _prefs.getInt('colorType') ?? AppColorType.common.value); + 'colorType', + _prefs.getInt('colorType') ?? AppColorType.common.value, + ); await _prefs.setInt('themeMode', _prefs.getInt('themeMode') ?? 0); await _prefs.setBool( - 'dynamicColor', _prefs.getBool('dynamicColor') ?? true); + 'dynamicColor', + _prefs.getBool('dynamicColor') ?? true, + ); await _prefs.setInt('quality', _prefs.getInt('quality') ?? 2); await _prefs.setBool('local', _prefs.getBool('local') ?? false); await _prefs.setBool('lock', _prefs.getBool('lock') ?? false); @@ -162,38 +148,66 @@ class PrefUtil { /// 支持相关,重新获取 await _prefs.setString( - 'supportPath', (await getApplicationSupportDirectory()).path); + 'supportPath', + (await getApplicationSupportDirectory()).path, + ); await _prefs.setString( - 'cachePath', (await getApplicationCacheDirectory()).path); + 'cachePath', + (await getApplicationCacheDirectory()).path, + ); await _prefs.setBool('getWeather', _prefs.getBool('getWeather') ?? false); - await _prefs.setInt('startTime', - _prefs.getInt('startTime') ?? DateTime.now().millisecondsSinceEpoch); + await _prefs.setInt( + 'startTime', + _prefs.getInt('startTime') ?? DateTime.now().millisecondsSinceEpoch, + ); await _prefs.setString( - 'customTitleName', _prefs.getString('customTitleName') ?? ''); - await _prefs.setInt('homeViewMode', - _prefs.getInt('homeViewMode') ?? ViewModeType.list.number); + 'customTitleName', + _prefs.getString('customTitleName') ?? '', + ); + await _prefs.setInt( + 'homeViewMode', + _prefs.getInt('homeViewMode') ?? ViewModeType.list.number, + ); await _prefs.setBool('autoWeather', _prefs.getBool('autoWeather') ?? false); await _prefs.setStringList( - 'webDavOption', _prefs.getStringList('webDavOption') ?? []); + 'webDavOption', + _prefs.getStringList('webDavOption') ?? [], + ); await _prefs.setBool('diaryHeader', _prefs.getBool('diaryHeader') ?? true); await _prefs.setBool( - 'firstLineIndent', _prefs.getBool('firstLineIndent') ?? false); + 'firstLineIndent', + _prefs.getBool('firstLineIndent') ?? false, + ); await _prefs.setBool( - 'autoCategory', _prefs.getBool('autoCategory') ?? false); + 'autoCategory', + _prefs.getBool('autoCategory') ?? false, + ); await _prefs.setBool( - 'showWritingTime', _prefs.getBool('showWritingTime') ?? true); + 'showWritingTime', + _prefs.getBool('showWritingTime') ?? true, + ); await _prefs.setBool( - 'showWordCount', _prefs.getBool('showWordCount') ?? true); + 'showWordCount', + _prefs.getBool('showWordCount') ?? true, + ); await _prefs.setString('customFont', _prefs.getString('customFont') ?? ''); await _prefs.setBool( - 'backendPrivacy', _prefs.getBool('backendPrivacy') ?? true); + 'backendPrivacy', + _prefs.getBool('backendPrivacy') ?? true, + ); await _prefs.setBool( - 'autoSyncAfterChange', _prefs.getBool('autoSyncAfterChange') ?? false); + 'autoSyncAfterChange', + _prefs.getBool('autoSyncAfterChange') ?? false, + ); await _prefs.setString( - 'language', _prefs.getString('language') ?? 'system'); + 'language', + _prefs.getString('language') ?? 'system', + ); await _prefs.setBool( - 'syncEncryption', _prefs.getBool('syncEncryption') ?? false); + 'syncEncryption', + _prefs.getBool('syncEncryption') ?? false, + ); } static Future setValue(String key, T value) async { diff --git a/lib/utils/theme_util.dart b/lib/utils/theme_util.dart index edd7cb7..c5a877c 100644 --- a/lib/utils/theme_util.dart +++ b/lib/utils/theme_util.dart @@ -1,28 +1,49 @@ import 'dart:io'; +import 'package:dartx/dartx.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/internal.dart'; +import 'package:material_color_utilities/palettes/core_palette.dart'; import 'package:moodiary/common/values/colors.dart'; import 'package:moodiary/presentation/isar.dart'; import 'package:moodiary/presentation/pref.dart'; import 'package:moodiary/utils/file_util.dart'; import 'package:moodiary/utils/font_util.dart'; +import 'package:moodiary/utils/log_util.dart'; +import 'package:refreshed/refreshed.dart'; class ThemeUtil { - static Future supportDynamicColor() async { - return (await DynamicColorPlugin.getCorePalette()) != null; - } + ThemeUtil._(); - static Future getDynamicColor() async { - return Color((await DynamicColorPlugin.getCorePalette())!.primary.get(40)); - } + static final ThemeUtil instance = ThemeUtil._(); - static Map _unifyFontWeights( - Map fontWeights, - ) { + factory ThemeUtil() => instance; + + // 亮色模式的主题缓存 + ThemeData? _lightTheme; + + // 暗色模式的主题缓存 + ThemeData? _darkTheme; + + // 字体的字重缓存 + Map wghtAxisMap = {}; + + // 动态配色的浅色主题 + ColorScheme? lightDynamic; + + // 动态配色的深色主题 + ColorScheme? darkDynamic; + + bool get supportDynamic => lightDynamic != null && darkDynamic != null; + + // 字体的名称缓存 + String? fontFamily; + + Map _unifyFontWeights(Map fontWeights) { // 标准 final regular = fontWeights['default'] ?? 400; // 名称映射表:将各种名称映射到统一的标准名称 @@ -69,11 +90,7 @@ class ThemeUtil { return unified; } - static TextTheme _applyFontVariations( - TextTheme baseTheme, - String? fontFamily, { - required Map wghtAxisMap, - }) { + TextTheme _applyFontVariations(TextTheme baseTheme) { final regularFontWeight = wghtAxisMap['Regular'] ?? 400; final mediumFontWeight = wghtAxisMap['Medium'] ?? 500; final semiBoldFontWeight = wghtAxisMap['SemiBold'] ?? 600; @@ -157,23 +174,49 @@ class ThemeUtil { ); } - static Future buildTheme(Brightness brightness) async { - final color = PrefUtil.getValue('color')!; - final seedColor = - (color == -1) - ? Color(PrefUtil.getValue('systemColor')!) - : AppColor.themeColorList[(color >= 0 && - color < AppColor.themeColorList.length) - ? color - : 0]; + /// 构建主题 + /// 第一个返回值为亮色主题,第二个为暗色主题 + Future buildTheme() async { + await findDynamicColor(); - final customFont = PrefUtil.getValue('customFont')!; - String? fontFamily; - Map wghtAxisMap = {}; + var color = PrefUtil.getValue('color'); + + // 如果是首次打开软件,还没有设置配色,检查是否支持动态配色 + if (color == null) { + // 如果支持动态配色,设置为动态配色 + if (supportDynamic) { + PrefUtil.setValue('color', -1); + color = -1; + } else { + // 否则设置为默认配色 + PrefUtil.setValue('color', 0); + color = 0; + } + } + + final isDynamic = color == -1 && supportDynamic; + + late final normalColor = + AppColor.themeColorList[(color! >= 0 && + color < AppColor.themeColorList.length) + ? color + : 0]; + + final lightColorScheme = + isDynamic + ? lightDynamic! + : buildColorScheme(normalColor, Brightness.light, color); + + final darkColorScheme = + isDynamic + ? darkDynamic! + : buildColorScheme(normalColor, Brightness.dark, color); + + final customFont = PrefUtil.getValue('customFont'); // 加载自定义字体 - if (customFont.isNotEmpty) { - final font = await IsarUtil.getFontByFontFamily(customFont); + if (customFont.isNotNullOrBlank) { + final font = await IsarUtil.getFontByFontFamily(customFont!); if (font != null) { await FontUtil.loadFont( fontName: font.fontFamily, @@ -188,23 +231,77 @@ class ThemeUtil { fontFamily = 'Microsoft Yahei UI'; } - // colorScheme - final colorScheme = ColorScheme.fromSeed( + final lightTextTheme = buildTextTheme(lightColorScheme); + final darkTextTheme = buildTextTheme(darkColorScheme); + + final lightTypography = buildTypography(lightColorScheme); + final darkTypography = buildTypography(darkColorScheme); + + _lightTheme = buildThemeData( + lightColorScheme, + lightTextTheme, + lightTypography, + fontFamily, + wghtAxisMap, + Brightness.light, + ); + _darkTheme = buildThemeData( + darkColorScheme, + darkTextTheme, + darkTypography, + fontFamily, + wghtAxisMap, + Brightness.dark, + ); + } + + // 辅助函数:构建 colorScheme + ColorScheme buildColorScheme( + Color seedColor, + Brightness brightness, + int color, + ) { + // 默认的配色生成算法,这个会生成低饱和度的配色 + var dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot; + if (color == 0) { + dynamicSchemeVariant = DynamicSchemeVariant.monochrome; + } + if (color == -1) { + dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot; + } + return ColorScheme.fromSeed( seedColor: seedColor, brightness: brightness, - dynamicSchemeVariant: - color == 0 - ? DynamicSchemeVariant.monochrome - : DynamicSchemeVariant.tonalSpot, - ); - // typography - final typography = Typography.material2021( + dynamicSchemeVariant: dynamicSchemeVariant, + ).harmonized(); + } + + // 辅助函数:构建 typography + Typography buildTypography(ColorScheme colorScheme) { + return Typography.material2021( platform: defaultTargetPlatform, colorScheme: colorScheme, ); - final defaultTextTheme = - brightness == Brightness.light ? typography.black : typography.white; + } + TextTheme buildTextTheme(ColorScheme colorScheme) { + final typography = buildTypography(colorScheme); + final textTheme = + colorScheme.brightness == Brightness.light + ? typography.black + : typography.white; + return _applyFontVariations(textTheme); + } + + // 辅助函数:构建 ThemeData + ThemeData buildThemeData( + ColorScheme colorScheme, + TextTheme textTheme, + Typography typography, + String? fontFamily, + Map wghtAxisMap, + Brightness brightness, + ) { return ThemeData( colorScheme: colorScheme, materialTapTargetSize: MaterialTapTargetSize.padded, @@ -220,14 +317,76 @@ class ThemeUtil { backgroundColor: colorScheme.surface, ), fontFamily: fontFamily, - textTheme: _applyFontVariations( - defaultTextTheme, - fontFamily, - wghtAxisMap: wghtAxisMap, - ), + typography: typography, + textTheme: _applyFontVariations(textTheme), ); } + Future findDynamicColor() async { + try { + final CorePalette? corePalette = + await DynamicColorPlugin.getCorePalette(); + + if (corePalette != null) { + final seedColor = Color(corePalette.primary.get(40)); + + lightDynamic = buildColorScheme(seedColor, Brightness.light, -1); + darkDynamic = buildColorScheme(seedColor, Brightness.dark, -1); + return; + } + } on PlatformException { + LogUtil.printInfo('dynamic_color: Failed to obtain core palette.'); + } + + try { + final Color? accentColor = await DynamicColorPlugin.getAccentColor(); + + if (accentColor != null) { + lightDynamic = buildColorScheme(accentColor, Brightness.light, -1); + darkDynamic = buildColorScheme(accentColor, Brightness.dark, -1); + return; + } + } on PlatformException { + LogUtil.printInfo('dynamic_color: Failed to obtain accent color.'); + } + + LogUtil.printInfo( + 'dynamic_color: Dynamic color not detected on this device.', + ); + } + + (ThemeData, ThemeData) getThemeData() { + final isDynamic = supportDynamic && PrefUtil.getValue('color') == -1; + if (isDynamic) { + return ( + _lightTheme?.copyWith( + colorScheme: lightDynamic, + textTheme: buildTextTheme(lightDynamic!), + typography: buildTypography(lightDynamic!), + ) ?? + ThemeData.light(), + _darkTheme?.copyWith( + colorScheme: darkDynamic, + textTheme: buildTextTheme(darkDynamic!), + typography: buildTypography(darkDynamic!), + ) ?? + ThemeData.dark(), + ); + } else { + return (_lightTheme ?? ThemeData.light(), _darkTheme ?? ThemeData.dark()); + } + } + + /// 强制更新主题 + /// 一般在更改了主题色或者字体后调用 + static Future forceUpdateTheme() async { + await ThemeUtil().buildTheme(); + final themeData = ThemeUtil().getThemeData(); + Get.changeTheme(themeData.$1); + Get.changeTheme(themeData.$2); + await Get.forceAppUpdate(); + } + static DefaultStyles getInstance( BuildContext context, { required ColorScheme customColorScheme, diff --git a/pubspec.lock b/pubspec.lock index 7609218..d24aad4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -98,10 +98,10 @@ packages: dependency: "direct main" description: name: archive - sha256: "528579c7e4579719f04b21eeeeddfd73a18b31dabc22766893b7d1be7f49b967" + sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" args: dependency: transitive description: @@ -314,10 +314,10 @@ packages: dependency: transitive description: name: built_value - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" url: "https://pub.dev" source: hosted - version: "8.9.3" + version: "8.9.4" cached_network_image: dependency: "direct main" description: @@ -510,6 +510,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.8" + dartx: + dependency: "direct main" + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" dbus: dependency: transitive description: @@ -522,10 +530,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da" + sha256: "610739247975c2d0de43482afa13ec1018f63c9fddf97ef3d8dc895faa3b4543" url: "https://pub.dev" source: hosted - version: "11.3.0" + version: "11.3.2" device_info_plus_platform_interface: dependency: transitive description: @@ -554,10 +562,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" dismissible_page: dependency: "direct main" description: @@ -650,10 +658,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "6f6bfa8797f296965bdc3e1f702574ab49a540c19b9237b401e7c2b25dfe594c" + sha256: "7423298f08f6fc8cce05792bae329f9a93653fc9c08712831b1a55540127995d" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.0.2" file_selector_linux: dependency: transitive description: @@ -988,10 +996,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.26" flutter_quill: dependency: "direct main" description: @@ -1118,10 +1126,10 @@ packages: dependency: transitive description: name: functions_client - sha256: "61597ed93be197b1be6387855e4b760e6aac2355fcfc4df6d20d2b4579982158" + sha256: a49876ebae32a50eb62483c5c5ac80ed0d8da34f98ccc23986b03a8d28cee07c url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" gal: dependency: "direct main" description: @@ -1563,7 +1571,7 @@ packages: source: hosted version: "0.12.17" material_color_utilities: - dependency: transitive + dependency: "direct main" description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec @@ -1765,18 +1773,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.2.1" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" page_transition: dependency: "direct main" description: @@ -2069,10 +2077,10 @@ packages: dependency: transitive description: name: realtime_client - sha256: "1bfcb7455fdcf15953bf18ac2817634ea5b8f7f350c7e8c9873141a3ee2c3e9c" + sha256: e3089dac2121917cc0c72d42ab056fea0abbaf3c2229048fc50e64bafc731adf url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" record: dependency: "direct main" description: @@ -2451,10 +2459,10 @@ packages: dependency: transitive description: name: storage_client - sha256: d80d34f0aa60e5199646bc301f5750767ee37310c2ecfe8d4bbdd29351e09ab0 + sha256: "9f9ed283943313b23a1b27139bb18986e9b152a6d34530232c702c468d98e91a" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" stream_channel: dependency: transitive description: @@ -2483,34 +2491,34 @@ packages: dependency: transitive description: name: supabase - sha256: "270f63cd87a16578fee87e40cbf61062e8cdbce68d5e723e665f4651d70ddd8c" + sha256: c3ebddba69ddcf16d8b78e8c44c4538b0193d1cf944fde3b72eb5b279892a370 url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.6.3" supabase_flutter: dependency: "direct main" description: name: supabase_flutter - sha256: ca8dfe3d4b109e7338cdf7778f3ec2c660a0178006876bfac343eb39b0f3d1e3 + sha256: "3b5b5b492e342f63f301605d0c66f6528add285b5744f53c9fd9abd5ffdbce5b" url: "https://pub.dev" source: hosted - version: "2.8.3" + version: "2.8.4" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: "634adbc5c7d41e31513e9e3d60213bbc6aedff6e90ddb23b6495bfc0a23a260e" + sha256: "393db014b90865222f8c442dd989a9ba6107bb92bf4d4774257d60a3ee05053f" url: "https://pub.dev" source: hosted - version: "28.2.6" + version: "28.2.7" syncfusion_flutter_sliders: dependency: "direct main" description: name: syncfusion_flutter_sliders - sha256: a376f2495888fc6b18cbb42694ec7f1d7805ff966326bcb1ecf4220dd779b160 + sha256: "131446a2818d31305596e7e94fd45f13fdc9a1b387939f4f814026aca99d8d7c" url: "https://pub.dev" source: hosted - version: "28.2.6" + version: "28.2.7" synchronized: dependency: "direct main" description: @@ -2543,6 +2551,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.0" + time: + dependency: transitive + description: + name: time + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + url: "https://pub.dev" + source: hosted + version: "2.1.5" timing: dependency: transitive description: @@ -2795,10 +2811,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -2835,10 +2851,10 @@ packages: dependency: transitive description: name: win32_registry - sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "2.1.0" wkt_parser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 86ae15a..58e248e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -113,6 +113,8 @@ dependencies: page_transition: 2.2.1 dismissible_page: 1.0.2 photo_view: 0.15.0 + dartx: 1.2.0 + material_color_utilities: 0.11.1 dev_dependencies: