feat(search): экран поиска — чаты, контакты, сообщения и поиск по номеру
This commit is contained in:
@@ -161,6 +161,22 @@ class CachedChat {
|
||||
};
|
||||
}
|
||||
|
||||
class ChatSearchHit {
|
||||
final int id;
|
||||
final String type;
|
||||
final String? title;
|
||||
final String? avatarUrl;
|
||||
final String? subtitle;
|
||||
|
||||
const ChatSearchHit({
|
||||
required this.id,
|
||||
required this.type,
|
||||
this.title,
|
||||
this.avatarUrl,
|
||||
this.subtitle,
|
||||
});
|
||||
}
|
||||
|
||||
sealed class MessageEvent {
|
||||
final int chatId;
|
||||
const MessageEvent(this.chatId);
|
||||
@@ -1151,6 +1167,72 @@ class ChatsModule {
|
||||
return packet.payload;
|
||||
}
|
||||
|
||||
static List<ChatSearchHit> _parseSearchResult(dynamic payload) {
|
||||
final result = (payload as Map?)?['result'];
|
||||
if (result is! List) return const [];
|
||||
final hits = <ChatSearchHit>[];
|
||||
for (final item in result) {
|
||||
if (item is! Map) continue;
|
||||
final chat = item['chat'];
|
||||
if (chat is! Map) continue;
|
||||
final id = chat['id'];
|
||||
if (id is! int) continue;
|
||||
final last = chat['lastMessage'];
|
||||
final link = chat['link'];
|
||||
hits.add(ChatSearchHit(
|
||||
id: id,
|
||||
type: (chat['type'] as String?) ?? 'CHAT',
|
||||
title: chat['title'] as String?,
|
||||
avatarUrl: chat['baseIconUrl'] as String?,
|
||||
subtitle: link is String && link.isNotEmpty
|
||||
? '@$link'
|
||||
: (last is Map ? last['text'] as String? : null),
|
||||
));
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
static Future<List<ChatSearchHit>> searchChats(
|
||||
Api api,
|
||||
String query, {
|
||||
int count = 50,
|
||||
}) async {
|
||||
final term = query.trim();
|
||||
if (term.isEmpty) return const [];
|
||||
try {
|
||||
final packet = await api.sendRequest(Opcode.chatSearch, {
|
||||
'count': count,
|
||||
'query': term,
|
||||
});
|
||||
if (packet.isError) return const [];
|
||||
return _parseSearchResult(packet.payload);
|
||||
} catch (e) {
|
||||
logger.w('searchChats failed: $e');
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<ChatSearchHit>> searchPublic(
|
||||
Api api,
|
||||
String query, {
|
||||
int count = 20,
|
||||
}) async {
|
||||
final term = query.trim();
|
||||
if (term.isEmpty) return const [];
|
||||
try {
|
||||
final packet = await api.sendRequest(Opcode.publicSearch, {
|
||||
'type': 'ALL',
|
||||
'count': count,
|
||||
'query': term,
|
||||
});
|
||||
if (packet.isError) return const [];
|
||||
return _parseSearchResult(packet.payload);
|
||||
} catch (e) {
|
||||
logger.w('searchPublic failed: $e');
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<CachedChat?> createGroupChat(
|
||||
Api api, {
|
||||
required String title,
|
||||
|
||||
@@ -54,9 +54,55 @@ class CachedContact {
|
||||
}
|
||||
}
|
||||
|
||||
class PhoneLookupResult {
|
||||
final int id;
|
||||
final String? name;
|
||||
final String? avatarUrl;
|
||||
|
||||
const PhoneLookupResult({required this.id, this.name, this.avatarUrl});
|
||||
}
|
||||
|
||||
class ContactsModule {
|
||||
static final ValueNotifier<int> revision = ValueNotifier<int>(0);
|
||||
|
||||
static Future<PhoneLookupResult?> findByPhone(Api api, String phone) async {
|
||||
final normalized = _normalizePhone(phone);
|
||||
if (normalized == null) return null;
|
||||
final packet = await api.sendRequest(Opcode.contactInfoByPhone, {
|
||||
'phone': normalized,
|
||||
});
|
||||
if (packet.isError) return null;
|
||||
final contact = (packet.payload as Map?)?['contact'];
|
||||
if (contact is! Map) return null;
|
||||
final id = contact['id'];
|
||||
if (id is! int) return null;
|
||||
|
||||
String? name;
|
||||
final names = contact['names'];
|
||||
if (names is List) {
|
||||
final n = names.firstWhere((e) => e is Map, orElse: () => null);
|
||||
if (n is Map) {
|
||||
final first =
|
||||
(n['firstName'] as String?) ?? (n['name'] as String?) ?? '';
|
||||
final last = (n['lastName'] as String?) ?? '';
|
||||
final full = '$first $last'.trim();
|
||||
if (full.isNotEmpty) name = full;
|
||||
}
|
||||
}
|
||||
|
||||
return PhoneLookupResult(
|
||||
id: id,
|
||||
name: name,
|
||||
avatarUrl: contact['baseUrl'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
static String? _normalizePhone(String raw) {
|
||||
final digits = raw.replaceAll(RegExp(r'[^\d]'), '');
|
||||
if (digits.length < 5) return null;
|
||||
return '+$digits';
|
||||
}
|
||||
|
||||
static Future<CachedContact?> addContact(
|
||||
Api api,
|
||||
int id,
|
||||
|
||||
Reference in New Issue
Block a user