Получение и отправка текста, статусы сообщения
This commit is contained in:
@@ -41,8 +41,8 @@ class _ChatDetailPageState extends State<ChatDetailPage> {
|
||||
title: BlocBuilder<ChatBloc, ChatState>(
|
||||
builder: (context, state) {
|
||||
final currentChat = state.maybeWhen(
|
||||
chatSelected: (chat, _, __) => chat,
|
||||
messagesLoaded: (chat, _, __) => chat,
|
||||
chatSelected: (chat, messages, typingUsers, lastUpdate) => chat,
|
||||
messagesLoaded: (chat, messages, typingUsers, lastUpdate) => chat,
|
||||
orElse: () => widget.chat,
|
||||
);
|
||||
return Column(
|
||||
@@ -65,10 +65,10 @@ class _ChatDetailPageState extends State<ChatDetailPage> {
|
||||
child: BlocBuilder<ChatBloc, ChatState>(
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
chatSelected: (_, messages, __) => _buildMessageList(messages),
|
||||
messagesLoaded: (_, messages, __) => _buildMessageList(messages),
|
||||
loading: (_) => const Center(child: CircularProgressIndicator()),
|
||||
error: (msg, _) => Center(child: Text('Ошибка: $msg')),
|
||||
chatSelected: (chat, messages, typingUsers, lastUpdate) => _buildMessageList(messages),
|
||||
messagesLoaded: (chat, messages, typingUsers, lastUpdate) => _buildMessageList(messages),
|
||||
loading: (typingUsers, lastUpdate) => const Center(child: CircularProgressIndicator()),
|
||||
error: (msg, typingUsers, lastUpdate) => Center(child: Text('Ошибка: $msg')),
|
||||
orElse: () => const Center(child: Text('Начните общение')),
|
||||
);
|
||||
},
|
||||
@@ -124,17 +124,46 @@ class _ChatDetailPageState extends State<ChatDetailPage> {
|
||||
],
|
||||
);
|
||||
} else if (otherMember.lastSeen != null) {
|
||||
final lastSeen = otherMember.lastSeen!.toLocal();
|
||||
final timeStr = DateFormat.Hm(locale).format(lastSeen);
|
||||
final dateStr = _formatDate(lastSeen, locale);
|
||||
return Text(
|
||||
l10n.lastSeen(dateStr, timeStr),
|
||||
_formatLastSeen(otherMember.lastSeen!, l10n, locale),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white70, fontWeight: FontWeight.normal),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
String _formatLastSeen(DateTime utcDate, AppLocalizations l10n, String locale) {
|
||||
final lastSeen = utcDate.toLocal();
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(lastSeen);
|
||||
final timeStr = DateFormat.Hm(locale).format(lastSeen);
|
||||
|
||||
// Case 1: Same day (within 24h of NOW, but specifically checking calendar day is better for "yesterday")
|
||||
// Let's use difference for "relative" and calendar days for "yesterday/day before"
|
||||
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final seenDay = DateTime(lastSeen.year, lastSeen.month, lastSeen.day);
|
||||
final dayDiff = today.difference(seenDay).inDays;
|
||||
|
||||
if (dayDiff == 0) {
|
||||
// Same calendar day
|
||||
if (difference.inHours > 0) {
|
||||
return l10n.lastSeenRelative(difference.inHours, difference.inMinutes % 60);
|
||||
} else if (difference.inMinutes > 0) {
|
||||
return l10n.lastSeenRelativeMinutes(difference.inMinutes);
|
||||
} else {
|
||||
return l10n.lastSeenJustNow;
|
||||
}
|
||||
} else if (dayDiff == 1) {
|
||||
return l10n.lastSeenYesterday(timeStr);
|
||||
} else if (dayDiff == 2) {
|
||||
return l10n.lastSeenDayBeforeYesterday(timeStr);
|
||||
} else {
|
||||
final dateStr = DateFormat.MMMMd(locale).format(lastSeen);
|
||||
return l10n.lastSeen(dateStr, timeStr);
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date, String locale) {
|
||||
final now = DateTime.now();
|
||||
final localDate = date.toLocal();
|
||||
@@ -152,6 +181,20 @@ class _ChatDetailPageState extends State<ChatDetailPage> {
|
||||
return Center(child: Text(l10n.noMessages));
|
||||
}
|
||||
|
||||
// Mark as read logic
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final unreadFromOthers = messages.where((m) =>
|
||||
m.senderId != widget.currentUserId &&
|
||||
m.status != MessageStatus.read
|
||||
).toList();
|
||||
|
||||
if (unreadFromOthers.isNotEmpty) {
|
||||
// Find the message with the highest sequenceId to mark everything up to it as read
|
||||
final latest = unreadFromOthers.reduce((a, b) => a.sequenceId > b.sequenceId ? a : b);
|
||||
context.read<ChatBloc>().add(ChatEvent.markAsRead(latest.chatId, latest.id, latest.sequenceId));
|
||||
}
|
||||
});
|
||||
|
||||
final List<dynamic> items = [];
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
final message = messages[i];
|
||||
@@ -233,14 +276,23 @@ class _ChatDetailPageState extends State<ChatDetailPage> {
|
||||
style: const TextStyle(color: Colors.black87, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (message.createdAt != null)
|
||||
Text(
|
||||
DateFormat.Hm(locale).format(message.createdAt!.toLocal()),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.black45,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (message.createdAt != null)
|
||||
Text(
|
||||
DateFormat.Hm(locale).format(message.createdAt!.toLocal()),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.black45,
|
||||
),
|
||||
),
|
||||
if (isMe) ...[
|
||||
const SizedBox(width: 4),
|
||||
_buildStatusIcon(message),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -249,6 +301,19 @@ class _ChatDetailPageState extends State<ChatDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusIcon(Message message) {
|
||||
switch (message.status) {
|
||||
case MessageStatus.sending:
|
||||
return const Icon(Icons.access_time, size: 12, color: Colors.black45);
|
||||
case MessageStatus.delivered:
|
||||
return const Icon(Icons.check, size: 12, color: Colors.black45);
|
||||
case MessageStatus.read:
|
||||
return const Icon(Icons.done_all, size: 12, color: Colors.blue);
|
||||
case MessageStatus.error:
|
||||
return const Icon(Icons.error_outline, size: 12, color: Colors.red);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildMessageInput(Chat chat) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
||||
@@ -49,20 +49,13 @@ class _ChatsPageState extends State<ChatsPage> {
|
||||
body: BlocConsumer<ChatBloc, ChatState>(
|
||||
listener: (context, state) {
|
||||
state.whenOrNull(
|
||||
chatSelected: (chat, _, __) {
|
||||
chatSelected: (chat, messages, typingUsers, lastUpdate) {
|
||||
if (_isNavigating) return;
|
||||
|
||||
// Extra safety: check if we are already on chat_detail
|
||||
bool isAlreadyOnDetail = false;
|
||||
Navigator.popUntil(context, (route) {
|
||||
if (route.settings.name == 'chat_detail') {
|
||||
isAlreadyOnDetail = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (isAlreadyOnDetail) return;
|
||||
|
||||
// Only navigate if we are currently on the ChatsPage
|
||||
final isCurrent = ModalRoute.of(context)?.isCurrent ?? false;
|
||||
if (!isCurrent) return;
|
||||
|
||||
_isNavigating = true;
|
||||
|
||||
final authState = context.read<AuthBloc>().state;
|
||||
@@ -91,9 +84,9 @@ class _ChatsPageState extends State<ChatsPage> {
|
||||
},
|
||||
builder: (context, state) {
|
||||
return state.maybeWhen(
|
||||
loading: (_) => const Center(child: CircularProgressIndicator()),
|
||||
chatsLoaded: (chats, _) => _buildChatList(chats, l10n),
|
||||
error: (message, _) => Center(child: Text('${l10n.error}: $message')),
|
||||
loading: (typingUsers, lastUpdate) => const Center(child: CircularProgressIndicator()),
|
||||
chatsLoaded: (chats, typingUsers, lastUpdate) => _buildChatList(chats, l10n),
|
||||
error: (message, typingUsers, lastUpdate) => Center(child: Text('${l10n.error}: $message')),
|
||||
orElse: () => const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user