From 69c37c43074ad20ab9c7a5b7f4464863c4d298d4 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sun, 22 Mar 2026 15:58:16 +0100
Subject: [PATCH] fix: image flicker, screenshot indicator, cross-session message storage
---
lib/widgets/message_bubble.dart | 17 ++++++++++++-----
lib/screens/chat_screen.dart | 20 ++++++++++++++++++++
2 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index e60e02a..d0f67d1 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -13,6 +13,7 @@
import '../models/server_config.dart';
import '../providers/providers.dart';
import '../services/audio_service.dart';
+import '../services/message_store.dart';
import '../services/websocket_service.dart';
import '../theme/app_theme.dart';
import '../widgets/command_bar.dart';
@@ -264,6 +265,8 @@
final activeId = ref.read(activeSessionIdProvider);
if (sessionId != null && sessionId != activeId) {
+ // Store message for the other session so it's there when user switches
+ _storeForSession(sessionId, message);
_incrementUnread(sessionId);
final sessions = ref.read(sessionsProvider);
final session = sessions.firstWhere(
@@ -304,6 +307,7 @@
final activeId = ref.read(activeSessionIdProvider);
if (sessionId != null && sessionId != activeId) {
+ _storeForSession(sessionId, message);
_incrementUnread(sessionId);
final sessions = ref.read(sessionsProvider);
final session = sessions.firstWhere(
@@ -366,6 +370,13 @@
ref.read(messagesProvider.notifier).addMessage(message);
ref.read(isTypingProvider.notifier).state = false;
_scrollToBottom();
+ }
+
+ /// Store a message for a non-active session so it persists when the user switches to it.
+ void _storeForSession(String sessionId, Message message) {
+ MessageStore.loadAll(sessionId).then((existing) {
+ MessageStore.save(sessionId, [...existing, message]);
+ });
}
void _incrementUnread(String sessionId) {
@@ -525,6 +536,15 @@
void _requestScreenshot() {
_screenshotForChat = true;
_sendCommand('screenshot');
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Capturing screenshot...'),
+ duration: Duration(seconds: 2),
+ behavior: SnackBarBehavior.floating,
+ ),
+ );
+ }
}
void _navigateToTerminal() {
diff --git a/lib/widgets/message_bubble.dart b/lib/widgets/message_bubble.dart
index e9c4882..c5df417 100644
--- a/lib/widgets/message_bubble.dart
+++ b/lib/widgets/message_bubble.dart
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:math';
+import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -8,6 +9,9 @@
import '../models/message.dart';
import '../theme/app_theme.dart';
import 'image_viewer.dart';
+
+// Cache decoded image bytes to prevent flicker on widget rebuild
+final Map<String, Uint8List> _imageCache = {};
/// Chat message bubble with support for text, voice, and image types.
class MessageBubble extends StatelessWidget {
@@ -208,11 +212,13 @@
return const Text('Image unavailable');
}
- final bytes = base64Decode(
- message.imageBase64!.contains(',')
- ? message.imageBase64!.split(',').last
- : message.imageBase64!,
- );
+ // Cache decoded bytes to prevent flicker on rebuild
+ final bytes = _imageCache.putIfAbsent(message.id, () {
+ final raw = message.imageBase64!;
+ return Uint8List.fromList(base64Decode(
+ raw.contains(',') ? raw.split(',').last : raw,
+ ));
+ });
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -232,6 +238,7 @@
width: 260,
height: 180,
fit: BoxFit.cover,
+ gaplessPlayback: true,
errorBuilder: (_, e, st) => const SizedBox(
width: 260,
height: 60,
--
Gitblit v1.3.1