кастомизация темы — режим, AMOLED, расписание переключения

This commit is contained in:
klockky
2026-05-21 05:59:34 +00:00
parent da22b0fbf3
commit 2fb4d89f79
6 changed files with 631 additions and 2 deletions
+104 -2
View File
@@ -8,10 +8,13 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'backend/api.dart';
import 'core/config/app_accent.dart';
import 'core/config/app_amoled.dart';
import 'core/config/app_bubble_behavior.dart';
import 'core/config/app_bubble_shape.dart';
import 'core/config/app_cache_extent.dart';
import 'core/config/app_fonts.dart';
import 'core/config/app_theme_mode.dart';
import 'core/config/app_theme_schedule.dart';
import 'backend/modules/account.dart';
import 'backend/modules/chats.dart';
import 'backend/modules/contacts.dart';
@@ -80,6 +83,9 @@ void main() async {
AppBubbleShape.current.value = await AppBubbleShape.load();
AppBubbleBehavior.current.value = await AppBubbleBehavior.load();
AppCacheExtent.current.value = await AppCacheExtent.load();
AppThemeModeConfig.current.value = await AppThemeModeConfig.load();
AppAmoled.current.value = await AppAmoled.load();
AppThemeSchedule.current.value = await AppThemeSchedule.load();
runApp(
KometApp(
initialLocale: initialLocale,
@@ -122,7 +128,7 @@ class KometApp extends StatefulWidget {
State<KometApp> createState() => KometAppState();
}
class KometAppState extends State<KometApp> {
class KometAppState extends State<KometApp> with WidgetsBindingObserver {
static const _fallbackSeed = Color(0xFFC1C4FF);
late Locale _locale;
@@ -134,6 +140,7 @@ class KometAppState extends State<KometApp> {
StreamSubscription<SessionExpiredException>? _sessionExpiredSub;
StreamSubscription<LoginStatus>? _loginStatusSub;
StreamSubscription<VpnBypassResult>? _vpnBypassSub;
Timer? _scheduleTimer;
String? _lastVpnNotice;
DateTime _lastVpnNoticeAt = DateTime.fromMillisecondsSinceEpoch(0);
late final ValueNotifier<bool> fpsOverlayEnabled = ValueNotifier(
@@ -157,6 +164,13 @@ class KometAppState extends State<KometApp> {
_locale = widget.initialLocale;
_fontId = widget.initialFontId;
WidgetsBinding.instance.addObserver(this);
AppThemeModeConfig.current.addListener(_onThemeModeChanged);
AppAmoled.current.addListener(_onAmoledChanged);
AppThemeSchedule.current.addListener(_onScheduleChanged);
_lastAppliedThemeMode = _effectiveThemeMode;
_rescheduleSwitch();
api.setReconnectCallback(() async {
try {
final accountId = await TokenStorage.getActiveAccountId();
@@ -228,6 +242,11 @@ class KometAppState extends State<KometApp> {
_sessionExpiredSub?.cancel();
_loginStatusSub?.cancel();
_vpnBypassSub?.cancel();
_scheduleTimer?.cancel();
AppThemeModeConfig.current.removeListener(_onThemeModeChanged);
AppAmoled.current.removeListener(_onAmoledChanged);
AppThemeSchedule.current.removeListener(_onScheduleChanged);
WidgetsBinding.instance.removeObserver(this);
_profileUpdateController.close();
fpsOverlayEnabled.dispose();
vpnBypassEnabled.dispose();
@@ -237,6 +256,79 @@ class KometAppState extends State<KometApp> {
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state != AppLifecycleState.resumed) return;
if (AppThemeModeConfig.current.value != AppThemeMode.schedule) return;
_rescheduleSwitch();
final next = _effectiveThemeMode;
if (next == _lastAppliedThemeMode) return;
_lastAppliedThemeMode = next;
if (mounted) setState(() {});
}
void _onThemeModeChanged() {
_rescheduleSwitch();
_lastAppliedThemeMode = _effectiveThemeMode;
if (mounted) setState(() {});
}
void _onAmoledChanged() {
if (mounted) setState(() {});
}
void _onScheduleChanged() {
if (AppThemeModeConfig.current.value != AppThemeMode.schedule) return;
_rescheduleSwitch();
final next = _effectiveThemeMode;
if (next == _lastAppliedThemeMode) return;
_lastAppliedThemeMode = next;
if (mounted) setState(() {});
}
ThemeMode _lastAppliedThemeMode = ThemeMode.system;
void _rescheduleSwitch() {
_scheduleTimer?.cancel();
_scheduleTimer = null;
if (AppThemeModeConfig.current.value != AppThemeMode.schedule) return;
final until = AppThemeSchedule.current.value.durationUntilNextSwitch(
DateTime.now(),
);
_scheduleTimer = Timer(until, () {
if (!mounted) return;
_lastAppliedThemeMode = _effectiveThemeMode;
setState(() {});
_rescheduleSwitch();
});
}
ThemeMode get _effectiveThemeMode {
switch (AppThemeModeConfig.current.value) {
case AppThemeMode.system:
return ThemeMode.system;
case AppThemeMode.light:
return ThemeMode.light;
case AppThemeMode.dark:
return ThemeMode.dark;
case AppThemeMode.schedule:
final isDark = AppThemeSchedule.current.value.isDarkAt(DateTime.now());
return isDark ? ThemeMode.dark : ThemeMode.light;
}
}
Future<void> applyThemeMode(AppThemeMode mode) async {
await AppThemeModeConfig.save(mode);
}
Future<void> applyAmoled(bool value) async {
await AppAmoled.save(value);
}
Future<void> applyThemeSchedule(ThemeSchedule schedule) async {
await AppThemeSchedule.save(schedule);
}
Future<void> setFpsOverlayEnabled(bool value) async {
if (fpsOverlayEnabled.value == value) return;
fpsOverlayEnabled.value = value;
@@ -359,6 +451,16 @@ class KometAppState extends State<KometApp> {
}
ColorScheme _adjustDarkScheme(ColorScheme base) {
if (AppAmoled.current.value) {
return base.copyWith(
surface: Colors.black,
surfaceContainerLowest: Colors.black,
surfaceContainerLow: const Color(0xFF080808),
surfaceContainer: const Color(0xFF101010),
surfaceContainerHigh: const Color(0xFF161616),
surfaceContainerHighest: const Color(0xFF1C1C1C),
);
}
return base.copyWith(
surface: Color.alphaBlend(
base.primary.withValues(alpha: 0.05),
@@ -423,7 +525,7 @@ class KometAppState extends State<KometApp> {
title: 'Komet',
debugShowCheckedModeBanner: false,
locale: _locale,
themeMode: ThemeMode.system,
themeMode: _effectiveThemeMode,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: _lightTheme,