Files
tatuchat/lib/utils/client_manager.dart
Welton Moura ebafa1dc39
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
feat: cartões de publicação em canais e corrreções de salas arquivadas
- Canais: publicações viram cartões de largura total mantendo todas as
  funções do balão (reações, resposta, editar, excluir, comentários em
  thread). Composer de publicação fica restrito a admins no feed; membros
  comentam via tela de thread. Canais novos usam events_default 0 para
  permitir comentários e reações.
- Ao sair/excluir uma sala, a cópia local é removida do banco imediatamente;
  salas já deixadas no servidor não apresentam mais erro ao excluir de novo.
- Classificação mural/canal carregada direto do sync (room_type em
  importantStateEvents + postLoad) sem precisar abrir a sala.
- Diálogos com raio e padding reduzidos (dialogTheme, text input e ok/cancel).
- Ícones do app regenerados (logo_background) e dependência linkify.
2026-09-14 08:30:25 -03:00

193 lines
6.6 KiB
Dart

// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:collection/collection.dart';
import 'package:tatchat/config/app_config.dart';
import 'package:tatchat/config/setting_keys.dart';
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/utils/custom_http_client.dart';
import 'package:tatchat/utils/custom_image_resizer.dart';
import 'package:tatchat/utils/init_with_restore.dart';
import 'package:tatchat/utils/matrix_live_kit_calls/matrix_live_kit_call_member.dart';
import 'package:tatchat/utils/matrix_sdk_extensions/room_extension.dart';
import 'package:tatchat/utils/platform_infos.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/key_verification.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart';
abstract class ClientManager {
static const String clientNamespace = 'im.fluffychat.store.clients';
static Future<List<Client>> getClients({
bool initialize = true,
required SharedPreferences store,
}) async {
final clientNames = <String>{};
try {
final clientNamesList = store.getStringList(clientNamespace) ?? [];
clientNames.addAll(clientNamesList);
} catch (e, s) {
Logs().w('Client names in store are corrupted', e, s);
await store.remove(clientNamespace);
}
if (clientNames.isEmpty) {
clientNames.add(PlatformInfos.appDisplayName);
await store.setStringList(clientNamespace, clientNames.toList());
}
final clients = await Future.wait(
clientNames.map((name) => createClient(name, store)),
);
if (initialize) {
await Future.wait(
clients.map(
(client) => client
.initWithRestore(
onMigration: () async {
final l10n = await lookupL10n(
PlatformDispatcher.instance.locale,
);
sendInitNotification(
l10n.databaseMigrationTitle,
l10n.databaseMigrationBody,
);
},
)
.catchError(
(e, s) => Logs().e('Unable to initialize client', e, s),
),
),
);
}
if (clients.length > 1 && clients.any((c) => !c.isLogged())) {
final loggedOutClients = clients.where((c) => !c.isLogged()).toList();
for (final client in loggedOutClients) {
Logs().w(
'Multi account is enabled but client ${client.userID} is not logged in. Removing...',
);
clientNames.remove(client.clientName);
clients.remove(client);
}
await store.setStringList(clientNamespace, clientNames.toList());
}
return clients;
}
static Future<void> addClientNameToStore(
String clientName,
SharedPreferences store,
) async {
final clientNamesList = store.getStringList(clientNamespace) ?? [];
clientNamesList.add(clientName);
await store.setStringList(clientNamespace, clientNamesList);
}
static Future<void> removeClientNameFromStore(
String clientName,
SharedPreferences store,
) async {
final clientNamesList = store.getStringList(clientNamespace) ?? [];
clientNamesList.remove(clientName);
await store.setStringList(clientNamespace, clientNamesList);
}
static NativeImplementations get nativeImplementations => kIsWeb
? NativeImplementationsWebWorker(
Uri.parse('native_executor.js'),
timeout: const Duration(minutes: 1),
)
: NativeImplementationsIsolate(
compute,
vodozemacInit: () => vod.init(
wasmPath:
'./assets/assets/vodozemac/${AppConfig.vodozemacVersion}/',
),
);
static Future<Client> createClient(
String clientName,
SharedPreferences store,
) async {
final shareKeysWith = AppSettings.shareKeysWith.value;
final enableSoftLogout = AppSettings.enableSoftLogout.value;
return Client(
clientName,
httpClient: CustomHttpClient.createHTTPClient(),
verificationMethods: {
KeyVerificationMethod.numbers,
if (kIsWeb || PlatformInfos.isMobile || PlatformInfos.isLinux)
KeyVerificationMethod.emoji,
},
importantStateEvents: <String>{
// To make room emotes work
'im.ponies.room_emotes',
// To display call state in chat list
MatrixRtcCallMember.eventType,
// Make the canal/mural room type marker available in the chat list
// right after sync, even for rooms that are still partially loaded.
roomTypeStateEvent,
},
customImageResizer: PlatformInfos.supportsCustomImageResizer
? customImageResizer
: null,
logLevel: Level.verbose,
database: await flutterMatrixSdkDatabaseBuilder(clientName),
supportedLoginTypes: {
AuthenticationTypes.password,
AuthenticationTypes.sso,
},
nativeImplementations: nativeImplementations,
defaultNetworkRequestTimeout: const Duration(minutes: 30),
enableDehydratedDevices: true,
shareKeysWith:
ShareKeysWith.values.singleWhereOrNull(
(share) => share.name == shareKeysWith,
) ??
ShareKeysWith.all,
onSoftLogout: enableSoftLogout ? (c) => c.refreshAccessToken() : null,
sendTimelineEventTimeout: Duration(
seconds: AppSettings.sendTimelineEventTimeout.value,
),
);
}
static Future<void> sendInitNotification(String title, String body) async {
if (kIsWeb) {
html.Notification(title, body: body);
return;
}
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin.initialize(
settings: const InitializationSettings(
android: AndroidInitializationSettings('notifications_icon'),
iOS: DarwinInitializationSettings(),
),
);
await flutterLocalNotificationsPlugin.show(
id: 0,
title: title,
body: body,
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails(
'error_message',
'Error Messages',
importance: Importance.high,
priority: Priority.max,
),
iOS: DarwinNotificationDetails(sound: 'notification.caf'),
),
);
}
}