Files
help4bis b941cdfe4b refactor: /simplify — 22 fixes from 3-agent code review
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>
2026-04-02 13:19:22 +10:00

94 lines
2.6 KiB
Dart

// Version: 1.1.0 | Created: 2026-04-01 | Updated: 2026-04-02
// Individual room list tile. Kept under 100 lines.
import 'package:flutter/material.dart';
import 'package:timeago/timeago.dart' as timeago;
import '../../../shared/widgets/matrix_avatar.dart';
import '../domain/room_model.dart';
class RoomTile extends StatelessWidget {
const RoomTile({super.key, required this.room, required this.onTap});
final RoomModel room;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final hasUnread = room.unreadCount > 0;
return ListTile(
leading: MatrixAvatar(
name: room.displayName,
avatarUrl: room.avatarUrl,
radius: 24,
),
title: Text(
room.displayName,
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: hasUnread ? FontWeight.w600 : FontWeight.normal,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: room.lastMessagePreview != null
? Text(
room.lastMessagePreview!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withAlpha(153),
),
)
: null,
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (room.lastActivityAt != null)
Text(
timeago.format(room.lastActivityAt!, locale: 'en_short'),
style: theme.textTheme.labelSmall?.copyWith(
color: hasUnread
? theme.colorScheme.primary
: theme.colorScheme.onSurface.withAlpha(102),
),
),
if (hasUnread) ...[
const SizedBox(height: 4),
_UnreadBadge(count: room.unreadCount),
],
],
),
onTap: onTap,
);
}
}
class _UnreadBadge extends StatelessWidget {
const _UnreadBadge({required this.count});
final int count;
@override
Widget build(BuildContext context) {
final label = count > 99 ? '99+' : count.toString();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(10),
),
child: Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
);
}
}