refactor: streamline theme management and dynamic color support

This commit is contained in:
ZhuJHua
2025-03-02 04:47:50 +08:00
parent 9b72ceaa6e
commit 3fb5573a7c
12 changed files with 470 additions and 407 deletions

View File

@@ -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<SettingLogic>();
@override
void onReady() {
//如果支持系统颜色,获取系统颜色
if (state.supportDynamic) {
state.systemColor = Color(PrefUtil.getValue<int>('systemColor')!);
update();
}
super.onReady();
}
//更改主题色
Future<void> changeSeedColor(index) async {
await PrefUtil.setValue<int>('color', index);
state.currentColor = index;
settingLogic.state.color = index;
update();
Get.changeTheme(await ThemeUtil.buildTheme(Brightness.light));
Get.changeTheme(await ThemeUtil.buildTheme(Brightness.dark));
}
}

View File

@@ -1,11 +0,0 @@
import 'package:flutter/material.dart';
import 'package:moodiary/presentation/pref.dart';
class ColorDialogState {
bool supportDynamic = PrefUtil.getValue<bool>('supportDynamicColor')!;
int currentColor = PrefUtil.getValue<int>('color')!;
Color? systemColor;
ColorDialogState();
}

View File

@@ -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<ColorDialogLogic>().state;
List<Widget> 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<ColorDialogLogic>(
assignId: true,
builder: (_) {
return SimpleDialog(
title: Text(l10n.settingColor),
children: [
if (state.supportDynamic) ...[buildSystemColor()],
...buildColorList()
],
);
},
);
}
}

View File

@@ -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<SettingLogic>();
@override
void onInit() {
if (state.supportDynamic) {
state.systemColor = Color(PrefUtil.getValue<int>('systemColor')!);
}
super.onInit();
}
int _counter = 0;
//更改主题色
Future<void> changeSeedColor(index) async {
await PrefUtil.setValue<int>('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));
}
}

View File

@@ -1,12 +1,7 @@
import 'dart:ui';
import 'package:moodiary/presentation/pref.dart';
class ColorSheetState {
bool supportDynamic = PrefUtil.getValue<bool>('supportDynamicColor')!;
int currentColor = PrefUtil.getValue<int>('color')!;
Color? systemColor;
ColorSheetState();
}

View File

@@ -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<double>('fontScale')!))
..layout();
text: TextSpan(text: AppColor.colorName(realIndex), style: textStyle),
textDirection: TextDirection.ltr,
textScaler: TextScaler.linear(PrefUtil.getValue<double>('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,
),

View File

@@ -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<void> _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<void> _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<double>('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<int>('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<double>('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<int>('themeMode')!],
getPages: AppPages.routes,
localizationsDelegates: const [
...AppLocalizations.localizationsDelegates,
FlutterQuillLocalizations.delegate
],
supportedLocales: AppLocalizations.supportedLocales,
));
);
}
class GetRootBackButtonDispatcher extends BackButtonDispatcher

View File

@@ -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 = <Future>[];
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<void> changeFontTheme() async {
await PrefUtil.setValue<String>(
'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();
}

View File

@@ -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<void> 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<void> setValue<T>(String key, T value) async {

View File

@@ -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<bool> supportDynamicColor() async {
return (await DynamicColorPlugin.getCorePalette()) != null;
}
ThemeUtil._();
static Future<Color> getDynamicColor() async {
return Color((await DynamicColorPlugin.getCorePalette())!.primary.get(40));
}
static final ThemeUtil instance = ThemeUtil._();
static Map<String, double> _unifyFontWeights(
Map<String, double> fontWeights,
) {
factory ThemeUtil() => instance;
// 亮色模式的主题缓存
ThemeData? _lightTheme;
// 暗色模式的主题缓存
ThemeData? _darkTheme;
// 字体的字重缓存
Map<String, double> wghtAxisMap = {};
// 动态配色的浅色主题
ColorScheme? lightDynamic;
// 动态配色的深色主题
ColorScheme? darkDynamic;
bool get supportDynamic => lightDynamic != null && darkDynamic != null;
// 字体的名称缓存
String? fontFamily;
Map<String, double> _unifyFontWeights(Map<String, double> fontWeights) {
// 标准
final regular = fontWeights['default'] ?? 400;
// 名称映射表:将各种名称映射到统一的标准名称
@@ -69,11 +90,7 @@ class ThemeUtil {
return unified;
}
static TextTheme _applyFontVariations(
TextTheme baseTheme,
String? fontFamily, {
required Map<String, double> 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<ThemeData> buildTheme(Brightness brightness) async {
final color = PrefUtil.getValue<int>('color')!;
final seedColor =
(color == -1)
? Color(PrefUtil.getValue<int>('systemColor')!)
: AppColor.themeColorList[(color >= 0 &&
color < AppColor.themeColorList.length)
? color
: 0];
/// 构建主题
/// 第一个返回值为亮色主题,第二个为暗色主题
Future<void> buildTheme() async {
await findDynamicColor();
final customFont = PrefUtil.getValue<String>('customFont')!;
String? fontFamily;
Map<String, double> wghtAxisMap = {};
var color = PrefUtil.getValue<int>('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<String>('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<String, double> 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<void> 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<int>('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<void> 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,

View File

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

View File

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