я как бы еще не доделал, это такой промежуточный пуш знаете
This commit is contained in:
+49
-61
@@ -16,12 +16,7 @@ import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||
|
||||
enum SessionState {
|
||||
disconnected,
|
||||
connecting,
|
||||
connected,
|
||||
online
|
||||
}
|
||||
enum SessionState { disconnected, connecting, connected, online }
|
||||
|
||||
/// Клиент API.
|
||||
///
|
||||
@@ -34,6 +29,9 @@ class Api {
|
||||
|
||||
SessionState _sessionState = SessionState.disconnected;
|
||||
final _stateController = StreamController<SessionState>.broadcast();
|
||||
Map<dynamic, dynamic>? _userAgent;
|
||||
|
||||
Map<dynamic, dynamic>? get userAgent => _userAgent;
|
||||
|
||||
Stream<SessionState> get stateStream => _stateController.stream;
|
||||
SessionState get state => _sessionState;
|
||||
@@ -98,86 +96,77 @@ class Api {
|
||||
_setSessionState(SessionState.disconnected);
|
||||
}
|
||||
|
||||
/// Отправляет хэндшейк (opcode 6).
|
||||
Future<Packet> sendHandshake() async {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
|
||||
// Если платформа Linux или Windows, то ставим DESKTOP, если нет, то проверяем на Android или IOS;
|
||||
String deviceType = (Platform.isLinux || Platform.isWindows) ? "DESKTOP" : (Platform.isAndroid) ? "ANDROID" : "IOS";
|
||||
String osVersion = "";
|
||||
String deviceName = "Unknown";
|
||||
String architecture = "arm64";
|
||||
|
||||
tz.initializeTimeZones();
|
||||
|
||||
final now = DateTime.now();
|
||||
String timezone = "Europe/Moscow";
|
||||
final deviceType = (Platform.isLinux || Platform.isWindows)
|
||||
? 'DESKTOP'
|
||||
: (Platform.isAndroid)
|
||||
? 'ANDROID'
|
||||
: 'IOS';
|
||||
String osVersion = '';
|
||||
String deviceName = 'Unknown';
|
||||
String architecture = 'arm64';
|
||||
|
||||
tz.initializeTimeZones();
|
||||
final timeZoneName = await FlutterTimezone.getLocalTimezone();
|
||||
timezone = timeZoneName.identifier;
|
||||
final timezone = timeZoneName.identifier;
|
||||
|
||||
// На каждой платформе свое инфо, поэтому делаем такую проверку
|
||||
if (Platform.isLinux) {
|
||||
LinuxDeviceInfo linuxInfo = await deviceInfo.linuxInfo;
|
||||
|
||||
final linuxInfo = await deviceInfo.linuxInfo;
|
||||
osVersion = linuxInfo.name;
|
||||
// Platform.version содержит в себе что-то такое
|
||||
// 3.11.1 (stable) (Tue Feb 24 00:03:07 2026 -0800) on "linux_x64"
|
||||
// Поэтому мы находим '_', прибавляем к его индексу 1 и берем символы до length - 1
|
||||
architecture = Platform.version.substring(Platform.version.indexOf('_') + 1, Platform.version.length - 1);
|
||||
architecture = Platform.version.substring(
|
||||
Platform.version.indexOf('_') + 1,
|
||||
Platform.version.length - 1,
|
||||
);
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||
|
||||
final iosInfo = await deviceInfo.iosInfo;
|
||||
osVersion = iosInfo.systemVersion;
|
||||
deviceName = iosInfo.utsname.machine;
|
||||
} else if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
|
||||
osVersion = "Android ${androidInfo.version.release}";
|
||||
deviceName = "${androidInfo.manufacturer} ${androidInfo.model}";
|
||||
architecture = androidInfo.supportedAbis.first;
|
||||
final androidInfo = await deviceInfo.androidInfo;
|
||||
osVersion = 'Android ${androidInfo.version.release}';
|
||||
deviceName = '${androidInfo.manufacturer} ${androidInfo.model}';
|
||||
architecture = androidInfo.supportedAbis.first;
|
||||
} else if (Platform.isWindows) {
|
||||
WindowsDeviceInfo windowsInfo = await deviceInfo.windowsInfo;
|
||||
|
||||
final windowsInfo = await deviceInfo.windowsInfo;
|
||||
osVersion = windowsInfo.productName;
|
||||
architecture = Platform.version.substring(Platform.version.indexOf('_') + 1, Platform.version.length - 1);
|
||||
architecture = Platform.version.substring(
|
||||
Platform.version.indexOf('_') + 1,
|
||||
Platform.version.length - 1,
|
||||
);
|
||||
}
|
||||
|
||||
print(deviceType);
|
||||
|
||||
_userAgent = {
|
||||
'deviceType': deviceType,
|
||||
'locale': 'ru',
|
||||
'deviceLocale': Platform.localeName.substring(0, 2),
|
||||
'osVersion': osVersion,
|
||||
'deviceName': deviceName,
|
||||
'appVersion': '26.8.1',
|
||||
'screen': '1920x1080',
|
||||
'timezone': timezone,
|
||||
'pushDeviceType': 'GCM',
|
||||
'arch': architecture,
|
||||
'buildNumber': 6606,
|
||||
};
|
||||
|
||||
final payload = <dynamic, dynamic>{
|
||||
'mt_instanceid': '550e8400-e29b-41d4-a716-446655440000',
|
||||
'clientSessionId': 42,
|
||||
'deviceId': 'a1b2c3d4e5f6a7b8',
|
||||
'userAgent': {
|
||||
'deviceType': deviceType,
|
||||
// Первые два символа из locale это и есть нужный нам аргумент
|
||||
'locale': "ru",
|
||||
'deviceLocale': Platform.localeName.substring(0, 2),
|
||||
'osVersion': osVersion,
|
||||
'deviceName': deviceName,
|
||||
'appVersion': '26.8.1',
|
||||
'screen': '1920x1080',
|
||||
// 'screen': screenSize.width + 'x' + screenSize.height,
|
||||
'timezone': timezone,
|
||||
'pushDeviceType': 'GCM',
|
||||
'arch': architecture,
|
||||
'buildNumber': 6606,
|
||||
},
|
||||
'userAgent': _userAgent,
|
||||
};
|
||||
|
||||
print(payload);
|
||||
print(Platform.version);
|
||||
return sendRequest(Opcode.sessionInit, payload);
|
||||
}
|
||||
|
||||
/// Отправляет запрос и ждёт ответ от сервера.
|
||||
Future<Packet> sendRequest(
|
||||
int opcode,
|
||||
Map<dynamic, dynamic> payload,
|
||||
) {
|
||||
Future<Packet> sendRequest(int opcode, Map<dynamic, dynamic> payload) {
|
||||
final seq = _sender.send(_connection, opcode, payload);
|
||||
return _dispatcher.registerPending(seq).timeout(
|
||||
return _dispatcher
|
||||
.registerPending(seq)
|
||||
.timeout(
|
||||
ServerConfig.requestTimeout,
|
||||
onTimeout: () =>
|
||||
throw TimeoutException('${Opcode.name(opcode)} таймаут'),
|
||||
@@ -203,7 +192,6 @@ class Api {
|
||||
|
||||
// Внутрянка
|
||||
|
||||
|
||||
void _setSessionState(SessionState state) {
|
||||
if (_sessionState == state) return;
|
||||
_sessionState = state;
|
||||
|
||||
@@ -5,6 +5,14 @@ import '../../core/storage/app_database.dart';
|
||||
import '../../core/storage/token_storage.dart';
|
||||
import '../../core/utils/logger.dart';
|
||||
import 'chats.dart';
|
||||
import 'contacts.dart';
|
||||
|
||||
class ServerException implements Exception {
|
||||
final String message;
|
||||
const ServerException(this.message);
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
enum AuthRequestType {
|
||||
startAuth('START_AUTH'),
|
||||
@@ -115,11 +123,13 @@ class LoginResult {
|
||||
final ProfileData profile;
|
||||
final String? updatedToken;
|
||||
final int serverTime;
|
||||
final Map<dynamic, dynamic> raw;
|
||||
|
||||
const LoginResult({
|
||||
required this.profile,
|
||||
required this.updatedToken,
|
||||
required this.serverTime,
|
||||
required this.raw,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,14 +141,12 @@ class AccountModule {
|
||||
Future<RequestCodeResult> requestCode(
|
||||
String phone, {
|
||||
String language = 'ru',
|
||||
}) =>
|
||||
_requestCodeInternal(phone, AuthRequestType.startAuth, language);
|
||||
}) => _requestCodeInternal(phone, AuthRequestType.startAuth, language);
|
||||
|
||||
Future<RequestCodeResult> resendCode(
|
||||
String phone, {
|
||||
String language = 'ru',
|
||||
}) =>
|
||||
_requestCodeInternal(phone, AuthRequestType.resend, language);
|
||||
}) => _requestCodeInternal(phone, AuthRequestType.resend, language);
|
||||
|
||||
Future<VerifyCodeResult> verifyCode(String code, String token) async {
|
||||
_ensureOnline();
|
||||
@@ -157,7 +165,9 @@ class AccountModule {
|
||||
|
||||
final data = packet.payload;
|
||||
if (data is! Map) {
|
||||
throw Exception('verifyCode: неожиданный тип payload: ${data.runtimeType}');
|
||||
throw Exception(
|
||||
'verifyCode: неожиданный тип payload: ${data.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
final result = VerifyCodeResult(payload: data.cast<dynamic, dynamic>());
|
||||
@@ -181,7 +191,8 @@ class AccountModule {
|
||||
}) async {
|
||||
_ensureOnline();
|
||||
|
||||
final resolvedAccountId = accountId ?? await TokenStorage.getActiveAccountId();
|
||||
final resolvedAccountId =
|
||||
accountId ?? await TokenStorage.getActiveAccountId();
|
||||
if (resolvedAccountId == null) {
|
||||
throw StateError('login: нет активного аккаунта');
|
||||
}
|
||||
@@ -191,13 +202,7 @@ class AccountModule {
|
||||
throw StateError('login: нет токена для аккаунта $resolvedAccountId');
|
||||
}
|
||||
|
||||
final resolvedSyncParams =
|
||||
syncParams ?? await LoginSyncParams.fromDatabase(resolvedAccountId);
|
||||
|
||||
final requestPayload = _buildLoginPayload(authToken, resolvedSyncParams);
|
||||
|
||||
logger.i('LOGIN opcode=${Opcode.login} '
|
||||
'account=$resolvedAccountId warm=${resolvedSyncParams != null}');
|
||||
final requestPayload = _buildLoginPayload(authToken, syncParams);
|
||||
|
||||
final packet = await _api.sendRequest(Opcode.login, requestPayload);
|
||||
|
||||
@@ -262,7 +267,9 @@ class AccountModule {
|
||||
|
||||
final data = packet.payload;
|
||||
if (data is! Map) {
|
||||
throw Exception('checkPassword: неожиданный тип payload: ${data.runtimeType}');
|
||||
throw Exception(
|
||||
'checkPassword: неожиданный тип payload: ${data.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
if (data['error'] != null) {
|
||||
@@ -310,7 +317,8 @@ class AccountModule {
|
||||
) {
|
||||
final payload = <dynamic, dynamic>{
|
||||
'token': token,
|
||||
'interactive': true
|
||||
'interactive': true,
|
||||
if (_api.userAgent != null) 'userAgent': _api.userAgent,
|
||||
};
|
||||
|
||||
if (sync != null) {
|
||||
@@ -342,7 +350,6 @@ class AccountModule {
|
||||
final updatedToken = data['token'] as String?;
|
||||
if (updatedToken != null) {
|
||||
await TokenStorage.saveToken(updatedToken, accountId);
|
||||
logger.i('Обновлённый токен аккаунта $accountId сохранён');
|
||||
}
|
||||
|
||||
final profileMap = data['profile'];
|
||||
@@ -356,15 +363,16 @@ class AccountModule {
|
||||
final profile = ProfileData.fromServerMap(contact.cast<dynamic, dynamic>());
|
||||
await AppDatabase.saveProfile(profile);
|
||||
await AppDatabase.setActiveAccount(profile.id);
|
||||
logger.i('Профиль сохранён: id=${profile.id}, name=${profile.firstName}');
|
||||
|
||||
await _saveSyncState(data, serverTime, profile.id);
|
||||
await ContactsModule.syncFromLoginPayload(data, profile.id);
|
||||
await ChatsModule.syncFromLoginPayload(data, profile.id, profile.id);
|
||||
|
||||
return LoginResult(
|
||||
profile: profile,
|
||||
updatedToken: updatedToken,
|
||||
serverTime: serverTime,
|
||||
raw: data,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -415,7 +423,9 @@ class AccountModule {
|
||||
|
||||
final data = packet.payload;
|
||||
if (data is! Map) {
|
||||
throw Exception('requestCode: неожиданный тип payload: ${data.runtimeType}');
|
||||
throw Exception(
|
||||
'requestCode: неожиданный тип payload: ${data.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
final token = data['token'];
|
||||
@@ -439,8 +449,8 @@ class AccountModule {
|
||||
if (packet.isError) {
|
||||
final errMsg = packet.payload is Map
|
||||
? (packet.payload as Map)['message'] ?? packet.payload.toString()
|
||||
: packet.payload?.toString() ?? 'unknown error';
|
||||
throw Exception('$method: ошибка от сервера — $errMsg');
|
||||
: packet.payload?.toString() ?? 'Неизвестная ошибка';
|
||||
throw ServerException(errMsg.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ class CachedChat {
|
||||
final int unreadCount;
|
||||
final int lastEventTime;
|
||||
final int cachedAt;
|
||||
final int? favIndex;
|
||||
final int dontDisturbUntil;
|
||||
final bool isOnline;
|
||||
final int seenTime;
|
||||
|
||||
const CachedChat({
|
||||
required this.id,
|
||||
@@ -27,37 +31,49 @@ class CachedChat {
|
||||
required this.unreadCount,
|
||||
required this.lastEventTime,
|
||||
required this.cachedAt,
|
||||
this.favIndex,
|
||||
required this.dontDisturbUntil,
|
||||
required this.isOnline,
|
||||
required this.seenTime,
|
||||
});
|
||||
|
||||
factory CachedChat.fromDbRow(Map<String, dynamic> row) => CachedChat(
|
||||
id: row['id'] as int,
|
||||
accountId: row['account_id'] as int,
|
||||
type: row['type'] as String,
|
||||
title: row['title'] as String?,
|
||||
iconUrl: row['icon_url'] as String?,
|
||||
lastMsgId: row['last_msg_id'] as int?,
|
||||
lastMsgTime: row['last_msg_time'] as int?,
|
||||
lastMsgText: row['last_msg_text'] as String?,
|
||||
lastMsgSenderId: row['last_msg_sender'] as int?,
|
||||
unreadCount: row['unread_count'] as int,
|
||||
lastEventTime: row['last_event_time'] as int,
|
||||
cachedAt: row['cached_at'] as int,
|
||||
);
|
||||
id: row['id'] as int,
|
||||
accountId: row['account_id'] as int,
|
||||
type: row['type'] as String,
|
||||
title: row['title'] as String?,
|
||||
iconUrl: row['icon_url'] as String?,
|
||||
lastMsgId: row['last_msg_id'] as int?,
|
||||
lastMsgTime: row['last_msg_time'] as int?,
|
||||
lastMsgText: row['last_msg_text'] as String?,
|
||||
lastMsgSenderId: row['last_msg_sender'] as int?,
|
||||
unreadCount: row['unread_count'] as int,
|
||||
lastEventTime: row['last_event_time'] as int,
|
||||
cachedAt: row['cached_at'] as int,
|
||||
favIndex: row['fav_index'] as int?,
|
||||
dontDisturbUntil: row['dont_disturb_until'] as int,
|
||||
isOnline: (row['is_online'] as int) == 1,
|
||||
seenTime: row['seen_time'] as int,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toDbRow() => {
|
||||
'id': id,
|
||||
'account_id': accountId,
|
||||
'type': type,
|
||||
'title': title,
|
||||
'icon_url': iconUrl,
|
||||
'last_msg_id': lastMsgId,
|
||||
'last_msg_time': lastMsgTime,
|
||||
'last_msg_text': lastMsgText,
|
||||
'last_msg_sender': lastMsgSenderId,
|
||||
'unread_count': unreadCount,
|
||||
'last_event_time': lastEventTime,
|
||||
'cached_at': cachedAt,
|
||||
};
|
||||
'id': id,
|
||||
'account_id': accountId,
|
||||
'type': type,
|
||||
'title': title,
|
||||
'icon_url': iconUrl,
|
||||
'last_msg_id': lastMsgId,
|
||||
'last_msg_time': lastMsgTime,
|
||||
'last_msg_text': lastMsgText,
|
||||
'last_msg_sender': lastMsgSenderId,
|
||||
'unread_count': unreadCount,
|
||||
'last_event_time': lastEventTime,
|
||||
'cached_at': cachedAt,
|
||||
'fav_index': favIndex,
|
||||
'dont_disturb_until': dontDisturbUntil,
|
||||
'is_online': isOnline ? 1 : 0,
|
||||
'seen_time': seenTime,
|
||||
};
|
||||
}
|
||||
|
||||
class ChatsModule {
|
||||
@@ -75,23 +91,35 @@ class ChatsModule {
|
||||
if (chats is! List || chats.isEmpty) return;
|
||||
|
||||
final contactsMap = _buildContactsMap(data['contacts']);
|
||||
// Config contains mute setup and fav indexes: config -> chats -> id
|
||||
final configMap = data['config'] is Map ? data['config'] as Map : {};
|
||||
final chatsConfig = configMap['chats'] is Map
|
||||
? configMap['chats'] as Map
|
||||
: {};
|
||||
// Presence for online statuses
|
||||
final presenceMap = data['presence'] is Map ? data['presence'] as Map : {};
|
||||
final cachedAt = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
final existingRows = await AppDatabase.loadChats(accountId);
|
||||
final existing = {
|
||||
for (final row in existingRows) row['id'] as int: CachedChat.fromDbRow(row),
|
||||
for (final row in existingRows)
|
||||
row['id'] as int: CachedChat.fromDbRow(row),
|
||||
};
|
||||
|
||||
final rows = chats
|
||||
.whereType<Map>()
|
||||
.map((c) => _parseChat(
|
||||
c.cast<dynamic, dynamic>(),
|
||||
accountId,
|
||||
currentUserId,
|
||||
contactsMap,
|
||||
existing,
|
||||
cachedAt,
|
||||
))
|
||||
.map(
|
||||
(c) => _parseChat(
|
||||
c.cast<dynamic, dynamic>(),
|
||||
accountId,
|
||||
currentUserId,
|
||||
contactsMap,
|
||||
chatsConfig,
|
||||
presenceMap,
|
||||
existing,
|
||||
cachedAt,
|
||||
),
|
||||
)
|
||||
.whereType<CachedChat>()
|
||||
.map((c) => c.toDbRow())
|
||||
.toList();
|
||||
@@ -109,7 +137,7 @@ class ChatsModule {
|
||||
static Future<void> clearCache(int accountId) =>
|
||||
AppDatabase.clearChatsCache(accountId);
|
||||
|
||||
// internal
|
||||
// internal
|
||||
|
||||
static Map<int, Map<dynamic, dynamic>> _buildContactsMap(dynamic contacts) {
|
||||
if (contacts is! List) return {};
|
||||
@@ -126,6 +154,8 @@ class ChatsModule {
|
||||
int accountId,
|
||||
int currentUserId,
|
||||
Map<int, Map<dynamic, dynamic>> contactsMap,
|
||||
Map<dynamic, dynamic> chatsConfig,
|
||||
Map<dynamic, dynamic> presenceMap,
|
||||
Map<int, CachedChat> existing,
|
||||
int cachedAt,
|
||||
) {
|
||||
@@ -133,19 +163,19 @@ class ChatsModule {
|
||||
if (id is! int) return null;
|
||||
|
||||
final type = (chat['type'] as String?) ?? 'DIALOG';
|
||||
int? otherId;
|
||||
|
||||
String? title;
|
||||
String? iconUrl;
|
||||
|
||||
if (type == 'DIALOG') {
|
||||
final otherId = _otherParticipantId(chat['participants'], currentUserId);
|
||||
otherId = _otherParticipantId(chat['participants'], currentUserId);
|
||||
final contact = otherId != null ? contactsMap[otherId] : null;
|
||||
|
||||
if (contact != null) {
|
||||
title = _nameFromContact(contact);
|
||||
iconUrl = contact['baseUrl'] as String?;
|
||||
} else {
|
||||
// Warm start: контакты не пришли — берём из кэша
|
||||
title = existing[id]?.title;
|
||||
iconUrl = existing[id]?.iconUrl;
|
||||
}
|
||||
@@ -167,6 +197,24 @@ class ChatsModule {
|
||||
lastMsgSenderId = lastMsg['sender'] as int?;
|
||||
}
|
||||
|
||||
final config = chatsConfig[id.toString()] ?? chatsConfig[id];
|
||||
int? favIndex;
|
||||
int dontDisturbUntil = 0;
|
||||
if (config is Map) {
|
||||
favIndex = config['favIndex'] as int?;
|
||||
dontDisturbUntil = (config['dontDisturbUntil'] as int?) ?? 0;
|
||||
}
|
||||
|
||||
int seenTime = 0;
|
||||
bool isOnline = false;
|
||||
if (type == 'DIALOG' && otherId != null) {
|
||||
final presence = presenceMap[otherId.toString()] ?? presenceMap[otherId];
|
||||
if (presence is Map) {
|
||||
seenTime = (presence['seen'] as int?) ?? 0;
|
||||
isOnline = (presence['status'] as int?) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
return CachedChat(
|
||||
id: id,
|
||||
accountId: accountId,
|
||||
@@ -180,6 +228,10 @@ class ChatsModule {
|
||||
unreadCount: (chat['newMessages'] as int?) ?? 0,
|
||||
lastEventTime: (chat['lastEventTime'] as int?) ?? 0,
|
||||
cachedAt: cachedAt,
|
||||
favIndex: favIndex,
|
||||
dontDisturbUntil: dontDisturbUntil,
|
||||
isOnline: isOnline,
|
||||
seenTime: seenTime,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,10 +247,12 @@ class ChatsModule {
|
||||
static String? _nameFromContact(Map<dynamic, dynamic> contact) {
|
||||
final names = contact['names'];
|
||||
if (names is! List || names.isEmpty) return null;
|
||||
final name = names.firstWhere(
|
||||
(n) => n is Map && n['type'] == 'ONEME',
|
||||
orElse: () => names.first,
|
||||
) as Map;
|
||||
final name =
|
||||
names.firstWhere(
|
||||
(n) => n is Map && n['type'] == 'ONEME',
|
||||
orElse: () => names.first,
|
||||
)
|
||||
as Map;
|
||||
return name['name'] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import '../../core/storage/app_database.dart';
|
||||
|
||||
class ContactsModule {
|
||||
static Future<void> syncFromLoginPayload(
|
||||
Map<dynamic, dynamic> data,
|
||||
int accountId,
|
||||
) async {
|
||||
final contacts = data['contacts'];
|
||||
if (contacts is! List || contacts.isEmpty) return;
|
||||
|
||||
final rows = contacts
|
||||
.whereType<Map>()
|
||||
.map((c) => _parseContact(c.cast<dynamic, dynamic>(), accountId))
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.toList();
|
||||
|
||||
if (rows.isNotEmpty) {
|
||||
await AppDatabase.saveContacts(rows);
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _parseContact(
|
||||
Map<dynamic, dynamic> contact,
|
||||
int accountId,
|
||||
) {
|
||||
final id = contact['id'];
|
||||
if (id is! int) return null;
|
||||
|
||||
String firstName = '';
|
||||
String? lastName;
|
||||
|
||||
final names = contact['names'];
|
||||
if (names is List && names.isNotEmpty) {
|
||||
final name =
|
||||
names.firstWhere(
|
||||
(n) => n is Map && n['type'] == 'ONEME',
|
||||
orElse: () => names.first,
|
||||
)
|
||||
as Map;
|
||||
firstName = (name['firstName'] as String?) ?? '';
|
||||
lastName = name['lastName'] as String?;
|
||||
}
|
||||
|
||||
return {
|
||||
'id': id,
|
||||
'account_id': accountId,
|
||||
'first_name': firstName,
|
||||
'last_name': lastName,
|
||||
'phone': (contact['phone'] as int?) ?? 0,
|
||||
'photo_id': contact['photoId'] as int?,
|
||||
'base_url': contact['baseUrl'] as String?,
|
||||
'base_raw_url': contact['baseRawUrl'] as String?,
|
||||
'update_time': (contact['updateTime'] as int?) ?? 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user