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.
This commit is contained in:
@@ -14,6 +14,7 @@ import 'package:tatchat/pages/chat_list/tabs/home_tab.dart';
|
||||
import 'package:tatchat/utils/error_reporter.dart';
|
||||
import 'package:tatchat/utils/localized_exception_extension.dart';
|
||||
import 'package:tatchat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:tatchat/utils/matrix_sdk_extensions/room_extension.dart';
|
||||
import 'package:tatchat/utils/platform_infos.dart';
|
||||
import 'package:tatchat/utils/show_scaffold_dialog.dart';
|
||||
import 'package:tatchat/utils/show_update_snackbar.dart';
|
||||
@@ -402,6 +403,7 @@ class ChatListController extends State<ChatList>
|
||||
}
|
||||
|
||||
StreamSubscription? _onRoomTagUpdate;
|
||||
StreamSubscription? _backfillSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -411,6 +413,8 @@ class ChatListController extends State<ChatList>
|
||||
|
||||
scrollController.addListener(_onScroll);
|
||||
_waitForFirstSync();
|
||||
_backfillSubscription =
|
||||
_clientStream.stream.listen((_) => backfillRoomTypeMarkers());
|
||||
if (PlatformInfos.isMobile) {
|
||||
_callEventSubscription = FlutterCallkitIncoming.onEvent.listen(
|
||||
_onCallEvent,
|
||||
@@ -474,6 +478,7 @@ class ChatListController extends State<ChatList>
|
||||
_intentFileStreamSubscription?.cancel();
|
||||
_callEventSubscription?.cancel();
|
||||
_onRoomTagUpdate?.cancel();
|
||||
_backfillSubscription?.cancel();
|
||||
scrollController.removeListener(_onScroll);
|
||||
searchController.dispose();
|
||||
searchFocusNode.dispose();
|
||||
@@ -775,7 +780,10 @@ class ChatListController extends State<ChatList>
|
||||
if (confirmed == OkCancelResult.cancel) return;
|
||||
if (!mounted) return;
|
||||
|
||||
await showFutureLoadingDialog(context: context, future: room.leave);
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => leaveRoom(room),
|
||||
);
|
||||
|
||||
return;
|
||||
case ChatContextAction.addToSpace:
|
||||
@@ -901,6 +909,65 @@ class ChatListController extends State<ChatList>
|
||||
|
||||
bool waitForFirstSync = false;
|
||||
|
||||
/// Leaves the room locally and removes it from the local database.
|
||||
Future<void> leaveRoom(Room room) async {
|
||||
try {
|
||||
await room.leave();
|
||||
} on MatrixException catch (e, s) {
|
||||
// If the room was already left on the server (e.g. a stale local copy
|
||||
// reappeared after a restart) the leave request fails, but the user
|
||||
// intent (room not being listed anymore) is already fulfilled. Clean up
|
||||
// locally instead of showing an error.
|
||||
if (!{
|
||||
MatrixError.M_FORBIDDEN,
|
||||
MatrixError.M_NOT_FOUND,
|
||||
MatrixError.M_UNKNOWN,
|
||||
}.contains(e.error)) {
|
||||
Logs().w('Failed to leave room ${room.id}', e, s);
|
||||
rethrow;
|
||||
}
|
||||
Logs().w('Room ${room.id} is already gone, cleaning up locally...', e, s);
|
||||
}
|
||||
final client = room.client;
|
||||
// Make sure the room does not reappear from the local database after an
|
||||
// app restart, even if the leave sync did not run in time.
|
||||
await client.database.forgetRoom(room.id);
|
||||
client.rooms.remove(room);
|
||||
}
|
||||
|
||||
/// Loads the shared [roomTypeStateEvent] marker from the synced room state
|
||||
/// (from the local database for partially loaded rooms) and writes it for
|
||||
/// existing canal/mural rooms that only have the per-account tag of the
|
||||
/// creating account. This makes the category visible right after the sync,
|
||||
/// without having to open the room first. Only admins can write the marker,
|
||||
/// non-admin members simply read it.
|
||||
Future<void> backfillRoomTypeMarkers() async {
|
||||
final client = Matrix.of(context).client;
|
||||
if (client.rooms.isEmpty) return;
|
||||
for (final room in client.rooms) {
|
||||
if (room.membership != Membership.join) continue;
|
||||
if (room.roomType != null) continue;
|
||||
// Partial rooms do not keep unimportant state events (like our room type
|
||||
// marker) in memory, so load them from the local database first.
|
||||
await room.postLoad();
|
||||
final type =
|
||||
room.roomType ??
|
||||
(room.tags.keys.contains(AppConfig.canalTag)
|
||||
? 'canal'
|
||||
: room.tags.keys.contains(AppConfig.muralTag)
|
||||
? 'mural'
|
||||
: null);
|
||||
if (type == null || room.roomType == type) continue;
|
||||
if (room.ownPowerLevel < PowerLevel.admin) continue;
|
||||
try {
|
||||
await room.setRoomType(type);
|
||||
} catch (e, s) {
|
||||
Logs().w('Failed to backfill room type $type for ${room.id}', e, s);
|
||||
}
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _waitForFirstSync() async {
|
||||
final router = GoRouter.of(context);
|
||||
final client = Matrix.of(context).client;
|
||||
@@ -921,6 +988,7 @@ class ChatListController extends State<ChatList>
|
||||
setState(() {
|
||||
waitForFirstSync = true;
|
||||
});
|
||||
backfillRoomTypeMarkers();
|
||||
|
||||
if (client.userDeviceKeys[client.userID!]?.deviceKeys.values.any(
|
||||
(device) => !device.verified && !device.blocked,
|
||||
|
||||
Reference in New Issue
Block a user