Initial Commit

This commit is contained in:
ivan2282
2025-11-15 20:06:40 +03:00
commit 205d11df0d
233 changed files with 52572 additions and 0 deletions

116
lib/models/attach.dart Normal file
View 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
View 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
View 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,
);
}
}

View 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
View 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
View 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
View 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']);
}
}