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:
@@ -44,10 +44,13 @@ class Matrix extends StatefulWidget {
|
||||
|
||||
final SharedPreferences store;
|
||||
|
||||
final ValueChanged<Client?>? onActiveClientChanged;
|
||||
|
||||
const Matrix({
|
||||
this.child,
|
||||
required this.clients,
|
||||
required this.store,
|
||||
this.onActiveClientChanged,
|
||||
this.queryParameters,
|
||||
super.key,
|
||||
});
|
||||
@@ -89,11 +92,21 @@ class MatrixState extends State<Matrix> {
|
||||
final i = widget.clients.indexWhere((c) => c == cl);
|
||||
if (i != -1) {
|
||||
_activeClient = i;
|
||||
_notifyActiveClientChanged();
|
||||
} else {
|
||||
Logs().w('Tried to set an unknown client ${cl!.userID} as active');
|
||||
}
|
||||
}
|
||||
|
||||
void _notifyActiveClientChanged() {
|
||||
final callback = widget.onActiveClientChanged;
|
||||
if (callback == null) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
callback(widget.clients.isEmpty ? null : client);
|
||||
});
|
||||
}
|
||||
|
||||
List<Client?>? get currentBundle {
|
||||
if (!hasComplexBundles) {
|
||||
return List.from(widget.clients);
|
||||
@@ -256,19 +269,27 @@ class MatrixState extends State<Matrix> {
|
||||
.where((state) => state == LoginState.loggedOut)
|
||||
.listen((_) {
|
||||
final loggedInWithMultipleClients = widget.clients.length > 1;
|
||||
final activeClient = client;
|
||||
final removedIndex = widget.clients.indexOf(c);
|
||||
final wasActive = removedIndex != -1 && activeClient == c;
|
||||
|
||||
_cancelSubs(c.clientName);
|
||||
widget.clients.remove(c);
|
||||
ClientManager.removeClientNameFromStore(c.clientName, store);
|
||||
InitWithRestoreExtension.deleteSessionBackup(name);
|
||||
if (removedIndex != -1 && _activeClient > removedIndex) {
|
||||
_activeClient--;
|
||||
}
|
||||
if (wasActive) {
|
||||
_activeClient = -1;
|
||||
}
|
||||
if (removedIndex != -1) {
|
||||
_notifyActiveClientChanged();
|
||||
}
|
||||
|
||||
if (loggedInWithMultipleClients) {
|
||||
final snackbarContext =
|
||||
TatuChatApp
|
||||
.router
|
||||
.routerDelegate
|
||||
.navigatorKey
|
||||
.currentContext ??
|
||||
TatuChatApp.router.routerDelegate.navigatorKey.currentContext ??
|
||||
context;
|
||||
|
||||
if (!snackbarContext.mounted) return;
|
||||
@@ -349,6 +370,7 @@ class MatrixState extends State<Matrix> {
|
||||
},
|
||||
);
|
||||
}
|
||||
_notifyActiveClientChanged();
|
||||
}
|
||||
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
|
||||
@@ -65,38 +65,46 @@ class TatuChatApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ThemeBuilder(
|
||||
builder: (context, themeMode, primaryColor) => MaterialApp.router(
|
||||
title: AppSettings.applicationName.value,
|
||||
themeMode: themeMode,
|
||||
theme: FluffyThemes.buildTheme(context, Brightness.light, primaryColor),
|
||||
darkTheme: FluffyThemes.buildTheme(
|
||||
context,
|
||||
Brightness.dark,
|
||||
primaryColor,
|
||||
),
|
||||
scrollBehavior: CustomScrollBehavior(),
|
||||
localizationsDelegates: [
|
||||
...L10n.localizationsDelegates,
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
...GlobalCupertinoLocalizations.delegates,
|
||||
],
|
||||
supportedLocales: L10n.supportedLocales,
|
||||
routerConfig: router,
|
||||
builder: (context, child) => AppLockWidget(
|
||||
pincode: appLockSettings.pincode,
|
||||
useBiometrics: appLockSettings.useBiometrics,
|
||||
isLoggedIn: clients.any((client) => client.isLogged()),
|
||||
// Need a navigator above the Matrix widget for
|
||||
// displaying dialogs
|
||||
child: Matrix(
|
||||
clients: clients,
|
||||
store: store,
|
||||
child: CallOverlay(
|
||||
child: testWidget ?? child ?? const SizedBox.shrink(),
|
||||
builder: (context, themeMode, primaryColor, themeController) =>
|
||||
MaterialApp.router(
|
||||
title: AppSettings.applicationName.value,
|
||||
themeMode: themeMode,
|
||||
theme: FluffyThemes.buildTheme(
|
||||
context,
|
||||
Brightness.light,
|
||||
primaryColor,
|
||||
),
|
||||
darkTheme: FluffyThemes.buildTheme(
|
||||
context,
|
||||
Brightness.dark,
|
||||
primaryColor,
|
||||
),
|
||||
scrollBehavior: CustomScrollBehavior(),
|
||||
localizationsDelegates: [
|
||||
...L10n.localizationsDelegates,
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
...GlobalCupertinoLocalizations.delegates,
|
||||
],
|
||||
supportedLocales: L10n.supportedLocales,
|
||||
routerConfig: router,
|
||||
builder: (context, child) => AppLockWidget(
|
||||
pincode: appLockSettings.pincode,
|
||||
useBiometrics: appLockSettings.useBiometrics,
|
||||
isLoggedIn: clients.any((client) => client.isLogged()),
|
||||
// Need a navigator above the Matrix widget for
|
||||
// displaying dialogs
|
||||
child: Matrix(
|
||||
clients: clients,
|
||||
store: store,
|
||||
onActiveClientChanged: (client) {
|
||||
themeController.setAccountClient(client);
|
||||
},
|
||||
child: CallOverlay(
|
||||
child: testWidget ?? child ?? const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user