fix: beim Gott-Datei-Split verlorene Erklärkommentare wiederhergestellt

Qualitäts-Review-Befund (a): Alle 6 part/part-of-Aufteilungen hatten Code
vollständig übernommen (Zeilen-Multiset-Vergleich Original vs. heutiger
Stand: keine Code-Zeile verloren, keine hinzugefügt), ABER erklärende
Kommentare über den Klassen gingen verloren (24 Blöcke, u. a. die
HTML-Subset-Doku über _MatrixHtmlText, die Selbst-Aussperr-Warnung über
updatePowerLevelsSafely, TURN-Verbrauchskarte, PDF-Cache/Viewer-Doku).
Alle an den richtigen Klassen wiederhergestellt; reine Trennlinien und
ALL-CAPS-Abschnittslabels bewusst nicht (Gott-Datei-Navigation, durch
die neuen Dateinamen ersetzt).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-03 14:37:48 +02:00
parent d254a36de7
commit f9979e4f32
13 changed files with 62 additions and 0 deletions
@@ -112,6 +112,7 @@ class _ArchiveEntry {
final bool isDir; final bool isDir;
const _ArchiveEntry({required this.name, required this.size, required this.isDir}); const _ArchiveEntry({required this.name, required this.size, required this.isDir});
} }
// Extracts the JPEG thumbnail stored in PSD Image Resource 1036.
Uint8List? _extractPsdThumbnail(Uint8List bytes) { Uint8List? _extractPsdThumbnail(Uint8List bytes) {
try { try {
if (bytes.length < 34) return null; if (bytes.length < 34) return null;
@@ -220,6 +221,8 @@ class _PsdThumbViewer extends StatelessWidget {
); );
} }
} }
// ZIP-preview viewer (Sketch, XD).
// These formats are ZIP archives that embed a preview PNG.
class _ZipPreviewViewer extends StatefulWidget { class _ZipPreviewViewer extends StatefulWidget {
final Uint8List bytes; final Uint8List bytes;
final String filename; final String filename;
@@ -1,5 +1,9 @@
part of '../document_viewer.dart'; part of '../document_viewer.dart';
// Static render cache.
// Survives widget dispose/recreate (e.g. scroll in chat list).
// Key: filename + file-size-bytes + page + render-dimensions.
// Evicts the oldest entry (FIFO) when the limit is reached.
class _RenderCache { class _RenderCache {
static const _max = 80; static const _max = 80;
static final _data = <String, Uint8List>{}; static final _data = <String, Uint8List>{};
@@ -37,6 +41,10 @@ Future<void> _saveBytes(BuildContext context, Uint8List bytes, String filename)
} }
} }
} }
// Inline chat preview.
// Used by _DocPreviewContent in message_group.dart.
// Shows the first page of a PDF as an image with page navigation arrows.
// Tap → opens the full DocumentViewer overlay.
class PdfInlinePreview extends StatefulWidget { class PdfInlinePreview extends StatefulWidget {
final Uint8List bytes; final Uint8List bytes;
final String filename; final String filename;
@@ -454,6 +462,9 @@ class _PdfFullViewerState extends State<_PdfFullViewer> {
); );
} }
} }
// Immersive fullscreen PDF viewer.
// Launched from the enlarged preview. Per-page high-res render, pan via drag,
// zoom via +/ buttons (20%/click) and Ctrl+wheel, page nav via wheel/arrows.
class _PdfFullscreenViewer extends StatefulWidget { class _PdfFullscreenViewer extends StatefulWidget {
final Uint8List bytes; final Uint8List bytes;
final String filename; final String filename;
@@ -6,6 +6,7 @@ String _formatMessageTime(DateTime t) {
return '$h:$m'; return '$h:$m';
} }
// Wraps a group of consecutive messages from the same sender
class MessageGroup extends StatefulWidget { class MessageGroup extends StatefulWidget {
final List<Event> events; final List<Event> events;
final String currentUserId; final String currentUserId;
@@ -304,6 +305,8 @@ class _MessageGroupState extends State<MessageGroup> {
} }
} }
// Shown under a message whose send failed (e.g. offline) — keeps the message
// visible with retry / delete instead of letting it silently disappear.
class _SendFailedRow extends StatelessWidget { class _SendFailedRow extends StatelessWidget {
final Event event; final Event event;
final PyramidTheme pt; final PyramidTheme pt;
@@ -346,6 +349,7 @@ class _SendFailedRow extends StatelessWidget {
); );
} }
// Single message (first in group) with full avatar + name shown
class _MessageBody extends StatelessWidget { class _MessageBody extends StatelessWidget {
final Event event; final Event event;
final PyramidTheme pt; final PyramidTheme pt;
@@ -374,6 +378,7 @@ class _MessageBody extends StatelessWidget {
} }
} }
// Continuation message — no avatar, just indented text with timestamp on hover
class _ContinuationMessage extends StatefulWidget { class _ContinuationMessage extends StatefulWidget {
final Event event; final Event event;
final PyramidTheme pt; final PyramidTheme pt;
@@ -155,6 +155,7 @@ Size? _reservedImageSize(Event event, {required double maxW, required double max
return Size(w * scale, h * scale); return Size(w * scale, h * scale);
} }
// Downloads media bytes then hands off to PyramidAudioPlayer / PyramidVideoPlayer
class _AudioContent extends ConsumerStatefulWidget { class _AudioContent extends ConsumerStatefulWidget {
final Event event; final Event event;
final PyramidTheme pt; final PyramidTheme pt;
@@ -433,6 +434,7 @@ class _MediaContentState extends ConsumerState<_MediaContent> {
_mediaFallback(pt, error, widget.event.body); _mediaFallback(pt, error, widget.event.body);
} }
// Downloads bytes for a PDF/SVG file and shows PdfInlinePreview or SvgImage.
class _DocPreviewContent extends ConsumerStatefulWidget { class _DocPreviewContent extends ConsumerStatefulWidget {
final Event event; final Event event;
final PyramidTheme pt; final PyramidTheme pt;
@@ -572,6 +574,8 @@ class _DocPreviewContentState extends ConsumerState<_DocPreviewContent> {
} }
} }
// Inline card for proprietary formats — shows immediately from event metadata,
// downloads lazily only when the user taps or hits the download button.
class _ProprietaryInlineCard extends ConsumerStatefulWidget { class _ProprietaryInlineCard extends ConsumerStatefulWidget {
final Event event; final Event event;
final PyramidTheme pt; final PyramidTheme pt;
@@ -953,6 +957,9 @@ String _classifyMediaError(Object e) {
return 'Download fehlgeschlagen'; return 'Download fehlgeschlagen';
} }
// Thin wrapper over the shared MediaCache so document previews share the same
// LRU byte budget as chat images (keeps the last-viewed media instantly
// available, evicts the oldest).
class _DocBytesCache { class _DocBytesCache {
static Uint8List? get(String eventId) => MediaCache.instance.get(eventId); static Uint8List? get(String eventId) => MediaCache.instance.get(eventId);
static void put(String eventId, Uint8List bytes) => static void put(String eventId, Uint8List bytes) =>
@@ -89,6 +89,8 @@ class _InitialBox extends StatelessWidget {
} }
} }
// Inline "Neue Nachrichten" divider rendered inside a MessageGroup when the
// fullyRead event is in the middle of the group (same sender, <5 min window).
class _InlineUnreadDivider extends StatelessWidget { class _InlineUnreadDivider extends StatelessWidget {
final PyramidTheme pt; final PyramidTheme pt;
const _InlineUnreadDivider({required this.pt}); const _InlineUnreadDivider({required this.pt});
@@ -114,6 +116,7 @@ class _InlineUnreadDivider extends StatelessWidget {
} }
} }
// Date divider between message groups
class DateDivider extends StatelessWidget { class DateDivider extends StatelessWidget {
final DateTime date; final DateTime date;
@@ -183,6 +186,8 @@ class _MessageStatus extends StatelessWidget {
} }
} }
// Read receipt: a ✓✓ shown at the partner's last-read message (any sender),
// so it moves next to their own message when they reply.
class _ReadMarker extends StatelessWidget { class _ReadMarker extends StatelessWidget {
final PyramidTheme pt; final PyramidTheme pt;
const _ReadMarker({required this.pt}); const _ReadMarker({required this.pt});
@@ -186,6 +186,18 @@ class _MessageContent extends StatelessWidget {
} }
} }
// Handles the subset of HTML that Matrix clients emit in formatted_body:
// <b>/<strong> → bold
// <i>/<em> → italic
// <del>/<s> → strikethrough
// <u> → underline
// <code> → monospace, slightly different background
// <a href> → tappable link (url_launcher)
// <br> → newline
// HTML entities → decoded
// Anything else → text content preserved, tags stripped
//
// Note: <pre> and <blockquote> are simplified (rendered as indented code/text).
class _MatrixHtmlText extends StatelessWidget { class _MatrixHtmlText extends StatelessWidget {
final String html; final String html;
final TextStyle baseStyle; final TextStyle baseStyle;
@@ -1,5 +1,7 @@
part of '../chat_view.dart'; part of '../chat_view.dart';
// Shows a slim banner when the client loses its connection to the homeserver.
// Debounced so brief transient sync errors don't flicker a banner.
class _ConnectionBanner extends ConsumerStatefulWidget { class _ConnectionBanner extends ConsumerStatefulWidget {
const _ConnectionBanner(); const _ConnectionBanner();
@override @override
+3
View File
@@ -182,6 +182,9 @@ class _ChatHeader extends ConsumerWidget {
} }
} }
/// DM-Header, der sich per Klick aufklappt: Das Banner blendet als Hintergrund
/// ein, Avatar und Name skalieren mit, Handle + Bio erscheinen darunter
/// (Design nach Nutzer-Mockup, Twitter/Discord-artig).
class _DmExpandingHeader extends ConsumerStatefulWidget { class _DmExpandingHeader extends ConsumerStatefulWidget {
final Room room; final Room room;
final String roomId; final String roomId;
+1
View File
@@ -99,6 +99,7 @@ class _DragOverlayState extends State<_DragOverlay>
} }
} }
// Placeholder shown when no room is selected
class NoChatSelected extends StatelessWidget { class NoChatSelected extends StatelessWidget {
const NoChatSelected({super.key}); const NoChatSelected({super.key});
@@ -341,6 +341,8 @@ class _MenuRow extends StatelessWidget {
]); ]);
} }
/// Glocke im Header: Popup mit allen Räumen, die ungelesene Nachrichten oder
/// Mentions haben. Tippen öffnet den jeweiligen Raum.
class _NotificationsButton extends ConsumerWidget { class _NotificationsButton extends ConsumerWidget {
final PyramidTheme pt; final PyramidTheme pt;
final bool hasBanner; final bool hasBanner;
@@ -2,6 +2,11 @@ part of '../space_admin_dialog.dart';
const _kSpaceBannerEvent = 'io.pyramid.space.banner'; const _kSpaceBannerEvent = 'io.pyramid.space.banner';
/// Ändert die Power-Levels auf Basis des aktuellen SERVER-Stands (nicht des
/// lokalen Caches!). Das SDK-`setPower` und ein lokal fehlendes
/// power_levels-Event haben sonst zur Folge, dass die users-Map des Servers
/// mit einem Minimal-Event überschrieben wird — und Admins sich selbst
/// aussperren (genau das ist hier bereits passiert).
Future<void> updatePowerLevelsSafely( Future<void> updatePowerLevelsSafely(
Room room, Room room,
void Function(Map<String, dynamic> content) mutate, void Function(Map<String, dynamic> content) mutate,
@@ -50,6 +55,7 @@ Future<void> updatePowerLevelsSafely(
.setRoomStateWithKey(room.id, EventTypes.RoomPowerLevels, '', content); .setRoomStateWithKey(room.id, EventTypes.RoomPowerLevels, '', content);
} }
// Power level labels
const _kPowerAdmin = 100; const _kPowerAdmin = 100;
const _kPowerMod = 50; const _kPowerMod = 50;
const _kPowerMember = 0; const _kPowerMember = 0;
@@ -1332,6 +1332,7 @@ class _FingerprintGroup extends ConsumerWidget {
} }
} }
// Passphrase dialog (for key export/import)
class _PassphraseDialog extends StatefulWidget { class _PassphraseDialog extends StatefulWidget {
final PyramidTheme pt; final PyramidTheme pt;
final bool forExport; final bool forExport;
+4
View File
@@ -334,6 +334,10 @@ class _VoiceSectionState extends State<_VoiceSection> {
} }
} }
/// Zeigt den TURN-Relay-Verbrauch des laufenden Monats an. Die Daten kommen
/// aus der turn-status.json, die der Guard-Cron auf dem Pi stündlich schreibt.
/// Solange der Endpoint nicht existiert (Analytics-Token fehlt), bleibt die
/// Karte unsichtbar.
class _TurnUsageCard extends StatefulWidget { class _TurnUsageCard extends StatefulWidget {
final PyramidTheme pt; final PyramidTheme pt;
const _TurnUsageCard({required this.pt}); const _TurnUsageCard({required this.pt});