Critical: - Fix MXC URI resolution: all avatars/images now resolve mxc:// to HTTP - Sync persistence: only write changed rooms, batch message upserts - lastActivityAt uses room.lastEvent.originServerTs, not creation time High: - Shared MatrixAvatar widget replaces 6 duplicate implementations - CallScreen decodes roomId before LiveKit JWT fetch - Decline button actually dismisses incoming call overlay - EventTypes constants replace raw string literals - LiveKitService uses lazy auth reads, onDispose disconnects Medium: - CallController is keepAlive with timer/room cleanup - authRepository is keepAlive (used from keepAlive notifier) - StreamController not closed in stopListening (crash fix) - Index on messages.roomId for query performance - 400ms debounce on user search - Static DateFormat in MessageBubble - Hardcoded strings replaced with AppConfig refs - Duplicate isDirectMessage field removed from RoomModel - E2EE profile claim corrected to Phase 3 Shared utilities: - lib/shared/widgets/matrix_avatar.dart - lib/shared/utils/mxc_url.dart - lib/shared/utils/room_preview.dart - lib/shared/utils/matrix_id.dart rawJson column removed (unused, caused main-thread jsonEncode) Schema migrated to v2 with roomId index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
73 lines
2.2 KiB
Dart
73 lines
2.2 KiB
Dart
// Version: 1.2.0 | Created: 2026-04-01 | Updated: 2026-04-02
|
|
// SpacesRepository — builds space list and child rooms from the Matrix SDK.
|
|
// A space is a room where isSpace == true.
|
|
|
|
import 'package:matrix/matrix.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
import '../../../core/network/matrix_client.dart';
|
|
import '../../../shared/utils/mxc_url.dart';
|
|
import '../domain/space_model.dart';
|
|
|
|
part 'spaces_repository.g.dart';
|
|
|
|
@riverpod
|
|
SpacesRepository spacesRepository(Ref ref) {
|
|
return SpacesRepository(client: ref.watch(matrixClientProvider));
|
|
}
|
|
|
|
class SpacesRepository {
|
|
SpacesRepository({required Client client}) : _client = client;
|
|
|
|
final Client _client;
|
|
|
|
/// Returns all rooms that are spaces.
|
|
List<SpaceModel> getSpaces() {
|
|
return _client.rooms.where((r) => r.isSpace).map(_toSpaceModel).toList();
|
|
}
|
|
|
|
/// Returns child rooms within [spaceId] that the client is a member of.
|
|
///
|
|
/// SpaceChild gives us the roomId only. We look up the Room from the client
|
|
/// to get display name and avatar. If the child room is not in the client's
|
|
/// room list (not joined), it is omitted.
|
|
List<SpaceRoomModel> getRoomsInSpace(String spaceId) {
|
|
final space = _client.getRoomById(spaceId);
|
|
if (space == null || !space.isSpace) return [];
|
|
|
|
final result = <SpaceRoomModel>[];
|
|
for (final child in space.spaceChildren) {
|
|
final childRoomId = child.roomId;
|
|
if (childRoomId == null) continue;
|
|
|
|
final room = _client.getRoomById(childRoomId);
|
|
if (room == null) continue; // not joined — skip
|
|
|
|
result.add(
|
|
SpaceRoomModel(
|
|
id: room.id,
|
|
displayName: room.getLocalizedDisplayname(),
|
|
avatarUrl: resolveMxcUrl(_client, room.avatar),
|
|
isDirect: room.isDirectChat,
|
|
),
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Stream that emits on every sync so the UI stays current.
|
|
Stream<List<SpaceModel>> watchSpaces() async* {
|
|
yield getSpaces();
|
|
yield* _client.onSync.stream.map((_) => getSpaces());
|
|
}
|
|
|
|
SpaceModel _toSpaceModel(Room room) {
|
|
return SpaceModel(
|
|
id: room.id,
|
|
displayName: room.getLocalizedDisplayname(),
|
|
avatarUrl: resolveMxcUrl(_client, room.avatar),
|
|
roomCount: room.spaceChildren.length,
|
|
);
|
|
}
|
|
}
|