From 51bfda69eabe88a8ed19d1e887f2dabd0f6c57fd Mon Sep 17 00:00:00 2001 From: Yurin Andrey Date: Thu, 27 Nov 2025 18:34:27 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/models/dialog/base_dialog_info.dart | 6 +++--- lib/core/models/dialog/dialog.dart | 13 +++++++++---- lib/core/models/dialog/group_dialog.dart | 10 ++++------ lib/core/models/dialog/preview_dialog.dart | 13 +++++++------ lib/core/models/dialog/preview_group_dialog.dart | 13 ++++--------- lib/core/models/dialog/preview_user_dialog.dart | 8 ++++---- lib/core/models/dialog/user_dialog.dart | 11 ++++------- 7 files changed, 35 insertions(+), 39 deletions(-) diff --git a/lib/core/models/dialog/base_dialog_info.dart b/lib/core/models/dialog/base_dialog_info.dart index ce0141a9..64a93384 100644 --- a/lib/core/models/dialog/base_dialog_info.dart +++ b/lib/core/models/dialog/base_dialog_info.dart @@ -1,13 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN -abstract class BaseDialogInfo { - final int chatId; +abstract class BaseDialogInfo { + final T dialogId; final String title; final String avatarUrl; BaseDialogInfo({ - required this.chatId, + required this.dialogId, required this.title, required this.avatarUrl, }); diff --git a/lib/core/models/dialog/dialog.dart b/lib/core/models/dialog/dialog.dart index c9c8deda..a7aba32d 100644 --- a/lib/core/models/dialog/dialog.dart +++ b/lib/core/models/dialog/dialog.dart @@ -8,6 +8,7 @@ import 'package:unn_mobile/core/models/dialog/enum/message_status.dart'; import 'package:unn_mobile/core/models/dialog/message/message_short_info.dart'; class _DialogJsonKeys { + static const String dialogId = 'id'; static const String chatId = 'chat_id'; static const String title = 'title'; static const String avatar = 'avatar'; @@ -22,7 +23,8 @@ class _DialogJsonKeys { static const String counter = 'counter'; } -class Dialog extends BaseDialogInfo { +class Dialog extends BaseDialogInfo { + final int chatId; final MessageShortInfo previewMessage; final MessageStatus lastMessageStatus; final int unreadMessagesCount; @@ -36,9 +38,10 @@ class Dialog extends BaseDialogInfo { } Dialog({ - required super.chatId, + required super.dialogId, required super.title, required super.avatarUrl, + required this.chatId, required this.previewMessage, required this.lastMessageStatus, required this.unreadMessagesCount, @@ -53,11 +56,13 @@ class Dialog extends BaseDialogInfo { final fileName = (fileInfo as JsonMap)[_DialogJsonKeys.name]! as String; messageMap[_DialogJsonKeys.text] = 'Файл: $fileName'; } + return Dialog( - chatId: jsonMap[_DialogJsonKeys.chatId]! as int, + dialogId: jsonMap[_DialogJsonKeys.dialogId]! as T, title: jsonMap[_DialogJsonKeys.title]! as String, avatarUrl: (jsonMap[_DialogJsonKeys.avatar]! as JsonMap)[_DialogJsonKeys.url]! as String, + chatId: jsonMap[_DialogJsonKeys.chatId]! as int, previewMessage: MessageShortInfo.fromJson({ ...messageMap, MessageShortInfoJsonKeys.author: jsonMap[_DialogJsonKeys.user], @@ -75,7 +80,7 @@ class Dialog extends BaseDialogInfo { final messageMap = previewMessage.toJson(); return { - _DialogJsonKeys.chatId: chatId, + _DialogJsonKeys.dialogId: dialogId, _DialogJsonKeys.title: title, _DialogJsonKeys.avatar: { _DialogJsonKeys.url: avatarUrl, diff --git a/lib/core/models/dialog/group_dialog.dart b/lib/core/models/dialog/group_dialog.dart index 5b7ac3a5..8b671de8 100644 --- a/lib/core/models/dialog/group_dialog.dart +++ b/lib/core/models/dialog/group_dialog.dart @@ -15,11 +15,11 @@ class GroupDialogJsonKeys { static const String sonetGroup = 'sonetGroup'; } -final class GroupDialog extends Dialog { - final String dialogId; +final class GroupDialog extends Dialog { final ChatSetting chatSetting; GroupDialog({ + required super.dialogId, required super.chatId, required super.title, required super.avatarUrl, @@ -27,21 +27,20 @@ final class GroupDialog extends Dialog { required super.unreadMessagesCount, required super.lastMessageStatus, required super.pinned, - required this.dialogId, required this.chatSetting, }); factory GroupDialog.fromJson(JsonMap json) { - final dialog = Dialog.fromJson(json); + final dialog = Dialog.fromJson(json); return GroupDialog( - chatId: dialog.chatId, title: dialog.title, avatarUrl: dialog.avatarUrl, previewMessage: dialog.previewMessage, unreadMessagesCount: dialog.unreadMessagesCount, lastMessageStatus: dialog.lastMessageStatus, pinned: dialog.pinned, + chatId: dialog.chatId, dialogId: json[GroupDialogJsonKeys.id]! as String, chatSetting: _parseChatSetting(json[GroupDialogJsonKeys.chat]! as JsonMap), @@ -51,7 +50,6 @@ final class GroupDialog extends Dialog { @override JsonMap toJson() => { ...super.toJson(), - GroupDialogJsonKeys.id: dialogId, GroupDialogJsonKeys.chat: chatSetting.toJson(), GroupDialogJsonKeys.type: GroupDialogJsonKeys.chat, }; diff --git a/lib/core/models/dialog/preview_dialog.dart b/lib/core/models/dialog/preview_dialog.dart index c76b183c..638c4176 100644 --- a/lib/core/models/dialog/preview_dialog.dart +++ b/lib/core/models/dialog/preview_dialog.dart @@ -11,23 +11,24 @@ class _PreviewDialogJsonKeys { static const String customData = 'customData'; } -class PreviewDialog extends BaseDialogInfo { +class PreviewDialog extends BaseDialogInfo { PreviewDialog({ - required super.chatId, + required super.dialogId, required super.title, required super.avatarUrl, }); - factory PreviewDialog.fromJson(JsonMap json) => PreviewDialog( - chatId: (json[_PreviewDialogJsonKeys.customData]! - as JsonMap)[_PreviewDialogJsonKeys.id]! as int, + factory PreviewDialog.fromJson(JsonMap json) => PreviewDialog( + dialogId: T == String + ? json[_PreviewDialogJsonKeys.id]! as T + : int.tryParse(json[_PreviewDialogJsonKeys.id]! as String) as T, title: json[_PreviewDialogJsonKeys.title]! as String, avatarUrl: json[_PreviewDialogJsonKeys.avatar]! as String, ); JsonMap toJson() => { _PreviewDialogJsonKeys.customData: { - _PreviewDialogJsonKeys.id: chatId, + _PreviewDialogJsonKeys.id: dialogId, }, _PreviewDialogJsonKeys.title: title, _PreviewDialogJsonKeys.avatar: avatarUrl, diff --git a/lib/core/models/dialog/preview_group_dialog.dart b/lib/core/models/dialog/preview_group_dialog.dart index 905707d1..10e3abe5 100644 --- a/lib/core/models/dialog/preview_group_dialog.dart +++ b/lib/core/models/dialog/preview_group_dialog.dart @@ -6,30 +6,26 @@ import 'package:unn_mobile/core/models/dialog/chat_settings/base_chat_setting.da import 'package:unn_mobile/core/models/dialog/preview_dialog.dart'; class _PreviewGroupDialogJsonKeys { - static const String id = 'id'; static const String customData = 'customData'; } -class PreviewGroupDialog extends PreviewDialog { - final String id; +class PreviewGroupDialog extends PreviewDialog { final BaseChatSetting baseChatSetting; PreviewGroupDialog({ - required super.chatId, + required super.dialogId, required super.title, required super.avatarUrl, - required this.id, required this.baseChatSetting, }); factory PreviewGroupDialog.fromJson(JsonMap json) { - final dialog = PreviewDialog.fromJson(json); + final dialog = PreviewDialog.fromJson(json); return PreviewGroupDialog( - chatId: dialog.chatId, + dialogId: dialog.dialogId, title: dialog.title, avatarUrl: dialog.avatarUrl, - id: json[_PreviewGroupDialogJsonKeys.id]! as String, baseChatSetting: BaseChatSetting.fromJson( json[_PreviewGroupDialogJsonKeys.customData]! as JsonMap, ), @@ -44,7 +40,6 @@ class PreviewGroupDialog extends PreviewDialog { return { ...superJson, - _PreviewGroupDialogJsonKeys.id: id, _PreviewGroupDialogJsonKeys.customData: { ...existingCustomData, ...baseChatSetting.toJson(), diff --git a/lib/core/models/dialog/preview_user_dialog.dart b/lib/core/models/dialog/preview_user_dialog.dart index 47755158..944d7bc0 100644 --- a/lib/core/models/dialog/preview_user_dialog.dart +++ b/lib/core/models/dialog/preview_user_dialog.dart @@ -10,12 +10,12 @@ class _PreviewUserDialogJsonKeys { static const String customData = 'customData'; } -class PreviewUserDialog extends PreviewDialog { +class PreviewUserDialog extends PreviewDialog { final DateTime? lastActivityAt; final String workPosition; PreviewUserDialog({ - required super.chatId, + required super.dialogId, required super.title, required super.avatarUrl, required this.lastActivityAt, @@ -23,11 +23,11 @@ class PreviewUserDialog extends PreviewDialog { }); factory PreviewUserDialog.fromJson(JsonMap json) { - final dialog = PreviewDialog.fromJson(json); + final dialog = PreviewDialog.fromJson(json); final lastActivityAt = (json[_PreviewUserDialogJsonKeys.customData]! as JsonMap)[_PreviewUserDialogJsonKeys.lastActivityDate]; return PreviewUserDialog( - chatId: dialog.chatId, + dialogId: dialog.dialogId, title: dialog.title, avatarUrl: dialog.avatarUrl, lastActivityAt: diff --git a/lib/core/models/dialog/user_dialog.dart b/lib/core/models/dialog/user_dialog.dart index 61792e00..210aec69 100644 --- a/lib/core/models/dialog/user_dialog.dart +++ b/lib/core/models/dialog/user_dialog.dart @@ -5,17 +5,16 @@ import 'package:unn_mobile/core/misc/json/json_utils.dart'; import 'package:unn_mobile/core/models/dialog/dialog.dart'; class _UserDialogJsonKeys { - static const String id = 'id'; static const String type = 'type'; static const String user = 'user'; static const String lastActivityDate = 'last_activity_date'; } -final class UserDialog extends Dialog { - final int dialogId; +final class UserDialog extends Dialog { final DateTime? lastActivityAt; UserDialog({ + required super.dialogId, required super.chatId, required super.title, required super.avatarUrl, @@ -23,13 +22,13 @@ final class UserDialog extends Dialog { required super.unreadMessagesCount, required super.lastMessageStatus, required super.pinned, - required this.dialogId, required this.lastActivityAt, }); factory UserDialog.fromJson(JsonMap json) { - final dialog = Dialog.fromJson(json); + final dialog = Dialog.fromJson(json); return UserDialog( + dialogId: dialog.dialogId, chatId: dialog.chatId, title: dialog.title, avatarUrl: dialog.avatarUrl, @@ -37,7 +36,6 @@ final class UserDialog extends Dialog { unreadMessagesCount: dialog.unreadMessagesCount, lastMessageStatus: dialog.lastMessageStatus, pinned: dialog.pinned, - dialogId: json[_UserDialogJsonKeys.id]! as int, lastActivityAt: DateTime.tryParse( (json[_UserDialogJsonKeys.user]! as JsonMap)[_UserDialogJsonKeys.lastActivityDate]! as String, @@ -48,7 +46,6 @@ final class UserDialog extends Dialog { @override JsonMap toJson() => { ...super.toJson(), - _UserDialogJsonKeys.id: dialogId, _UserDialogJsonKeys.type: _UserDialogJsonKeys.user, _UserDialogJsonKeys.user: { _UserDialogJsonKeys.lastActivityDate: From 43f032f6c3c2a8c049e67f45b4d93965bd729073 Mon Sep 17 00:00:00 2001 From: UNN MOBILE runner Date: Thu, 27 Nov 2025 15:35:11 +0000 Subject: [PATCH 2/7] Change version in pubspec --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 86f6c3d1..847790b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: unn_mobile description: A mobile application for UNN Portal website publish_to: 'none' -version: 0.6.0+372 +version: 0.6.0+373 environment: sdk: '>=3.1.2 <4.0.0' From 7a9f0c88e9974f5376c0a9f36f469a7167497493 Mon Sep 17 00:00:00 2001 From: Yurin Andrey Date: Fri, 28 Nov 2025 12:36:40 +0300 Subject: [PATCH 3/7] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 9501e1f086c0964fe3442065bf4509e43ca692ec Author: Yurin Andrey <100288192+Namxobick@users.noreply.github.com> Date: Thu Nov 27 20:09:53 2025 +0300 Получение сообщений по dialogId (#608) commit 85bac61a1f81ee6ac6cb16437ec71421f481bf58 Author: Yurin Andrey <100288192+Namxobick@users.noreply.github.com> Date: Thu Nov 27 19:46:28 2025 +0300 Исправил htmlToPlainText (#606) --- .../message_service_aggregator_impl.dart | 14 +++- lib/core/constants/api/ajax_action.dart | 7 +- .../misc/html_utils/html_to_plain_text.dart | 82 ++++++++++++++++++- lib/core/misc/objects_with_pagination.dart | 11 +++ .../message/message_fetcher_service_impl.dart | 73 ++++++++++++----- .../message/message_fetcher_service.dart | 19 ++++- .../chat/chat_inside_view_model.dart | 6 +- .../main_page/feed/widgets/feed_post.dart | 2 +- pubspec.yaml | 2 +- 9 files changed, 184 insertions(+), 32 deletions(-) diff --git a/lib/core/aggregators/implementations/message_service_aggregator_impl.dart b/lib/core/aggregators/implementations/message_service_aggregator_impl.dart index 8cca9974..eb4727ca 100644 --- a/lib/core/aggregators/implementations/message_service_aggregator_impl.dart +++ b/lib/core/aggregators/implementations/message_service_aggregator_impl.dart @@ -32,17 +32,27 @@ class MessageServiceAggregatorImpl implements MessageServiceAggregator { ); @override - Future?> fetch({ + Future?> fetchByChatId({ required int chatId, int limit = 25, int? lastMessageId, }) => - _fetcherService.fetch( + _fetcherService.fetchByChatId( chatId: chatId, limit: limit, lastMessageId: lastMessageId, ); + @override + Future?> fetchByDialogId({ + required String dialogId, + int limit = 25, + }) => + _fetcherService.fetchByDialogId( + dialogId: dialogId, + limit: limit, + ); + @override Future send({ required String dialogId, diff --git a/lib/core/constants/api/ajax_action.dart b/lib/core/constants/api/ajax_action.dart index b7191f8b..b6b76630 100644 --- a/lib/core/constants/api/ajax_action.dart +++ b/lib/core/constants/api/ajax_action.dart @@ -35,8 +35,11 @@ class AjaxActionStrings { static const String importantBlogPostUsers = 'socialnetwork.api.livefeed.blogpost.important.getUsers'; - /// Для получения сообщений в чате - static const String fetchFirstMessage = 'im.v2.Chat.Message.list'; + /// Для получения первых N сообщений в чате по chat id + static const String fetchFirstMessageByChatId = 'im.v2.Chat.Message.list'; + + /// Для получения первых N сообщений в чате по dialog id + static const String fetchFirstMessageByDialogId = 'im.v2.Chat.load'; /// Для получения сообщений в чате c конкретного сообщения static const String fetchMessage = 'im.v2.Chat.Message.tail'; diff --git a/lib/core/misc/html_utils/html_to_plain_text.dart b/lib/core/misc/html_utils/html_to_plain_text.dart index c5d5eb14..6d2866fd 100644 --- a/lib/core/misc/html_utils/html_to_plain_text.dart +++ b/lib/core/misc/html_utils/html_to_plain_text.dart @@ -1,11 +1,87 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN -import 'package:html/parser.dart'; +import 'package:html/dom.dart' as dom; +import 'package:html/parser.dart' as parser; import 'package:html_unescape/html_unescape.dart'; +void _processNode( + dom.Node node, + StringBuffer buffer, + Set blockTags, +) { + if (node is dom.Text) { + final text = node.text.replaceAll(RegExp(r'\s+'), ' ').trim(); + if (text.isNotEmpty) { + buffer.write('$text '); + } + } else if (node is dom.Element) { + final tagName = node.localName?.toLowerCase(); + + if (tagName == 'a') { + final href = node.attributes['href']; + if (href != null && href.trim().isNotEmpty) { + buffer.write(href); + } else { + for (final child in node.nodes) { + _processNode(child, buffer, blockTags); + } + } + } else { + for (final child in node.nodes) { + _processNode(child, buffer, blockTags); + } + } + + if (blockTags.contains(tagName)) { + buffer.write('\n'); + } + } +} + +String _getElementTextWithFormatting(dom.Element element) { + final buffer = StringBuffer(); + final blockTags = { + 'br', + 'p', + 'div', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'li', + 'tr', + 'pre', + 'blockquote', + 'hr', + 'ul', + 'ol', + 'dl', + 'section', + 'article', + 'aside', + 'header', + 'footer', + 'main', + }; + + _processNode(element, buffer, blockTags); + + return buffer + .toString() + .replaceAll(RegExp(r'[ \t]*\n[ \t]*'), '\n') + .replaceAll(RegExp(r'\n{3,}'), '\n\n') + .trim(); +} + String htmlToPlainText(String htmlText) { final unescaped = HtmlUnescape().convert(htmlText); - final document = parse(unescaped); - return document.body?.text ?? ''; + final document = parser.parse(unescaped); + final body = document.body; + if (body != null) { + return _getElementTextWithFormatting(body); + } + return ''; } diff --git a/lib/core/misc/objects_with_pagination.dart b/lib/core/misc/objects_with_pagination.dart index 8ff32ced..81c00108 100644 --- a/lib/core/misc/objects_with_pagination.dart +++ b/lib/core/misc/objects_with_pagination.dart @@ -22,6 +22,17 @@ class PaginatedResult { }); } +class PaginatedResultWithChatId extends PaginatedResult { + final int chatId; + + const PaginatedResultWithChatId({ + required this.chatId, + required super.items, + required super.hasPreviousPage, + required super.hasNextPage, + }); +} + class PartialResult { final List items; final bool hasMore; diff --git a/lib/core/services/implementations/dialog/message/message_fetcher_service_impl.dart b/lib/core/services/implementations/dialog/message/message_fetcher_service_impl.dart index 5fc3031a..9918745b 100644 --- a/lib/core/services/implementations/dialog/message/message_fetcher_service_impl.dart +++ b/lib/core/services/implementations/dialog/message/message_fetcher_service_impl.dart @@ -27,7 +27,9 @@ import 'package:unn_mobile/core/services/interfaces/dialog/message/message_fetch class _DataKeys { static const String chatId = 'chatId'; + static const String dialogId = 'dialogId'; static const String limit = 'limit'; + static const String messageLimit = 'messageLimit'; static const String lastId = 'filter[lastId]'; static const String orderId = 'order[id]'; } @@ -38,6 +40,8 @@ class _DataValue { class _JsonKeys { static const String data = 'data'; + static const String chat = 'chat'; + static const String id = 'id'; static const String messages = 'messages'; static const String messageId = 'messageId'; static const String reactions = 'reactions'; @@ -67,15 +71,43 @@ class MessageFetcherServiceImpl implements MessageFetcherService { ); @override - Future?> fetch({ + Future?> fetchByChatId({ required int chatId, int limit = 25, int? lastMessageId, - }) async { - final response = lastMessageId == null - ? await _fetchFirstMessages(chatId, limit) - : await _fetchMessages(chatId, lastMessageId, limit); + }) async => + _processPaginatedResponse>( + lastMessageId == null + ? await _fetchFirstMessages(chatId, limit) + : await _fetchMessages(chatId, lastMessageId, limit), + (data, messages) => PaginatedResult( + items: messages, + hasPreviousPage: + data[PaginatedResultJsonKeys.hasPrevPage] as bool? ?? false, + hasNextPage: data[PaginatedResultJsonKeys.hasNextPage]! as bool, + ), + ); + @override + Future?> fetchByDialogId({ + required String dialogId, + int limit = 25, + }) async => + _processPaginatedResponse>( + await _fetchFirstMessages(dialogId, limit, false), + (data, messages) => PaginatedResultWithChatId( + chatId: (data[_JsonKeys.chat]! as JsonMap)[_JsonKeys.id]! as int, + items: messages, + hasPreviousPage: + data[PaginatedResultJsonKeys.hasPrevPage] as bool? ?? false, + hasNextPage: data[PaginatedResultJsonKeys.hasNextPage]! as bool, + ), + ); + + Future _processPaginatedResponse( + Response? response, + T Function(JsonMap data, List messages) createResult, + ) async { if (response == null) { return null; } @@ -86,12 +118,7 @@ class MessageFetcherServiceImpl implements MessageFetcherService { final data = (response.data as JsonMap)[_JsonKeys.data]! as JsonMap; final messages = await _processMessagesData(data); - return PaginatedResult( - items: messages, - hasPreviousPage: - data[PaginatedResultJsonKeys.hasPrevPage] as bool? ?? false, - hasNextPage: data[PaginatedResultJsonKeys.hasNextPage]! as bool, - ); + return createResult(data, messages); } Future> _processMessagesData(Map data) { @@ -281,15 +308,23 @@ class MessageFetcherServiceImpl implements MessageFetcherService { }; Future _fetchFirstMessages( - int chatId, - int limit, - ) => + dynamic id, + int limit, [ + bool isChatId = true, + ]) => _fetchMessagesFromApi( - action: AjaxActionStrings.fetchFirstMessage, - data: { - _DataKeys.chatId: chatId, - _DataKeys.limit: limit, - }, + action: isChatId + ? AjaxActionStrings.fetchFirstMessageByChatId + : AjaxActionStrings.fetchFirstMessageByDialogId, + data: isChatId + ? { + _DataKeys.chatId: id as int, + _DataKeys.limit: limit, + } + : { + _DataKeys.dialogId: id as String, + _DataKeys.messageLimit: limit, + }, ); Future _fetchMessages( diff --git a/lib/core/services/interfaces/dialog/message/message_fetcher_service.dart b/lib/core/services/interfaces/dialog/message/message_fetcher_service.dart index 2d5fbe9d..0ff4a621 100644 --- a/lib/core/services/interfaces/dialog/message/message_fetcher_service.dart +++ b/lib/core/services/interfaces/dialog/message/message_fetcher_service.dart @@ -20,9 +20,26 @@ abstract interface class MessageFetcherService { /// /// В случае ошибки: /// - Возвращает `null` - Future?> fetch({ + Future?> fetchByChatId({ required int chatId, int limit, int? lastMessageId, }); + + /// Получает пагинированный список сообщений из чата. + /// + /// Параметры: + /// - [dialogId] - идентификатор чата, из которого запрашиваются сообщения (обязательный). + /// - [limit] - максимальное количество сообщений в результате (по умолчанию 25) + /// + /// Возвращает [PaginatedResult] с: + /// - Списком сообщений ([items]) + /// - Флагами наличия предыдущей/следующей страниц ([hasPreviousPage], [hasNextPage]) + /// + /// В случае ошибки: + /// - Возвращает `null` + Future?> fetchByDialogId({ + required String dialogId, + int limit = 25, + }); } diff --git a/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart b/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart index a930d7f5..02aba974 100644 --- a/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart +++ b/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart @@ -79,7 +79,7 @@ class ChatInsideViewModel extends BaseViewModel { } final messages = await tryLoginAndRetrieveData>( - () => _messagesAggregator.fetch(chatId: _dialog!.chatId), + () => _messagesAggregator.fetchByChatId(chatId: _dialog!.chatId), () => null, ); if (messages == null) { @@ -137,7 +137,7 @@ class ChatInsideViewModel extends BaseViewModel { } final messages = await tryLoginAndRetrieveData?>( - () => _messagesAggregator.fetch( + () => _messagesAggregator.fetchByChatId( chatId: _dialog!.chatId, lastMessageId: _unpartitionedMessages.last.messageId, ), @@ -214,7 +214,7 @@ class ChatInsideViewModel extends BaseViewModel { _dialog = _dialogsViewModel!.dialogs.firstWhere((d) => d.chatId == chatId); final messages = await tryLoginAndRetrieveData>( - () => _messagesAggregator.fetch(chatId: chatId), + () => _messagesAggregator.fetchByChatId(chatId: chatId), () => null, ); if (messages == null) { diff --git a/lib/ui/views/main_page/feed/widgets/feed_post.dart b/lib/ui/views/main_page/feed/widgets/feed_post.dart index 29285b32..c2ecaaee 100644 --- a/lib/ui/views/main_page/feed/widgets/feed_post.dart +++ b/lib/ui/views/main_page/feed/widgets/feed_post.dart @@ -291,7 +291,7 @@ class _FeedPostState extends State { ShareParams( files: xFiles.isEmpty ? null : xFiles, text: - 'Из ленты Портала ННГУ (автор ${model.profileViewModel.fullname}):\n${htmlToPlainText(model.postText)}', + '${htmlToPlainText(model.postText)}\n\nИсточник: Портал ННГУ\nАвтор: ${model.profileViewModel.fullname}', ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index 847790b9..49a6d86f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: unn_mobile description: A mobile application for UNN Portal website publish_to: 'none' -version: 0.6.0+373 +version: 0.6.0+374 environment: sdk: '>=3.1.2 <4.0.0' From 3b57228e69fdf9255d35a9e18f9156703e1d7796 Mon Sep 17 00:00:00 2001 From: UNN MOBILE runner Date: Fri, 28 Nov 2025 09:36:54 +0000 Subject: [PATCH 4/7] Change version in pubspec --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 49a6d86f..126406a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: unn_mobile description: A mobile application for UNN Portal website publish_to: 'none' -version: 0.6.0+374 +version: 0.6.0+375 environment: sdk: '>=3.1.2 <4.0.0' From 975ff981f0eec1a512a980254f688f86e3a52eaa Mon Sep 17 00:00:00 2001 From: Yurin Andrey Date: Fri, 28 Nov 2025 13:06:57 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20IntOrString=20=D0=B8=20=D0=B5=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../misc/{ => custom_types}/bounded_int.dart | 0 lib/core/misc/custom_types/int_or_string.dart | 22 +++++++++++++++++++ lib/core/models/dialog/base_dialog_info.dart | 6 +++-- lib/core/models/dialog/dialog.dart | 14 +++++++++--- lib/core/models/dialog/group_dialog.dart | 7 +++--- lib/core/models/dialog/preview_dialog.dart | 14 +++++++----- .../models/dialog/preview_group_dialog.dart | 4 ++-- .../models/dialog/preview_user_dialog.dart | 4 ++-- lib/core/models/dialog/user_dialog.dart | 4 ++-- .../models/distance_learning/semester.dart | 2 +- lib/core/models/profile/search_filter.dart | 2 +- .../chat/chat_inside_view_model.dart | 19 +++++----------- .../feed/widgets/packed_post_images.dart | 2 +- 13 files changed, 65 insertions(+), 35 deletions(-) rename lib/core/misc/{ => custom_types}/bounded_int.dart (100%) create mode 100644 lib/core/misc/custom_types/int_or_string.dart diff --git a/lib/core/misc/bounded_int.dart b/lib/core/misc/custom_types/bounded_int.dart similarity index 100% rename from lib/core/misc/bounded_int.dart rename to lib/core/misc/custom_types/bounded_int.dart diff --git a/lib/core/misc/custom_types/int_or_string.dart b/lib/core/misc/custom_types/int_or_string.dart new file mode 100644 index 00000000..02e45580 --- /dev/null +++ b/lib/core/misc/custom_types/int_or_string.dart @@ -0,0 +1,22 @@ +sealed class IntOrString { + Object get value; + String get stringValue; +} + +final class IntValue extends IntOrString { + @override + final int value; + IntValue(this.value); + + @override + String get stringValue => value.toString(); +} + +final class StringValue extends IntOrString { + @override + final String value; + StringValue(this.value); + + @override + String get stringValue => value; +} diff --git a/lib/core/models/dialog/base_dialog_info.dart b/lib/core/models/dialog/base_dialog_info.dart index 64a93384..12635ed2 100644 --- a/lib/core/models/dialog/base_dialog_info.dart +++ b/lib/core/models/dialog/base_dialog_info.dart @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN -abstract class BaseDialogInfo { - final T dialogId; +import 'package:unn_mobile/core/misc/custom_types/int_or_string.dart'; + +abstract class BaseDialogInfo { + final IntOrString dialogId; final String title; final String avatarUrl; diff --git a/lib/core/models/dialog/dialog.dart b/lib/core/models/dialog/dialog.dart index a7aba32d..272ca8c6 100644 --- a/lib/core/models/dialog/dialog.dart +++ b/lib/core/models/dialog/dialog.dart @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN +import 'package:unn_mobile/core/misc/custom_types/int_or_string.dart'; import 'package:unn_mobile/core/misc/enum_from_string.dart'; import 'package:unn_mobile/core/misc/json/json_utils.dart'; import 'package:unn_mobile/core/models/dialog/base_dialog_info.dart'; @@ -23,7 +24,7 @@ class _DialogJsonKeys { static const String counter = 'counter'; } -class Dialog extends BaseDialogInfo { +class Dialog extends BaseDialogInfo { final int chatId; final MessageShortInfo previewMessage; final MessageStatus lastMessageStatus; @@ -56,9 +57,16 @@ class Dialog extends BaseDialogInfo { final fileName = (fileInfo as JsonMap)[_DialogJsonKeys.name]! as String; messageMap[_DialogJsonKeys.text] = 'Файл: $fileName'; } + final IntOrString dialogId = switch (jsonMap[_DialogJsonKeys.dialogId]!) { + final int value => IntValue(value), + final String value => StringValue(value), + _ => throw ArgumentError( + 'Expected int or String', + ), + }; return Dialog( - dialogId: jsonMap[_DialogJsonKeys.dialogId]! as T, + dialogId: dialogId, title: jsonMap[_DialogJsonKeys.title]! as String, avatarUrl: (jsonMap[_DialogJsonKeys.avatar]! as JsonMap)[_DialogJsonKeys.url]! as String, @@ -80,7 +88,7 @@ class Dialog extends BaseDialogInfo { final messageMap = previewMessage.toJson(); return { - _DialogJsonKeys.dialogId: dialogId, + _DialogJsonKeys.dialogId: dialogId.value, _DialogJsonKeys.title: title, _DialogJsonKeys.avatar: { _DialogJsonKeys.url: avatarUrl, diff --git a/lib/core/models/dialog/group_dialog.dart b/lib/core/models/dialog/group_dialog.dart index 8b671de8..58745603 100644 --- a/lib/core/models/dialog/group_dialog.dart +++ b/lib/core/models/dialog/group_dialog.dart @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN +import 'package:unn_mobile/core/misc/custom_types/int_or_string.dart'; import 'package:unn_mobile/core/misc/json/json_utils.dart'; import 'package:unn_mobile/core/models/dialog/chat_settings/calendar_chat_setting.dart'; import 'package:unn_mobile/core/models/dialog/chat_settings/chat_setting.dart'; @@ -15,7 +16,7 @@ class GroupDialogJsonKeys { static const String sonetGroup = 'sonetGroup'; } -final class GroupDialog extends Dialog { +final class GroupDialog extends Dialog { final ChatSetting chatSetting; GroupDialog({ @@ -31,7 +32,7 @@ final class GroupDialog extends Dialog { }); factory GroupDialog.fromJson(JsonMap json) { - final dialog = Dialog.fromJson(json); + final dialog = Dialog.fromJson(json); return GroupDialog( title: dialog.title, @@ -41,7 +42,7 @@ final class GroupDialog extends Dialog { lastMessageStatus: dialog.lastMessageStatus, pinned: dialog.pinned, chatId: dialog.chatId, - dialogId: json[GroupDialogJsonKeys.id]! as String, + dialogId: StringValue(json[GroupDialogJsonKeys.id]! as String), chatSetting: _parseChatSetting(json[GroupDialogJsonKeys.chat]! as JsonMap), ); diff --git a/lib/core/models/dialog/preview_dialog.dart b/lib/core/models/dialog/preview_dialog.dart index 638c4176..cf7812f4 100644 --- a/lib/core/models/dialog/preview_dialog.dart +++ b/lib/core/models/dialog/preview_dialog.dart @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN +import 'package:unn_mobile/core/misc/custom_types/int_or_string.dart'; import 'package:unn_mobile/core/misc/json/json_utils.dart'; import 'package:unn_mobile/core/models/dialog/base_dialog_info.dart'; @@ -11,17 +12,20 @@ class _PreviewDialogJsonKeys { static const String customData = 'customData'; } -class PreviewDialog extends BaseDialogInfo { +class PreviewDialog extends BaseDialogInfo { PreviewDialog({ required super.dialogId, required super.title, required super.avatarUrl, }); - factory PreviewDialog.fromJson(JsonMap json) => PreviewDialog( - dialogId: T == String - ? json[_PreviewDialogJsonKeys.id]! as T - : int.tryParse(json[_PreviewDialogJsonKeys.id]! as String) as T, + factory PreviewDialog.fromJson(JsonMap json, {required bool isString}) => + PreviewDialog( + dialogId: isString + ? StringValue(json[_PreviewDialogJsonKeys.id]! as String) + : IntValue( + int.tryParse(json[_PreviewDialogJsonKeys.id]! as String)!, + ), title: json[_PreviewDialogJsonKeys.title]! as String, avatarUrl: json[_PreviewDialogJsonKeys.avatar]! as String, ); diff --git a/lib/core/models/dialog/preview_group_dialog.dart b/lib/core/models/dialog/preview_group_dialog.dart index 10e3abe5..81700760 100644 --- a/lib/core/models/dialog/preview_group_dialog.dart +++ b/lib/core/models/dialog/preview_group_dialog.dart @@ -9,7 +9,7 @@ class _PreviewGroupDialogJsonKeys { static const String customData = 'customData'; } -class PreviewGroupDialog extends PreviewDialog { +class PreviewGroupDialog extends PreviewDialog { final BaseChatSetting baseChatSetting; PreviewGroupDialog({ @@ -20,7 +20,7 @@ class PreviewGroupDialog extends PreviewDialog { }); factory PreviewGroupDialog.fromJson(JsonMap json) { - final dialog = PreviewDialog.fromJson(json); + final dialog = PreviewDialog.fromJson(json, isString: true); return PreviewGroupDialog( dialogId: dialog.dialogId, diff --git a/lib/core/models/dialog/preview_user_dialog.dart b/lib/core/models/dialog/preview_user_dialog.dart index 944d7bc0..d05f6df1 100644 --- a/lib/core/models/dialog/preview_user_dialog.dart +++ b/lib/core/models/dialog/preview_user_dialog.dart @@ -10,7 +10,7 @@ class _PreviewUserDialogJsonKeys { static const String customData = 'customData'; } -class PreviewUserDialog extends PreviewDialog { +class PreviewUserDialog extends PreviewDialog { final DateTime? lastActivityAt; final String workPosition; @@ -23,7 +23,7 @@ class PreviewUserDialog extends PreviewDialog { }); factory PreviewUserDialog.fromJson(JsonMap json) { - final dialog = PreviewDialog.fromJson(json); + final dialog = PreviewDialog.fromJson(json, isString: false); final lastActivityAt = (json[_PreviewUserDialogJsonKeys.customData]! as JsonMap)[_PreviewUserDialogJsonKeys.lastActivityDate]; return PreviewUserDialog( diff --git a/lib/core/models/dialog/user_dialog.dart b/lib/core/models/dialog/user_dialog.dart index 210aec69..435bf251 100644 --- a/lib/core/models/dialog/user_dialog.dart +++ b/lib/core/models/dialog/user_dialog.dart @@ -10,7 +10,7 @@ class _UserDialogJsonKeys { static const String lastActivityDate = 'last_activity_date'; } -final class UserDialog extends Dialog { +final class UserDialog extends Dialog { final DateTime? lastActivityAt; UserDialog({ @@ -26,7 +26,7 @@ final class UserDialog extends Dialog { }); factory UserDialog.fromJson(JsonMap json) { - final dialog = Dialog.fromJson(json); + final dialog = Dialog.fromJson(json); return UserDialog( dialogId: dialog.dialogId, chatId: dialog.chatId, diff --git a/lib/core/models/distance_learning/semester.dart b/lib/core/models/distance_learning/semester.dart index 6712325c..9ab0ae6d 100644 --- a/lib/core/models/distance_learning/semester.dart +++ b/lib/core/models/distance_learning/semester.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:unn_mobile/core/constants/academic_year.dart'; -import 'package:unn_mobile/core/misc/bounded_int.dart'; +import 'package:unn_mobile/core/misc/custom_types/bounded_int.dart'; import 'package:unn_mobile/core/misc/date_time_utilities/date_time_extensions.dart'; import 'package:unn_mobile/core/misc/json/json_utils.dart'; diff --git a/lib/core/models/profile/search_filter.dart b/lib/core/models/profile/search_filter.dart index 230a4838..a7698f8c 100644 --- a/lib/core/models/profile/search_filter.dart +++ b/lib/core/models/profile/search_filter.dart @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 BitCodersNN -import 'package:unn_mobile/core/misc/bounded_int.dart'; import 'package:unn_mobile/core/misc/camel_case_converter.dart'; +import 'package:unn_mobile/core/misc/custom_types/bounded_int.dart'; typedef FilterMap = Map; diff --git a/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart b/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart index 02aba974..1007056e 100644 --- a/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart +++ b/lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart @@ -12,10 +12,8 @@ import 'package:unn_mobile/core/misc/objects_with_pagination.dart'; import 'package:unn_mobile/core/misc/user/current_user_sync_storage.dart'; import 'package:unn_mobile/core/models/common/file_data.dart'; import 'package:unn_mobile/core/models/dialog/dialog.dart'; -import 'package:unn_mobile/core/models/dialog/group_dialog.dart'; import 'package:unn_mobile/core/models/dialog/message/enum/message_state.dart'; import 'package:unn_mobile/core/models/dialog/message/message.dart'; -import 'package:unn_mobile/core/models/dialog/user_dialog.dart'; import 'package:unn_mobile/core/viewmodels/base_view_model.dart'; import 'package:unn_mobile/core/viewmodels/factories/main_page_routes_view_models_factory.dart'; import 'package:unn_mobile/core/viewmodels/main_page/chat/chat_screen_view_model.dart'; @@ -185,23 +183,18 @@ class ChatInsideViewModel extends BaseViewModel { ); FutureOr sendMessage(String text) async => - await _sendMessageWrapper(() async { - final dialogId = switch (_dialog) { - final UserDialog userDialog => userDialog.dialogId.toString(), - final GroupDialog groupDialog => groupDialog.dialogId, - _ => '', - }; - return _replyMessage == null + await _sendMessageWrapper( + () async => _replyMessage == null ? await _messagesAggregator.send( - dialogId: dialogId, + dialogId: _dialog?.dialogId.stringValue ?? '', text: text, ) : await _messagesAggregator.reply( - dialogId: dialogId, + dialogId: _dialog?.dialogId.stringValue ?? '', text: text, replyMessageId: _replyMessage!.messageId, - ); - }); + ), + ); FutureOr _init(int chatId) async { _hasError = false; diff --git a/lib/ui/views/main_page/feed/widgets/packed_post_images.dart b/lib/ui/views/main_page/feed/widgets/packed_post_images.dart index 28d9e204..eb10294f 100644 --- a/lib/ui/views/main_page/feed/widgets/packed_post_images.dart +++ b/lib/ui/views/main_page/feed/widgets/packed_post_images.dart @@ -5,7 +5,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:carousel_slider_plus/carousel_slider_plus.dart'; import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; -import 'package:unn_mobile/core/misc/bounded_int.dart'; +import 'package:unn_mobile/core/misc/custom_types/bounded_int.dart'; import 'package:unn_mobile/ui/widgets/dismissable_image.dart'; import 'package:unn_mobile/ui/widgets/packed_images_view.dart'; From 75e50319670061e023ae2f4cd92c419ce1aa5576 Mon Sep 17 00:00:00 2001 From: UNN MOBILE runner Date: Fri, 28 Nov 2025 10:07:33 +0000 Subject: [PATCH 6/7] Add license headers to files --- lib/core/misc/custom_types/int_or_string.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/core/misc/custom_types/int_or_string.dart b/lib/core/misc/custom_types/int_or_string.dart index 02e45580..47bdc87d 100644 --- a/lib/core/misc/custom_types/int_or_string.dart +++ b/lib/core/misc/custom_types/int_or_string.dart @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 BitCodersNN + sealed class IntOrString { Object get value; String get stringValue; From 80211b9622735ff6af6abd8d286f8555f0068033 Mon Sep 17 00:00:00 2001 From: Yurin Andrey Date: Fri, 28 Nov 2025 13:10:03 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/models/dialog/preview_dialog.dart | 4 ++-- lib/core/models/dialog/preview_group_dialog.dart | 2 +- lib/core/models/dialog/preview_user_dialog.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/models/dialog/preview_dialog.dart b/lib/core/models/dialog/preview_dialog.dart index cf7812f4..934c6e50 100644 --- a/lib/core/models/dialog/preview_dialog.dart +++ b/lib/core/models/dialog/preview_dialog.dart @@ -19,9 +19,9 @@ class PreviewDialog extends BaseDialogInfo { required super.avatarUrl, }); - factory PreviewDialog.fromJson(JsonMap json, {required bool isString}) => + factory PreviewDialog.fromJson(JsonMap json, {required bool idIsString}) => PreviewDialog( - dialogId: isString + dialogId: idIsString ? StringValue(json[_PreviewDialogJsonKeys.id]! as String) : IntValue( int.tryParse(json[_PreviewDialogJsonKeys.id]! as String)!, diff --git a/lib/core/models/dialog/preview_group_dialog.dart b/lib/core/models/dialog/preview_group_dialog.dart index 81700760..55f5bb23 100644 --- a/lib/core/models/dialog/preview_group_dialog.dart +++ b/lib/core/models/dialog/preview_group_dialog.dart @@ -20,7 +20,7 @@ class PreviewGroupDialog extends PreviewDialog { }); factory PreviewGroupDialog.fromJson(JsonMap json) { - final dialog = PreviewDialog.fromJson(json, isString: true); + final dialog = PreviewDialog.fromJson(json, idIsString: true); return PreviewGroupDialog( dialogId: dialog.dialogId, diff --git a/lib/core/models/dialog/preview_user_dialog.dart b/lib/core/models/dialog/preview_user_dialog.dart index d05f6df1..8c4fcdf8 100644 --- a/lib/core/models/dialog/preview_user_dialog.dart +++ b/lib/core/models/dialog/preview_user_dialog.dart @@ -23,7 +23,7 @@ class PreviewUserDialog extends PreviewDialog { }); factory PreviewUserDialog.fromJson(JsonMap json) { - final dialog = PreviewDialog.fromJson(json, isString: false); + final dialog = PreviewDialog.fromJson(json, idIsString: false); final lastActivityAt = (json[_PreviewUserDialogJsonKeys.customData]! as JsonMap)[_PreviewUserDialogJsonKeys.lastActivityDate]; return PreviewUserDialog(