Mark message as read

This commit is contained in:
LittleSheep 2025-05-18 05:36:20 +08:00
parent dd50cfd2e9
commit 93267eb327
7 changed files with 147 additions and 19 deletions

View File

@ -10,7 +10,20 @@ class AppDatabase extends _$AppDatabase {
AppDatabase(super.e); AppDatabase(super.e);
@override @override
int get schemaVersion => 1; int get schemaVersion => 2;
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (Migrator m) async {
await m.createAll();
},
onUpgrade: (Migrator m, int from, int to) async {
if (from < 2) {
// Add isRead column with default value false
await m.addColumn(chatMessages, chatMessages.isRead);
}
},
);
// Methods for chat messages // Methods for chat messages
Future<List<ChatMessage>> getMessagesForRoom( Future<List<ChatMessage>> getMessagesForRoom(
@ -40,6 +53,12 @@ class AppDatabase extends _$AppDatabase {
)).write(ChatMessagesCompanion(status: Value(status))); )).write(ChatMessagesCompanion(status: Value(status)));
} }
Future<int> markMessageAsRead(String id) {
return (update(chatMessages)..where(
(m) => m.id.equals(id),
)).write(ChatMessagesCompanion(isRead: const Value(true)));
}
Future<int> deleteMessage(String id) { Future<int> deleteMessage(String id) {
return (delete(chatMessages)..where((m) => m.id.equals(id))).go(); return (delete(chatMessages)..where((m) => m.id.equals(id))).go();
} }
@ -55,6 +74,7 @@ class AppDatabase extends _$AppDatabase {
data: Value(jsonEncode(message.data)), data: Value(jsonEncode(message.data)),
createdAt: Value(message.createdAt), createdAt: Value(message.createdAt),
status: Value(message.status), status: Value(message.status),
isRead: Value(message.isRead),
); );
} }
@ -68,6 +88,7 @@ class AppDatabase extends _$AppDatabase {
createdAt: dbMessage.createdAt, createdAt: dbMessage.createdAt,
status: dbMessage.status, status: dbMessage.status,
nonce: dbMessage.nonce, nonce: dbMessage.nonce,
isRead: dbMessage.isRead,
); );
} }
} }

View File

@ -87,6 +87,19 @@ class $ChatMessagesTable extends ChatMessages
type: DriftSqlType.int, type: DriftSqlType.int,
requiredDuringInsert: true, requiredDuringInsert: true,
).withConverter<MessageStatus>($ChatMessagesTable.$converterstatus); ).withConverter<MessageStatus>($ChatMessagesTable.$converterstatus);
static const VerificationMeta _isReadMeta = const VerificationMeta('isRead');
@override
late final GeneratedColumn<bool> isRead = GeneratedColumn<bool>(
'is_read',
aliasedName,
false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_read" IN (0, 1))',
),
defaultValue: const Constant(false),
);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
id, id,
@ -97,6 +110,7 @@ class $ChatMessagesTable extends ChatMessages
data, data,
createdAt, createdAt,
status, status,
isRead,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@ -159,6 +173,12 @@ class $ChatMessagesTable extends ChatMessages
} else if (isInserting) { } else if (isInserting) {
context.missing(_createdAtMeta); context.missing(_createdAtMeta);
} }
if (data.containsKey('is_read')) {
context.handle(
_isReadMeta,
isRead.isAcceptableOrUnknown(data['is_read']!, _isReadMeta),
);
}
return context; return context;
} }
@ -207,6 +227,11 @@ class $ChatMessagesTable extends ChatMessages
data['${effectivePrefix}status'], data['${effectivePrefix}status'],
)!, )!,
), ),
isRead:
attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}is_read'],
)!,
); );
} }
@ -228,6 +253,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
final String data; final String data;
final DateTime createdAt; final DateTime createdAt;
final MessageStatus status; final MessageStatus status;
final bool isRead;
const ChatMessage({ const ChatMessage({
required this.id, required this.id,
required this.roomId, required this.roomId,
@ -237,6 +263,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
required this.data, required this.data,
required this.createdAt, required this.createdAt,
required this.status, required this.status,
required this.isRead,
}); });
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
@ -257,6 +284,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
$ChatMessagesTable.$converterstatus.toSql(status), $ChatMessagesTable.$converterstatus.toSql(status),
); );
} }
map['is_read'] = Variable<bool>(isRead);
return map; return map;
} }
@ -274,6 +302,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
data: Value(data), data: Value(data),
createdAt: Value(createdAt), createdAt: Value(createdAt),
status: Value(status), status: Value(status),
isRead: Value(isRead),
); );
} }
@ -293,6 +322,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
status: $ChatMessagesTable.$converterstatus.fromJson( status: $ChatMessagesTable.$converterstatus.fromJson(
serializer.fromJson<int>(json['status']), serializer.fromJson<int>(json['status']),
), ),
isRead: serializer.fromJson<bool>(json['isRead']),
); );
} }
@override @override
@ -309,6 +339,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
'status': serializer.toJson<int>( 'status': serializer.toJson<int>(
$ChatMessagesTable.$converterstatus.toJson(status), $ChatMessagesTable.$converterstatus.toJson(status),
), ),
'isRead': serializer.toJson<bool>(isRead),
}; };
} }
@ -321,6 +352,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
String? data, String? data,
DateTime? createdAt, DateTime? createdAt,
MessageStatus? status, MessageStatus? status,
bool? isRead,
}) => ChatMessage( }) => ChatMessage(
id: id ?? this.id, id: id ?? this.id,
roomId: roomId ?? this.roomId, roomId: roomId ?? this.roomId,
@ -330,6 +362,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
data: data ?? this.data, data: data ?? this.data,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
status: status ?? this.status, status: status ?? this.status,
isRead: isRead ?? this.isRead,
); );
ChatMessage copyWithCompanion(ChatMessagesCompanion data) { ChatMessage copyWithCompanion(ChatMessagesCompanion data) {
return ChatMessage( return ChatMessage(
@ -341,6 +374,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
data: data.data.present ? data.data.value : this.data, data: data.data.present ? data.data.value : this.data,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
status: data.status.present ? data.status.value : this.status, status: data.status.present ? data.status.value : this.status,
isRead: data.isRead.present ? data.isRead.value : this.isRead,
); );
} }
@ -354,7 +388,8 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
..write('nonce: $nonce, ') ..write('nonce: $nonce, ')
..write('data: $data, ') ..write('data: $data, ')
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('status: $status') ..write('status: $status, ')
..write('isRead: $isRead')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -369,6 +404,7 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
data, data,
createdAt, createdAt,
status, status,
isRead,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -381,7 +417,8 @@ class ChatMessage extends DataClass implements Insertable<ChatMessage> {
other.nonce == this.nonce && other.nonce == this.nonce &&
other.data == this.data && other.data == this.data &&
other.createdAt == this.createdAt && other.createdAt == this.createdAt &&
other.status == this.status); other.status == this.status &&
other.isRead == this.isRead);
} }
class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> { class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
@ -393,6 +430,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
final Value<String> data; final Value<String> data;
final Value<DateTime> createdAt; final Value<DateTime> createdAt;
final Value<MessageStatus> status; final Value<MessageStatus> status;
final Value<bool> isRead;
final Value<int> rowid; final Value<int> rowid;
const ChatMessagesCompanion({ const ChatMessagesCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
@ -403,6 +441,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
this.data = const Value.absent(), this.data = const Value.absent(),
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.status = const Value.absent(), this.status = const Value.absent(),
this.isRead = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}); });
ChatMessagesCompanion.insert({ ChatMessagesCompanion.insert({
@ -414,6 +453,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
required String data, required String data,
required DateTime createdAt, required DateTime createdAt,
required MessageStatus status, required MessageStatus status,
this.isRead = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
roomId = Value(roomId), roomId = Value(roomId),
@ -430,6 +470,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
Expression<String>? data, Expression<String>? data,
Expression<DateTime>? createdAt, Expression<DateTime>? createdAt,
Expression<int>? status, Expression<int>? status,
Expression<bool>? isRead,
Expression<int>? rowid, Expression<int>? rowid,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
@ -441,6 +482,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
if (data != null) 'data': data, if (data != null) 'data': data,
if (createdAt != null) 'created_at': createdAt, if (createdAt != null) 'created_at': createdAt,
if (status != null) 'status': status, if (status != null) 'status': status,
if (isRead != null) 'is_read': isRead,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
}); });
} }
@ -454,6 +496,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
Value<String>? data, Value<String>? data,
Value<DateTime>? createdAt, Value<DateTime>? createdAt,
Value<MessageStatus>? status, Value<MessageStatus>? status,
Value<bool>? isRead,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
return ChatMessagesCompanion( return ChatMessagesCompanion(
@ -465,6 +508,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
data: data ?? this.data, data: data ?? this.data,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
status: status ?? this.status, status: status ?? this.status,
isRead: isRead ?? this.isRead,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
); );
} }
@ -498,6 +542,9 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
$ChatMessagesTable.$converterstatus.toSql(status.value), $ChatMessagesTable.$converterstatus.toSql(status.value),
); );
} }
if (isRead.present) {
map['is_read'] = Variable<bool>(isRead.value);
}
if (rowid.present) { if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value); map['rowid'] = Variable<int>(rowid.value);
} }
@ -515,6 +562,7 @@ class ChatMessagesCompanion extends UpdateCompanion<ChatMessage> {
..write('data: $data, ') ..write('data: $data, ')
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('status: $status, ') ..write('status: $status, ')
..write('isRead: $isRead, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
..write(')')) ..write(')'))
.toString(); .toString();
@ -542,6 +590,7 @@ typedef $$ChatMessagesTableCreateCompanionBuilder =
required String data, required String data,
required DateTime createdAt, required DateTime createdAt,
required MessageStatus status, required MessageStatus status,
Value<bool> isRead,
Value<int> rowid, Value<int> rowid,
}); });
typedef $$ChatMessagesTableUpdateCompanionBuilder = typedef $$ChatMessagesTableUpdateCompanionBuilder =
@ -554,6 +603,7 @@ typedef $$ChatMessagesTableUpdateCompanionBuilder =
Value<String> data, Value<String> data,
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<MessageStatus> status, Value<MessageStatus> status,
Value<bool> isRead,
Value<int> rowid, Value<int> rowid,
}); });
@ -606,6 +656,11 @@ class $$ChatMessagesTableFilterComposer
column: $table.status, column: $table.status,
builder: (column) => ColumnWithTypeConverterFilters(column), builder: (column) => ColumnWithTypeConverterFilters(column),
); );
ColumnFilters<bool> get isRead => $composableBuilder(
column: $table.isRead,
builder: (column) => ColumnFilters(column),
);
} }
class $$ChatMessagesTableOrderingComposer class $$ChatMessagesTableOrderingComposer
@ -656,6 +711,11 @@ class $$ChatMessagesTableOrderingComposer
column: $table.status, column: $table.status,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<bool> get isRead => $composableBuilder(
column: $table.isRead,
builder: (column) => ColumnOrderings(column),
);
} }
class $$ChatMessagesTableAnnotationComposer class $$ChatMessagesTableAnnotationComposer
@ -690,6 +750,9 @@ class $$ChatMessagesTableAnnotationComposer
GeneratedColumnWithTypeConverter<MessageStatus, int> get status => GeneratedColumnWithTypeConverter<MessageStatus, int> get status =>
$composableBuilder(column: $table.status, builder: (column) => column); $composableBuilder(column: $table.status, builder: (column) => column);
GeneratedColumn<bool> get isRead =>
$composableBuilder(column: $table.isRead, builder: (column) => column);
} }
class $$ChatMessagesTableTableManager class $$ChatMessagesTableTableManager
@ -732,6 +795,7 @@ class $$ChatMessagesTableTableManager
Value<String> data = const Value.absent(), Value<String> data = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<MessageStatus> status = const Value.absent(), Value<MessageStatus> status = const Value.absent(),
Value<bool> isRead = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => ChatMessagesCompanion( }) => ChatMessagesCompanion(
id: id, id: id,
@ -742,6 +806,7 @@ class $$ChatMessagesTableTableManager
data: data, data: data,
createdAt: createdAt, createdAt: createdAt,
status: status, status: status,
isRead: isRead,
rowid: rowid, rowid: rowid,
), ),
createCompanionCallback: createCompanionCallback:
@ -754,6 +819,7 @@ class $$ChatMessagesTableTableManager
required String data, required String data,
required DateTime createdAt, required DateTime createdAt,
required MessageStatus status, required MessageStatus status,
Value<bool> isRead = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => ChatMessagesCompanion.insert( }) => ChatMessagesCompanion.insert(
id: id, id: id,
@ -764,6 +830,7 @@ class $$ChatMessagesTableTableManager
data: data, data: data,
createdAt: createdAt, createdAt: createdAt,
status: status, status: status,
isRead: isRead,
rowid: rowid, rowid: rowid,
), ),
withReferenceMapper: withReferenceMapper:

View File

@ -11,6 +11,7 @@ class ChatMessages extends Table {
TextColumn get data => text()(); TextColumn get data => text()();
DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get createdAt => dateTime()();
IntColumn get status => intEnum<MessageStatus>()(); IntColumn get status => intEnum<MessageStatus>()();
BoolColumn get isRead => boolean().withDefault(const Constant(false))();
@override @override
Set<Column> get primaryKey => {id}; Set<Column> get primaryKey => {id};
@ -25,6 +26,7 @@ class LocalChatMessage {
MessageStatus status; MessageStatus status;
final String? nonce; final String? nonce;
List<UniversalFile>? localAttachments; List<UniversalFile>? localAttachments;
bool isRead;
LocalChatMessage({ LocalChatMessage({
required this.id, required this.id,
@ -35,6 +37,7 @@ class LocalChatMessage {
required this.nonce, required this.nonce,
required this.status, required this.status,
this.localAttachments, this.localAttachments,
this.isRead = false,
}); });
SnChatMessage toRemoteMessage() { SnChatMessage toRemoteMessage() {
@ -45,6 +48,7 @@ class LocalChatMessage {
SnChatMessage message, SnChatMessage message,
MessageStatus status, { MessageStatus status, {
String? nonce, String? nonce,
bool isRead = false,
}) { }) {
return LocalChatMessage( return LocalChatMessage(
id: message.id, id: message.id,
@ -54,6 +58,7 @@ class LocalChatMessage {
createdAt: message.createdAt, createdAt: message.createdAt,
status: status, status: status,
nonce: nonce ?? message.nonce, nonce: nonce ?? message.nonce,
isRead: isRead,
); );
} }
} }

View File

@ -457,4 +457,12 @@ class MessageRepository {
rethrow; rethrow;
} }
} }
Future<void> markMessageAsRead(String messageId) async {
try {
await _database.markMessageAsRead(messageId);
} catch (e) {
showErrorAlert(e);
}
}
} }

View File

@ -26,7 +26,7 @@ abstract class WebSocketPacket with _$WebSocketPacket {
const factory WebSocketPacket({ const factory WebSocketPacket({
required String type, required String type,
required Map<String, dynamic>? data, required Map<String, dynamic>? data,
required String? errorMessage, String? errorMessage,
}) = _WebSocketPacket; }) = _WebSocketPacket;
factory WebSocketPacket.fromJson(Map<String, dynamic> json) => factory WebSocketPacket.fromJson(Map<String, dynamic> json) =>
@ -87,7 +87,9 @@ class WebSocketService {
data is Uint8List ? utf8.decode(data) : data.toString(); data is Uint8List ? utf8.decode(data) : data.toString();
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr)); final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
_streamController.sink.add(packet); _streamController.sink.add(packet);
log("[WebSocket] Received packet: ${packet.type}"); log(
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
);
}, },
onDone: () { onDone: () {
log('[WebSocket] Connection closed, attempting to reconnect...'); log('[WebSocket] Connection closed, attempting to reconnect...');

View File

@ -310,7 +310,7 @@ as String?,
@JsonSerializable() @JsonSerializable()
class _WebSocketPacket with DiagnosticableTreeMixin implements WebSocketPacket { class _WebSocketPacket with DiagnosticableTreeMixin implements WebSocketPacket {
const _WebSocketPacket({required this.type, required final Map<String, dynamic>? data, required this.errorMessage}): _data = data; const _WebSocketPacket({required this.type, required final Map<String, dynamic>? data, this.errorMessage}): _data = data;
factory _WebSocketPacket.fromJson(Map<String, dynamic> json) => _$WebSocketPacketFromJson(json); factory _WebSocketPacket.fromJson(Map<String, dynamic> json) => _$WebSocketPacketFromJson(json);
@override final String type; @override final String type;

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -19,6 +20,7 @@ import 'package:island/screens/posts/compose.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/chat/message_bubble.dart'; import 'package:island/widgets/chat/message_bubble.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -306,6 +308,30 @@ class ChatRoomScreen extends HookConsumerWidget {
final attachments = useState<List<UniversalFile>>([]); final attachments = useState<List<UniversalFile>>([]);
final attachmentProgress = useState<Map<String, Map<int, double>>>({}); final attachmentProgress = useState<Map<String, Map<int, double>>>({});
// Function to send read receipt
void sendReadReceipt(String messageId) async {
// Get message from repository to check read status
final repository = await ref.read(messageRepositoryProvider(id).future);
final message = await repository.getMessageById(messageId);
// Skip if message is already marked as read
if (message?.isRead ?? false) return;
// Send websocket packet
final wsState = ref.read(websocketStateProvider.notifier);
wsState.sendMessage(
jsonEncode(
WebSocketPacket(
type: 'messages.read',
data: {'chat_room_id': id, 'message_id': messageId},
),
),
);
// Mark as read in local database
await repository.markMessageAsRead(messageId);
}
// Add scroll listener for pagination // Add scroll listener for pagination
useEffect(() { useEffect(() {
void onScroll() { void onScroll() {
@ -319,7 +345,6 @@ class ChatRoomScreen extends HookConsumerWidget {
return () => scrollController.removeListener(onScroll); return () => scrollController.removeListener(onScroll);
}, [scrollController]); }, [scrollController]);
// Add websocket listener
// Add websocket listener for new messages // Add websocket listener for new messages
useEffect(() { useEffect(() {
void onMessage(WebSocketPacket pkt) { void onMessage(WebSocketPacket pkt) {
@ -329,6 +354,8 @@ class ChatRoomScreen extends HookConsumerWidget {
switch (pkt.type) { switch (pkt.type) {
case 'messages.new': case 'messages.new':
messagesNotifier.receiveMessage(message); messagesNotifier.receiveMessage(message);
// Send read receipt for new message
sendReadReceipt(message.id);
case 'messages.update': case 'messages.update':
messagesNotifier.receiveMessageUpdate(message); messagesNotifier.receiveMessageUpdate(message);
case 'messages.delete': case 'messages.delete':
@ -428,7 +455,11 @@ class ChatRoomScreen extends HookConsumerWidget {
], ],
), ),
loading: () => const Text('Loading...'), loading: () => const Text('Loading...'),
error: (_, __) => const Text('Error'), error:
(err, __) => ResponseErrorWidget(
error: err,
onRetry: () => messagesNotifier.loadInitial(),
),
), ),
actions: [ actions: [
IconButton( IconButton(
@ -469,6 +500,8 @@ class ChatRoomScreen extends HookConsumerWidget {
nextMessage == null || nextMessage == null ||
nextMessage.senderId != message.senderId; nextMessage.senderId != message.senderId;
sendReadReceipt(message.id);
return chatIdentity.when( return chatIdentity.when(
skipError: true, skipError: true,
data: data:
@ -527,17 +560,9 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error:
(error, stack) => Center( (error, _) => ResponseErrorWidget(
child: Column( error: error,
mainAxisSize: MainAxisSize.min, onRetry: () => messagesNotifier.loadInitial(),
children: [
Text('Error: $error'),
ElevatedButton(
onPressed: () => messagesNotifier.loadInitial(),
child: Text('Retry'.tr()),
),
],
),
), ),
), ),
), ),