Initial Commit
This commit is contained in:
116
lib/models/attach.dart
Normal file
116
lib/models/attach.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
enum AttachTypes { call, control, inlineKeyboard, share }
|
||||
|
||||
abstract class Attachment {
|
||||
final AttachTypes type;
|
||||
|
||||
Attachment(this.type);
|
||||
|
||||
factory Attachment.fromJson(Map<String, dynamic> json) {
|
||||
final typeString = json['_type'] as String;
|
||||
switch (typeString) {
|
||||
case 'CALL':
|
||||
return CallAttachment.fromJson(json);
|
||||
case 'CONTROL':
|
||||
return ControlAttachment.fromJson(json);
|
||||
case 'INLINE_KEYBOARD':
|
||||
return InlineKeyboardAttachment.fromJson(json);
|
||||
case 'SHARE':
|
||||
return ShareAttachment.fromJson(json);
|
||||
default:
|
||||
throw ArgumentError('Unknown attachment type: $typeString');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CallAttachment extends Attachment {
|
||||
final int duration;
|
||||
final String conversationId;
|
||||
final String hangupType;
|
||||
final String joinLink;
|
||||
final String callType;
|
||||
|
||||
CallAttachment({
|
||||
required this.duration,
|
||||
required this.conversationId,
|
||||
required this.hangupType,
|
||||
required this.joinLink,
|
||||
required this.callType,
|
||||
}) : super(AttachTypes.call);
|
||||
|
||||
factory CallAttachment.fromJson(Map<String, dynamic> json) {
|
||||
return CallAttachment(
|
||||
duration: json['duration'] as int,
|
||||
conversationId: json['conversationId'] as String,
|
||||
hangupType: json['hangupType'] as String,
|
||||
joinLink: json['joinLink'] as String,
|
||||
callType: json['callType'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ControlAttachment extends Attachment {
|
||||
final String event;
|
||||
|
||||
ControlAttachment({required this.event}) : super(AttachTypes.control);
|
||||
|
||||
factory ControlAttachment.fromJson(Map<String, dynamic> json) {
|
||||
return ControlAttachment(event: json['event'] as String);
|
||||
}
|
||||
}
|
||||
|
||||
class InlineKeyboardAttachment extends Attachment {
|
||||
final Map<String, dynamic> keyboard;
|
||||
final String callbackId;
|
||||
|
||||
InlineKeyboardAttachment({required this.keyboard, required this.callbackId})
|
||||
: super(AttachTypes.inlineKeyboard);
|
||||
|
||||
factory InlineKeyboardAttachment.fromJson(Map<String, dynamic> json) {
|
||||
return InlineKeyboardAttachment(
|
||||
keyboard: json['keyboard'] as Map<String, dynamic>,
|
||||
callbackId: json['callbackId'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShareAttachment extends Attachment {
|
||||
final Map<String, dynamic> image;
|
||||
final String description;
|
||||
final bool contentLevel;
|
||||
final int shareId;
|
||||
final String title;
|
||||
final String url;
|
||||
|
||||
ShareAttachment({
|
||||
required this.image,
|
||||
required this.description,
|
||||
required this.contentLevel,
|
||||
required this.shareId,
|
||||
required this.title,
|
||||
required this.url,
|
||||
}) : super(AttachTypes.share);
|
||||
|
||||
factory ShareAttachment.fromJson(Map<String, dynamic> json) {
|
||||
return ShareAttachment(
|
||||
image: json['image'] as Map<String, dynamic>,
|
||||
description: json['description'] as String,
|
||||
contentLevel: json['contentLevel'] as bool,
|
||||
shareId: json['shareId'] as int,
|
||||
title: json['title'] as String,
|
||||
url: json['url'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentsParser {
|
||||
static List<Attachment> parse(List<dynamic> jsonList) {
|
||||
return jsonList.map((jsonItem) {
|
||||
if (jsonItem is Map<String, dynamic>) {
|
||||
return Attachment.fromJson(jsonItem);
|
||||
} else {
|
||||
throw ArgumentError('Invalid JSON item in the list: $jsonItem');
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
37
lib/models/channel.dart
Normal file
37
lib/models/channel.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
class Channel {
|
||||
final int id;
|
||||
final String name;
|
||||
final String? description;
|
||||
final String? photoBaseUrl;
|
||||
final String? link;
|
||||
final String? webApp;
|
||||
final List<String> options;
|
||||
final int updateTime;
|
||||
|
||||
Channel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.description,
|
||||
this.photoBaseUrl,
|
||||
this.link,
|
||||
this.webApp,
|
||||
required this.options,
|
||||
required this.updateTime,
|
||||
});
|
||||
|
||||
factory Channel.fromJson(Map<String, dynamic> json) {
|
||||
final names = json['names'] as List<dynamic>?;
|
||||
final nameData = names?.isNotEmpty == true ? names![0] : null;
|
||||
|
||||
return Channel(
|
||||
id: json['id'] as int,
|
||||
name: nameData?['name'] as String? ?? 'Неизвестный канал',
|
||||
description: nameData?['description'] as String?,
|
||||
photoBaseUrl: json['baseUrl'] as String?,
|
||||
link: json['link'] as String?,
|
||||
webApp: json['webApp'] as String?,
|
||||
options: List<String>.from(json['options'] ?? []),
|
||||
updateTime: json['updateTime'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
99
lib/models/chat.dart
Normal file
99
lib/models/chat.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'package:gwid/models/message.dart';
|
||||
|
||||
class Chat {
|
||||
final int id;
|
||||
final int ownerId;
|
||||
final Message lastMessage;
|
||||
final List<int> participantIds;
|
||||
final int newMessages;
|
||||
final String? title; // Название группы
|
||||
final String? type; // Тип чата (DIALOG, CHAT)
|
||||
final String? baseIconUrl; // URL иконки группы
|
||||
final String? description;
|
||||
final int? participantsCount;
|
||||
|
||||
Chat({
|
||||
required this.id,
|
||||
required this.ownerId,
|
||||
required this.lastMessage,
|
||||
required this.participantIds,
|
||||
required this.newMessages,
|
||||
this.title,
|
||||
this.type,
|
||||
this.baseIconUrl,
|
||||
this.description,
|
||||
this.participantsCount,
|
||||
});
|
||||
|
||||
factory Chat.fromJson(Map<String, dynamic> json) {
|
||||
var participantsMap = json['participants'] as Map<String, dynamic>? ?? {};
|
||||
List<int> participantIds = participantsMap.keys
|
||||
.map((id) => int.parse(id))
|
||||
.toList();
|
||||
|
||||
|
||||
Message lastMessage;
|
||||
if (json['lastMessage'] != null) {
|
||||
lastMessage = Message.fromJson(json['lastMessage']);
|
||||
} else {
|
||||
lastMessage = Message(
|
||||
id: 'empty',
|
||||
senderId: 0,
|
||||
time: DateTime.now().millisecondsSinceEpoch,
|
||||
text: '',
|
||||
cid: null,
|
||||
attaches: [],
|
||||
);
|
||||
}
|
||||
|
||||
return Chat(
|
||||
id: json['id'] ?? 0,
|
||||
ownerId: json['owner'] ?? 0,
|
||||
lastMessage: lastMessage,
|
||||
participantIds: participantIds,
|
||||
newMessages: json['newMessages'] ?? 0,
|
||||
title: json['title'],
|
||||
type: json['type'],
|
||||
baseIconUrl: json['baseIconUrl'],
|
||||
description: json['description'],
|
||||
participantsCount: json['participantsCount'],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
bool get isGroup => type == 'CHAT' || participantIds.length > 2;
|
||||
|
||||
List<int> get groupParticipantIds => participantIds;
|
||||
|
||||
int get onlineParticipantsCount => participantIds.length; // Упрощенная версия
|
||||
|
||||
String get displayTitle {
|
||||
if (title != null && title!.isNotEmpty) {
|
||||
return title!;
|
||||
}
|
||||
if (isGroup) {
|
||||
return 'Группа ${participantIds.length}';
|
||||
}
|
||||
return 'Чат';
|
||||
}
|
||||
|
||||
Chat copyWith({
|
||||
Message? lastMessage,
|
||||
int? newMessages,
|
||||
String? title,
|
||||
String? type,
|
||||
String? baseIconUrl,
|
||||
}) {
|
||||
return Chat(
|
||||
id: id,
|
||||
ownerId: ownerId,
|
||||
lastMessage: lastMessage ?? this.lastMessage,
|
||||
participantIds: participantIds,
|
||||
newMessages: newMessages ?? this.newMessages,
|
||||
title: title ?? this.title,
|
||||
type: type ?? this.type,
|
||||
baseIconUrl: baseIconUrl ?? this.baseIconUrl,
|
||||
description: description ?? this.description,
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/models/chat_folder.dart
Normal file
83
lib/models/chat_folder.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
class ChatFolder {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? emoji;
|
||||
final List<int>? include;
|
||||
final List<dynamic> filters;
|
||||
final bool hideEmpty;
|
||||
final List<ChatFolderWidget> widgets;
|
||||
final List<String>? favorites;
|
||||
final Map<String, dynamic>? filterSubjects;
|
||||
final List<int>? options;
|
||||
|
||||
ChatFolder({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.emoji,
|
||||
this.include,
|
||||
required this.filters,
|
||||
required this.hideEmpty,
|
||||
required this.widgets,
|
||||
this.favorites,
|
||||
this.filterSubjects,
|
||||
this.options,
|
||||
});
|
||||
|
||||
factory ChatFolder.fromJson(Map<String, dynamic> json) {
|
||||
return ChatFolder(
|
||||
id: json['id'],
|
||||
title: json['title'],
|
||||
emoji: json['emoji'],
|
||||
include: json['include'] != null ? List<int>.from(json['include']) : null,
|
||||
filters: json['filters'] != null
|
||||
? List<dynamic>.from(json['filters'])
|
||||
: [],
|
||||
hideEmpty: json['hideEmpty'] ?? false,
|
||||
widgets:
|
||||
(json['widgets'] as List<dynamic>?)
|
||||
?.map((widget) => ChatFolderWidget.fromJson(widget))
|
||||
.toList() ??
|
||||
[],
|
||||
favorites: json['favorites'] != null
|
||||
? List<String>.from(json['favorites'])
|
||||
: null,
|
||||
filterSubjects: json['filterSubjects'],
|
||||
options: json['options'] != null ? List<int>.from(json['options']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatFolderWidget {
|
||||
final int id;
|
||||
final String name;
|
||||
final String description;
|
||||
final String? iconUrl;
|
||||
final String? url;
|
||||
final String? startParam;
|
||||
final String? background;
|
||||
final int? appId;
|
||||
|
||||
ChatFolderWidget({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
this.iconUrl,
|
||||
this.url,
|
||||
this.startParam,
|
||||
this.background,
|
||||
this.appId,
|
||||
});
|
||||
|
||||
factory ChatFolderWidget.fromJson(Map<String, dynamic> json) {
|
||||
return ChatFolderWidget(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
description: json['description'],
|
||||
iconUrl: json['iconUrl'],
|
||||
url: json['url'],
|
||||
startParam: json['startParam'],
|
||||
background: json['background'],
|
||||
appId: json['appId'],
|
||||
);
|
||||
}
|
||||
}
|
||||
68
lib/models/contact.dart
Normal file
68
lib/models/contact.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
class Contact {
|
||||
final int id;
|
||||
final String name;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String? description;
|
||||
final String? photoBaseUrl;
|
||||
final bool isBlocked;
|
||||
final bool isBlockedByMe;
|
||||
final int accountStatus;
|
||||
final String? status;
|
||||
final List<String> options;
|
||||
|
||||
Contact({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
this.description,
|
||||
this.photoBaseUrl,
|
||||
this.isBlocked = false,
|
||||
this.isBlockedByMe = false,
|
||||
this.accountStatus = 0,
|
||||
this.status,
|
||||
this.options = const [],
|
||||
});
|
||||
|
||||
bool get isBot => options.contains('BOT');
|
||||
|
||||
bool get isUserBlocked => isBlockedByMe || isBlocked;
|
||||
|
||||
factory Contact.fromJson(Map<String, dynamic> json) {
|
||||
final nameData = json['names']?[0];
|
||||
|
||||
String finalFirstName = '';
|
||||
String finalLastName = '';
|
||||
String finalName = 'Unknown';
|
||||
|
||||
if (nameData != null) {
|
||||
finalFirstName = nameData['firstName'] ?? '';
|
||||
finalLastName = nameData['lastName'] ?? '';
|
||||
final fullName = '$finalFirstName $finalLastName'.trim();
|
||||
finalName = fullName.isNotEmpty
|
||||
? fullName
|
||||
: (nameData['name'] ?? 'Unknown');
|
||||
}
|
||||
|
||||
|
||||
final status = json['status'];
|
||||
final isBlocked = status == 'BLOCKED';
|
||||
|
||||
final isBlockedByMe = status == 'BLOCKED';
|
||||
|
||||
return Contact(
|
||||
id: json['id'],
|
||||
name: finalName,
|
||||
firstName: finalFirstName,
|
||||
lastName: finalLastName,
|
||||
description: json['description'],
|
||||
photoBaseUrl: json['baseUrl'],
|
||||
isBlocked: isBlocked,
|
||||
isBlockedByMe: isBlockedByMe,
|
||||
accountStatus: json['accountStatus'] ?? 0,
|
||||
status: json['status'],
|
||||
options: List<String>.from(json['options'] ?? []),
|
||||
);
|
||||
}
|
||||
}
|
||||
133
lib/models/message.dart
Normal file
133
lib/models/message.dart
Normal file
@@ -0,0 +1,133 @@
|
||||
class Message {
|
||||
final String id;
|
||||
final String text;
|
||||
final int time;
|
||||
final int senderId;
|
||||
final String? status; // EDITED, DELETED, etc.
|
||||
final int? updateTime; // Время последнего редактирования
|
||||
final List<Map<String, dynamic>> attaches;
|
||||
final int? cid; // клиентский id (timestamp)
|
||||
final Map<String, dynamic>? reactionInfo; // Информация о реакциях
|
||||
final Map<String, dynamic>? link; // Информация об ответе на сообщение
|
||||
|
||||
Message({
|
||||
required this.id,
|
||||
required this.text,
|
||||
required this.time,
|
||||
required this.senderId,
|
||||
this.status,
|
||||
this.updateTime,
|
||||
this.attaches = const [],
|
||||
this.cid,
|
||||
this.reactionInfo,
|
||||
this.link,
|
||||
});
|
||||
|
||||
factory Message.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
|
||||
|
||||
|
||||
int senderId;
|
||||
if (json['sender'] is int) {
|
||||
senderId = json['sender'];
|
||||
} else {
|
||||
|
||||
senderId = 0;
|
||||
}
|
||||
|
||||
|
||||
int time;
|
||||
if (json['time'] is int) {
|
||||
time = json['time'];
|
||||
} else {
|
||||
time = 0;
|
||||
}
|
||||
|
||||
return Message(
|
||||
|
||||
id:
|
||||
json['id']?.toString() ??
|
||||
'local_${DateTime.now().millisecondsSinceEpoch}',
|
||||
text: json['text'] ?? '',
|
||||
time: time,
|
||||
senderId: senderId, // Use the new safe logic
|
||||
status: json['status'],
|
||||
updateTime: json['updateTime'],
|
||||
attaches:
|
||||
(json['attaches'] as List?)
|
||||
?.map((e) => (e as Map).cast<String, dynamic>())
|
||||
.toList() ??
|
||||
const [],
|
||||
cid: json['cid'],
|
||||
reactionInfo: json['reactionInfo'],
|
||||
link: json['link'],
|
||||
);
|
||||
}
|
||||
|
||||
Message copyWith({
|
||||
String? id,
|
||||
String? text,
|
||||
int? time,
|
||||
int? senderId,
|
||||
String? status,
|
||||
int? updateTime,
|
||||
List<Map<String, dynamic>>? attaches,
|
||||
int? cid,
|
||||
Map<String, dynamic>? reactionInfo,
|
||||
Map<String, dynamic>? link,
|
||||
}) {
|
||||
return Message(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
time: time ?? this.time,
|
||||
senderId: senderId ?? this.senderId,
|
||||
status: status ?? this.status,
|
||||
updateTime: updateTime ?? this.updateTime,
|
||||
attaches: attaches ?? this.attaches,
|
||||
cid: cid ?? this.cid,
|
||||
reactionInfo: reactionInfo ?? this.reactionInfo,
|
||||
link: link ?? this.link,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isEdited => status == 'EDITED';
|
||||
bool get isDeleted => status == 'DELETED';
|
||||
bool get isReply => link != null && link!['type'] == 'REPLY';
|
||||
bool get isForwarded => link != null && link!['type'] == 'FORWARD';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool canEdit(int currentUserId) {
|
||||
if (isDeleted) return false;
|
||||
if (senderId != currentUserId) return false;
|
||||
if (attaches.isNotEmpty) {
|
||||
return false; // Нельзя редактировать сообщения с вложениями
|
||||
}
|
||||
|
||||
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final messageTime = time;
|
||||
final hoursSinceCreation = (now - messageTime) / (1000 * 60 * 60);
|
||||
|
||||
return hoursSinceCreation <= 24;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'text': text,
|
||||
'time': time,
|
||||
'sender': senderId,
|
||||
'status': status,
|
||||
'updateTime': updateTime,
|
||||
'cid': cid,
|
||||
'attaches': attaches,
|
||||
'link': link,
|
||||
'reactionInfo': reactionInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
82
lib/models/profile.dart
Normal file
82
lib/models/profile.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
class Profile {
|
||||
final int id;
|
||||
final String phone;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String? description;
|
||||
final String? photoBaseUrl;
|
||||
final int photoId;
|
||||
final int updateTime;
|
||||
final List<String> options;
|
||||
final int accountStatus;
|
||||
final List<ProfileOption> profileOptions;
|
||||
|
||||
Profile({
|
||||
required this.id,
|
||||
required this.phone,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
this.description,
|
||||
this.photoBaseUrl,
|
||||
required this.photoId,
|
||||
required this.updateTime,
|
||||
required this.options,
|
||||
required this.accountStatus,
|
||||
required this.profileOptions,
|
||||
});
|
||||
|
||||
factory Profile.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
Map<String, dynamic> profileData;
|
||||
if (json.containsKey('contact')) {
|
||||
profileData = json['contact'] as Map<String, dynamic>;
|
||||
} else {
|
||||
|
||||
profileData = json;
|
||||
}
|
||||
|
||||
final names = profileData['names'] as List<dynamic>? ?? [];
|
||||
final nameData = names.isNotEmpty ? names[0] as Map<String, dynamic> : {};
|
||||
|
||||
return Profile(
|
||||
id: profileData['id'],
|
||||
phone: profileData['phone'].toString(),
|
||||
firstName: nameData['firstName'] ?? '',
|
||||
lastName: nameData['lastName'] ?? '',
|
||||
description: profileData['description'],
|
||||
photoBaseUrl: profileData['baseUrl'],
|
||||
photoId: profileData['photoId'] ?? 0,
|
||||
updateTime: profileData['updateTime'] ?? 0,
|
||||
options: List<String>.from(profileData['options'] ?? []),
|
||||
accountStatus: profileData['accountStatus'] ?? 0,
|
||||
profileOptions:
|
||||
(json['profileOptions'] as List<dynamic>?)
|
||||
?.map((option) => ProfileOption.fromJson(option))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
final fullName = '$firstName $lastName'.trim();
|
||||
return fullName.isNotEmpty ? fullName : 'Пользователь';
|
||||
}
|
||||
|
||||
String get formattedPhone {
|
||||
if (phone.length == 11 && phone.startsWith('7')) {
|
||||
return '+7 (${phone.substring(1, 4)}) ${phone.substring(4, 7)}-${phone.substring(7, 9)}-${phone.substring(9)}';
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileOption {
|
||||
final String key;
|
||||
final dynamic value;
|
||||
|
||||
ProfileOption({required this.key, required this.value});
|
||||
|
||||
factory ProfileOption.fromJson(Map<String, dynamic> json) {
|
||||
return ProfileOption(key: json['key'], value: json['value']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user