// SPDX-FileCopyrightText: 2019-Present Christian Kußowski // SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat // // SPDX-License-Identifier: AGPL-3.0-or-later import 'dart:async'; import 'package:collection/collection.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:tatchat/utils/account_config.dart'; import 'package:tatchat/utils/color_value.dart'; import 'package:material_ui/material_ui.dart'; import 'package:matrix/matrix.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; class ThemeBuilder extends StatefulWidget { final Widget Function( BuildContext context, ThemeMode themeMode, Color? primaryColor, ThemeController controller, ) builder; final String themeModeSettingsKey; final String primaryColorSettingsKey; const ThemeBuilder({ required this.builder, this.themeModeSettingsKey = 'theme_mode', this.primaryColorSettingsKey = 'primary_color', super.key, }); @override State createState() => ThemeController(); } class ThemeController extends State { SharedPreferences? _sharedPreferences; ThemeMode? _themeMode; Color? _primaryColor; Color? _localPrimaryColor; Color? _accountPrimaryColor; Client? _accountClient; StreamSubscription? _accountClientSubscription; int _accountClientGeneration = 0; ThemeMode get themeMode => _themeMode ?? ThemeMode.system; Color? get primaryColor => _primaryColor; static ThemeController of(BuildContext context) => Provider.of(context, listen: false); void _updatePrimaryColor() { if (!mounted) return; final newColor = _accountPrimaryColor ?? _localPrimaryColor; if (newColor == _primaryColor) return; setState(() { _primaryColor = newColor; }); } Future _loadData(_) async { final preferences = _sharedPreferences ??= await SharedPreferences.getInstance(); final rawThemeMode = preferences.getString(widget.themeModeSettingsKey); final rawColor = preferences.getInt(widget.primaryColorSettingsKey); if (!mounted) return; setState(() { _themeMode = ThemeMode.values.singleWhereOrNull( (value) => value.name == rawThemeMode, ); _localPrimaryColor = rawColor == null ? null : Color(rawColor); }); _updatePrimaryColor(); } Future setThemeMode(ThemeMode newThemeMode) async { final preferences = _sharedPreferences ??= await SharedPreferences.getInstance(); await preferences.setString(widget.themeModeSettingsKey, newThemeMode.name); setState(() { _themeMode = newThemeMode; }); } Future setPrimaryColor(Color? newPrimaryColor) async { if (!mounted) return; setState(() { _localPrimaryColor = newPrimaryColor; _accountPrimaryColor = newPrimaryColor; _primaryColor = newPrimaryColor; }); final preferences = _sharedPreferences ??= await SharedPreferences.getInstance(); if (newPrimaryColor == null) { await preferences.remove(widget.primaryColorSettingsKey); } else { await preferences.setInt( widget.primaryColorSettingsKey, newPrimaryColor.hexValue, ); } } void setAccountClient(Client? client) { if (!mounted) return; final generation = ++_accountClientGeneration; _accountClientSubscription?.cancel(); _accountClientSubscription = null; _accountClient = client; _accountPrimaryColor = null; _updatePrimaryColor(); if (client == null) return; unawaited(_loadAccountClientColor(client, generation)); _accountClientSubscription = client.onSync.stream .where( (sync) => sync.accountData?.any( (accountData) => accountData.type == ApplicationAccountConfigExtension.accountDataKey, ) ?? false, ) .listen((_) => _applyAccountClientColor(client, generation)); } Future _loadAccountClientColor(Client client, int generation) async { final accountDataLoading = client.accountDataLoading; if (accountDataLoading != null) { try { await accountDataLoading; } catch (_) {} } if (!mounted || generation != _accountClientGeneration || !identical(_accountClient, client)) { return; } _applyAccountClientColor(client, generation); } void _applyAccountClientColor(Client client, int generation) { if (!mounted || generation != _accountClientGeneration || !identical(_accountClient, client)) { return; } final primaryColor = client.applicationAccountConfig.primaryColor; _accountPrimaryColor = primaryColor == null ? null : Color(primaryColor); _updatePrimaryColor(); } @override void initState() { WidgetsBinding.instance.addPostFrameCallback(_loadData); super.initState(); } @override Widget build(BuildContext context) { return Provider( create: (_) => this, child: DynamicColorBuilder( builder: (light, _) => widget.builder( context, themeMode, primaryColor ?? light?.primary, this, ), ), ); } @override void dispose() { _accountClientSubscription?.cancel(); super.dispose(); } }