бля а какой запрос на поиск по айди
This commit is contained in:
@@ -1,10 +1,20 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'package:flutter/foundation.dart';
|
||||||
import '../api.dart';
|
import '../api.dart';
|
||||||
import '../../core/protocol/opcode_map.dart';
|
import '../../core/protocol/opcode_map.dart';
|
||||||
import '../../core/storage/app_database.dart';
|
import '../../core/storage/app_database.dart';
|
||||||
import '../../models/attachment.dart';
|
import '../../models/attachment.dart';
|
||||||
|
|
||||||
|
class ContactCache {
|
||||||
|
static final Map<int, String> _cache = {};
|
||||||
|
|
||||||
|
static void put(int id, String name) {
|
||||||
|
_cache[id] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? get(int id) => _cache[id];
|
||||||
|
}
|
||||||
|
|
||||||
class CachedMessage {
|
class CachedMessage {
|
||||||
final String id;
|
final String id;
|
||||||
final int accountId;
|
final int accountId;
|
||||||
@@ -39,11 +49,20 @@ class CachedMessage {
|
|||||||
|
|
||||||
List<MessageAttachment>? attachments;
|
List<MessageAttachment>? attachments;
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
final attaches = payload['attaches'] as List?;
|
final linkType = payload['link']?['type'] as String?;
|
||||||
if (attaches != null) {
|
if (linkType == 'FORWARD') {
|
||||||
attachments = attaches
|
attachments = [ForwardedMessageAttachment.fromMap(payload)];
|
||||||
.map((a) => MessageAttachment.fromMap(a as Map<String, dynamic>))
|
} else {
|
||||||
.toList();
|
final attaches = payload['attaches'] as List?;
|
||||||
|
if (attaches != null) {
|
||||||
|
attachments = attaches
|
||||||
|
.map(
|
||||||
|
(a) => MessageAttachment.fromMap(
|
||||||
|
Map<String, dynamic>.from(a as Map),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,13 +177,24 @@ class MessagesModule {
|
|||||||
final id = m['id']?.toString();
|
final id = m['id']?.toString();
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
|
|
||||||
final attaches = m['attaches'] as List?;
|
final linkRaw = m['link'];
|
||||||
|
String? linkType;
|
||||||
|
if (linkRaw is Map) {
|
||||||
|
linkType = linkRaw['type'] as String?;
|
||||||
|
}
|
||||||
|
|
||||||
List<MessageAttachment>? attachments;
|
List<MessageAttachment>? attachments;
|
||||||
if (attaches != null) {
|
if (linkType == 'FORWARD') {
|
||||||
attachments = attaches
|
final fwdMap = Map<String, dynamic>.from(m.cast());
|
||||||
.whereType<Map>()
|
attachments = [ForwardedMessageAttachment.fromMap(fwdMap)];
|
||||||
.map((a) => MessageAttachment.fromMap(a.cast<String, dynamic>()))
|
} else {
|
||||||
.toList();
|
final attaches = m['attaches'] as List?;
|
||||||
|
if (attaches != null) {
|
||||||
|
attachments = attaches
|
||||||
|
.whereType<Map>()
|
||||||
|
.map((a) => MessageAttachment.fromMap(Map<String, dynamic>.from(a)))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CachedMessage(
|
return CachedMessage(
|
||||||
@@ -175,7 +205,7 @@ class MessagesModule {
|
|||||||
text: m['text'] as String?,
|
text: m['text'] as String?,
|
||||||
time: (m['time'] as int?) ?? 0,
|
time: (m['time'] as int?) ?? 0,
|
||||||
status: m['status'] as String?,
|
status: m['status'] as String?,
|
||||||
payload: m.cast<String, dynamic>(),
|
payload: Map<String, dynamic>.from(m.cast()),
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -313,4 +343,36 @@ class MessagesModule {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> searchContactById(int contactId) async {
|
||||||
|
final cached = ContactCache.get(contactId);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _api.sendRequest(Opcode.contactInfo, {
|
||||||
|
'id': contactId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.isOk) return null;
|
||||||
|
final data = response.payload;
|
||||||
|
if (data is! Map) return null;
|
||||||
|
|
||||||
|
final names = data['names'] as List?;
|
||||||
|
if (names != null && names.isNotEmpty) {
|
||||||
|
final name = names.first;
|
||||||
|
if (name is Map) {
|
||||||
|
final firstName = name['firstName'] as String? ?? '';
|
||||||
|
final lastName = name['lastName'] as String?;
|
||||||
|
final fullName = lastName != null
|
||||||
|
? '$firstName $lastName'
|
||||||
|
: firstName;
|
||||||
|
ContactCache.put(contactId, fullName);
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('searchContactById error: $e');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '../../../main.dart';
|
|||||||
import '../../../backend/api.dart';
|
import '../../../backend/api.dart';
|
||||||
import '../../../backend/modules/messages.dart';
|
import '../../../backend/modules/messages.dart';
|
||||||
import '../../../core/storage/app_database.dart';
|
import '../../../core/storage/app_database.dart';
|
||||||
|
import '../../../models/attachment.dart';
|
||||||
import '../../widgets/message_bubble.dart';
|
import '../../widgets/message_bubble.dart';
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
@@ -81,6 +82,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_loadForwardedSenderNames();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error fetching history: $e');
|
debugPrint('Error fetching history: $e');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -164,6 +166,62 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadForwardedSenderNames() async {
|
||||||
|
final forwardIds = <int>{};
|
||||||
|
for (final msg in _messages) {
|
||||||
|
if (msg.attachments != null) {
|
||||||
|
for (final a in msg.attachments!) {
|
||||||
|
if (a is ForwardedMessageAttachment) {
|
||||||
|
if (a.originalSenderName == null) {
|
||||||
|
forwardIds.add(a.originalSenderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (forwardIds.isEmpty) return;
|
||||||
|
|
||||||
|
for (final id in forwardIds) {
|
||||||
|
final name = await messagesModule.searchContactById(id);
|
||||||
|
if (name != null && mounted) {
|
||||||
|
setState(() {
|
||||||
|
for (var i = 0; i < _messages.length; i++) {
|
||||||
|
final msg = _messages[i];
|
||||||
|
if (msg.attachments != null) {
|
||||||
|
final newAttaches = msg.attachments!.map((a) {
|
||||||
|
if (a is ForwardedMessageAttachment &&
|
||||||
|
a.originalSenderId == id &&
|
||||||
|
a.originalSenderName == null) {
|
||||||
|
return ForwardedMessageAttachment(
|
||||||
|
originalSenderId: id,
|
||||||
|
originalSenderName: name,
|
||||||
|
originalMessageId: a.originalMessageId,
|
||||||
|
originalTime: a.originalTime,
|
||||||
|
originalText: a.originalText,
|
||||||
|
originalChatId: a.originalChatId,
|
||||||
|
originalAttachments: a.originalAttachments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}).toList();
|
||||||
|
_messages[i] = CachedMessage(
|
||||||
|
id: msg.id,
|
||||||
|
accountId: msg.accountId,
|
||||||
|
chatId: msg.chatId,
|
||||||
|
senderId: msg.senderId,
|
||||||
|
text: msg.text,
|
||||||
|
time: msg.time,
|
||||||
|
status: msg.status,
|
||||||
|
payload: msg.payload,
|
||||||
|
attachments: newAttaches,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _scrollToBottom() {
|
void _scrollToBottom() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (_scrollController.hasClients) {
|
if (_scrollController.hasClients) {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ class MessageBubble extends StatelessWidget {
|
|||||||
MessageType get contentType {
|
MessageType get contentType {
|
||||||
if (message.attachments != null && message.attachments!.isNotEmpty) {
|
if (message.attachments != null && message.attachments!.isNotEmpty) {
|
||||||
final first = message.attachments!.first;
|
final first = message.attachments!.first;
|
||||||
|
if (first is ForwardedMessageAttachment) return MessageType.text;
|
||||||
if (first is UnknownAttachment) return MessageType.text;
|
if (first is UnknownAttachment) return MessageType.text;
|
||||||
if (first.type == AttachmentType.audio) return MessageType.voice;
|
if (first.type == AttachmentType.audio) return MessageType.voice;
|
||||||
return MessageType.attachment;
|
return MessageType.attachment;
|
||||||
@@ -317,14 +318,26 @@ class MessageBubble extends StatelessWidget {
|
|||||||
? Colors.white
|
? Colors.white
|
||||||
: (isDark ? cs.onSurface : const Color(0xFF1C1C1E));
|
: (isDark ? cs.onSurface : const Color(0xFF1C1C1E));
|
||||||
|
|
||||||
|
final forwarded = _getForwardedAttachment();
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Column(
|
||||||
message.text ?? '',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: TextStyle(color: textColor, fontSize: 16, height: 1.3),
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (forwarded != null) ...[
|
||||||
|
_buildForwardedHeader(context, forwarded),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
message.text ?? '',
|
||||||
|
style: TextStyle(color: textColor, fontSize: 16, height: 1.3),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -343,12 +356,76 @@ class MessageBubble extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForwardedMessageAttachment? _getForwardedAttachment() {
|
||||||
|
if (message.attachments == null || message.attachments!.isEmpty)
|
||||||
|
return null;
|
||||||
|
for (final a in message.attachments!) {
|
||||||
|
if (a is ForwardedMessageAttachment) return a;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildForwardedHeader(
|
||||||
|
BuildContext context,
|
||||||
|
ForwardedMessageAttachment forwarded,
|
||||||
|
) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
final isDark = cs.brightness == Brightness.dark;
|
||||||
|
final textColor = isMe
|
||||||
|
? Colors.white
|
||||||
|
: (isDark ? cs.onSurface : const Color(0xFF1C1C1E));
|
||||||
|
final headerColor = isMe
|
||||||
|
? Colors.white.withValues(alpha: 0.7)
|
||||||
|
: (isDark ? cs.onSurfaceVariant : const Color(0xFF8E8E93));
|
||||||
|
|
||||||
|
final senderName = forwarded.originalSenderName;
|
||||||
|
final displaySender = senderName ?? forwarded.originalSenderId.toString();
|
||||||
|
final origText = forwarded.originalText;
|
||||||
|
final hasOrigText = origText != null && origText.isNotEmpty;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.forward, size: 14, color: headerColor),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
displaySender,
|
||||||
|
style: TextStyle(
|
||||||
|
color: headerColor,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (hasOrigText) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
origText,
|
||||||
|
style: TextStyle(color: textColor, fontSize: 14),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildAttachmentContent(BuildContext context) {
|
Widget _buildAttachmentContent(BuildContext context) {
|
||||||
final attachments = message.attachments;
|
final attachments = message.attachments;
|
||||||
if (attachments == null || attachments.isEmpty) {
|
if (attachments == null || attachments.isEmpty) {
|
||||||
return _buildTextContent(context);
|
return _buildTextContent(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final first = attachments.first;
|
||||||
|
if (first is ForwardedMessageAttachment) {
|
||||||
|
return _buildTextContent(context);
|
||||||
|
}
|
||||||
|
|
||||||
final photos = attachments.whereType<PhotoAttachment>().toList();
|
final photos = attachments.whereType<PhotoAttachment>().toList();
|
||||||
if (photos.isEmpty) {
|
if (photos.isEmpty) {
|
||||||
return _buildGenericAttachment(context, attachments.first);
|
return _buildGenericAttachment(context, attachments.first);
|
||||||
|
|||||||
@@ -433,6 +433,73 @@ class ControlAttachment extends MessageAttachment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ForwardedMessageAttachment extends MessageAttachment {
|
||||||
|
final int originalSenderId;
|
||||||
|
final String? originalSenderName;
|
||||||
|
final String? originalMessageId;
|
||||||
|
final int? originalTime;
|
||||||
|
final String? originalText;
|
||||||
|
final int? originalChatId;
|
||||||
|
final List<MessageAttachment>? originalAttachments;
|
||||||
|
|
||||||
|
const ForwardedMessageAttachment({
|
||||||
|
required this.originalSenderId,
|
||||||
|
this.originalSenderName,
|
||||||
|
this.originalMessageId,
|
||||||
|
this.originalTime,
|
||||||
|
this.originalText,
|
||||||
|
this.originalChatId,
|
||||||
|
this.originalAttachments,
|
||||||
|
}) : super(type: AttachmentType.photo);
|
||||||
|
|
||||||
|
factory ForwardedMessageAttachment.fromMap(Map<String, dynamic> map) {
|
||||||
|
final linkRaw = map['link'];
|
||||||
|
Map<String, dynamic>? link;
|
||||||
|
if (linkRaw is Map) {
|
||||||
|
link = Map<String, dynamic>.from(linkRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic>? message;
|
||||||
|
if (link != null) {
|
||||||
|
final msgRaw = link['message'];
|
||||||
|
if (msgRaw is Map) {
|
||||||
|
message = Map<String, dynamic>.from(msgRaw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageAttachment>? originalAttaches;
|
||||||
|
if (message != null) {
|
||||||
|
final attaches = message['attaches'] as List?;
|
||||||
|
if (attaches != null) {
|
||||||
|
originalAttaches = attaches
|
||||||
|
.whereType<Map>()
|
||||||
|
.map((a) => MessageAttachment.fromMap(Map<String, dynamic>.from(a)))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ForwardedMessageAttachment(
|
||||||
|
originalSenderId: (message?['sender'] as int?) ?? 0,
|
||||||
|
originalMessageId: message?['id']?.toString(),
|
||||||
|
originalTime: message?['time'] as int?,
|
||||||
|
originalText: message?['text'] as String?,
|
||||||
|
originalChatId: link?['chatId'] as int?,
|
||||||
|
originalAttachments: originalAttaches,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
'_type': 'FORWARD',
|
||||||
|
'originalSenderId': originalSenderId,
|
||||||
|
'originalSenderName': originalSenderName,
|
||||||
|
'originalMessageId': originalMessageId,
|
||||||
|
'originalTime': originalTime,
|
||||||
|
'originalText': originalText,
|
||||||
|
'originalChatId': originalChatId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class UnknownAttachment extends MessageAttachment {
|
class UnknownAttachment extends MessageAttachment {
|
||||||
final Map<String, dynamic> rawData;
|
final Map<String, dynamic> rawData;
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -21,10 +21,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -313,18 +313,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
material_symbols_icons:
|
material_symbols_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -638,10 +638,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.10"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user