feat: Handy-Chatliste als Messenger-Kachel (Vorschau, Uhrzeit, Ungelesen) + deutsche Beschriftungen
- rooms_mobile_tile.dart: auf dem Handy grosser Avatar, Name + Uhrzeit der letzten Nachricht, Vorschau (gleiche Texte wie Push: Medien-Labels, Antwort-Zitat entfernt; "Du:" bzw. Absender in Gruppen), Ungelesen-Zaehler. PC behaelt die kompakte Liste, Voice-Kanaele ihre Darstellung. - utils/date_labels.dart: chatListTime (heute Uhrzeit, Gestern, Wochentag, Datum; sicher an Zeitumstellungs-Tagen) + 5 Tests. - Sichtbare englische Texte eingedeutscht (Chats durchsuchen, Direktnachrichten, Raeume, Nachricht..., Mitglieder, Suchen, ...). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c5f10901e6
commit
c38e9c18b0
@@ -0,0 +1,159 @@
|
||||
part of '../rooms_panel.dart';
|
||||
|
||||
// Chatlisten-Eintrag im Handy-Layout (Bernds Feedback 2026-10-07: die Handy-App
|
||||
// soll sich wie ein gewohnter Messenger anfühlen): großer Avatar, Name mit
|
||||
// Uhrzeit der letzten Nachricht, darunter die letzte Nachricht als Vorschau
|
||||
// und der Ungelesen-Zähler. Am PC bleibt die kompakte Discord-Liste
|
||||
// (_RoomItem); Voice-Kanäle behalten ihre eigene Darstellung.
|
||||
|
||||
const _kTileAvatar = 46.0;
|
||||
|
||||
class _MobileRoomTile extends StatelessWidget {
|
||||
final Room room;
|
||||
final bool isActive;
|
||||
final PyramidTheme pt;
|
||||
final String? myUserId;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback onLongPress;
|
||||
|
||||
const _MobileRoomTile({
|
||||
required this.room,
|
||||
required this.isActive,
|
||||
required this.pt,
|
||||
required this.myUserId,
|
||||
required this.onTap,
|
||||
required this.onLongPress,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final name = room.getLocalizedDisplayname();
|
||||
final unread = room.notificationCount;
|
||||
final mention = room.highlightCount > 0;
|
||||
final last = room.lastEvent;
|
||||
final preview = _roomPreview(room, myUserId);
|
||||
final time = last == null ? null : chatListTime(last.originServerTs);
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
child: Container(
|
||||
color: isActive ? pt.accentSoft : Colors.transparent,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 9),
|
||||
child: Row(
|
||||
children: [
|
||||
room.isDirectChat
|
||||
? _DmAvatar(room: room, pt: pt, size: _kTileAvatar)
|
||||
: _RoomTileAvatar(room: room, pt: pt),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: pt.fg,
|
||||
fontSize: 15,
|
||||
fontWeight:
|
||||
unread > 0 ? FontWeight.w700 : FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (time != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(
|
||||
color: unread > 0 ? pt.accent : pt.fgDim,
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
unread > 0 ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
preview ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: unread > 0 ? pt.fgMuted : pt.fgDim,
|
||||
fontSize: 13.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (unread > 0) ...[
|
||||
const SizedBox(width: 8),
|
||||
_UnreadBadge(count: unread, isMention: mention, pt: pt),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Avatar für Räume (kein DM): Raumbild oder abgerundetes Symbol.
|
||||
class _RoomTileAvatar extends ConsumerWidget {
|
||||
final Room room;
|
||||
final PyramidTheme pt;
|
||||
const _RoomTileAvatar({required this.room, required this.pt});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final client = ref.watch(matrixClientProvider).valueOrNull;
|
||||
final fallback = Container(
|
||||
width: _kTileAvatar,
|
||||
height: _kTileAvatar,
|
||||
decoration: BoxDecoration(
|
||||
color: pt.bg3,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(
|
||||
room.encrypted ? Icons.lock_outline_rounded : Icons.tag_rounded,
|
||||
size: 22,
|
||||
color: pt.fgMuted,
|
||||
),
|
||||
);
|
||||
if (client == null || room.avatar == null) return fallback;
|
||||
return MxcAvatar(
|
||||
mxcUri: room.avatar,
|
||||
client: client,
|
||||
size: _kTileAvatar,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
placeholder: (_) => fallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Vorschau der letzten Nachricht: gleiche Texte wie in Benachrichtigungen
|
||||
/// (Medien als „📷 Foto“ usw., Antwort-Zitat entfernt), in Gruppen mit
|
||||
/// Absender, eigene Nachrichten mit „Du:“.
|
||||
String? _roomPreview(Room room, String? myUserId) {
|
||||
final e = room.lastEvent;
|
||||
if (e == null) return null;
|
||||
if (e.type == EventTypes.Encrypted) return '🔒 Verschlüsselte Nachricht';
|
||||
final body = notificationBody(e);
|
||||
if (body == null) return null;
|
||||
if (e.senderId == myUserId) return 'Du: $body';
|
||||
if (room.isDirectChat) return body;
|
||||
return '${e.senderFromMemoryOrFallback.calcDisplayname()}: $body';
|
||||
}
|
||||
Reference in New Issue
Block a user