feat: sincronizar cor do tema via account data da conta Matrix

A cor escolhida no Appearance passa a ser guardada em im.fluffychat.account_config (primary_color), em vez de apenas nas preferencias locais do dispositivo.

- ApplicationAccountConfig ganha primaryColor, preservado em updateApplicationAccountConfig e no novo setApplicationAccountColor
- ThemeController restaura a cor da conta activa, aguarda accountDataLoading e reage a alteracoes remotas via onSync
- A cor final e derivada de _accountPrimaryColor ?? _localPrimaryColor, evitando corrida entre as preferencias locais e o sync
- Matrix notifica a troca de conta activa em initMatrix, setActiveClient e logout, corrigindo o indice do cliente activo
- Apagar o wallpaper preserva a cor; testes de serializacao do modelo
This commit is contained in:
2026-09-25 12:53:21 -03:00
parent 7534ecf7d1
commit 058926cf6f
6 changed files with 262 additions and 49 deletions

View File

@@ -3,10 +3,14 @@
//
// 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';
@@ -15,6 +19,7 @@ class ThemeBuilder extends StatefulWidget {
BuildContext context,
ThemeMode themeMode,
Color? primaryColor,
ThemeController controller,
)
builder;
@@ -36,6 +41,11 @@ class ThemeController extends State<ThemeBuilder> {
SharedPreferences? _sharedPreferences;
ThemeMode? _themeMode;
Color? _primaryColor;
Color? _localPrimaryColor;
Color? _accountPrimaryColor;
Client? _accountClient;
StreamSubscription<SyncUpdate>? _accountClientSubscription;
int _accountClientGeneration = 0;
ThemeMode get themeMode => _themeMode ?? ThemeMode.system;
@@ -44,6 +54,15 @@ class ThemeController extends State<ThemeBuilder> {
static ThemeController of(BuildContext context) =>
Provider.of<ThemeController>(context, listen: false);
void _updatePrimaryColor() {
if (!mounted) return;
final newColor = _accountPrimaryColor ?? _localPrimaryColor;
if (newColor == _primaryColor) return;
setState(() {
_primaryColor = newColor;
});
}
Future<void> _loadData(_) async {
final preferences = _sharedPreferences ??=
await SharedPreferences.getInstance();
@@ -51,12 +70,14 @@ class ThemeController extends State<ThemeBuilder> {
final rawThemeMode = preferences.getString(widget.themeModeSettingsKey);
final rawColor = preferences.getInt(widget.primaryColorSettingsKey);
if (!mounted) return;
setState(() {
_themeMode = ThemeMode.values.singleWhereOrNull(
(value) => value.name == rawThemeMode,
);
_primaryColor = rawColor == null ? null : Color(rawColor);
_localPrimaryColor = rawColor == null ? null : Color(rawColor);
});
_updatePrimaryColor();
}
Future<void> setThemeMode(ThemeMode newThemeMode) async {
@@ -69,6 +90,12 @@ class ThemeController extends State<ThemeBuilder> {
}
Future<void> setPrimaryColor(Color? newPrimaryColor) async {
if (!mounted) return;
setState(() {
_localPrimaryColor = newPrimaryColor;
_accountPrimaryColor = newPrimaryColor;
_primaryColor = newPrimaryColor;
});
final preferences = _sharedPreferences ??=
await SharedPreferences.getInstance();
if (newPrimaryColor == null) {
@@ -79,9 +106,56 @@ class ThemeController extends State<ThemeBuilder> {
newPrimaryColor.hexValue,
);
}
setState(() {
_primaryColor = newPrimaryColor;
});
}
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<void> _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
@@ -95,9 +169,19 @@ class ThemeController extends State<ThemeBuilder> {
return Provider(
create: (_) => this,
child: DynamicColorBuilder(
builder: (light, _) =>
widget.builder(context, themeMode, primaryColor ?? light?.primary),
builder: (light, _) => widget.builder(
context,
themeMode,
primaryColor ?? light?.primary,
this,
),
),
);
}
@override
void dispose() {
_accountClientSubscription?.cancel();
super.dispose();
}
}