1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "history/history_widget.h"
9
10 #include "api/api_editing.h"
11 #include "api/api_bot.h"
12 #include "api/api_sending.h"
13 #include "api/api_text_entities.h"
14 #include "api/api_send_progress.h"
15 #include "ui/boxes/confirm_box.h"
16 #include "boxes/delete_messages_box.h"
17 #include "boxes/send_files_box.h"
18 #include "boxes/share_box.h"
19 #include "boxes/edit_caption_box.h"
20 #include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
21 #include "boxes/peers/edit_peer_requests_box.h"
22 #include "core/file_utilities.h"
23 #include "ui/toast/toast.h"
24 #include "ui/toasts/common_toasts.h"
25 #include "ui/special_buttons.h"
26 #include "ui/emoji_config.h"
27 #include "ui/chat/attach/attach_prepare.h"
28 #include "ui/chat/choose_theme_controller.h"
29 #include "ui/widgets/buttons.h"
30 #include "ui/widgets/inner_dropdown.h"
31 #include "ui/widgets/dropdown_menu.h"
32 #include "ui/widgets/labels.h"
33 #include "ui/widgets/shadow.h"
34 #include "ui/effects/ripple_animation.h"
35 #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
36 #include "ui/text/format_values.h"
37 #include "ui/chat/forward_options_box.h"
38 #include "ui/chat/message_bar.h"
39 #include "ui/chat/attach/attach_send_files_way.h"
40 #include "ui/image/image.h"
41 #include "ui/special_buttons.h"
42 #include "ui/controls/emoji_button.h"
43 #include "ui/controls/send_button.h"
44 #include "inline_bots/inline_bot_result.h"
45 #include "base/event_filter.h"
46 #include "base/qt_signal_producer.h"
47 #include "base/unixtime.h"
48 #include "base/call_delayed.h"
49 #include "data/data_changes.h"
50 #include "data/data_drafts.h"
51 #include "data/data_session.h"
52 #include "data/data_web_page.h"
53 #include "data/data_document.h"
54 #include "data/data_photo.h"
55 #include "data/data_media_types.h"
56 #include "data/data_channel.h"
57 #include "data/data_chat.h"
58 #include "data/data_user.h"
59 #include "data/data_chat_filters.h"
60 #include "data/data_scheduled_messages.h"
61 #include "data/data_sponsored_messages.h"
62 #include "data/data_file_origin.h"
63 #include "data/data_histories.h"
64 #include "data/data_group_call.h"
65 #include "data/stickers/data_stickers.h"
66 #include "history/history.h"
67 #include "history/history_item.h"
68 #include "history/history_message.h"
69 #include "history/history_drag_area.h"
70 #include "history/history_inner_widget.h"
71 #include "history/history_item_components.h"
72 #include "history/view/controls/history_view_voice_record_bar.h"
73 #include "history/view/controls/history_view_ttl_button.h"
74 #include "history/view/history_view_service_message.h"
75 #include "history/view/history_view_element.h"
76 #include "history/view/history_view_scheduled_section.h"
77 #include "history/view/history_view_schedule_box.h"
78 #include "history/view/history_view_webpage_preview.h"
79 #include "history/view/history_view_top_bar_widget.h"
80 #include "history/view/history_view_contact_status.h"
81 #include "history/view/history_view_context_menu.h"
82 #include "history/view/history_view_pinned_tracker.h"
83 #include "history/view/history_view_pinned_section.h"
84 #include "history/view/history_view_pinned_bar.h"
85 #include "history/view/history_view_group_call_bar.h"
86 #include "history/view/history_view_requests_bar.h"
87 #include "history/view/media/history_view_media.h"
88 #include "profile/profile_block_group_members.h"
89 #include "info/info_memento.h"
90 #include "core/click_handler_types.h"
91 #include "chat_helpers/tabbed_panel.h"
92 #include "chat_helpers/tabbed_selector.h"
93 #include "chat_helpers/tabbed_section.h"
94 #include "chat_helpers/bot_keyboard.h"
95 #include "chat_helpers/message_field.h"
96 #include "chat_helpers/send_context_menu.h"
97 #include "mtproto/mtproto_config.h"
98 #include "lang/lang_keys.h"
99 #include "mainwidget.h"
100 #include "mainwindow.h"
101 #include "storage/localimageloader.h"
102 #include "storage/storage_account.h"
103 #include "storage/file_upload.h"
104 #include "storage/storage_media_prepare.h"
105 #include "media/audio/media_audio.h"
106 #include "media/audio/media_audio_capture.h"
107 #include "media/player/media_player_instance.h"
108 #include "core/application.h"
109 #include "apiwrap.h"
110 #include "base/qthelp_regex.h"
111 #include "ui/boxes/report_box.h"
112 #include "ui/chat/pinned_bar.h"
113 #include "ui/chat/group_call_bar.h"
114 #include "ui/chat/requests_bar.h"
115 #include "ui/chat/chat_theme.h"
116 #include "ui/chat/chat_style.h"
117 #include "ui/chat/continuous_scroll.h"
118 #include "ui/widgets/popup_menu.h"
119 #include "ui/item_text_options.h"
120 #include "ui/unread_badge.h"
121 #include "main/main_session.h"
122 #include "main/main_session_settings.h"
123 #include "window/notifications_manager.h"
124 #include "window/window_adaptive.h"
125 #include "window/window_controller.h"
126 #include "window/window_session_controller.h"
127 #include "window/window_slide_animation.h"
128 #include "window/window_peer_menu.h"
129 #include "inline_bots/inline_results_widget.h"
130 #include "info/profile/info_profile_values.h" // SharedMediaCountValue.
131 #include "chat_helpers/emoji_suggestions_widget.h"
132 #include "core/crash_reports.h"
133 #include "core/shortcuts.h"
134 #include "support/support_common.h"
135 #include "support/support_autocomplete.h"
136 #include "dialogs/dialogs_key.h"
137 #include "calls/calls_instance.h"
138 #include "facades.h"
139 #include "app.h"
140 #include "styles/style_chat.h"
141 #include "styles/style_dialogs.h"
142 #include "styles/style_window.h"
143 #include "styles/style_boxes.h"
144 #include "styles/style_profile.h"
145 #include "styles/style_chat_helpers.h"
146 #include "styles/style_info.h"
147
148 #include <QGuiApplication> // keyboardModifiers()
149 #include <QtGui/QWindow>
150 #include <QtCore/QMimeData>
151
152 namespace {
153
154 constexpr auto kMessagesPerPageFirst = 30;
155 constexpr auto kMessagesPerPage = 50;
156 constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
157 constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
158 constexpr auto kSkipRepaintWhileScrollMs = 100;
159 constexpr auto kShowMembersDropdownTimeoutMs = 300;
160 constexpr auto kDisplayEditTimeWarningMs = 300 * 1000;
161 constexpr auto kFullDayInMs = 86400 * 1000;
162 constexpr auto kSaveDraftTimeout = 1000;
163 constexpr auto kSaveDraftAnywayTimeout = 5000;
164 constexpr auto kSaveCloudDraftIdleTimeout = 14000;
165 constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
166 constexpr auto kCommonModifiers = 0
167 | Qt::ShiftModifier
168 | Qt::MetaModifier
169 | Qt::ControlModifier;
170 const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
171
CountToastDuration(const TextWithEntities & text)172 [[nodiscard]] crl::time CountToastDuration(const TextWithEntities &text) {
173 return std::clamp(
174 crl::time(1000) * int(text.text.size()) / 14,
175 crl::time(1000) * 5,
176 crl::time(1000) * 8);
177 }
178
ActivePeerValue(not_null<Window::SessionController * > controller)179 [[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
180 not_null<Window::SessionController*> controller) {
181 return controller->activeChatValue(
182 ) | rpl::map([](const Dialogs::Key &key) {
183 const auto history = key.history();
184 return history ? history->peer.get() : nullptr;
185 });
186 }
187
188 } // namespace
189
HistoryWidget(QWidget * parent,not_null<Window::SessionController * > controller)190 HistoryWidget::HistoryWidget(
191 QWidget *parent,
192 not_null<Window::SessionController*> controller)
193 : Window::AbstractSectionWidget(
194 parent,
195 controller,
196 ActivePeerValue(controller))
197 , _api(&controller->session().mtp())
198 , _updateEditTimeLeftDisplay([=] { updateField(); })
199 , _fieldBarCancel(this, st::historyReplyCancel)
__anon05ff6a840402null200 , _previewTimer([=] { requestPreview(); })
201 , _previewState(Data::PreviewState::Allowed)
202 , _topBar(this, controller)
203 , _scroll(
204 this,
205 controller->chatStyle()->value(lifetime(), st::historyScroll),
206 false)
__anon05ff6a840502null207 , _updateHistoryItems([=] { updateHistoryItemsByTimer(); })
208 , _historyDown(
209 _scroll,
210 controller->chatStyle()->value(lifetime(), st::historyToDown))
211 , _unreadMentions(
212 _scroll,
213 controller->chatStyle()->value(lifetime(), st::historyUnreadMentions))
214 , _fieldAutocomplete(this, controller)
215 , _supportAutocomplete(session().supportMode()
216 ? object_ptr<Support::Autocomplete>(this, &session())
217 : nullptr)
218 , _send(std::make_shared<Ui::SendButton>(this))
219 , _unblock(this, tr::lng_unblock_button(tr::now).toUpper(), st::historyUnblock)
220 , _botStart(this, tr::lng_bot_start(tr::now).toUpper(), st::historyComposeButton)
221 , _joinChannel(
222 this,
223 tr::lng_profile_join_channel(tr::now).toUpper(),
224 st::historyComposeButton)
225 , _muteUnmute(
226 this,
227 tr::lng_channel_mute(tr::now).toUpper(),
228 st::historyComposeButton)
229 , _reportMessages(this, QString(), st::historyComposeButton)
230 , _attachToggle(this, st::historyAttach)
231 , _tabbedSelectorToggle(this, st::historyAttachEmoji)
232 , _botKeyboardShow(this, st::historyBotKeyboardShow)
233 , _botKeyboardHide(this, st::historyBotKeyboardHide)
234 , _botCommandStart(this, st::historyBotCommandStart)
235 , _voiceRecordBar(std::make_unique<HistoryWidget::VoiceRecordBar>(
236 this,
237 controller,
238 _send,
239 st::historySendSize.height()))
240 , _field(
241 this,
242 st::historyComposeField,
243 Ui::InputField::Mode::MultiLine,
244 tr::lng_message_ph())
245 , _kbScroll(this, st::botKbScroll)
246 , _keyboard(_kbScroll->setOwnedWidget(object_ptr<BotKeyboard>(
247 controller,
248 this)))
__anon05ff6a840602null249 , _membersDropdownShowTimer([=] { showMembersDropdown(); })
__anon05ff6a840702null250 , _saveDraftTimer([=] { saveDraft(); })
__anon05ff6a840802null251 , _saveCloudDraftTimer([=] { saveCloudDraft(); })
252 , _topShadow(this) {
253 setAcceptDrops(true);
254
255 session().downloaderTaskFinished(
__anon05ff6a840902null256 ) | rpl::start_with_next([=] {
257 update();
258 }, lifetime());
259
260 _scroll->scrolls(
__anon05ff6a840a02null261 ) | rpl::start_with_next([=] {
262 handleScroll();
263 }, lifetime());
264 _scroll->geometryChanged(
__anon05ff6a840b02null265 ) | rpl::start_with_next(crl::guard(_list, [=] {
266 _list->onParentGeometryChanged();
267 }), lifetime());
268 _scroll->addContentRequests(
__anon05ff6a840c02null269 ) | rpl::start_with_next([=] {
270 if (_history
271 && _history->loadedAtBottom()
272 && session().data().sponsoredMessages().append(_history)) {
273 _scroll->contentAdded();
274 }
275 }, lifetime());
276
__anon05ff6a840d02null277 _historyDown->addClickHandler([=] { historyDownClicked(); });
__anon05ff6a840e02null278 _unreadMentions->addClickHandler([=] { showNextUnreadMention(); });
__anon05ff6a840f02null279 _fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
__anon05ff6a841002null280 _send->addClickHandler([=] { sendButtonClicked(); });
281
282 SendMenu::SetupMenuAndShortcuts(
283 _send.get(),
__anon05ff6a841102null284 [=] { return sendButtonMenuType(); },
__anon05ff6a841202null285 [=] { sendSilent(); },
__anon05ff6a841302null286 [=] { sendScheduled(); });
287
__anon05ff6a841402null288 _unblock->addClickHandler([=] { unblockUser(); });
__anon05ff6a841502null289 _botStart->addClickHandler([=] { sendBotStartCommand(); });
__anon05ff6a841602null290 _joinChannel->addClickHandler([=] { joinChannel(); });
__anon05ff6a841702null291 _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); });
__anon05ff6a841802null292 _reportMessages->addClickHandler([=] { reportSelectedMessages(); });
293 connect(
294 _field,
295 &Ui::InputField::submitted,
__anon05ff6a841902(Qt::KeyboardModifiers modifiers) 296 [=](Qt::KeyboardModifiers modifiers) { sendWithModifiers(modifiers); });
__anon05ff6a841a02null297 connect(_field, &Ui::InputField::cancelled, [=] {
298 escape();
299 });
__anon05ff6a841b02null300 connect(_field, &Ui::InputField::tabbed, [=] {
301 fieldTabbed();
302 });
__anon05ff6a841c02null303 connect(_field, &Ui::InputField::resized, [=] {
304 fieldResized();
305 });
__anon05ff6a841d02null306 connect(_field, &Ui::InputField::focused, [=] {
307 fieldFocused();
308 });
__anon05ff6a841e02null309 connect(_field, &Ui::InputField::changed, [=] {
310 fieldChanged();
311 });
312 connect(
313 controller->widget()->windowHandle(),
314 &QWindow::visibleChanged,
315 this,
__anon05ff6a841f02null316 [=] { windowIsVisibleChanged(); });
317
318 initTabbedSelector();
319
320 _attachToggle->addClickHandler(App::LambdaDelayed(
321 st::historyAttach.ripple.hideDuration,
322 this,
__anon05ff6a842002null323 [=] { chooseAttach(); }));
324
__anon05ff6a842102null325 _highlightTimer.setCallback([this] { updateHighlightedMessage(); });
326
327 const auto rawTextEdit = _field->rawTextEdit().get();
328 rpl::merge(
329 _field->scrollTop().changes() | rpl::to_empty,
330 base::qt_signal_producer(
331 rawTextEdit,
332 &QTextEdit::cursorPositionChanged)
__anon05ff6a842202null333 ) | rpl::start_with_next([=] {
334 saveDraftDelayed();
335 }, _field->lifetime());
336
__anon05ff6a842302null337 connect(rawTextEdit, &QTextEdit::cursorPositionChanged, this, [=] {
338 checkFieldAutocomplete();
339 }, Qt::QueuedConnection);
340
341 _fieldBarCancel->hide();
342
343 _topBar->hide();
344 _scroll->hide();
345 _kbScroll->hide();
346
347 controller->chatStyle()->paletteChanged(
__anon05ff6a842402null348 ) | rpl::start_with_next([=] {
349 _scroll->updateBars();
350 }, lifetime());
351
352 _historyDown->installEventFilter(this);
353 _unreadMentions->installEventFilter(this);
__anon05ff6a842502null354 SendMenu::SetupUnreadMentionsMenu(_unreadMentions.data(), [=] {
355 return _history ? _history->peer.get() : nullptr;
356 });
357
358 InitMessageField(controller, _field);
359
360 _keyboard->sendCommandRequests(
__anon05ff6a842602(Bot::SendCommandRequest r) 361 ) | rpl::start_with_next([=](Bot::SendCommandRequest r) {
362 sendBotCommand(r);
363 }, lifetime());
364
365 _fieldAutocomplete->mentionChosen(
__anon05ff6a842702(FieldAutocomplete::MentionChosen data) 366 ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
367 insertMention(data.user);
368 }, lifetime());
369
370 _fieldAutocomplete->hashtagChosen(
__anon05ff6a842802(FieldAutocomplete::HashtagChosen data) 371 ) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
372 insertHashtagOrBotCommand(data.hashtag, data.method);
373 }, lifetime());
374
375 _fieldAutocomplete->botCommandChosen(
__anon05ff6a842902(FieldAutocomplete::BotCommandChosen data) 376 ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
377 insertHashtagOrBotCommand(data.command, data.method);
378 }, lifetime());
379
380 _fieldAutocomplete->stickerChosen(
__anon05ff6a842a02(FieldAutocomplete::StickerChosen data) 381 ) | rpl::start_with_next([=](FieldAutocomplete::StickerChosen data) {
382 sendExistingDocument(data.sticker, data.options);
383 }, lifetime());
384
__anon05ff6a842b02(int key) 385 _fieldAutocomplete->setModerateKeyActivateCallback([=](int key) {
386 return _keyboard->isHidden()
387 ? false
388 : _keyboard->moderateKeyActivate(key);
389 });
390
391 _fieldAutocomplete->choosingProcesses(
__anon05ff6a842c02(FieldAutocomplete::Type type) 392 ) | rpl::start_with_next([=](FieldAutocomplete::Type type) {
393 if (!_history) {
394 return;
395 }
396 if (type == FieldAutocomplete::Type::Stickers) {
397 session().sendProgressManager().update(
398 _history,
399 Api::SendProgressType::ChooseSticker);
400 }
401 }, lifetime());
402
__anon05ff6a842d02null403 _fieldAutocomplete->setSendMenuType([=] { return sendMenuType(); });
404
405 if (_supportAutocomplete) {
406 supportInitAutocomplete();
407 }
408 _fieldLinksParser = std::make_unique<MessageLinksParser>(_field);
409 _fieldLinksParser->list().changes(
__anon05ff6a842e02(QStringList &&parsed) 410 ) | rpl::start_with_next([=](QStringList &&parsed) {
411 if (_previewState == Data::PreviewState::EmptyOnEdit
412 && _parsedLinks != parsed) {
413 _previewState = Data::PreviewState::Allowed;
414 }
415 _parsedLinks = std::move(parsed);
416 checkPreview();
417 }, lifetime());
418 _field->rawTextEdit()->installEventFilter(this);
419 _field->rawTextEdit()->installEventFilter(_fieldAutocomplete);
420 _field->setMimeDataHook([=](
421 not_null<const QMimeData*> data,
__anon05ff6a842f02( not_null<const QMimeData*> data, Ui::InputField::MimeAction action) 422 Ui::InputField::MimeAction action) {
423 if (action == Ui::InputField::MimeAction::Check) {
424 return canSendFiles(data);
425 } else if (action == Ui::InputField::MimeAction::Insert) {
426 return confirmSendingFiles(data, std::nullopt, data->text());
427 }
428 Unexpected("action in MimeData hook.");
429 });
430 InitSpellchecker(controller, _field);
431
432 const auto suggestions = Ui::Emoji::SuggestionsController::Init(
433 this,
434 _field,
435 &controller->session());
__anon05ff6a843002null436 _raiseEmojiSuggestions = [=] { suggestions->raise(); };
437 updateFieldSubmitSettings();
438
439 _field->hide();
440 _send->hide();
441 _unblock->hide();
442 _botStart->hide();
443 _joinChannel->hide();
444 _muteUnmute->hide();
445 _reportMessages->hide();
446
447 initVoiceRecordBar();
448
449 _attachToggle->hide();
450 _tabbedSelectorToggle->hide();
451 _botKeyboardShow->hide();
452 _botKeyboardHide->hide();
453 _botCommandStart->hide();
454
__anon05ff6a843102null455 _botKeyboardShow->addClickHandler([=] { toggleKeyboard(); });
__anon05ff6a843202null456 _botKeyboardHide->addClickHandler([=] { toggleKeyboard(); });
__anon05ff6a843302null457 _botCommandStart->addClickHandler([=] { startBotCommand(); });
458
459 _topShadow->hide();
460
461 _attachDragAreas = DragArea::SetupDragAreaToContainer(
462 this,
__anon05ff6a843402(not_null<const QMimeData*> d) 463 crl::guard(this, [=](not_null<const QMimeData*> d) {
464 return _history && _canSendMessages && !isRecording();
465 }),
__anon05ff6a843502(bool f) 466 crl::guard(this, [=](bool f) { _field->setAcceptDrops(f); }),
__anon05ff6a843602null467 crl::guard(this, [=] { updateControlsGeometry(); }));
__anon05ff6a843702(const QMimeData *data) 468 _attachDragAreas.document->setDroppedCallback([=](const QMimeData *data) {
469 confirmSendingFiles(data, false);
470 Window::ActivateWindow(controller);
471 });
__anon05ff6a843802(const QMimeData *data) 472 _attachDragAreas.photo->setDroppedCallback([=](const QMimeData *data) {
473 confirmSendingFiles(data, true);
474 Window::ActivateWindow(controller);
475 });
476
477 controller->adaptive().changes(
__anon05ff6a843902null478 ) | rpl::start_with_next([=] {
479 if (_history) {
480 _history->forceFullResize();
481 if (_migrated) {
482 _migrated->forceFullResize();
483 }
484 updateHistoryGeometry();
485 update();
486 }
487 }, lifetime());
488
489 session().data().newItemAdded(
__anon05ff6a843a02(not_null<HistoryItem*> item) 490 ) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
491 newItemAdded(item);
492 }, lifetime());
493
494 session().data().historyChanged(
__anon05ff6a843b02(not_null<History*> history) 495 ) | rpl::start_with_next([=](not_null<History*> history) {
496 handleHistoryChange(history);
497 }, lifetime());
498
499 session().data().viewResizeRequest(
__anon05ff6a843c02(not_null<HistoryView::Element*> view) 500 ) | rpl::start_with_next([=](not_null<HistoryView::Element*> view) {
501 if (view->data()->mainView() == view) {
502 updateHistoryGeometry();
503 }
504 }, lifetime());
505
506 Core::App().settings().largeEmojiChanges(
__anon05ff6a843d02null507 ) | rpl::start_with_next([=] {
508 crl::on_main(this, [=] {
509 updateHistoryGeometry();
510 });
511 }, lifetime());
512
513 session().data().animationPlayInlineRequest(
__anon05ff6a843f02(not_null<HistoryItem*> item) 514 ) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
515 if (const auto view = item->mainView()) {
516 if (const auto media = view->media()) {
517 media->playAnimation();
518 }
519 }
520 }, lifetime());
521
522 session().data().webPageUpdates(
__anon05ff6a844002(not_null<WebPageData*> page) 523 ) | rpl::filter([=](not_null<WebPageData*> page) {
524 return (_previewData == page.get());
525 }) | rpl::start_with_next([=] {
526 updatePreview();
527 }, lifetime());
528
529 session().data().channelDifferenceTooLong(
__anon05ff6a844202(not_null<ChannelData*> channel) 530 ) | rpl::filter([=](not_null<ChannelData*> channel) {
531 return _peer == channel.get();
532 }) | rpl::start_with_next([=] {
533 updateHistoryDownVisibility();
534 preloadHistoryIfNeeded();
535 }, lifetime());
536
537 session().data().userIsBotChanges(
__anon05ff6a844402(not_null<UserData*> user) 538 ) | rpl::filter([=](not_null<UserData*> user) {
539 return (_peer == user.get());
540 }) | rpl::start_with_next([=](not_null<UserData*> user) {
541 _list->notifyIsBotChanged();
542 _list->updateBotInfo();
543 updateControlsVisibility();
544 updateControlsGeometry();
545 }, lifetime());
546
547 session().data().botCommandsChanges(
__anon05ff6a844602(not_null<PeerData*> peer) 548 ) | rpl::filter([=](not_null<PeerData*> peer) {
549 return _peer && (_peer == peer);
550 }) | rpl::start_with_next([=] {
551 if (_fieldAutocomplete->clearFilteredBotCommands()) {
552 checkFieldAutocomplete();
553 }
554 }, lifetime());
555
556 using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
557 session().changes().historyUpdates(
558 HistoryUpdateFlag::MessageSent
559 | HistoryUpdateFlag::ForwardDraft
560 | HistoryUpdateFlag::BotKeyboard
561 | HistoryUpdateFlag::CloudDraft
562 | HistoryUpdateFlag::UnreadMentions
563 | HistoryUpdateFlag::UnreadView
564 | HistoryUpdateFlag::TopPromoted
565 | HistoryUpdateFlag::ClientSideMessages
566 | HistoryUpdateFlag::PinnedMessages
__anon05ff6a844802(const Data::HistoryUpdate &update) 567 ) | rpl::filter([=](const Data::HistoryUpdate &update) {
568 if (_migrated && update.history.get() == _migrated) {
569 if (_pinnedTracker
570 && (update.flags & HistoryUpdateFlag::PinnedMessages)) {
571 checkPinnedBarState();
572 }
573 }
574 return (_history == update.history.get());
575 }) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
576 const auto flags = update.flags;
577 if (flags & HistoryUpdateFlag::MessageSent) {
578 synteticScrollToY(_scroll->scrollTopMax());
579 }
580 if (flags & HistoryUpdateFlag::ForwardDraft) {
581 updateForwarding();
582 }
583 if (flags & HistoryUpdateFlag::BotKeyboard) {
584 updateBotKeyboard(update.history);
585 }
586 if (flags & HistoryUpdateFlag::CloudDraft) {
587 applyCloudDraft(update.history);
588 }
589 if (flags & HistoryUpdateFlag::ClientSideMessages) {
590 updateSendButtonType();
591 }
592 if (flags & HistoryUpdateFlag::UnreadMentions) {
593 updateUnreadMentionsVisibility();
594 }
595 if (flags & HistoryUpdateFlag::UnreadView) {
596 unreadCountUpdated();
597 }
598 if (_pinnedTracker && (flags & HistoryUpdateFlag::PinnedMessages)) {
599 checkPinnedBarState();
600 }
601 if (flags & HistoryUpdateFlag::TopPromoted) {
602 updateHistoryGeometry();
603 updateControlsVisibility();
604 updateControlsGeometry();
605 this->update();
606 }
607 }, lifetime());
608
609 using MessageUpdateFlag = Data::MessageUpdate::Flag;
610 session().changes().messageUpdates(
611 MessageUpdateFlag::Destroyed
612 | MessageUpdateFlag::Edited
613 | MessageUpdateFlag::ReplyMarkup
614 | MessageUpdateFlag::BotCallbackSent
__anon05ff6a844a02(const Data::MessageUpdate &update) 615 ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
616 const auto flags = update.flags;
617 if (flags & MessageUpdateFlag::Destroyed) {
618 itemRemoved(update.item);
619 return;
620 }
621 if (flags & MessageUpdateFlag::Edited) {
622 itemEdited(update.item);
623 }
624 if (flags & MessageUpdateFlag::ReplyMarkup) {
625 if (_keyboard->forMsgId() == update.item->fullId()) {
626 updateBotKeyboard(update.item->history(), true);
627 }
628 }
629 if (flags & MessageUpdateFlag::BotCallbackSent) {
630 botCallbackSent(update.item);
631 }
632 }, lifetime());
633
__anon05ff6a844b02(const Media::Player::Instance::Switch &pair) 634 subscribe(Media::Player::instance()->switchToNextNotifier(), [this](const Media::Player::Instance::Switch &pair) {
635 if (pair.from.type() == AudioMsgId::Type::Voice) {
636 scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to);
637 }
638 });
639
640 using PeerUpdateFlag = Data::PeerUpdate::Flag;
641 session().changes().peerUpdates(
642 PeerUpdateFlag::Rights
643 | PeerUpdateFlag::Migration
644 | PeerUpdateFlag::UnavailableReason
645 | PeerUpdateFlag::IsBlocked
646 | PeerUpdateFlag::Admins
647 | PeerUpdateFlag::Members
648 | PeerUpdateFlag::OnlineStatus
649 | PeerUpdateFlag::Notifications
650 | PeerUpdateFlag::ChannelAmIn
651 | PeerUpdateFlag::ChannelLinkedChat
652 | PeerUpdateFlag::Slowmode
653 | PeerUpdateFlag::BotStartToken
654 | PeerUpdateFlag::MessagesTTL
655 | PeerUpdateFlag::ChatThemeEmoji
656 | PeerUpdateFlag::FullInfo
__anon05ff6a844c02(const Data::PeerUpdate &update) 657 ) | rpl::filter([=](const Data::PeerUpdate &update) {
658 return (update.peer.get() == _peer);
659 }) | rpl::map([](const Data::PeerUpdate &update) {
660 return update.flags;
__anon05ff6a844e02(Data::PeerUpdate::Flags flags) 661 }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
662 if (flags & PeerUpdateFlag::Rights) {
663 checkPreview();
664 updateStickersByEmoji();
665 updateFieldPlaceholder();
666 }
667 if (flags & PeerUpdateFlag::Migration) {
668 handlePeerMigration();
669 }
670 if (flags & PeerUpdateFlag::Notifications) {
671 updateNotifyControls();
672 }
673 if (flags & PeerUpdateFlag::UnavailableReason) {
674 const auto unavailable = _peer->computeUnavailableReason();
675 if (!unavailable.isEmpty()) {
676 controller->showBackFromStack();
677 controller->show(Box<Ui::InformBox>(unavailable));
678 return;
679 }
680 }
681 if (flags & PeerUpdateFlag::BotStartToken) {
682 updateControlsVisibility();
683 updateControlsGeometry();
684 }
685 if (flags & PeerUpdateFlag::Slowmode) {
686 updateSendButtonType();
687 }
688 if (flags & (PeerUpdateFlag::IsBlocked
689 | PeerUpdateFlag::Admins
690 | PeerUpdateFlag::Members
691 | PeerUpdateFlag::OnlineStatus
692 | PeerUpdateFlag::Rights
693 | PeerUpdateFlag::ChannelAmIn
694 | PeerUpdateFlag::ChannelLinkedChat)) {
695 handlePeerUpdate();
696 }
697 if (flags & PeerUpdateFlag::MessagesTTL) {
698 checkMessagesTTL();
699 }
700 if ((flags & PeerUpdateFlag::ChatThemeEmoji) && _list) {
701 const auto emoji = _peer->themeEmoji();
702 if (Data::CloudThemes::TestingColors() && !emoji.isEmpty()) {
703 _peer->owner().cloudThemes().themeForEmojiValue(
704 emoji
705 ) | rpl::filter_optional(
706 ) | rpl::take(
707 1
708 ) | rpl::start_with_next([=](const Data::CloudTheme &theme) {
709 const auto &themes = _peer->owner().cloudThemes();
710 const auto text = themes.prepareTestingLink(theme);
711 if (!text.isEmpty()) {
712 _field->setText(text);
713 }
714 }, _list->lifetime());
715 }
716 }
717 if (flags & PeerUpdateFlag::FullInfo) {
718 fullInfoUpdated();
719 }
720 }, lifetime());
721
722 rpl::merge(
723 session().data().defaultUserNotifyUpdates(),
724 session().data().defaultChatNotifyUpdates(),
725 session().data().defaultBroadcastNotifyUpdates()
__anon05ff6a845002null726 ) | rpl::start_with_next([=] {
727 updateNotifyControls();
728 }, lifetime());
729
730 subscribe(session().data().queryItemVisibility(), [=](
__anon05ff6a845102( const Data::Session::ItemVisibilityQuery &query) 731 const Data::Session::ItemVisibilityQuery &query) {
732 if (_a_show.animating()
733 || _history != query.item->history()
734 || !query.item->mainView() || !isVisible()) {
735 return;
736 }
737 if (const auto view = query.item->mainView()) {
738 auto top = _list->itemTop(view);
739 if (top >= 0) {
740 auto scrollTop = _scroll->scrollTop();
741 if (top + view->height() > scrollTop
742 && top < scrollTop + _scroll->height()) {
743 *query.isVisible = true;
744 }
745 }
746 }
747 });
748 _topBar->membersShowAreaActive(
__anon05ff6a845202(bool active) 749 ) | rpl::start_with_next([=](bool active) {
750 setMembersShowAreaActive(active);
751 }, _topBar->lifetime());
752 _topBar->forwardSelectionRequest(
__anon05ff6a845302null753 ) | rpl::start_with_next([=] {
754 forwardSelected();
755 }, _topBar->lifetime());
756 _topBar->deleteSelectionRequest(
__anon05ff6a845402null757 ) | rpl::start_with_next([=] {
758 confirmDeleteSelected();
759 }, _topBar->lifetime());
760 _topBar->clearSelectionRequest(
__anon05ff6a845502null761 ) | rpl::start_with_next([=] {
762 clearSelected();
763 }, _topBar->lifetime());
764 _topBar->cancelChooseForReportRequest(
__anon05ff6a845602null765 ) | rpl::start_with_next([=] {
766 setChooseReportMessagesDetails({}, nullptr);
767 }, _topBar->lifetime());
768
769 session().api().sendActions(
__anon05ff6a845702(const Api::SendAction &action) 770 ) | rpl::filter([=](const Api::SendAction &action) {
771 return (action.history == _history);
772 }) | rpl::start_with_next([=](const Api::SendAction &action) {
773 const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId(
774 action.history->channelId(),
775 action.replyTo));
776 if (action.options.scheduled) {
777 cancelReply(lastKeyboardUsed);
__anon05ff6a845902null778 crl::on_main(this, [=, history = action.history]{
779 controller->showSection(
780 std::make_shared<HistoryView::ScheduledMemento>(history));
781 });
782 } else {
783 fastShowAtEnd(action.history);
784 if (cancelReply(lastKeyboardUsed) && !action.clearDraft) {
785 saveCloudDraft();
786 }
787 }
788 if (action.options.handleSupportSwitch) {
789 handleSupportSwitch(action.history);
790 }
791 }, lifetime());
792
793 setupScheduledToggle();
794 orderWidgets();
795 setupShortcuts();
796 }
797
setGeometryWithTopMoved(const QRect & newGeometry,int topDelta)798 void HistoryWidget::setGeometryWithTopMoved(
799 const QRect &newGeometry,
800 int topDelta) {
801 _topDelta = topDelta;
802 bool willBeResized = (size() != newGeometry.size());
803 if (geometry() != newGeometry) {
804 auto weak = Ui::MakeWeak(this);
805 setGeometry(newGeometry);
806 if (!weak) {
807 return;
808 }
809 }
810 if (!willBeResized) {
811 resizeEvent(nullptr);
812 }
813 _topDelta = 0;
814 }
815
computeDialogsEntryState() const816 Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const {
817 return Dialogs::EntryState{
818 .key = _history,
819 .section = Dialogs::EntryState::Section::History,
820 .currentReplyToId = replyToId(),
821 };
822 }
823
refreshTopBarActiveChat()824 void HistoryWidget::refreshTopBarActiveChat() {
825 const auto state = computeDialogsEntryState();
826 _topBar->setActiveChat(state, _history->sendActionPainter());
827 if (_inlineResults) {
828 _inlineResults->setCurrentDialogsEntryState(state);
829 }
830 }
831
refreshTabbedPanel()832 void HistoryWidget::refreshTabbedPanel() {
833 if (_peer && controller()->hasTabbedSelectorOwnership()) {
834 createTabbedPanel();
835 } else {
836 setTabbedPanel(nullptr);
837 }
838 }
839
initVoiceRecordBar()840 void HistoryWidget::initVoiceRecordBar() {
841 {
842 auto scrollHeight = rpl::combine(
843 _scroll->topValue(),
844 _scroll->heightValue()
845 ) | rpl::map([=](int top, int height) {
846 return top + height - st::historyRecordLockPosition.y();
847 });
848 _voiceRecordBar->setLockBottom(std::move(scrollHeight));
849 }
850
851 _voiceRecordBar->setSendButtonGeometryValue(_send->geometryValue());
852
853 _voiceRecordBar->setStartRecordingFilter([=] {
854 const auto error = _peer
855 ? Data::RestrictionError(_peer, ChatRestriction::SendMedia)
856 : std::nullopt;
857 if (error) {
858 controller()->show(Box<Ui::InformBox>(*error));
859 return true;
860 } else if (showSlowmodeError()) {
861 return true;
862 }
863 return false;
864 });
865
866 const auto applyLocalDraft = [=] {
867 if (_history && _history->localDraft()) {
868 applyDraft();
869 }
870 };
871
872 _voiceRecordBar->sendActionUpdates(
873 ) | rpl::start_with_next([=](const auto &data) {
874 if (!_history) {
875 return;
876 }
877 session().sendProgressManager().update(
878 _history,
879 data.type,
880 data.progress);
881 }, lifetime());
882
883 _voiceRecordBar->sendVoiceRequests(
884 ) | rpl::start_with_next([=](const auto &data) {
885 if (!canWriteMessage() || data.bytes.isEmpty() || !_history) {
886 return;
887 }
888
889 auto action = Api::SendAction(_history);
890 action.replyTo = replyToId();
891 action.options = data.options;
892 session().api().sendVoiceMessage(
893 data.bytes,
894 data.waveform,
895 data.duration,
896 action);
897 _voiceRecordBar->clearListenState();
898 applyLocalDraft();
899 }, lifetime());
900
901 _voiceRecordBar->cancelRequests(
902 ) | rpl::start_with_next(applyLocalDraft, lifetime());
903
904 _voiceRecordBar->lockShowStarts(
905 ) | rpl::start_with_next([=] {
906 updateHistoryDownVisibility();
907 updateUnreadMentionsVisibility();
908 }, lifetime());
909
910 _voiceRecordBar->updateSendButtonTypeRequests(
911 ) | rpl::start_with_next([=] {
912 updateSendButtonType();
913 }, lifetime());
914
915 _voiceRecordBar->lockViewportEvents(
916 ) | rpl::start_with_next([=](not_null<QEvent*> e) {
917 _scroll->viewportEvent(e);
918 }, lifetime());
919
920 _voiceRecordBar->recordingTipRequests(
921 ) | rpl::start_with_next([=] {
922 Ui::ShowMultilineToast({
923 .text = { tr::lng_record_hold_tip(tr::now) },
924 });
925 }, lifetime());
926
927 _voiceRecordBar->hideFast();
928 }
929
initTabbedSelector()930 void HistoryWidget::initTabbedSelector() {
931 refreshTabbedPanel();
932
933 _tabbedSelectorToggle->addClickHandler([=] {
934 toggleTabbedSelectorMode();
935 });
936
937 const auto selector = controller()->tabbedSelector();
938
939 base::install_event_filter(this, selector, [=](not_null<QEvent*> e) {
940 if (_tabbedPanel && e->type() == QEvent::ParentChange) {
941 setTabbedPanel(nullptr);
942 }
943 return base::EventFilterResult::Continue;
944 });
945
946 auto filter = rpl::filter([=] {
947 return !isHidden();
948 });
949 using Selector = TabbedSelector;
950
951 selector->emojiChosen(
952 ) | rpl::filter([=] {
953 return !isHidden() && !_field->isHidden();
954 }) | rpl::start_with_next([=](EmojiPtr emoji) {
955 Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
956 }, lifetime());
957
958 selector->fileChosen(
959 ) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
960 sendExistingDocument(data.document, data.options);
961 }, lifetime());
962
963 selector->photoChosen(
964 ) | filter | rpl::start_with_next([=](Selector::PhotoChosen data) {
965 sendExistingPhoto(data.photo, data.options);
966 }, lifetime());
967
968 selector->inlineResultChosen(
969 ) | filter | rpl::start_with_next([=](Selector::InlineChosen data) {
970 sendInlineResult(data);
971 }, lifetime());
972
973 selector->contextMenuRequested(
974 ) | filter | rpl::start_with_next([=] {
975 selector->showMenuWithType(sendMenuType());
976 }, lifetime());
977
978 selector->choosingStickerUpdated(
979 ) | rpl::start_with_next([=](const Selector::Action &data) {
980 if (!_history) {
981 return;
982 }
983 const auto type = Api::SendProgressType::ChooseSticker;
984 if (data != Selector::Action::Cancel) {
985 session().sendProgressManager().update(_history, type);
986 } else {
987 session().sendProgressManager().cancel(_history, type);
988 }
989 }, lifetime());
990 }
991
supportInitAutocomplete()992 void HistoryWidget::supportInitAutocomplete() {
993 _supportAutocomplete->hide();
994
995 _supportAutocomplete->insertRequests(
996 ) | rpl::start_with_next([=](const QString &text) {
997 supportInsertText(text);
998 }, _supportAutocomplete->lifetime());
999
1000 _supportAutocomplete->shareContactRequests(
1001 ) | rpl::start_with_next([=](const Support::Contact &contact) {
1002 supportShareContact(contact);
1003 }, _supportAutocomplete->lifetime());
1004 }
1005
supportInsertText(const QString & text)1006 void HistoryWidget::supportInsertText(const QString &text) {
1007 _field->setFocus();
1008 _field->textCursor().insertText(text);
1009 _field->ensureCursorVisible();
1010 }
1011
supportShareContact(Support::Contact contact)1012 void HistoryWidget::supportShareContact(Support::Contact contact) {
1013 if (!_history) {
1014 return;
1015 }
1016 supportInsertText(contact.comment);
1017 contact.comment = _field->getLastText();
1018
1019 const auto submit = [=](Qt::KeyboardModifiers modifiers) {
1020 const auto history = _history;
1021 if (!history) {
1022 return;
1023 }
1024 auto options = Api::SendOptions();
1025 auto action = Api::SendAction(history);
1026 send(options);
1027 options.handleSupportSwitch = Support::HandleSwitch(modifiers);
1028 action.options = options;
1029 session().api().shareContact(
1030 contact.phone,
1031 contact.firstName,
1032 contact.lastName,
1033 action);
1034 };
1035 const auto box = controller()->show(Box<Support::ConfirmContactBox>(
1036 controller(),
1037 _history,
1038 contact,
1039 crl::guard(this, submit)));
1040 box->boxClosing(
1041 ) | rpl::start_with_next([=] {
1042 _field->document()->undo();
1043 }, lifetime());
1044 }
1045
scrollToCurrentVoiceMessage(FullMsgId fromId,FullMsgId toId)1046 void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId) {
1047 if (crl::now() <= _lastUserScrolled + kScrollToVoiceAfterScrolledMs) {
1048 return;
1049 }
1050 if (!_list) {
1051 return;
1052 }
1053
1054 auto from = session().data().message(fromId);
1055 auto to = session().data().message(toId);
1056 if (!from || !to) {
1057 return;
1058 }
1059
1060 // If history has pending resize items, the scrollTopItem won't be updated.
1061 // And the scrollTop will be reset back to scrollTopItem + scrollTopOffset.
1062 handlePendingHistoryUpdate();
1063
1064 if (const auto toView = to->mainView()) {
1065 auto toTop = _list->itemTop(toView);
1066 if (toTop >= 0 && !isItemCompletelyHidden(from)) {
1067 auto scrollTop = _scroll->scrollTop();
1068 auto scrollBottom = scrollTop + _scroll->height();
1069 auto toBottom = toTop + toView->height();
1070 if ((toTop < scrollTop && toBottom < scrollBottom) || (toTop > scrollTop && toBottom > scrollBottom)) {
1071 animatedScrollToItem(to->id);
1072 }
1073 }
1074 }
1075 }
1076
animatedScrollToItem(MsgId msgId)1077 void HistoryWidget::animatedScrollToItem(MsgId msgId) {
1078 Expects(_history != nullptr);
1079
1080 if (hasPendingResizedItems()) {
1081 updateListSize();
1082 }
1083
1084 auto to = session().data().message(_channel, msgId);
1085 if (_list->itemTop(to) < 0) {
1086 return;
1087 }
1088
1089 auto scrollTo = std::clamp(
1090 itemTopForHighlight(to->mainView()),
1091 0,
1092 _scroll->scrollTopMax());
1093 animatedScrollToY(scrollTo, to);
1094 }
1095
animatedScrollToY(int scrollTo,HistoryItem * attachTo)1096 void HistoryWidget::animatedScrollToY(int scrollTo, HistoryItem *attachTo) {
1097 Expects(_history != nullptr);
1098
1099 if (hasPendingResizedItems()) {
1100 updateListSize();
1101 }
1102
1103 // Attach our scroll animation to some item.
1104 auto itemTop = _list->itemTop(attachTo);
1105 auto scrollTop = _scroll->scrollTop();
1106 if (itemTop < 0 && !_history->isEmpty()) {
1107 attachTo = _history->blocks.back()->messages.back()->data();
1108 itemTop = _list->itemTop(attachTo);
1109 }
1110 if (itemTop < 0 || (scrollTop == scrollTo)) {
1111 synteticScrollToY(scrollTo);
1112 return;
1113 }
1114
1115 _scrollToAnimation.stop();
1116 auto maxAnimatedDelta = _scroll->height();
1117 auto transition = anim::sineInOut;
1118 if (scrollTo > scrollTop + maxAnimatedDelta) {
1119 scrollTop = scrollTo - maxAnimatedDelta;
1120 synteticScrollToY(scrollTop);
1121 transition = anim::easeOutCubic;
1122 } else if (scrollTo + maxAnimatedDelta < scrollTop) {
1123 scrollTop = scrollTo + maxAnimatedDelta;
1124 synteticScrollToY(scrollTop);
1125 transition = anim::easeOutCubic;
1126 } else {
1127 // In local showHistory() we forget current scroll state,
1128 // so we need to restore it synchronously, otherwise we may
1129 // jump to the bottom of history in some updateHistoryGeometry() call.
1130 synteticScrollToY(scrollTop);
1131 }
1132 const auto itemId = attachTo->fullId();
1133 const auto relativeFrom = scrollTop - itemTop;
1134 const auto relativeTo = scrollTo - itemTop;
1135 _scrollToAnimation.start(
1136 [=] { scrollToAnimationCallback(itemId, relativeTo); },
1137 relativeFrom,
1138 relativeTo,
1139 st::slideDuration,
1140 anim::sineInOut);
1141 }
1142
scrollToAnimationCallback(FullMsgId attachToId,int relativeTo)1143 void HistoryWidget::scrollToAnimationCallback(
1144 FullMsgId attachToId,
1145 int relativeTo) {
1146 auto itemTop = _list->itemTop(session().data().message(attachToId));
1147 if (itemTop < 0) {
1148 _scrollToAnimation.stop();
1149 } else {
1150 synteticScrollToY(qRound(_scrollToAnimation.value(relativeTo)) + itemTop);
1151 }
1152 if (!_scrollToAnimation.animating()) {
1153 preloadHistoryByScroll();
1154 checkReplyReturns();
1155 }
1156 }
1157
enqueueMessageHighlight(not_null<HistoryView::Element * > view)1158 void HistoryWidget::enqueueMessageHighlight(
1159 not_null<HistoryView::Element*> view) {
1160 auto enqueueMessageId = [this](MsgId universalId) {
1161 if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
1162 highlightMessage(universalId);
1163 } else if (_highlightedMessageId != universalId
1164 && !base::contains(_highlightQueue, universalId)) {
1165 _highlightQueue.push_back(universalId);
1166 checkNextHighlight();
1167 }
1168 };
1169 const auto item = view->data();
1170 if (item->history() == _history) {
1171 enqueueMessageId(item->id);
1172 } else if (item->history() == _migrated) {
1173 enqueueMessageId(-item->id);
1174 }
1175 }
1176
highlightMessage(MsgId universalMessageId)1177 void HistoryWidget::highlightMessage(MsgId universalMessageId) {
1178 _highlightStart = crl::now();
1179 _highlightedMessageId = universalMessageId;
1180 _highlightTimer.callEach(AnimationTimerDelta);
1181 }
1182
checkNextHighlight()1183 void HistoryWidget::checkNextHighlight() {
1184 if (_highlightTimer.isActive()) {
1185 return;
1186 }
1187 auto nextHighlight = [this] {
1188 while (!_highlightQueue.empty()) {
1189 auto msgId = _highlightQueue.front();
1190 _highlightQueue.pop_front();
1191 auto item = getItemFromHistoryOrMigrated(msgId);
1192 if (item && item->mainView()) {
1193 return msgId;
1194 }
1195 }
1196 return MsgId();
1197 }();
1198 if (!nextHighlight) {
1199 return;
1200 }
1201 highlightMessage(nextHighlight);
1202 }
1203
updateHighlightedMessage()1204 void HistoryWidget::updateHighlightedMessage() {
1205 const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId);
1206 auto view = item ? item->mainView() : nullptr;
1207 if (!view) {
1208 return stopMessageHighlight();
1209 }
1210 auto duration = st::activeFadeInDuration + st::activeFadeOutDuration;
1211 if (crl::now() - _highlightStart > duration) {
1212 return stopMessageHighlight();
1213 }
1214
1215 if (const auto group = session().data().groups().find(view->data())) {
1216 if (const auto leader = group->items.front()->mainView()) {
1217 view = leader;
1218 }
1219 }
1220 session().data().requestViewRepaint(view);
1221 }
1222
highlightStartTime(not_null<const HistoryItem * > item) const1223 crl::time HistoryWidget::highlightStartTime(not_null<const HistoryItem*> item) const {
1224 auto isHighlighted = [this](not_null<const HistoryItem*> item) {
1225 if (item->id == _highlightedMessageId) {
1226 return (item->history() == _history);
1227 } else if (item->id == -_highlightedMessageId) {
1228 return (item->history() == _migrated);
1229 }
1230 return false;
1231 };
1232 return (isHighlighted(item) && _highlightTimer.isActive())
1233 ? _highlightStart
1234 : 0;
1235 }
1236
stopMessageHighlight()1237 void HistoryWidget::stopMessageHighlight() {
1238 _highlightTimer.cancel();
1239 _highlightedMessageId = 0;
1240 checkNextHighlight();
1241 }
1242
clearHighlightMessages()1243 void HistoryWidget::clearHighlightMessages() {
1244 _highlightQueue.clear();
1245 stopMessageHighlight();
1246 }
1247
itemTopForHighlight(not_null<HistoryView::Element * > view) const1248 int HistoryWidget::itemTopForHighlight(
1249 not_null<HistoryView::Element*> view) const {
1250 if (const auto group = session().data().groups().find(view->data())) {
1251 if (const auto leader = group->items.front()->mainView()) {
1252 view = leader;
1253 }
1254 }
1255 auto itemTop = _list->itemTop(view);
1256 Assert(itemTop >= 0);
1257
1258 auto heightLeft = (_scroll->height() - view->height());
1259 if (heightLeft <= 0) {
1260 return itemTop;
1261 }
1262 return qMax(itemTop - (heightLeft / 2), 0);
1263 }
1264
start()1265 void HistoryWidget::start() {
1266 session().data().stickers().updated(
1267 ) | rpl::start_with_next([=] {
1268 updateStickersByEmoji();
1269 }, lifetime());
1270 session().data().stickers().notifySavedGifsUpdated();
1271 }
1272
insertMention(UserData * user)1273 void HistoryWidget::insertMention(UserData *user) {
1274 QString replacement, entityTag;
1275 if (user->username.isEmpty()) {
1276 replacement = user->firstName;
1277 if (replacement.isEmpty()) {
1278 replacement = user->name;
1279 }
1280 entityTag = PrepareMentionTag(user);
1281 } else {
1282 replacement = '@' + user->username;
1283 }
1284 _field->insertTag(replacement, entityTag);
1285 }
1286
insertHashtagOrBotCommand(QString str,FieldAutocomplete::ChooseMethod method)1287 void HistoryWidget::insertHashtagOrBotCommand(
1288 QString str,
1289 FieldAutocomplete::ChooseMethod method) {
1290 if (!_peer) {
1291 return;
1292 }
1293
1294 // Send bot command at once, if it was not inserted by pressing Tab.
1295 if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
1296 sendBotCommand({ _peer, str, FullMsgId(), replyToId() });
1297 session().api().finishForwarding(Api::SendAction(_history));
1298 setFieldText(_field->getTextWithTagsPart(_field->textCursor().position()));
1299 } else {
1300 _field->insertTag(str);
1301 }
1302 }
1303
1304
parseInlineBotQuery() const1305 InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
1306 return (isChoosingTheme() || _editMsgId)
1307 ? InlineBotQuery()
1308 : ParseInlineBotQuery(&session(), _field);
1309 }
1310
parseMentionHashtagBotCommandQuery() const1311 AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const {
1312 const auto result = (isChoosingTheme()
1313 || (_inlineBot && !_inlineLookingUpBot))
1314 ? AutocompleteQuery()
1315 : ParseMentionHashtagBotCommandQuery(_field);
1316 if (result.query.isEmpty()) {
1317 return result;
1318 } else if (result.query[0] == '#'
1319 && cRecentWriteHashtags().isEmpty()
1320 && cRecentSearchHashtags().isEmpty()) {
1321 session().local().readRecentHashtagsAndBots();
1322 } else if (result.query[0] == '@'
1323 && cRecentInlineBots().isEmpty()) {
1324 session().local().readRecentHashtagsAndBots();
1325 } else if (result.query[0] == '/'
1326 && ((_peer->isUser() && !_peer->asUser()->isBot()) || _editMsgId)) {
1327 return AutocompleteQuery();
1328 }
1329 return result;
1330 }
1331
updateInlineBotQuery()1332 void HistoryWidget::updateInlineBotQuery() {
1333 if (!_history) {
1334 return;
1335 }
1336 const auto query = parseInlineBotQuery();
1337 if (_inlineBotUsername != query.username) {
1338 _inlineBotUsername = query.username;
1339 if (_inlineBotResolveRequestId) {
1340 _api.request(_inlineBotResolveRequestId).cancel();
1341 _inlineBotResolveRequestId = 0;
1342 }
1343 if (query.lookingUpBot) {
1344 _inlineBot = nullptr;
1345 _inlineLookingUpBot = true;
1346 const auto username = _inlineBotUsername;
1347 _inlineBotResolveRequestId = _api.request(MTPcontacts_ResolveUsername(
1348 MTP_string(username)
1349 )).done([=](const MTPcontacts_ResolvedPeer &result) {
1350 inlineBotResolveDone(result);
1351 }).fail([=](const MTP::Error &error) {
1352 inlineBotResolveFail(error, username);
1353 }).send();
1354 } else {
1355 applyInlineBotQuery(query.bot, query.query);
1356 }
1357 } else if (query.lookingUpBot) {
1358 if (!_inlineLookingUpBot) {
1359 applyInlineBotQuery(_inlineBot, query.query);
1360 }
1361 } else {
1362 applyInlineBotQuery(query.bot, query.query);
1363 }
1364 }
1365
applyInlineBotQuery(UserData * bot,const QString & query)1366 void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
1367 if (bot) {
1368 if (_inlineBot != bot) {
1369 _inlineBot = bot;
1370 _inlineLookingUpBot = false;
1371 inlineBotChanged();
1372 }
1373 if (!_inlineResults) {
1374 _inlineResults.create(this, controller());
1375 _inlineResults->setResultSelectedCallback([=](
1376 InlineBots::ResultSelected result) {
1377 if (result.open) {
1378 const auto request = result.result->openRequest();
1379 if (const auto photo = request.photo()) {
1380 controller()->openPhoto(photo, FullMsgId());
1381 } else if (const auto document = request.document()) {
1382 controller()->openDocument(document, FullMsgId());
1383 }
1384 } else {
1385 sendInlineResult(result);
1386 }
1387 });
1388 _inlineResults->setCurrentDialogsEntryState(
1389 computeDialogsEntryState());
1390 _inlineResults->setSendMenuType([=] { return sendMenuType(); });
1391 _inlineResults->requesting(
1392 ) | rpl::start_with_next([=](bool requesting) {
1393 _tabbedSelectorToggle->setLoading(requesting);
1394 }, _inlineResults->lifetime());
1395 updateControlsGeometry();
1396 orderWidgets();
1397 }
1398 _inlineResults->queryInlineBot(_inlineBot, _peer, query);
1399 if (!_fieldAutocomplete->isHidden()) {
1400 _fieldAutocomplete->hideAnimated();
1401 }
1402 } else {
1403 clearInlineBot();
1404 }
1405 }
1406
orderWidgets()1407 void HistoryWidget::orderWidgets() {
1408 _voiceRecordBar->raise();
1409 _send->raise();
1410 if (_contactStatus) {
1411 _contactStatus->raise();
1412 }
1413 if (_pinnedBar) {
1414 _pinnedBar->raise();
1415 }
1416 if (_groupCallBar) {
1417 _groupCallBar->raise();
1418 }
1419 if (_requestsBar) {
1420 _requestsBar->raise();
1421 }
1422 if (_chooseTheme) {
1423 _chooseTheme->raise();
1424 }
1425 _topShadow->raise();
1426 _fieldAutocomplete->raise();
1427 if (_membersDropdown) {
1428 _membersDropdown->raise();
1429 }
1430 if (_inlineResults) {
1431 _inlineResults->raise();
1432 }
1433 if (_tabbedPanel) {
1434 _tabbedPanel->raise();
1435 }
1436 _raiseEmojiSuggestions();
1437 _attachDragAreas.document->raise();
1438 _attachDragAreas.photo->raise();
1439 }
1440
updateStickersByEmoji()1441 bool HistoryWidget::updateStickersByEmoji() {
1442 if (!_peer) {
1443 return false;
1444 }
1445 const auto emoji = [&] {
1446 const auto errorForStickers = Data::RestrictionError(
1447 _peer,
1448 ChatRestriction::SendStickers);
1449 if (!_editMsgId && !errorForStickers) {
1450 const auto &text = _field->getTextWithTags().text;
1451 auto length = 0;
1452 if (const auto emoji = Ui::Emoji::Find(text, &length)) {
1453 if (text.size() <= length) {
1454 return emoji;
1455 }
1456 }
1457 }
1458 return EmojiPtr(nullptr);
1459 }();
1460 _fieldAutocomplete->showStickers(emoji);
1461 return (emoji != nullptr);
1462 }
1463
toggleChooseChatTheme(not_null<PeerData * > peer)1464 void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
1465 const auto update = [=] {
1466 updateInlineBotQuery();
1467 updateControlsGeometry();
1468 updateControlsVisibility();
1469 };
1470 if (peer.get() != _peer) {
1471 return;
1472 } else if (_chooseTheme) {
1473 if (isChoosingTheme()) {
1474 const auto was = base::take(_chooseTheme);
1475 if (Ui::InFocusChain(this)) {
1476 setInnerFocus();
1477 }
1478 update();
1479 }
1480 return;
1481 } else if (_voiceRecordBar->isActive()) {
1482 Ui::ShowMultilineToast({
1483 .text = { tr::lng_chat_theme_cant_voice(tr::now) },
1484 });
1485 return;
1486 }
1487 _chooseTheme = std::make_unique<Ui::ChooseThemeController>(
1488 this,
1489 controller(),
1490 peer);
1491 _chooseTheme->shouldBeShownValue(
1492 ) | rpl::start_with_next(update, _chooseTheme->lifetime());
1493 }
1494
fieldChanged()1495 void HistoryWidget::fieldChanged() {
1496 const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
1497
1498 InvokeQueued(this, [=] {
1499 updateInlineBotQuery();
1500 const auto choosingSticker = updateStickersByEmoji();
1501 if (_history
1502 && !_inlineBot
1503 && !_editMsgId
1504 && !choosingSticker
1505 && updateTyping) {
1506 session().sendProgressManager().update(
1507 _history,
1508 Api::SendProgressType::Typing);
1509 }
1510 });
1511
1512 updateSendButtonType();
1513 if (!HasSendText(_field)) {
1514 _previewState = Data::PreviewState::Allowed;
1515 }
1516 if (updateCmdStartShown()) {
1517 updateControlsVisibility();
1518 updateControlsGeometry();
1519 }
1520
1521 _saveCloudDraftTimer.cancel();
1522 if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
1523 return;
1524 }
1525
1526 _saveDraftText = true;
1527 saveDraft(true);
1528 }
1529
saveDraftDelayed()1530 void HistoryWidget::saveDraftDelayed() {
1531 if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
1532 return;
1533 }
1534 if (!_field->textCursor().position()
1535 && !_field->textCursor().anchor()
1536 && !_field->scrollTop().current()) {
1537 if (!session().local().hasDraftCursors(_peer->id)) {
1538 return;
1539 }
1540 }
1541 saveDraft(true);
1542 }
1543
saveDraft(bool delayed)1544 void HistoryWidget::saveDraft(bool delayed) {
1545 if (!_peer) {
1546 return;
1547 } else if (delayed) {
1548 auto ms = crl::now();
1549 if (!_saveDraftStart) {
1550 _saveDraftStart = ms;
1551 return _saveDraftTimer.callOnce(kSaveDraftTimeout);
1552 } else if (ms - _saveDraftStart < kSaveDraftAnywayTimeout) {
1553 return _saveDraftTimer.callOnce(kSaveDraftTimeout);
1554 }
1555 }
1556 writeDrafts();
1557 }
1558
saveFieldToHistoryLocalDraft()1559 void HistoryWidget::saveFieldToHistoryLocalDraft() {
1560 if (!_history) return;
1561
1562 if (_editMsgId) {
1563 _history->setLocalEditDraft(std::make_unique<Data::Draft>(
1564 _field,
1565 _editMsgId,
1566 _previewState,
1567 _saveEditMsgRequestId));
1568 } else {
1569 if (_replyToId || !_field->empty()) {
1570 _history->setLocalDraft(std::make_unique<Data::Draft>(
1571 _field,
1572 _replyToId,
1573 _previewState));
1574 } else {
1575 _history->clearLocalDraft();
1576 }
1577 _history->clearLocalEditDraft();
1578 }
1579 }
1580
saveCloudDraft()1581 void HistoryWidget::saveCloudDraft() {
1582 controller()->session().api().saveCurrentDraftToCloud();
1583 }
1584
writeDraftTexts()1585 void HistoryWidget::writeDraftTexts() {
1586 Expects(_history != nullptr);
1587
1588 session().local().writeDrafts(_history);
1589 if (_migrated) {
1590 _migrated->clearDrafts();
1591 session().local().writeDrafts(_migrated);
1592 }
1593 }
1594
writeDraftCursors()1595 void HistoryWidget::writeDraftCursors() {
1596 Expects(_history != nullptr);
1597
1598 session().local().writeDraftCursors(_history);
1599 if (_migrated) {
1600 _migrated->clearDrafts();
1601 session().local().writeDraftCursors(_migrated);
1602 }
1603 }
1604
writeDrafts()1605 void HistoryWidget::writeDrafts() {
1606 const auto save = (_history != nullptr) && (_saveDraftStart > 0);
1607 _saveDraftStart = 0;
1608 _saveDraftTimer.cancel();
1609 if (save) {
1610 if (_saveDraftText) {
1611 writeDraftTexts();
1612 }
1613 writeDraftCursors();
1614 }
1615 _saveDraftText = false;
1616
1617 if (!_editMsgId && !_inlineBot) {
1618 _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout);
1619 }
1620 }
1621
isRecording() const1622 bool HistoryWidget::isRecording() const {
1623 return _voiceRecordBar->isRecording();
1624 }
1625
activate()1626 void HistoryWidget::activate() {
1627 if (_history) {
1628 if (!_historyInited) {
1629 updateHistoryGeometry(true);
1630 } else if (hasPendingResizedItems()) {
1631 updateHistoryGeometry();
1632 }
1633 }
1634 controller()->widget()->setInnerFocus();
1635 }
1636
setInnerFocus()1637 void HistoryWidget::setInnerFocus() {
1638 if (_scroll->isHidden()) {
1639 setFocus();
1640 } else if (_list) {
1641 if (_chooseTheme && _chooseTheme->shouldBeShown()) {
1642 _chooseTheme->setFocus();
1643 } else if (_nonEmptySelection
1644 || (_list && _list->wasSelectedText())
1645 || isRecording()
1646 || isBotStart()
1647 || isBlocked()
1648 || !_canSendMessages) {
1649 _list->setFocus();
1650 } else {
1651 _field->setFocus();
1652 }
1653 }
1654 }
1655
notify_switchInlineBotButtonReceived(const QString & query,UserData * samePeerBot,MsgId samePeerReplyTo)1656 bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) {
1657 if (samePeerBot) {
1658 if (_history) {
1659 TextWithTags textWithTags = { '@' + samePeerBot->username + ' ' + query, TextWithTags::Tags() };
1660 MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX };
1661 _history->setLocalDraft(std::make_unique<Data::Draft>(
1662 textWithTags,
1663 0,
1664 cursor,
1665 Data::PreviewState::Allowed));
1666 applyDraft();
1667 return true;
1668 }
1669 } else if (const auto bot = _peer ? _peer->asUser() : nullptr) {
1670 const auto to = bot->isBot()
1671 ? bot->botInfo->inlineReturnTo
1672 : Dialogs::EntryState();
1673 const auto history = to.key.history();
1674 if (!history) {
1675 return false;
1676 }
1677 bot->botInfo->inlineReturnTo = Dialogs::EntryState();
1678 using Section = Dialogs::EntryState::Section;
1679
1680 TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
1681 MessageCursor cursor = { int(textWithTags.text.size()), int(textWithTags.text.size()), QFIXED_MAX };
1682 auto draft = std::make_unique<Data::Draft>(
1683 textWithTags,
1684 to.currentReplyToId,
1685 cursor,
1686 Data::PreviewState::Allowed);
1687
1688 if (to.section == Section::Replies) {
1689 history->setDraft(
1690 Data::DraftKey::Replies(to.rootId),
1691 std::move(draft));
1692 controller()->showRepliesForMessage(history, to.rootId);
1693 } else if (to.section == Section::Scheduled) {
1694 history->setDraft(Data::DraftKey::Scheduled(), std::move(draft));
1695 controller()->showSection(
1696 std::make_shared<HistoryView::ScheduledMemento>(history));
1697 } else {
1698 history->setLocalDraft(std::move(draft));
1699 if (history == _history) {
1700 applyDraft();
1701 } else {
1702 Ui::showPeerHistory(history->peer, ShowAtUnreadMsgId);
1703 }
1704 }
1705 return true;
1706 }
1707 return false;
1708 }
1709
setupShortcuts()1710 void HistoryWidget::setupShortcuts() {
1711 Shortcuts::Requests(
1712 ) | rpl::filter([=] {
1713 return Ui::AppInFocus()
1714 && Ui::InFocusChain(this)
1715 && !Ui::isLayerShown();
1716 }) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
1717 using Command = Shortcuts::Command;
1718 if (_history) {
1719 request->check(Command::Search, 1) && request->handle([=] {
1720 controller()->content()->searchInChat(_history);
1721 return true;
1722 });
1723 if (session().supportMode()) {
1724 request->check(
1725 Command::SupportToggleMuted
1726 ) && request->handle([=] {
1727 toggleMuteUnmute();
1728 return true;
1729 });
1730 }
1731 }
1732 }, lifetime());
1733 }
1734
clearReplyReturns()1735 void HistoryWidget::clearReplyReturns() {
1736 _replyReturns.clear();
1737 _replyReturn = nullptr;
1738 }
1739
pushReplyReturn(not_null<HistoryItem * > item)1740 void HistoryWidget::pushReplyReturn(not_null<HistoryItem*> item) {
1741 if (item->history() == _history) {
1742 _replyReturns.push_back(item->id);
1743 } else if (item->history() == _migrated) {
1744 _replyReturns.push_back(-item->id);
1745 } else {
1746 return;
1747 }
1748 _replyReturn = item;
1749 updateControlsVisibility();
1750 }
1751
replyReturns()1752 QList<MsgId> HistoryWidget::replyReturns() {
1753 return _replyReturns;
1754 }
1755
setReplyReturns(PeerId peer,const QList<MsgId> & replyReturns)1756 void HistoryWidget::setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns) {
1757 if (!_peer || _peer->id != peer) return;
1758
1759 _replyReturns = replyReturns;
1760 if (_replyReturns.isEmpty()) {
1761 _replyReturn = nullptr;
1762 } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
1763 _replyReturn = session().data().message(0, -_replyReturns.back());
1764 } else {
1765 _replyReturn = session().data().message(_channel, _replyReturns.back());
1766 }
1767 while (!_replyReturns.isEmpty() && !_replyReturn) {
1768 _replyReturns.pop_back();
1769 if (_replyReturns.isEmpty()) {
1770 _replyReturn = nullptr;
1771 } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
1772 _replyReturn = session().data().message(0, -_replyReturns.back());
1773 } else {
1774 _replyReturn = session().data().message(_channel, _replyReturns.back());
1775 }
1776 }
1777 }
1778
calcNextReplyReturn()1779 void HistoryWidget::calcNextReplyReturn() {
1780 _replyReturn = nullptr;
1781 while (!_replyReturns.isEmpty() && !_replyReturn) {
1782 _replyReturns.pop_back();
1783 if (_replyReturns.isEmpty()) {
1784 _replyReturn = nullptr;
1785 } else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
1786 _replyReturn = session().data().message(0, -_replyReturns.back());
1787 } else {
1788 _replyReturn = session().data().message(_channel, _replyReturns.back());
1789 }
1790 }
1791 if (!_replyReturn) {
1792 updateControlsVisibility();
1793 }
1794 }
1795
fastShowAtEnd(not_null<History * > history)1796 void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
1797 if (_history != history) {
1798 return;
1799 }
1800
1801 clearAllLoadRequests();
1802 setMsgId(ShowAtUnreadMsgId);
1803 _pinnedClickedId = FullMsgId();
1804 _minPinnedId = std::nullopt;
1805 if (_history->isReadyFor(_showAtMsgId)) {
1806 historyLoaded();
1807 } else {
1808 firstLoadMessages();
1809 doneShow();
1810 }
1811 }
1812
applyDraft(FieldHistoryAction fieldHistoryAction)1813 void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
1814 InvokeQueued(this, [=] { updateStickersByEmoji(); });
1815
1816 if (_voiceRecordBar->isActive()) {
1817 return;
1818 }
1819
1820 auto draft = !_history
1821 ? nullptr
1822 : _history->localEditDraft()
1823 ? _history->localEditDraft()
1824 : _history->localDraft();
1825 auto fieldAvailable = canWriteMessage();
1826 if (!draft || (!_history->localEditDraft() && !fieldAvailable)) {
1827 auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0);
1828 clearFieldText(0, fieldHistoryAction);
1829 _field->setFocus();
1830 _replyEditMsg = nullptr;
1831 _replyToId = 0;
1832 setEditMsgId(0);
1833 if (fieldWillBeHiddenAfterEdit) {
1834 updateControlsVisibility();
1835 updateControlsGeometry();
1836 }
1837 refreshTopBarActiveChat();
1838 return;
1839 }
1840
1841 _textUpdateEvents = 0;
1842 setFieldText(draft->textWithTags, 0, fieldHistoryAction);
1843 _field->setFocus();
1844 draft->cursor.applyTo(_field);
1845 _textUpdateEvents = TextUpdateEvent::SaveDraft
1846 | TextUpdateEvent::SendTyping;
1847
1848 // Save links from _field to _parsedLinks without generating preview.
1849 _previewState = Data::PreviewState::Cancelled;
1850 _fieldLinksParser->parseNow();
1851 _parsedLinks = _fieldLinksParser->list().current();
1852 _previewState = draft->previewState;
1853
1854 _replyEditMsg = nullptr;
1855 if (const auto editDraft = _history->localEditDraft()) {
1856 setEditMsgId(editDraft->msgId);
1857 _replyToId = 0;
1858 } else {
1859 _replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
1860 setEditMsgId(0);
1861 }
1862 updateCmdStartShown();
1863 updateControlsVisibility();
1864 updateControlsGeometry();
1865 refreshTopBarActiveChat();
1866 if (_editMsgId || _replyToId) {
1867 updateReplyEditTexts();
1868 if (!_replyEditMsg) {
1869 requestMessageData(_editMsgId ? _editMsgId : _replyToId);
1870 }
1871 }
1872 }
1873
applyCloudDraft(History * history)1874 void HistoryWidget::applyCloudDraft(History *history) {
1875 Expects(!session().supportMode());
1876
1877 if (_history == history && !_editMsgId) {
1878 applyDraft(Ui::InputField::HistoryAction::NewEntry);
1879
1880 updateControlsVisibility();
1881 updateControlsGeometry();
1882 }
1883 }
1884
insideJumpToEndInsteadOfToUnread() const1885 bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
1886 if (session().supportMode()) {
1887 return true;
1888 } else if (!_historyInited) {
1889 return false;
1890 }
1891 _history->calculateFirstUnreadMessage();
1892 const auto unread = _history->firstUnreadMessage();
1893 const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
1894 return unread && _list->itemTop(unread) <= visibleBottom;
1895 }
1896
showHistory(const PeerId & peerId,MsgId showAtMsgId,bool reload)1897 void HistoryWidget::showHistory(
1898 const PeerId &peerId,
1899 MsgId showAtMsgId,
1900 bool reload) {
1901 _pinnedClickedId = FullMsgId();
1902 _minPinnedId = std::nullopt;
1903
1904 const auto wasDialogsEntryState = computeDialogsEntryState();
1905 const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
1906 if (startBot) {
1907 showAtMsgId = ShowAtTheEndMsgId;
1908 }
1909
1910 clearHighlightMessages();
1911 hideInfoTooltip(anim::type::instant);
1912 if (_history) {
1913 if (_peer->id == peerId && !reload) {
1914 updateForwarding();
1915
1916 if (showAtMsgId == ShowAtUnreadMsgId
1917 && insideJumpToEndInsteadOfToUnread()) {
1918 showAtMsgId = ShowAtTheEndMsgId;
1919 } else if (showAtMsgId == ShowForChooseMessagesMsgId) {
1920 if (_chooseForReport) {
1921 clearSelected();
1922 _chooseForReport->active = true;
1923 _list->setChooseReportReason(_chooseForReport->reason);
1924 updateControlsVisibility();
1925 updateControlsGeometry();
1926 updateTopBarChooseForReport();
1927 }
1928 return;
1929 }
1930 if (!IsServerMsgId(showAtMsgId)
1931 && !IsServerMsgId(-showAtMsgId)) {
1932 // To end or to unread.
1933 destroyUnreadBar();
1934 }
1935 const auto canShowNow = _history->isReadyFor(showAtMsgId);
1936 if (!canShowNow) {
1937 delayedShowAt(showAtMsgId);
1938 } else {
1939 _history->forgetScrollState();
1940 if (_migrated) {
1941 _migrated->forgetScrollState();
1942 }
1943
1944 clearDelayedShowAt();
1945 while (_replyReturn) {
1946 if (_replyReturn->history() == _history && _replyReturn->id == showAtMsgId) {
1947 calcNextReplyReturn();
1948 } else if (_replyReturn->history() == _migrated && -_replyReturn->id == showAtMsgId) {
1949 calcNextReplyReturn();
1950 } else {
1951 break;
1952 }
1953 }
1954
1955 setMsgId(showAtMsgId);
1956 if (_historyInited) {
1957 const auto to = countInitialScrollTop();
1958 const auto item = getItemFromHistoryOrMigrated(
1959 _showAtMsgId);
1960 animatedScrollToY(
1961 std::clamp(to, 0, _scroll->scrollTopMax()),
1962 item);
1963 } else {
1964 historyLoaded();
1965 }
1966 }
1967
1968 _topBar->update();
1969 update();
1970
1971 if (const auto user = _peer->asUser()) {
1972 if (const auto &info = user->botInfo) {
1973 if (startBot) {
1974 if (wasDialogsEntryState.key) {
1975 info->inlineReturnTo = wasDialogsEntryState;
1976 }
1977 sendBotStartCommand();
1978 _history->clearLocalDraft();
1979 applyDraft();
1980 _send->finishAnimating();
1981 }
1982 }
1983 }
1984 return;
1985 } else {
1986 session().data().sponsoredMessages().clearItems(_history);
1987 }
1988 session().sendProgressManager().update(
1989 _history,
1990 Api::SendProgressType::Typing,
1991 -1);
1992 session().data().histories().sendPendingReadInbox(_history);
1993 session().sendProgressManager().cancelTyping(_history);
1994 }
1995
1996 clearReplyReturns();
1997 if (_history) {
1998 if (Ui::InFocusChain(_list)) {
1999 // Removing focus from list clears selected and updates top bar.
2000 setFocus();
2001 }
2002 controller()->session().api().saveCurrentDraftToCloud();
2003 if (_migrated) {
2004 _migrated->clearDrafts(); // use migrated draft only once
2005 }
2006
2007 _history->showAtMsgId = _showAtMsgId;
2008
2009 destroyUnreadBarOnClose();
2010 _pinnedBar = nullptr;
2011 _pinnedTracker = nullptr;
2012 _groupCallBar = nullptr;
2013 _requestsBar = nullptr;
2014 _chooseTheme = nullptr;
2015 _membersDropdown.destroy();
2016 _scrollToAnimation.stop();
2017
2018 clearAllLoadRequests();
2019 setHistory(nullptr);
2020 _list = nullptr;
2021 _peer = nullptr;
2022 _channel = NoChannel;
2023 _canSendMessages = false;
2024 _silent.destroy();
2025 updateBotKeyboard();
2026 } else {
2027 Assert(_list == nullptr);
2028 }
2029
2030 App::clearMousedItems();
2031
2032 _saveEditMsgRequestId = 0;
2033 _replyEditMsg = nullptr;
2034 _editMsgId = _replyToId = 0;
2035 _previewData = nullptr;
2036 _previewCache.clear();
2037 _fieldBarCancel->hide();
2038
2039 _membersDropdownShowTimer.cancel();
2040 _scroll->takeWidget<HistoryInner>().destroy();
2041
2042 clearInlineBot();
2043
2044 _showAtMsgId = showAtMsgId;
2045 _historyInited = false;
2046 _contactStatus = nullptr;
2047
2048 // Unload lottie animations.
2049 session().data().unloadHeavyViewParts(HistoryInner::ElementDelegate());
2050
2051 if (peerId) {
2052 _peer = session().data().peer(peerId);
2053 _channel = peerToChannel(_peer->id);
2054 _canSendMessages = _peer->canWrite();
2055 _contactStatus = std::make_unique<HistoryView::ContactStatus>(
2056 controller(),
2057 this,
2058 _peer);
2059 _contactStatus->heightValue() | rpl::start_with_next([=] {
2060 updateControlsGeometry();
2061 }, _contactStatus->lifetime());
2062 orderWidgets();
2063 controller()->tabbedSelector()->setCurrentPeer(_peer);
2064 }
2065 refreshTabbedPanel();
2066
2067 if (_peer) {
2068 _unblock->setText(((_peer->isUser()
2069 && _peer->asUser()->isBot()
2070 && !_peer->asUser()->isSupport())
2071 ? tr::lng_restart_button(tr::now)
2072 : tr::lng_unblock_button(tr::now)).toUpper());
2073 if (const auto channel = _peer->asChannel()) {
2074 channel->updateFull();
2075 _joinChannel->setText((channel->isMegagroup()
2076 ? tr::lng_profile_join_group(tr::now)
2077 : tr::lng_profile_join_channel(tr::now)).toUpper());
2078 }
2079 }
2080
2081 _nonEmptySelection = false;
2082 _itemRevealPending.clear();
2083 _itemRevealAnimations.clear();
2084 _itemsRevealHeight = 0;
2085
2086 if (_peer) {
2087 setHistory(_peer->owner().history(_peer));
2088 if (_migrated
2089 && !_migrated->isEmpty()
2090 && (!_history->loadedAtTop() || !_migrated->loadedAtBottom())) {
2091 _migrated->clear(History::ClearType::Unload);
2092 }
2093 _history->setFakeUnreadWhileOpened(true);
2094
2095 if (_showAtMsgId == ShowForChooseMessagesMsgId) {
2096 _showAtMsgId = ShowAtUnreadMsgId;
2097 if (_chooseForReport) {
2098 _chooseForReport->active = true;
2099 }
2100 } else {
2101 _chooseForReport = nullptr;
2102 }
2103 refreshTopBarActiveChat();
2104 updateTopBarSelection();
2105
2106 if (_channel) {
2107 updateNotifyControls();
2108 session().data().requestNotifySettings(_peer);
2109 refreshSilentToggle();
2110 } else if (_peer->isRepliesChat()) {
2111 updateNotifyControls();
2112 }
2113 refreshScheduledToggle();
2114
2115 if (_showAtMsgId == ShowAtUnreadMsgId) {
2116 if (_history->scrollTopItem) {
2117 _showAtMsgId = _history->showAtMsgId;
2118 }
2119 } else {
2120 _history->forgetScrollState();
2121 if (_migrated) {
2122 _migrated->forgetScrollState();
2123 }
2124 }
2125
2126 _scroll->hide();
2127 _list = _scroll->setOwnedWidget(
2128 object_ptr<HistoryInner>(this, _scroll, controller(), _history));
2129 _list->show();
2130
2131 if (_chooseForReport && _chooseForReport->active) {
2132 _list->setChooseReportReason(_chooseForReport->reason);
2133 }
2134 updateTopBarChooseForReport();
2135
2136 _updateHistoryItems.cancel();
2137
2138 setupPinnedTracker();
2139 setupGroupCallBar();
2140 setupRequestsBar();
2141 checkMessagesTTL();
2142 if (_history->scrollTopItem
2143 || (_migrated && _migrated->scrollTopItem)
2144 || _history->isReadyFor(_showAtMsgId)) {
2145 historyLoaded();
2146 } else {
2147 firstLoadMessages();
2148 doneShow();
2149 }
2150
2151 handlePeerUpdate();
2152
2153 session().local().readDraftsWithCursors(_history);
2154 applyDraft();
2155 _send->finishAnimating();
2156
2157 updateControlsGeometry();
2158
2159 if (const auto user = _peer->asUser()) {
2160 if (const auto &info = user->botInfo) {
2161 if (startBot) {
2162 if (wasDialogsEntryState.key) {
2163 info->inlineReturnTo = wasDialogsEntryState;
2164 }
2165 sendBotStartCommand();
2166 }
2167 }
2168 }
2169 if (!_history->folderKnown()) {
2170 session().data().histories().requestDialogEntry(_history);
2171 }
2172 if (_history->chatListUnreadMark()) {
2173 _history->owner().histories().changeDialogUnreadMark(
2174 _history,
2175 false);
2176 if (_migrated) {
2177 _migrated->owner().histories().changeDialogUnreadMark(
2178 _migrated,
2179 false);
2180 }
2181
2182 // Must be done before unreadCountUpdated(), or we auto-close.
2183 _history->setUnreadMark(false);
2184 if (_migrated) {
2185 _migrated->setUnreadMark(false);
2186 }
2187 }
2188 unreadCountUpdated(); // set _historyDown badge.
2189 showAboutTopPromotion();
2190
2191 {
2192 auto &sponsored = session().data().sponsoredMessages();
2193 sponsored.request(_history);
2194 _scroll->setTrackingContent(sponsored.canHaveFor(_history));
2195 }
2196 } else {
2197 _chooseForReport = nullptr;
2198 refreshTopBarActiveChat();
2199 updateTopBarSelection();
2200 checkMessagesTTL();
2201 // Restore default theme.
2202 controller()->setChatStyleTheme(controller()->defaultChatTheme());
2203 clearFieldText();
2204 doneShow();
2205 }
2206 updateForwarding();
2207 updateOverStates(mapFromGlobal(QCursor::pos()));
2208
2209 if (_history) {
2210 controller()->setActiveChatEntry({
2211 _history,
2212 FullMsgId(_history->channelId(), _showAtMsgId) });
2213 }
2214 update();
2215 controller()->floatPlayerAreaUpdated();
2216
2217 crl::on_main(this, [=] { controller()->widget()->setInnerFocus(); });
2218 }
2219
setHistory(History * history)2220 void HistoryWidget::setHistory(History *history) {
2221 if (_history == history) {
2222 return;
2223 }
2224 unregisterDraftSources();
2225 _history = history;
2226 _migrated = _history ? _history->migrateFrom() : nullptr;
2227 registerDraftSource();
2228 }
2229
unregisterDraftSources()2230 void HistoryWidget::unregisterDraftSources() {
2231 if (!_history) {
2232 return;
2233 }
2234 session().local().unregisterDraftSource(
2235 _history,
2236 Data::DraftKey::Local());
2237 session().local().unregisterDraftSource(
2238 _history,
2239 Data::DraftKey::LocalEdit());
2240 }
2241
registerDraftSource()2242 void HistoryWidget::registerDraftSource() {
2243 if (!_history) {
2244 return;
2245 }
2246 const auto editMsgId = _editMsgId;
2247 const auto draft = [=] {
2248 return Storage::MessageDraft{
2249 editMsgId ? editMsgId : _replyToId,
2250 _field->getTextWithTags(),
2251 _previewState,
2252 };
2253 };
2254 auto draftSource = Storage::MessageDraftSource{
2255 .draft = draft,
2256 .cursor = [=] { return MessageCursor(_field); },
2257 };
2258 session().local().registerDraftSource(
2259 _history,
2260 editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
2261 std::move(draftSource));
2262 }
2263
setEditMsgId(MsgId msgId)2264 void HistoryWidget::setEditMsgId(MsgId msgId) {
2265 unregisterDraftSources();
2266 _editMsgId = msgId;
2267 registerDraftSource();
2268 }
2269
clearDelayedShowAt()2270 void HistoryWidget::clearDelayedShowAt() {
2271 _delayedShowAtMsgId = -1;
2272 clearDelayedShowAtRequest();
2273 }
2274
clearDelayedShowAtRequest()2275 void HistoryWidget::clearDelayedShowAtRequest() {
2276 Expects(_history != nullptr);
2277
2278 if (_delayedShowAtRequest) {
2279 _history->owner().histories().cancelRequest(_delayedShowAtRequest);
2280 _delayedShowAtRequest = 0;
2281 }
2282 }
2283
clearAllLoadRequests()2284 void HistoryWidget::clearAllLoadRequests() {
2285 Expects(_history != nullptr);
2286
2287 auto &histories = _history->owner().histories();
2288 clearDelayedShowAtRequest();
2289 if (_firstLoadRequest) {
2290 histories.cancelRequest(_firstLoadRequest);
2291 _firstLoadRequest = 0;
2292 }
2293 if (_preloadRequest) {
2294 histories.cancelRequest(_preloadRequest);
2295 _preloadRequest = 0;
2296 }
2297 if (_preloadDownRequest) {
2298 histories.cancelRequest(_preloadDownRequest);
2299 _preloadDownRequest = 0;
2300 }
2301 }
2302
updateFieldSubmitSettings()2303 void HistoryWidget::updateFieldSubmitSettings() {
2304 const auto settings = _isInlineBot
2305 ? Ui::InputField::SubmitSettings::None
2306 : Core::App().settings().sendSubmitWay();
2307 _field->setSubmitSettings(settings);
2308 }
2309
updateNotifyControls()2310 void HistoryWidget::updateNotifyControls() {
2311 if (!_peer || (!_peer->isChannel() && !_peer->isRepliesChat())) {
2312 return;
2313 }
2314
2315 _muteUnmute->setText((_history->mute()
2316 ? tr::lng_channel_unmute(tr::now)
2317 : tr::lng_channel_mute(tr::now)).toUpper());
2318 if (!session().data().notifySilentPostsUnknown(_peer)) {
2319 if (_silent) {
2320 _silent->setChecked(session().data().notifySilentPosts(_peer));
2321 updateFieldPlaceholder();
2322 } else if (hasSilentToggle()) {
2323 refreshSilentToggle();
2324 updateControlsVisibility();
2325 updateControlsGeometry();
2326 }
2327 }
2328 }
2329
refreshSilentToggle()2330 void HistoryWidget::refreshSilentToggle() {
2331 if (!_silent && hasSilentToggle()) {
2332 _silent.create(this, _peer->asChannel());
2333 orderWidgets();
2334 } else if (_silent && !hasSilentToggle()) {
2335 _silent.destroy();
2336 }
2337 }
2338
setupScheduledToggle()2339 void HistoryWidget::setupScheduledToggle() {
2340 controller()->activeChatValue(
2341 ) | rpl::map([=](const Dialogs::Key &key) -> rpl::producer<> {
2342 if (const auto history = key.history()) {
2343 return session().data().scheduledMessages().updates(history);
2344 }
2345 return rpl::never<rpl::empty_value>();
2346 }) | rpl::flatten_latest(
2347 ) | rpl::start_with_next([=] {
2348 refreshScheduledToggle();
2349 updateControlsVisibility();
2350 updateControlsGeometry();
2351 }, lifetime());
2352 }
2353
refreshScheduledToggle()2354 void HistoryWidget::refreshScheduledToggle() {
2355 const auto has = _history
2356 && _peer->canWrite()
2357 && (session().data().scheduledMessages().count(_history) > 0);
2358 if (!_scheduled && has) {
2359 _scheduled.create(this, st::historyScheduledToggle);
2360 _scheduled->show();
2361 _scheduled->addClickHandler([=] {
2362 controller()->showSection(
2363 std::make_shared<HistoryView::ScheduledMemento>(_history));
2364 });
2365 orderWidgets(); // Raise drag areas to the top.
2366 } else if (_scheduled && !has) {
2367 _scheduled.destroy();
2368 }
2369 }
2370
contentOverlapped(const QRect & globalRect)2371 bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
2372 return (_attachDragAreas.document->overlaps(globalRect)
2373 || _attachDragAreas.photo->overlaps(globalRect)
2374 || _fieldAutocomplete->overlaps(globalRect)
2375 || (_tabbedPanel && _tabbedPanel->overlaps(globalRect))
2376 || (_inlineResults && _inlineResults->overlaps(globalRect)));
2377 }
2378
canWriteMessage() const2379 bool HistoryWidget::canWriteMessage() const {
2380 if (!_history || !_canSendMessages) return false;
2381 if (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart()) return false;
2382 return true;
2383 }
2384
writeRestriction() const2385 std::optional<QString> HistoryWidget::writeRestriction() const {
2386 return _peer
2387 ? Data::RestrictionError(_peer, ChatRestriction::SendMessages)
2388 : std::nullopt;
2389 }
2390
updateControlsVisibility()2391 void HistoryWidget::updateControlsVisibility() {
2392 if (!_a_show.animating()) {
2393 _topShadow->setVisible(_peer != nullptr);
2394 _topBar->setVisible(_peer != nullptr);
2395 }
2396 updateHistoryDownVisibility();
2397 updateUnreadMentionsVisibility();
2398 if (!_history || _a_show.animating()) {
2399 hideChildWidgets();
2400 return;
2401 }
2402
2403 if (_pinnedBar) {
2404 _pinnedBar->show();
2405 }
2406 if (_groupCallBar) {
2407 _groupCallBar->show();
2408 }
2409 if (_requestsBar) {
2410 _requestsBar->show();
2411 }
2412 if (_firstLoadRequest && !_scroll->isHidden()) {
2413 _scroll->hide();
2414 } else if (!_firstLoadRequest && _scroll->isHidden()) {
2415 _scroll->show();
2416 }
2417 if (_contactStatus) {
2418 _contactStatus->show();
2419 }
2420 if (isChoosingTheme()
2421 || (!editingMessage()
2422 && (isBlocked()
2423 || isJoinChannel()
2424 || isMuteUnmute()
2425 || isBotStart()
2426 || isReportMessages()))) {
2427 const auto toggle = [&](Ui::FlatButton *shown) {
2428 const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
2429 if (button.get() != shown) {
2430 button->hide();
2431 } else if (button->isHidden()) {
2432 button->clearState();
2433 button->show();
2434 }
2435 };
2436 toggleOne(_reportMessages);
2437 toggleOne(_joinChannel);
2438 toggleOne(_muteUnmute);
2439 toggleOne(_botStart);
2440 toggleOne(_unblock);
2441 };
2442 if (isChoosingTheme()) {
2443 _chooseTheme->show();
2444 setInnerFocus();
2445 toggle(nullptr);
2446 } else if (isReportMessages()) {
2447 toggle(_reportMessages);
2448 } else if (isBlocked()) {
2449 toggle(_unblock);
2450 } else if (isJoinChannel()) {
2451 toggle(_joinChannel);
2452 } else if (isMuteUnmute()) {
2453 toggle(_muteUnmute);
2454 } else if (isBotStart()) {
2455 toggle(_botStart);
2456 }
2457 _kbShown = false;
2458 _fieldAutocomplete->hide();
2459 if (_supportAutocomplete) {
2460 _supportAutocomplete->hide();
2461 }
2462 _send->hide();
2463 if (_silent) {
2464 _silent->hide();
2465 }
2466 if (_scheduled) {
2467 _scheduled->hide();
2468 }
2469 if (_ttlInfo) {
2470 _ttlInfo->hide();
2471 }
2472 _kbScroll->hide();
2473 _fieldBarCancel->hide();
2474 _attachToggle->hide();
2475 _tabbedSelectorToggle->hide();
2476 _botKeyboardShow->hide();
2477 _botKeyboardHide->hide();
2478 _botCommandStart->hide();
2479 if (_tabbedPanel) {
2480 _tabbedPanel->hide();
2481 }
2482 if (_voiceRecordBar) {
2483 _voiceRecordBar->hideFast();
2484 }
2485 if (_inlineResults) {
2486 _inlineResults->hide();
2487 }
2488 if (!_field->isHidden()) {
2489 _field->hide();
2490 updateControlsGeometry();
2491 update();
2492 }
2493 } else if (editingMessage() || _canSendMessages) {
2494 checkFieldAutocomplete();
2495 _unblock->hide();
2496 _botStart->hide();
2497 _joinChannel->hide();
2498 _muteUnmute->hide();
2499 _reportMessages->hide();
2500 _send->show();
2501 updateSendButtonType();
2502
2503 _field->show();
2504 if (_kbShown) {
2505 _kbScroll->show();
2506 _tabbedSelectorToggle->hide();
2507 _botKeyboardHide->show();
2508 _botKeyboardShow->hide();
2509 _botCommandStart->hide();
2510 } else if (_kbReplyTo) {
2511 _kbScroll->hide();
2512 _tabbedSelectorToggle->show();
2513 _botKeyboardHide->hide();
2514 _botKeyboardShow->hide();
2515 _botCommandStart->hide();
2516 } else {
2517 _kbScroll->hide();
2518 _tabbedSelectorToggle->show();
2519 _botKeyboardHide->hide();
2520 if (_keyboard->hasMarkup()) {
2521 _botKeyboardShow->show();
2522 _botCommandStart->hide();
2523 } else {
2524 _botKeyboardShow->hide();
2525 _botCommandStart->setVisible(_cmdStartShown);
2526 }
2527 }
2528 _attachToggle->show();
2529 if (_silent) {
2530 _silent->show();
2531 }
2532 if (_scheduled) {
2533 _scheduled->show();
2534 }
2535 if (_ttlInfo) {
2536 _ttlInfo->show();
2537 }
2538 updateFieldPlaceholder();
2539
2540 if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) {
2541 if (_fieldBarCancel->isHidden()) {
2542 _fieldBarCancel->show();
2543 updateControlsGeometry();
2544 update();
2545 }
2546 } else {
2547 _fieldBarCancel->hide();
2548 }
2549 } else {
2550 _fieldAutocomplete->hide();
2551 if (_supportAutocomplete) {
2552 _supportAutocomplete->hide();
2553 }
2554 _send->hide();
2555 _unblock->hide();
2556 _botStart->hide();
2557 _joinChannel->hide();
2558 _muteUnmute->hide();
2559 _reportMessages->hide();
2560 _attachToggle->hide();
2561 if (_silent) {
2562 _silent->hide();
2563 }
2564 if (_scheduled) {
2565 _scheduled->hide();
2566 }
2567 if (_ttlInfo) {
2568 _ttlInfo->hide();
2569 }
2570 _kbScroll->hide();
2571 _fieldBarCancel->hide();
2572 _attachToggle->hide();
2573 _tabbedSelectorToggle->hide();
2574 _botKeyboardShow->hide();
2575 _botKeyboardHide->hide();
2576 _botCommandStart->hide();
2577 if (_tabbedPanel) {
2578 _tabbedPanel->hide();
2579 }
2580 if (_voiceRecordBar) {
2581 _voiceRecordBar->hideFast();
2582 }
2583 if (_inlineResults) {
2584 _inlineResults->hide();
2585 }
2586 _kbScroll->hide();
2587 if (!_field->isHidden()) {
2588 _field->hide();
2589 updateControlsGeometry();
2590 update();
2591 }
2592 }
2593 //checkTabbedSelectorToggleTooltip();
2594 updateMouseTracking();
2595 }
2596
showAboutTopPromotion()2597 void HistoryWidget::showAboutTopPromotion() {
2598 Expects(_history != nullptr);
2599 Expects(_list != nullptr);
2600
2601 if (!_history->useTopPromotion() || _history->topPromotionAboutShown()) {
2602 return;
2603 }
2604 _history->markTopPromotionAboutShown();
2605 const auto type = _history->topPromotionType();
2606 const auto custom = type.isEmpty()
2607 ? QString()
2608 : Lang::GetNonDefaultValue(kPsaAboutPrefix + type.toUtf8());
2609 const auto text = type.isEmpty()
2610 ? tr::lng_proxy_sponsor_about(tr::now, Ui::Text::RichLangValue)
2611 : custom.isEmpty()
2612 ? tr::lng_about_psa_default(tr::now, Ui::Text::RichLangValue)
2613 : Ui::Text::RichLangValue(custom);
2614 showInfoTooltip(text, nullptr);
2615 }
2616
updateMouseTracking()2617 void HistoryWidget::updateMouseTracking() {
2618 const auto trackMouse = !_fieldBarCancel->isHidden();
2619 setMouseTracking(trackMouse);
2620 }
2621
destroyUnreadBar()2622 void HistoryWidget::destroyUnreadBar() {
2623 if (_history) _history->destroyUnreadBar();
2624 if (_migrated) _migrated->destroyUnreadBar();
2625 }
2626
destroyUnreadBarOnClose()2627 void HistoryWidget::destroyUnreadBarOnClose() {
2628 if (!_history || !_historyInited) {
2629 return;
2630 } else if (_scroll->scrollTop() == _scroll->scrollTopMax()) {
2631 destroyUnreadBar();
2632 return;
2633 }
2634 const auto top = unreadBarTop();
2635 if (top && *top < _scroll->scrollTop()) {
2636 destroyUnreadBar();
2637 return;
2638 }
2639 }
2640
newItemAdded(not_null<HistoryItem * > item)2641 void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
2642 if (_history != item->history()
2643 || !_historyInited
2644 || item->isScheduled()) {
2645 return;
2646 }
2647 if (item->isSponsored()) {
2648 if (const auto view = item->mainView()) {
2649 view->resizeGetHeight(width());
2650 updateHistoryGeometry(
2651 false,
2652 true,
2653 { ScrollChangeNoJumpToBottom, 0 });
2654 }
2655 return;
2656 }
2657
2658 // If we get here in non-resized state we can't rely on results of
2659 // doWeReadServerHistory() and mark chat as read.
2660 // If we receive N messages being not at bottom:
2661 // - on first message we set unreadcount += 1, firstUnreadMessage.
2662 // - on second we get wrong doWeReadServerHistory() and read both.
2663 session().data().sendHistoryChangeNotifications();
2664
2665 if (item->isSending()) {
2666 synteticScrollToY(_scroll->scrollTopMax());
2667 } else if (_scroll->scrollTop() < _scroll->scrollTopMax()) {
2668 return;
2669 }
2670 if (item->showNotification()) {
2671 destroyUnreadBar();
2672 if (doWeReadServerHistory()) {
2673 if (item->isUnreadMention() && !item->isUnreadMedia()) {
2674 session().api().markMediaRead(item);
2675 }
2676 session().data().histories().readInboxOnNewMessage(item);
2677
2678 // Also clear possible scheduled messages notifications.
2679 Core::App().notifications().clearFromHistory(_history);
2680 }
2681 }
2682 const auto view = item->mainView();
2683 if (anim::Disabled() || !view) {
2684 return;
2685 }
2686 _itemRevealPending.emplace(item);
2687 }
2688
unreadCountUpdated()2689 void HistoryWidget::unreadCountUpdated() {
2690 if (_history->chatListUnreadMark()) {
2691 crl::on_main(this, [=, history = _history] {
2692 if (history == _history) {
2693 controller()->showBackFromStack();
2694 _cancelRequests.fire({});
2695 }
2696 });
2697 } else {
2698 updateHistoryDownVisibility();
2699 _historyDown->setUnreadCount(_history->chatListUnreadCount());
2700 }
2701 }
2702
messagesFailed(const MTP::Error & error,int requestId)2703 void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) {
2704 if (error.type() == qstr("CHANNEL_PRIVATE")
2705 && _peer->isChannel()
2706 && _peer->asChannel()->invitePeekExpires()) {
2707 _peer->asChannel()->privateErrorReceived();
2708 } else if (error.type() == qstr("CHANNEL_PRIVATE")
2709 || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA")
2710 || error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
2711 auto was = _peer;
2712 controller()->showBackFromStack();
2713 Ui::ShowMultilineToast({
2714 .text = { (was && was->isMegagroup())
2715 ? tr::lng_group_not_accessible(tr::now)
2716 : tr::lng_channel_not_accessible(tr::now) },
2717 });
2718 return;
2719 }
2720
2721 LOG(("RPC Error: %1 %2: %3").arg(
2722 QString::number(error.code()),
2723 error.type(),
2724 error.description()));
2725
2726 if (_preloadRequest == requestId) {
2727 _preloadRequest = 0;
2728 } else if (_preloadDownRequest == requestId) {
2729 _preloadDownRequest = 0;
2730 } else if (_firstLoadRequest == requestId) {
2731 _firstLoadRequest = 0;
2732 controller()->showBackFromStack();
2733 } else if (_delayedShowAtRequest == requestId) {
2734 _delayedShowAtRequest = 0;
2735 }
2736 }
2737
messagesReceived(PeerData * peer,const MTPmessages_Messages & messages,int requestId)2738 void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, int requestId) {
2739 Expects(_history != nullptr);
2740
2741 bool toMigrated = (peer == _peer->migrateFrom());
2742 if (peer != _peer && !toMigrated) {
2743 if (_preloadRequest == requestId) {
2744 _preloadRequest = 0;
2745 } else if (_preloadDownRequest == requestId) {
2746 _preloadDownRequest = 0;
2747 } else if (_firstLoadRequest == requestId) {
2748 _firstLoadRequest = 0;
2749 } else if (_delayedShowAtRequest == requestId) {
2750 _delayedShowAtRequest = 0;
2751 }
2752 return;
2753 }
2754
2755 auto count = 0;
2756 const QVector<MTPMessage> emptyList, *histList = &emptyList;
2757 switch (messages.type()) {
2758 case mtpc_messages_messages: {
2759 auto &d(messages.c_messages_messages());
2760 _history->owner().processUsers(d.vusers());
2761 _history->owner().processChats(d.vchats());
2762 histList = &d.vmessages().v;
2763 count = histList->size();
2764 } break;
2765 case mtpc_messages_messagesSlice: {
2766 auto &d(messages.c_messages_messagesSlice());
2767 _history->owner().processUsers(d.vusers());
2768 _history->owner().processChats(d.vchats());
2769 histList = &d.vmessages().v;
2770 count = d.vcount().v;
2771 } break;
2772 case mtpc_messages_channelMessages: {
2773 auto &d(messages.c_messages_channelMessages());
2774 if (peer && peer->isChannel()) {
2775 peer->asChannel()->ptsReceived(d.vpts().v);
2776 } else {
2777 LOG(("API Error: received messages.channelMessages when no channel was passed! (HistoryWidget::messagesReceived)"));
2778 }
2779 _history->owner().processUsers(d.vusers());
2780 _history->owner().processChats(d.vchats());
2781 histList = &d.vmessages().v;
2782 count = d.vcount().v;
2783 } break;
2784 case mtpc_messages_messagesNotModified: {
2785 LOG(("API Error: received messages.messagesNotModified! (HistoryWidget::messagesReceived)"));
2786 } break;
2787 }
2788
2789 if (_preloadRequest == requestId) {
2790 addMessagesToFront(peer, *histList);
2791 _preloadRequest = 0;
2792 preloadHistoryIfNeeded();
2793 } else if (_preloadDownRequest == requestId) {
2794 addMessagesToBack(peer, *histList);
2795 _preloadDownRequest = 0;
2796 preloadHistoryIfNeeded();
2797 if (_history->loadedAtBottom()) {
2798 checkHistoryActivation();
2799 }
2800 } else if (_firstLoadRequest == requestId) {
2801 if (toMigrated) {
2802 _history->clear(History::ClearType::Unload);
2803 } else if (_migrated) {
2804 _migrated->clear(History::ClearType::Unload);
2805 }
2806 addMessagesToFront(peer, *histList);
2807 _firstLoadRequest = 0;
2808 if (_history->loadedAtTop() && _history->isEmpty() && count > 0) {
2809 firstLoadMessages();
2810 return;
2811 }
2812
2813 historyLoaded();
2814 } else if (_delayedShowAtRequest == requestId) {
2815 if (toMigrated) {
2816 _history->clear(History::ClearType::Unload);
2817 } else if (_migrated) {
2818 _migrated->clear(History::ClearType::Unload);
2819 }
2820
2821 clearAllLoadRequests();
2822 _firstLoadRequest = -1; // hack - don't updateListSize yet
2823 _history->getReadyFor(_delayedShowAtMsgId);
2824 if (_history->isEmpty()) {
2825 addMessagesToFront(peer, *histList);
2826 }
2827 _firstLoadRequest = 0;
2828
2829 if (_history->loadedAtTop()
2830 && _history->isEmpty()
2831 && count > 0) {
2832 firstLoadMessages();
2833 return;
2834 }
2835 while (_replyReturn) {
2836 if (_replyReturn->history() == _history
2837 && _replyReturn->id == _delayedShowAtMsgId) {
2838 calcNextReplyReturn();
2839 } else if (_replyReturn->history() == _migrated
2840 && -_replyReturn->id == _delayedShowAtMsgId) {
2841 calcNextReplyReturn();
2842 } else {
2843 break;
2844 }
2845 }
2846
2847 _delayedShowAtRequest = 0;
2848 setMsgId(_delayedShowAtMsgId);
2849 historyLoaded();
2850 }
2851 }
2852
historyLoaded()2853 void HistoryWidget::historyLoaded() {
2854 _historyInited = false;
2855 doneShow();
2856 }
2857
windowShown()2858 void HistoryWidget::windowShown() {
2859 updateControlsGeometry();
2860 }
2861
doWeReadServerHistory() const2862 bool HistoryWidget::doWeReadServerHistory() const {
2863 return doWeReadMentions() && !session().supportMode();
2864 }
2865
doWeReadMentions() const2866 bool HistoryWidget::doWeReadMentions() const {
2867 return _history
2868 && _list
2869 && _historyInited
2870 && !_firstLoadRequest
2871 && !_delayedShowAtRequest
2872 && !_a_show.animating()
2873 && controller()->widget()->doWeMarkAsRead();
2874 }
2875
checkHistoryActivation()2876 void HistoryWidget::checkHistoryActivation() {
2877 if (_list) {
2878 _list->checkHistoryActivation();
2879 }
2880 }
2881
firstLoadMessages()2882 void HistoryWidget::firstLoadMessages() {
2883 if (!_history || _firstLoadRequest) {
2884 return;
2885 }
2886
2887 auto from = _history;
2888 auto offsetId = MsgId();
2889 auto offset = 0;
2890 auto loadCount = kMessagesPerPage;
2891 if (_showAtMsgId == ShowAtUnreadMsgId) {
2892 if (const auto around = _migrated ? _migrated->loadAroundId() : 0) {
2893 _history->getReadyFor(_showAtMsgId);
2894 from = _migrated;
2895 offset = -loadCount / 2;
2896 offsetId = around;
2897 } else if (const auto around = _history->loadAroundId()) {
2898 _history->getReadyFor(_showAtMsgId);
2899 offset = -loadCount / 2;
2900 offsetId = around;
2901 } else {
2902 _history->getReadyFor(ShowAtTheEndMsgId);
2903 }
2904 } else if (_showAtMsgId == ShowAtTheEndMsgId) {
2905 _history->getReadyFor(_showAtMsgId);
2906 loadCount = kMessagesPerPageFirst;
2907 } else if (_showAtMsgId > 0) {
2908 _history->getReadyFor(_showAtMsgId);
2909 offset = -loadCount / 2;
2910 offsetId = _showAtMsgId;
2911 } else if (_showAtMsgId < 0 && _history->isChannel()) {
2912 if (_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId && _migrated) {
2913 _history->getReadyFor(_showAtMsgId);
2914 from = _migrated;
2915 offset = -loadCount / 2;
2916 offsetId = -_showAtMsgId;
2917 } else if (_showAtMsgId == SwitchAtTopMsgId) {
2918 _history->getReadyFor(_showAtMsgId);
2919 }
2920 }
2921
2922 const auto offsetDate = 0;
2923 const auto maxId = 0;
2924 const auto minId = 0;
2925 const auto historyHash = uint64(0);
2926
2927 const auto history = from;
2928 const auto type = Data::Histories::RequestType::History;
2929 auto &histories = history->owner().histories();
2930 _firstLoadRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
2931 return history->session().api().request(MTPmessages_GetHistory(
2932 history->peer->input,
2933 MTP_int(offsetId),
2934 MTP_int(offsetDate),
2935 MTP_int(offset),
2936 MTP_int(loadCount),
2937 MTP_int(maxId),
2938 MTP_int(minId),
2939 MTP_long(historyHash)
2940 )).done([=](const MTPmessages_Messages &result) {
2941 messagesReceived(history->peer, result, _firstLoadRequest);
2942 finish();
2943 }).fail([=](const MTP::Error &error) {
2944 messagesFailed(error, _firstLoadRequest);
2945 finish();
2946 }).send();
2947 });
2948 }
2949
loadMessages()2950 void HistoryWidget::loadMessages() {
2951 if (!_history || _preloadRequest) {
2952 return;
2953 }
2954
2955 if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
2956 return firstLoadMessages();
2957 }
2958
2959 auto loadMigrated = _migrated
2960 && (_history->isEmpty()
2961 || _history->loadedAtTop()
2962 || (!_migrated->isEmpty() && !_migrated->loadedAtBottom()));
2963 const auto from = loadMigrated ? _migrated : _history;
2964 if (from->loadedAtTop()) {
2965 return;
2966 }
2967
2968 const auto offsetId = from->minMsgId();
2969 const auto addOffset = 0;
2970 const auto loadCount = offsetId
2971 ? kMessagesPerPage
2972 : kMessagesPerPageFirst;
2973 const auto offsetDate = 0;
2974 const auto maxId = 0;
2975 const auto minId = 0;
2976 const auto historyHash = uint64(0);
2977
2978 const auto history = from;
2979 const auto type = Data::Histories::RequestType::History;
2980 auto &histories = history->owner().histories();
2981 _preloadRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
2982 return history->session().api().request(MTPmessages_GetHistory(
2983 history->peer->input,
2984 MTP_int(offsetId),
2985 MTP_int(offsetDate),
2986 MTP_int(addOffset),
2987 MTP_int(loadCount),
2988 MTP_int(maxId),
2989 MTP_int(minId),
2990 MTP_long(historyHash)
2991 )).done([=](const MTPmessages_Messages &result) {
2992 messagesReceived(history->peer, result, _preloadRequest);
2993 finish();
2994 }).fail([=](const MTP::Error &error) {
2995 messagesFailed(error, _preloadRequest);
2996 finish();
2997 }).send();
2998 });
2999 }
3000
loadMessagesDown()3001 void HistoryWidget::loadMessagesDown() {
3002 if (!_history || _preloadDownRequest) {
3003 return;
3004 }
3005
3006 if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
3007 return firstLoadMessages();
3008 }
3009
3010 auto loadMigrated = _migrated && !(_migrated->isEmpty() || _migrated->loadedAtBottom() || (!_history->isEmpty() && !_history->loadedAtTop()));
3011 auto from = loadMigrated ? _migrated : _history;
3012 if (from->loadedAtBottom()) {
3013 session().data().sponsoredMessages().request(_history);
3014 return;
3015 }
3016
3017 const auto loadCount = kMessagesPerPage;
3018 auto addOffset = -loadCount;
3019 auto offsetId = from->maxMsgId();
3020 if (!offsetId) {
3021 if (loadMigrated || !_migrated) return;
3022 ++offsetId;
3023 ++addOffset;
3024 }
3025 const auto offsetDate = 0;
3026 const auto maxId = 0;
3027 const auto minId = 0;
3028 const auto historyHash = uint64(0);
3029
3030 const auto history = from;
3031 const auto type = Data::Histories::RequestType::History;
3032 auto &histories = history->owner().histories();
3033 _preloadDownRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
3034 return history->session().api().request(MTPmessages_GetHistory(
3035 history->peer->input,
3036 MTP_int(offsetId + 1),
3037 MTP_int(offsetDate),
3038 MTP_int(addOffset),
3039 MTP_int(loadCount),
3040 MTP_int(maxId),
3041 MTP_int(minId),
3042 MTP_long(historyHash)
3043 )).done([=](const MTPmessages_Messages &result) {
3044 messagesReceived(history->peer, result, _preloadDownRequest);
3045 finish();
3046 }).fail([=](const MTP::Error &error) {
3047 messagesFailed(error, _preloadDownRequest);
3048 finish();
3049 }).send();
3050 });
3051 }
3052
delayedShowAt(MsgId showAtMsgId)3053 void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
3054 if (!_history
3055 || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) {
3056 return;
3057 }
3058
3059 clearAllLoadRequests();
3060 _delayedShowAtMsgId = showAtMsgId;
3061
3062 auto from = _history;
3063 auto offsetId = MsgId();
3064 auto offset = 0;
3065 auto loadCount = kMessagesPerPage;
3066 if (_delayedShowAtMsgId == ShowAtUnreadMsgId) {
3067 if (const auto around = _migrated ? _migrated->loadAroundId() : 0) {
3068 from = _migrated;
3069 offset = -loadCount / 2;
3070 offsetId = around;
3071 } else if (const auto around = _history->loadAroundId()) {
3072 offset = -loadCount / 2;
3073 offsetId = around;
3074 } else {
3075 loadCount = kMessagesPerPageFirst;
3076 }
3077 } else if (_delayedShowAtMsgId == ShowAtTheEndMsgId) {
3078 loadCount = kMessagesPerPageFirst;
3079 } else if (_delayedShowAtMsgId > 0) {
3080 offset = -loadCount / 2;
3081 offsetId = _delayedShowAtMsgId;
3082 } else if (_delayedShowAtMsgId < 0 && _history->isChannel()) {
3083 if (_delayedShowAtMsgId < 0 && -_delayedShowAtMsgId < ServerMaxMsgId && _migrated) {
3084 from = _migrated;
3085 offset = -loadCount / 2;
3086 offsetId = -_delayedShowAtMsgId;
3087 }
3088 }
3089 const auto offsetDate = 0;
3090 const auto maxId = 0;
3091 const auto minId = 0;
3092 const auto historyHash = uint64(0);
3093
3094 const auto history = from;
3095 const auto type = Data::Histories::RequestType::History;
3096 auto &histories = history->owner().histories();
3097 _delayedShowAtRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
3098 return history->session().api().request(MTPmessages_GetHistory(
3099 history->peer->input,
3100 MTP_int(offsetId),
3101 MTP_int(offsetDate),
3102 MTP_int(offset),
3103 MTP_int(loadCount),
3104 MTP_int(maxId),
3105 MTP_int(minId),
3106 MTP_long(historyHash)
3107 )).done([=](const MTPmessages_Messages &result) {
3108 messagesReceived(history->peer, result, _delayedShowAtRequest);
3109 finish();
3110 }).fail([=](const MTP::Error &error) {
3111 messagesFailed(error, _delayedShowAtRequest);
3112 finish();
3113 }).send();
3114 });
3115 }
3116
handleScroll()3117 void HistoryWidget::handleScroll() {
3118 if (!_itemsRevealHeight) {
3119 preloadHistoryIfNeeded();
3120 }
3121 visibleAreaUpdated();
3122 if (!_itemsRevealHeight) {
3123 updatePinnedViewer();
3124 }
3125 if (!_synteticScrollEvent) {
3126 _lastUserScrolled = crl::now();
3127 }
3128 const auto scrollTop = _scroll->scrollTop();
3129 if (scrollTop != _lastScrollTop) {
3130 if (!_synteticScrollEvent) {
3131 checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop);
3132 }
3133 _lastScrolled = crl::now();
3134 _lastScrollTop = scrollTop;
3135 }
3136 }
3137
isItemCompletelyHidden(HistoryItem * item) const3138 bool HistoryWidget::isItemCompletelyHidden(HistoryItem *item) const {
3139 const auto view = item ? item->mainView() : nullptr;
3140 if (!view) {
3141 return true;
3142 }
3143 auto top = _list ? _list->itemTop(item) : -2;
3144 if (top < 0) {
3145 return true;
3146 }
3147
3148 auto bottom = top + view->height();
3149 auto scrollTop = _scroll->scrollTop();
3150 auto scrollBottom = scrollTop + _scroll->height();
3151 return (top >= scrollBottom || bottom <= scrollTop);
3152 }
3153
visibleAreaUpdated()3154 void HistoryWidget::visibleAreaUpdated() {
3155 if (_list && !_scroll->isHidden()) {
3156 const auto scrollTop = _scroll->scrollTop();
3157 const auto scrollBottom = scrollTop + _scroll->height();
3158 _list->visibleAreaUpdated(scrollTop, scrollBottom);
3159 controller()->floatPlayerAreaUpdated();
3160 }
3161 }
3162
preloadHistoryIfNeeded()3163 void HistoryWidget::preloadHistoryIfNeeded() {
3164 if (_firstLoadRequest
3165 || _delayedShowAtRequest
3166 || _scroll->isHidden()
3167 || !_peer
3168 || !_historyInited) {
3169 return;
3170 }
3171
3172 updateHistoryDownVisibility();
3173 updateUnreadMentionsVisibility();
3174 if (!_scrollToAnimation.animating()) {
3175 preloadHistoryByScroll();
3176 checkReplyReturns();
3177 }
3178 }
3179
preloadHistoryByScroll()3180 void HistoryWidget::preloadHistoryByScroll() {
3181 if (_firstLoadRequest
3182 || _delayedShowAtRequest
3183 || _scroll->isHidden()
3184 || !_peer
3185 || !_historyInited) {
3186 return;
3187 }
3188
3189 auto scrollTop = _scroll->scrollTop();
3190 auto scrollTopMax = _scroll->scrollTopMax();
3191 auto scrollHeight = _scroll->height();
3192 if (scrollTop + kPreloadHeightsCount * scrollHeight >= scrollTopMax) {
3193 loadMessagesDown();
3194 }
3195 if (scrollTop <= kPreloadHeightsCount * scrollHeight) {
3196 loadMessages();
3197 }
3198 }
3199
checkReplyReturns()3200 void HistoryWidget::checkReplyReturns() {
3201 if (_firstLoadRequest
3202 || _scroll->isHidden()
3203 || !_peer
3204 || !_historyInited) {
3205 return;
3206 }
3207 auto scrollTop = _scroll->scrollTop();
3208 auto scrollTopMax = _scroll->scrollTopMax();
3209 auto scrollHeight = _scroll->height();
3210 while (_replyReturn) {
3211 auto below = (!_replyReturn->mainView() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->messages.back()->data()->id);
3212 if (!below) {
3213 below = (!_replyReturn->mainView() && _replyReturn->history() == _migrated && !_history->isEmpty());
3214 }
3215 if (!below) {
3216 below = (!_replyReturn->mainView() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->messages.back()->data()->id);
3217 }
3218 if (!below && _replyReturn->mainView()) {
3219 below = (scrollTop >= scrollTopMax) || (_list->itemTop(_replyReturn) < scrollTop + scrollHeight / 2);
3220 }
3221 if (below) {
3222 calcNextReplyReturn();
3223 } else {
3224 break;
3225 }
3226 }
3227 }
3228
cancelInlineBot()3229 void HistoryWidget::cancelInlineBot() {
3230 auto &textWithTags = _field->getTextWithTags();
3231 if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
3232 setFieldText(
3233 { '@' + _inlineBotUsername + ' ', TextWithTags::Tags() },
3234 TextUpdateEvent::SaveDraft,
3235 Ui::InputField::HistoryAction::NewEntry);
3236 } else {
3237 clearFieldText(
3238 TextUpdateEvent::SaveDraft,
3239 Ui::InputField::HistoryAction::NewEntry);
3240 }
3241 }
3242
windowIsVisibleChanged()3243 void HistoryWidget::windowIsVisibleChanged() {
3244 InvokeQueued(this, [=] {
3245 preloadHistoryIfNeeded();
3246 });
3247 }
3248
historyDownClicked()3249 void HistoryWidget::historyDownClicked() {
3250 if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
3251 showHistory(_peer->id, ShowAtUnreadMsgId);
3252 } else if (_replyReturn && _replyReturn->history() == _history) {
3253 showHistory(_peer->id, _replyReturn->id);
3254 } else if (_replyReturn && _replyReturn->history() == _migrated) {
3255 showHistory(_peer->id, -_replyReturn->id);
3256 } else if (_peer) {
3257 showHistory(_peer->id, ShowAtUnreadMsgId);
3258 }
3259 }
3260
showNextUnreadMention()3261 void HistoryWidget::showNextUnreadMention() {
3262 const auto msgId = _history->getMinLoadedUnreadMention();
3263 const auto already = (_showAtMsgId == msgId);
3264
3265 // Mark mention voice/video message as read.
3266 // See https://github.com/telegramdesktop/tdesktop/issues/5623
3267 if (msgId && already) {
3268 const auto item = _history->owner().message(
3269 _history->channelId(),
3270 msgId);
3271 if (const auto media = item ? item->media() : nullptr) {
3272 if (const auto document = media->document()) {
3273 if (!media->webpage()
3274 && (document->isVoiceMessage()
3275 || document->isVideoMessage())) {
3276 document->owner().markMediaRead(document);
3277 }
3278 }
3279 }
3280 }
3281 showHistory(_peer->id, msgId);
3282 }
3283
saveEditMsg()3284 void HistoryWidget::saveEditMsg() {
3285 Expects(_history != nullptr);
3286
3287 if (_saveEditMsgRequestId) {
3288 return;
3289 }
3290
3291 const auto item = session().data().message(_channel, _editMsgId);
3292 if (!item) {
3293 cancelEdit();
3294 return;
3295 }
3296 const auto webPageId = (_previewState != Data::PreviewState::Allowed)
3297 ? CancelledWebPageId
3298 : ((_previewData && _previewData->pendingTill >= 0)
3299 ? _previewData->id
3300 : WebPageId(0));
3301
3302 const auto textWithTags = _field->getTextWithAppliedMarkdown();
3303 const auto prepareFlags = Ui::ItemTextOptions(
3304 _history,
3305 session().user()).flags;
3306 auto sending = TextWithEntities();
3307 auto left = TextWithEntities {
3308 textWithTags.text,
3309 TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) };
3310 TextUtilities::PrepareForSending(left, prepareFlags);
3311
3312 if (!TextUtilities::CutPart(sending, left, MaxMessageSize)) {
3313 const auto suggestModerateActions = false;
3314 controller()->show(
3315 Box<DeleteMessagesBox>(item, suggestModerateActions));
3316 return;
3317 } else if (!left.text.isEmpty()) {
3318 controller()->show(Box<Ui::InformBox>(
3319 tr::lng_edit_too_long(tr::now)));
3320 return;
3321 }
3322
3323 const auto weak = Ui::MakeWeak(this);
3324 const auto history = _history;
3325
3326 const auto done = [=](const MTPUpdates &result, mtpRequestId requestId) {
3327 crl::guard(weak, [=] {
3328 if (requestId == _saveEditMsgRequestId) {
3329 _saveEditMsgRequestId = 0;
3330 cancelEdit();
3331 }
3332 })();
3333 if (const auto editDraft = history->localEditDraft()) {
3334 if (editDraft->saveRequestId == requestId) {
3335 history->clearLocalEditDraft();
3336 history->session().local().writeDrafts(history);
3337 }
3338 }
3339 };
3340
3341 const auto fail = [=](const MTP::Error &error, mtpRequestId requestId) {
3342 if (const auto editDraft = history->localEditDraft()) {
3343 if (editDraft->saveRequestId == requestId) {
3344 editDraft->saveRequestId = 0;
3345 }
3346 }
3347 crl::guard(weak, [=] {
3348 if (requestId == _saveEditMsgRequestId) {
3349 _saveEditMsgRequestId = 0;
3350 }
3351 const auto &err = error.type();
3352 if (ranges::contains(Api::kDefaultEditMessagesErrors, err)) {
3353 controller()->show(
3354 Box<Ui::InformBox>(tr::lng_edit_error(tr::now)));
3355 } else if (err == u"MESSAGE_NOT_MODIFIED"_q) {
3356 cancelEdit();
3357 } else if (err == u"MESSAGE_EMPTY"_q) {
3358 _field->selectAll();
3359 _field->setFocus();
3360 } else {
3361 controller()->show(
3362 Box<Ui::InformBox>(tr::lng_edit_error(tr::now)));
3363 }
3364 update();
3365 })();
3366 };
3367
3368 _saveEditMsgRequestId = Api::EditTextMessage(
3369 item,
3370 sending,
3371 { .removeWebPageId = (webPageId == CancelledWebPageId) },
3372 done,
3373 fail);
3374 }
3375
hideChildWidgets()3376 void HistoryWidget::hideChildWidgets() {
3377 if (_tabbedPanel) {
3378 _tabbedPanel->hideFast();
3379 }
3380 if (_pinnedBar) {
3381 _pinnedBar->hide();
3382 }
3383 if (_groupCallBar) {
3384 _groupCallBar->hide();
3385 }
3386 if (_requestsBar) {
3387 _requestsBar->hide();
3388 }
3389 if (_voiceRecordBar) {
3390 _voiceRecordBar->hideFast();
3391 }
3392 if (_chooseTheme) {
3393 _chooseTheme->hide();
3394 }
3395 hideChildren();
3396 }
3397
hideSelectorControlsAnimated()3398 void HistoryWidget::hideSelectorControlsAnimated() {
3399 _fieldAutocomplete->hideAnimated();
3400 if (_supportAutocomplete) {
3401 _supportAutocomplete->hide();
3402 }
3403 if (_tabbedPanel) {
3404 _tabbedPanel->hideAnimated();
3405 }
3406 if (_inlineResults) {
3407 _inlineResults->hideAnimated();
3408 }
3409 }
3410
send(Api::SendOptions options)3411 void HistoryWidget::send(Api::SendOptions options) {
3412 if (!_history) {
3413 return;
3414 } else if (_editMsgId) {
3415 saveEditMsg();
3416 return;
3417 } else if (!options.scheduled && showSlowmodeError()) {
3418 return;
3419 }
3420
3421 if (_voiceRecordBar->isListenState()) {
3422 _voiceRecordBar->requestToSendWithOptions(options);
3423 return;
3424 }
3425
3426 const auto webPageId = (_previewState != Data::PreviewState::Allowed)
3427 ? CancelledWebPageId
3428 : ((_previewData && _previewData->pendingTill >= 0)
3429 ? _previewData->id
3430 : WebPageId(0));
3431
3432 auto message = ApiWrap::MessageToSend(_history);
3433 message.textWithTags = _field->getTextWithAppliedMarkdown();
3434 message.action.options = options;
3435 message.action.replyTo = replyToId();
3436 message.webPageId = webPageId;
3437
3438 if (_canSendMessages) {
3439 const auto error = GetErrorTextForSending(
3440 _peer,
3441 _toForward.items,
3442 message.textWithTags,
3443 options.scheduled);
3444 if (!error.isEmpty()) {
3445 Ui::ShowMultilineToast({
3446 .text = { error },
3447 });
3448 return;
3449 }
3450 }
3451
3452 session().api().sendMessage(std::move(message));
3453
3454 clearFieldText();
3455 _saveDraftText = true;
3456 _saveDraftStart = crl::now();
3457 saveDraft();
3458
3459 hideSelectorControlsAnimated();
3460
3461 if (_previewData && _previewData->pendingTill) previewCancel();
3462 _field->setFocus();
3463
3464 if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) {
3465 toggleKeyboard();
3466 }
3467 session().changes().historyUpdated(
3468 _history,
3469 (options.scheduled
3470 ? Data::HistoryUpdate::Flag::ScheduledSent
3471 : Data::HistoryUpdate::Flag::MessageSent));
3472 }
3473
sendWithModifiers(Qt::KeyboardModifiers modifiers)3474 void HistoryWidget::sendWithModifiers(Qt::KeyboardModifiers modifiers) {
3475 auto options = Api::SendOptions();
3476 options.handleSupportSwitch = Support::HandleSwitch(modifiers);
3477 send(options);
3478 }
3479
sendSilent()3480 void HistoryWidget::sendSilent() {
3481 auto options = Api::SendOptions();
3482 options.silent = true;
3483 send(options);
3484 }
3485
sendScheduled()3486 void HistoryWidget::sendScheduled() {
3487 if (!_list) {
3488 return;
3489 }
3490 const auto callback = [=](Api::SendOptions options) { send(options); };
3491 controller()->show(
3492 HistoryView::PrepareScheduleBox(_list, sendMenuType(), callback),
3493 Ui::LayerOption::KeepOther);
3494 }
3495
sendMenuType() const3496 SendMenu::Type HistoryWidget::sendMenuType() const {
3497 return !_peer
3498 ? SendMenu::Type::Disabled
3499 : _peer->isSelf()
3500 ? SendMenu::Type::Reminder
3501 : HistoryView::CanScheduleUntilOnline(_peer)
3502 ? SendMenu::Type::ScheduledToUser
3503 : SendMenu::Type::Scheduled;
3504 }
3505
computeSendButtonType() const3506 auto HistoryWidget::computeSendButtonType() const {
3507 using Type = Ui::SendButton::Type;
3508
3509 if (_editMsgId) {
3510 return Type::Save;
3511 } else if (_isInlineBot) {
3512 return Type::Cancel;
3513 } else if (showRecordButton()) {
3514 return Type::Record;
3515 }
3516 return Type::Send;
3517 }
3518
sendButtonMenuType() const3519 SendMenu::Type HistoryWidget::sendButtonMenuType() const {
3520 return (computeSendButtonType() == Ui::SendButton::Type::Send)
3521 ? sendMenuType()
3522 : SendMenu::Type::Disabled;
3523 }
3524
unblockUser()3525 void HistoryWidget::unblockUser() {
3526 if (const auto user = _peer ? _peer->asUser() : nullptr) {
3527 Window::PeerMenuUnblockUserWithBotRestart(user);
3528 } else {
3529 updateControlsVisibility();
3530 }
3531 }
3532
sendBotStartCommand()3533 void HistoryWidget::sendBotStartCommand() {
3534 if (!_peer
3535 || !_peer->isUser()
3536 || !_peer->asUser()->isBot()
3537 || !_canSendMessages) {
3538 updateControlsVisibility();
3539 return;
3540 }
3541 session().api().sendBotStart(_peer->asUser());
3542 updateControlsVisibility();
3543 updateControlsGeometry();
3544 }
3545
joinChannel()3546 void HistoryWidget::joinChannel() {
3547 if (!_peer || !_peer->isChannel() || !isJoinChannel()) {
3548 updateControlsVisibility();
3549 return;
3550 }
3551 session().api().joinChannel(_peer->asChannel());
3552 }
3553
toggleMuteUnmute()3554 void HistoryWidget::toggleMuteUnmute() {
3555 const auto muteForSeconds = _history->mute()
3556 ? 0
3557 : Data::NotifySettings::kDefaultMutePeriod;
3558 session().data().updateNotifySettings(_peer, muteForSeconds);
3559 }
3560
reportSelectedMessages()3561 void HistoryWidget::reportSelectedMessages() {
3562 if (!_list || !_chooseForReport || !_list->getSelectionState().count) {
3563 return;
3564 }
3565 const auto ids = _list->getSelectedItems();
3566 const auto peer = _peer;
3567 const auto reason = _chooseForReport->reason;
3568 const auto box = std::make_shared<QPointer<Ui::GenericBox>>();
3569 const auto weak = Ui::MakeWeak(_list.data());
3570 const auto send = [=](const QString &text) {
3571 if (weak) {
3572 clearSelected();
3573 controller()->clearChooseReportMessages();
3574 }
3575 HistoryView::SendReport(peer, reason, text, ids);
3576 if (*box) {
3577 (*box)->closeBox();
3578 }
3579 };
3580 *box = controller()->window().show(Box(Ui::ReportDetailsBox, send));
3581 }
3582
history() const3583 History *HistoryWidget::history() const {
3584 return _history;
3585 }
3586
peer() const3587 PeerData *HistoryWidget::peer() const {
3588 return _peer;
3589 }
3590
3591 // Sometimes _showAtMsgId is set directly.
setMsgId(MsgId showAtMsgId)3592 void HistoryWidget::setMsgId(MsgId showAtMsgId) {
3593 if (_showAtMsgId != showAtMsgId) {
3594 _showAtMsgId = showAtMsgId;
3595 if (_history) {
3596 controller()->setActiveChatEntry({
3597 _history,
3598 FullMsgId(_history->channelId(), _showAtMsgId) });
3599 }
3600 }
3601 }
3602
msgId() const3603 MsgId HistoryWidget::msgId() const {
3604 return _showAtMsgId;
3605 }
3606
showAnimated(Window::SlideDirection direction,const Window::SectionSlideParams & params)3607 void HistoryWidget::showAnimated(
3608 Window::SlideDirection direction,
3609 const Window::SectionSlideParams ¶ms) {
3610 _showDirection = direction;
3611
3612 _a_show.stop();
3613
3614 _cacheUnder = params.oldContentCache;
3615
3616 // If we show pinned bar here, we don't want it to change the
3617 // calculated and prepared scrollTop of the messages history.
3618 _preserveScrollTop = true;
3619 show();
3620 _topBar->finishAnimating();
3621 historyDownAnimationFinish();
3622 unreadMentionsAnimationFinish();
3623 if (_pinnedBar) {
3624 _pinnedBar->finishAnimating();
3625 }
3626 if (_groupCallBar) {
3627 _groupCallBar->finishAnimating();
3628 }
3629 if (_requestsBar) {
3630 _requestsBar->finishAnimating();
3631 }
3632 _topShadow->setVisible(params.withTopBarShadow ? false : true);
3633 _preserveScrollTop = false;
3634
3635 _cacheOver = controller()->content()->grabForShowAnimation(params);
3636
3637 hideChildWidgets();
3638 if (params.withTopBarShadow) _topShadow->show();
3639
3640 if (_showDirection == Window::SlideDirection::FromLeft) {
3641 std::swap(_cacheUnder, _cacheOver);
3642 }
3643 _a_show.start([=] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition());
3644 if (_history) {
3645 _topBar->show();
3646 _topBar->setAnimatingMode(true);
3647 }
3648
3649 activate();
3650 }
3651
animationCallback()3652 void HistoryWidget::animationCallback() {
3653 update();
3654 if (!_a_show.animating()) {
3655 historyDownAnimationFinish();
3656 unreadMentionsAnimationFinish();
3657 if (_pinnedBar) {
3658 _pinnedBar->finishAnimating();
3659 }
3660 if (_groupCallBar) {
3661 _groupCallBar->finishAnimating();
3662 }
3663 if (_requestsBar) {
3664 _requestsBar->finishAnimating();
3665 }
3666 _cacheUnder = _cacheOver = QPixmap();
3667 doneShow();
3668 synteticScrollToY(_scroll->scrollTop());
3669 }
3670 }
3671
doneShow()3672 void HistoryWidget::doneShow() {
3673 _topBar->setAnimatingMode(false);
3674 updateBotKeyboard();
3675 updateControlsVisibility();
3676 if (!_historyInited) {
3677 updateHistoryGeometry(true);
3678 } else {
3679 handlePendingHistoryUpdate();
3680 }
3681 // If we show pinned bar here, we don't want it to change the
3682 // calculated and prepared scrollTop of the messages history.
3683 _preserveScrollTop = true;
3684 preloadHistoryIfNeeded();
3685 updatePinnedViewer();
3686 if (_pinnedBar) {
3687 _pinnedBar->finishAnimating();
3688 }
3689 if (_groupCallBar) {
3690 _groupCallBar->finishAnimating();
3691 }
3692 if (_requestsBar) {
3693 _requestsBar->finishAnimating();
3694 }
3695 checkHistoryActivation();
3696 controller()->widget()->setInnerFocus();
3697 _preserveScrollTop = false;
3698 checkSuggestToGigagroup();
3699 }
3700
checkSuggestToGigagroup()3701 void HistoryWidget::checkSuggestToGigagroup() {
3702 const auto group = _peer ? _peer->asMegagroup() : nullptr;
3703 if (!group || !group->owner().suggestToGigagroup(group)) {
3704 return;
3705 }
3706 InvokeQueued(_list, [=] {
3707 if (!Ui::isLayerShown()) {
3708 group->owner().setSuggestToGigagroup(group, false);
3709 group->session().api().request(MTPhelp_DismissSuggestion(
3710 group->input,
3711 MTP_string("convert_to_gigagroup")
3712 )).send();
3713 controller()->show(Box([=](not_null<Ui::GenericBox*> box) {
3714 box->setTitle(tr::lng_gigagroup_suggest_title());
3715 box->addRow(
3716 object_ptr<Ui::FlatLabel>(
3717 box,
3718 tr::lng_gigagroup_suggest_text(
3719 ) | Ui::Text::ToRichLangValue(),
3720 st::infoAboutGigagroup));
3721 box->addButton(
3722 tr::lng_gigagroup_suggest_more(),
3723 AboutGigagroupCallback(group));
3724 box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
3725 }));
3726 }
3727 });
3728 }
3729
finishAnimating()3730 void HistoryWidget::finishAnimating() {
3731 if (!_a_show.animating()) return;
3732 _a_show.stop();
3733 _topShadow->setVisible(_peer != nullptr);
3734 _topBar->setVisible(_peer != nullptr);
3735 historyDownAnimationFinish();
3736 unreadMentionsAnimationFinish();
3737 }
3738
historyDownAnimationFinish()3739 void HistoryWidget::historyDownAnimationFinish() {
3740 _historyDownShown.stop();
3741 updateHistoryDownPosition();
3742 }
3743
unreadMentionsAnimationFinish()3744 void HistoryWidget::unreadMentionsAnimationFinish() {
3745 _unreadMentionsShown.stop();
3746 updateUnreadMentionsPosition();
3747 }
3748
chooseAttach()3749 void HistoryWidget::chooseAttach() {
3750 if (_editMsgId) {
3751 controller()->show(
3752 Box<Ui::InformBox>(tr::lng_edit_caption_attach(tr::now)));
3753 return;
3754 }
3755
3756 if (!_peer || !_peer->canWrite()) {
3757 return;
3758 } else if (const auto error = Data::RestrictionError(
3759 _peer,
3760 ChatRestriction::SendMedia)) {
3761 Ui::ShowMultilineToast({
3762 .text = { *error },
3763 });
3764 return;
3765 } else if (showSlowmodeError()) {
3766 return;
3767 }
3768
3769 const auto filter = FileDialog::AllOrImagesFilter();
3770
3771 FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
3772 FileDialog::OpenResult &&result) {
3773 if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
3774 return;
3775 }
3776
3777 if (!result.remoteContent.isEmpty()) {
3778 auto read = Images::Read({
3779 .content = result.remoteContent,
3780 });
3781 if (!read.image.isNull() && !read.animated) {
3782 confirmSendingFiles(
3783 std::move(read.image),
3784 std::move(result.remoteContent));
3785 } else {
3786 uploadFile(result.remoteContent, SendMediaType::File);
3787 }
3788 } else {
3789 auto list = Storage::PrepareMediaList(
3790 result.paths,
3791 st::sendMediaPreviewSize);
3792 confirmSendingFiles(std::move(list));
3793 }
3794 }), nullptr);
3795 }
3796
sendButtonClicked()3797 void HistoryWidget::sendButtonClicked() {
3798 const auto type = _send->type();
3799 if (type == Ui::SendButton::Type::Cancel) {
3800 cancelInlineBot();
3801 } else if (type != Ui::SendButton::Type::Record) {
3802 send({});
3803 }
3804 }
3805
leaveEventHook(QEvent * e)3806 void HistoryWidget::leaveEventHook(QEvent *e) {
3807 if (hasMouseTracking()) {
3808 mouseMoveEvent(nullptr);
3809 }
3810 }
3811
mouseMoveEvent(QMouseEvent * e)3812 void HistoryWidget::mouseMoveEvent(QMouseEvent *e) {
3813 auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
3814 updateOverStates(pos);
3815 }
3816
updateOverStates(QPoint pos)3817 void HistoryWidget::updateOverStates(QPoint pos) {
3818 auto inReplyEditForward = QRect(st::historyReplySkip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - st::historyReplySkip - _fieldBarCancel->width(), st::historyReplyHeight).contains(pos) && (_editMsgId || replyToId() || readyToForward());
3819 auto inClickable = inReplyEditForward;
3820 _inReplyEditForward = inReplyEditForward;
3821 if (inClickable != _inClickable) {
3822 _inClickable = inClickable;
3823 setCursor(_inClickable ? style::cur_pointer : style::cur_default);
3824 }
3825 }
3826
leaveToChildEvent(QEvent * e,QWidget * child)3827 void HistoryWidget::leaveToChildEvent(QEvent *e, QWidget *child) { // e -- from enterEvent() of child TWidget
3828 if (hasMouseTracking()) {
3829 updateOverStates(mapFromGlobal(QCursor::pos()));
3830 }
3831 }
3832
mouseReleaseEvent(QMouseEvent * e)3833 void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) {
3834 if (_replyForwardPressed) {
3835 _replyForwardPressed = false;
3836 update(0, _field->y() - st::historySendPadding - st::historyReplyHeight, width(), st::historyReplyHeight);
3837 }
3838 }
3839
sendBotCommand(const Bot::SendCommandRequest & request)3840 void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) {
3841 // replyTo != 0 from ReplyKeyboardMarkup, == 0 from command links
3842 if (_peer != request.peer.get()) {
3843 return;
3844 } else if (showSlowmodeError()) {
3845 return;
3846 }
3847
3848 const auto lastKeyboardUsed = (_keyboard->forMsgId()
3849 == FullMsgId(_channel, _history->lastKeyboardId))
3850 && (_keyboard->forMsgId() == FullMsgId(_channel, request.replyTo));
3851
3852 // 'bot' may be nullptr in case of sending from FieldAutocomplete.
3853 const auto toSend = (request.replyTo/* || !bot*/)
3854 ? request.command
3855 : Bot::WrapCommandInChat(_peer, request.command, request.context);
3856
3857 auto message = ApiWrap::MessageToSend(_history);
3858 message.textWithTags = { toSend, TextWithTags::Tags() };
3859 message.action.replyTo = request.replyTo
3860 ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/)
3861 ? request.replyTo
3862 : replyToId())
3863 : 0;
3864 session().api().sendMessage(std::move(message));
3865 if (request.replyTo) {
3866 if (_replyToId == request.replyTo) {
3867 cancelReply();
3868 saveCloudDraft();
3869 }
3870 if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) {
3871 if (_kbShown) toggleKeyboard(false);
3872 _history->lastKeyboardUsed = true;
3873 }
3874 }
3875
3876 _field->setFocus();
3877 }
3878
hideSingleUseKeyboard(PeerData * peer,MsgId replyTo)3879 void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
3880 if (!_peer || _peer != peer) return;
3881
3882 bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo));
3883 if (replyTo) {
3884 if (_replyToId == replyTo) {
3885 cancelReply();
3886 saveCloudDraft();
3887 }
3888 if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) {
3889 if (_kbShown) toggleKeyboard(false);
3890 _history->lastKeyboardUsed = true;
3891 }
3892 }
3893 }
3894
insertBotCommand(const QString & cmd)3895 bool HistoryWidget::insertBotCommand(const QString &cmd) {
3896 if (!canWriteMessage()) return false;
3897
3898 auto insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@');
3899 auto toInsert = cmd;
3900 if (!toInsert.isEmpty() && !insertingInlineBot) {
3901 auto bot = _peer->isUser()
3902 ? _peer
3903 : (App::hoveredLinkItem()
3904 ? App::hoveredLinkItem()->data()->fromOriginal().get()
3905 : nullptr);
3906 if (bot && (!bot->isUser() || !bot->asUser()->isBot())) {
3907 bot = nullptr;
3908 }
3909 auto username = bot ? bot->asUser()->username : QString();
3910 auto botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
3911 if (toInsert.indexOf('@') < 0 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
3912 toInsert += '@' + username;
3913 }
3914 }
3915 toInsert += ' ';
3916
3917 if (!insertingInlineBot) {
3918 auto &textWithTags = _field->getTextWithTags();
3919 TextWithTags textWithTagsToSet;
3920 QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text);
3921 if (m.hasMatch()) {
3922 textWithTagsToSet = _field->getTextWithTagsPart(m.capturedLength());
3923 } else {
3924 textWithTagsToSet = textWithTags;
3925 }
3926 textWithTagsToSet.text = toInsert + textWithTagsToSet.text;
3927 for (auto &tag : textWithTagsToSet.tags) {
3928 tag.offset += toInsert.size();
3929 }
3930 _field->setTextWithTags(textWithTagsToSet);
3931
3932 QTextCursor cur(_field->textCursor());
3933 cur.movePosition(QTextCursor::End);
3934 _field->setTextCursor(cur);
3935 } else {
3936 setFieldText(
3937 { toInsert, TextWithTags::Tags() },
3938 TextUpdateEvent::SaveDraft,
3939 Ui::InputField::HistoryAction::NewEntry);
3940 _field->setFocus();
3941 return true;
3942 }
3943 return false;
3944 }
3945
eventFilter(QObject * obj,QEvent * e)3946 bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
3947 if (e->type() == QEvent::KeyPress) {
3948 const auto k = static_cast<QKeyEvent*>(e);
3949 if ((k->modifiers() & kCommonModifiers) == Qt::ControlModifier) {
3950 if (k->key() == Qt::Key_Up) {
3951 #ifdef Q_OS_MAC
3952 // Cmd + Up is used instead of Home.
3953 if (!_field->textCursor().atStart()) {
3954 return false;
3955 }
3956 #endif
3957 return replyToPreviousMessage();
3958 } else if (k->key() == Qt::Key_Down) {
3959 #ifdef Q_OS_MAC
3960 // Cmd + Down is used instead of End.
3961 if (!_field->textCursor().atEnd()) {
3962 return false;
3963 }
3964 #endif
3965 return replyToNextMessage();
3966 }
3967 }
3968 }
3969 if ((obj == _historyDown || obj == _unreadMentions) && e->type() == QEvent::Wheel) {
3970 return _scroll->viewportEvent(e);
3971 }
3972 return TWidget::eventFilter(obj, e);
3973 }
3974
floatPlayerHandleWheelEvent(QEvent * e)3975 bool HistoryWidget::floatPlayerHandleWheelEvent(QEvent *e) {
3976 return _peer ? _scroll->viewportEvent(e) : false;
3977 }
3978
floatPlayerAvailableRect()3979 QRect HistoryWidget::floatPlayerAvailableRect() {
3980 return _peer ? mapToGlobal(_scroll->geometry()) : mapToGlobal(rect());
3981 }
3982
readyToForward() const3983 bool HistoryWidget::readyToForward() const {
3984 return _canSendMessages && !_toForward.items.empty();
3985 }
3986
hasSilentToggle() const3987 bool HistoryWidget::hasSilentToggle() const {
3988 return _peer
3989 && _peer->isChannel()
3990 && !_peer->isMegagroup()
3991 && _peer->canWrite()
3992 && !session().data().notifySilentPostsUnknown(_peer);
3993 }
3994
handleSupportSwitch(not_null<History * > updated)3995 void HistoryWidget::handleSupportSwitch(not_null<History*> updated) {
3996 if (_history != updated || !session().supportMode()) {
3997 return;
3998 }
3999
4000 const auto setting = session().settings().supportSwitch();
4001 if (auto method = Support::GetSwitchMethod(setting)) {
4002 crl::on_main(this, std::move(method));
4003 }
4004 }
4005
inlineBotResolveDone(const MTPcontacts_ResolvedPeer & result)4006 void HistoryWidget::inlineBotResolveDone(
4007 const MTPcontacts_ResolvedPeer &result) {
4008 Expects(result.type() == mtpc_contacts_resolvedPeer);
4009
4010 _inlineBotResolveRequestId = 0;
4011 const auto &data = result.c_contacts_resolvedPeer();
4012 const auto resolvedBot = [&]() -> UserData* {
4013 if (const auto result = session().data().processUsers(data.vusers())) {
4014 if (result->isBot()
4015 && !result->botInfo->inlinePlaceholder.isEmpty()) {
4016 return result;
4017 }
4018 }
4019 return nullptr;
4020 }();
4021 session().data().processChats(data.vchats());
4022
4023 const auto query = parseInlineBotQuery();
4024 if (_inlineBotUsername == query.username) {
4025 applyInlineBotQuery(
4026 query.lookingUpBot ? resolvedBot : query.bot,
4027 query.query);
4028 } else {
4029 clearInlineBot();
4030 }
4031 }
4032
inlineBotResolveFail(const MTP::Error & error,const QString & username)4033 void HistoryWidget::inlineBotResolveFail(
4034 const MTP::Error &error,
4035 const QString &username) {
4036 _inlineBotResolveRequestId = 0;
4037 if (username == _inlineBotUsername) {
4038 clearInlineBot();
4039 }
4040 }
4041
isBotStart() const4042 bool HistoryWidget::isBotStart() const {
4043 const auto user = _peer ? _peer->asUser() : nullptr;
4044 if (!user
4045 || !user->isBot()
4046 || !_canSendMessages) {
4047 return false;
4048 } else if (!user->botInfo->startToken.isEmpty()) {
4049 return true;
4050 } else if (_history->isEmpty() && !_history->lastMessage()) {
4051 return true;
4052 }
4053 return false;
4054 }
4055
isReportMessages() const4056 bool HistoryWidget::isReportMessages() const {
4057 return _peer && _chooseForReport && _chooseForReport->active;
4058 }
4059
isBlocked() const4060 bool HistoryWidget::isBlocked() const {
4061 return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
4062 }
4063
isJoinChannel() const4064 bool HistoryWidget::isJoinChannel() const {
4065 return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
4066 }
4067
isChoosingTheme() const4068 bool HistoryWidget::isChoosingTheme() const {
4069 return _chooseTheme && _chooseTheme->shouldBeShown();
4070 }
4071
isMuteUnmute() const4072 bool HistoryWidget::isMuteUnmute() const {
4073 return _peer
4074 && ((_peer->isBroadcast() && !_peer->asChannel()->canPublish())
4075 || (_peer->isGigagroup() && !_peer->asChannel()->canWrite())
4076 || _peer->isRepliesChat());
4077 }
4078
showRecordButton() const4079 bool HistoryWidget::showRecordButton() const {
4080 return Media::Capture::instance()->available()
4081 && !_voiceRecordBar->isListenState()
4082 && !HasSendText(_field)
4083 && !readyToForward()
4084 && !_editMsgId;
4085 }
4086
showInlineBotCancel() const4087 bool HistoryWidget::showInlineBotCancel() const {
4088 return _inlineBot && !_inlineLookingUpBot;
4089 }
4090
updateSendButtonType()4091 void HistoryWidget::updateSendButtonType() {
4092 using Type = Ui::SendButton::Type;
4093
4094 const auto type = computeSendButtonType();
4095 _send->setType(type);
4096
4097 // This logic is duplicated in RepliesWidget.
4098 const auto disabledBySlowmode = _peer
4099 && _peer->slowmodeApplied()
4100 && (_history->latestSendingMessage() != nullptr);
4101
4102 const auto delay = [&] {
4103 return (type != Type::Cancel && type != Type::Save && _peer)
4104 ? _peer->slowmodeSecondsLeft()
4105 : 0;
4106 }();
4107 _send->setSlowmodeDelay(delay);
4108 _send->setDisabled(disabledBySlowmode
4109 && (type == Type::Send || type == Type::Record));
4110
4111 if (delay != 0) {
4112 base::call_delayed(
4113 kRefreshSlowmodeLabelTimeout,
4114 this,
4115 [=] { updateSendButtonType(); });
4116 }
4117 }
4118
updateCmdStartShown()4119 bool HistoryWidget::updateCmdStartShown() {
4120 bool cmdStartShown = false;
4121 if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->isBot()))) {
4122 if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) {
4123 if (!HasSendText(_field)) {
4124 cmdStartShown = true;
4125 }
4126 }
4127 }
4128 if (_cmdStartShown != cmdStartShown) {
4129 _cmdStartShown = cmdStartShown;
4130 return true;
4131 }
4132 return false;
4133 }
4134
kbWasHidden() const4135 bool HistoryWidget::kbWasHidden() const {
4136 return _history && (_keyboard->forMsgId() == FullMsgId(_history->channelId(), _history->lastKeyboardHiddenId));
4137 }
4138
toggleKeyboard(bool manual)4139 void HistoryWidget::toggleKeyboard(bool manual) {
4140 auto fieldEnabled = canWriteMessage() && !_a_show.animating();
4141 if (_kbShown || _kbReplyTo) {
4142 _botKeyboardHide->hide();
4143 if (_kbShown) {
4144 if (fieldEnabled) {
4145 _botKeyboardShow->show();
4146 }
4147 if (manual && _history) {
4148 _history->lastKeyboardHiddenId = _keyboard->forMsgId().msg;
4149 }
4150
4151 _kbScroll->hide();
4152 _kbShown = false;
4153
4154 _field->setMaxHeight(computeMaxFieldHeight());
4155
4156 _kbReplyTo = nullptr;
4157 if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) {
4158 _fieldBarCancel->hide();
4159 updateMouseTracking();
4160 }
4161 } else {
4162 if (_history) {
4163 _history->clearLastKeyboard();
4164 } else {
4165 updateBotKeyboard();
4166 }
4167 }
4168 } else if (!_keyboard->hasMarkup() && _keyboard->forceReply()) {
4169 _botKeyboardHide->hide();
4170 _botKeyboardShow->hide();
4171 if (fieldEnabled) {
4172 _botCommandStart->show();
4173 }
4174 _kbScroll->hide();
4175 _kbShown = false;
4176
4177 _field->setMaxHeight(computeMaxFieldHeight());
4178
4179 _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply())
4180 ? session().data().message(_keyboard->forMsgId())
4181 : nullptr;
4182 if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
4183 updateReplyToName();
4184 updateReplyEditText(_kbReplyTo);
4185 }
4186 if (manual && _history) {
4187 _history->lastKeyboardHiddenId = 0;
4188 }
4189 } else if (fieldEnabled) {
4190 _botKeyboardHide->show();
4191 _botKeyboardShow->hide();
4192 _kbScroll->show();
4193 _kbShown = true;
4194
4195 const auto maxheight = computeMaxFieldHeight();
4196 const auto kbheight = qMin(_keyboard->height(), maxheight - (maxheight / 2));
4197 _field->setMaxHeight(maxheight - kbheight);
4198
4199 _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply())
4200 ? session().data().message(_keyboard->forMsgId())
4201 : nullptr;
4202 if (_kbReplyTo && !_editMsgId && !_replyToId) {
4203 updateReplyToName();
4204 updateReplyEditText(_kbReplyTo);
4205 }
4206 if (manual && _history) {
4207 _history->lastKeyboardHiddenId = 0;
4208 }
4209 }
4210 updateControlsGeometry();
4211 updateFieldPlaceholder();
4212 if (_botKeyboardHide->isHidden() && canWriteMessage() && !_a_show.animating()) {
4213 _tabbedSelectorToggle->show();
4214 } else {
4215 _tabbedSelectorToggle->hide();
4216 }
4217 updateField();
4218 }
4219
startBotCommand()4220 void HistoryWidget::startBotCommand() {
4221 setFieldText(
4222 { qsl("/"), TextWithTags::Tags() },
4223 0,
4224 Ui::InputField::HistoryAction::NewEntry);
4225 }
4226
setMembersShowAreaActive(bool active)4227 void HistoryWidget::setMembersShowAreaActive(bool active) {
4228 if (!active) {
4229 _membersDropdownShowTimer.cancel();
4230 }
4231 if (active && _peer && (_peer->isChat() || _peer->isMegagroup())) {
4232 if (_membersDropdown) {
4233 _membersDropdown->otherEnter();
4234 } else if (!_membersDropdownShowTimer.isActive()) {
4235 _membersDropdownShowTimer.callOnce(kShowMembersDropdownTimeoutMs);
4236 }
4237 } else if (_membersDropdown) {
4238 _membersDropdown->otherLeave();
4239 }
4240 }
4241
showMembersDropdown()4242 void HistoryWidget::showMembersDropdown() {
4243 if (!_peer) {
4244 return;
4245 }
4246 if (!_membersDropdown) {
4247 _membersDropdown.create(this, st::membersInnerDropdown);
4248 _membersDropdown->setOwnedWidget(object_ptr<Profile::GroupMembersWidget>(this, _peer, st::membersInnerItem));
4249 _membersDropdown->resizeToWidth(st::membersInnerWidth);
4250
4251 _membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
4252 _membersDropdown->moveToLeft(0, _topBar->height());
4253 _membersDropdown->setHiddenCallback([this] { _membersDropdown.destroyDelayed(); });
4254 }
4255 _membersDropdown->otherEnter();
4256 }
4257
pushTabbedSelectorToThirdSection(not_null<PeerData * > peer,const Window::SectionShow & params)4258 bool HistoryWidget::pushTabbedSelectorToThirdSection(
4259 not_null<PeerData*> peer,
4260 const Window::SectionShow ¶ms) {
4261 if (!_tabbedPanel) {
4262 return true;
4263 } else if (!peer->canWrite()) {
4264 Core::App().settings().setTabbedReplacedWithInfo(true);
4265 controller()->showPeerInfo(peer, params.withThirdColumn());
4266 return false;
4267 }
4268 Core::App().settings().setTabbedReplacedWithInfo(false);
4269 controller()->resizeForThirdSection();
4270 controller()->showSection(
4271 std::make_shared<ChatHelpers::TabbedMemento>(),
4272 params.withThirdColumn());
4273 return true;
4274 }
4275
returnTabbedSelector()4276 bool HistoryWidget::returnTabbedSelector() {
4277 createTabbedPanel();
4278 moveFieldControls();
4279 return true;
4280 }
4281
createTabbedPanel()4282 void HistoryWidget::createTabbedPanel() {
4283 setTabbedPanel(std::make_unique<TabbedPanel>(
4284 this,
4285 controller(),
4286 controller()->tabbedSelector()));
4287 }
4288
setTabbedPanel(std::unique_ptr<TabbedPanel> panel)4289 void HistoryWidget::setTabbedPanel(std::unique_ptr<TabbedPanel> panel) {
4290 _tabbedPanel = std::move(panel);
4291 if (const auto raw = _tabbedPanel.get()) {
4292 _tabbedSelectorToggle->installEventFilter(raw);
4293 _tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
4294 } else {
4295 _tabbedSelectorToggle->setColorOverrides(
4296 &st::historyAttachEmojiActive,
4297 &st::historyRecordVoiceFgActive,
4298 &st::historyRecordVoiceRippleBgActive);
4299 }
4300 }
4301
preventsClose(Fn<void ()> && continueCallback) const4302 bool HistoryWidget::preventsClose(Fn<void()> &&continueCallback) const {
4303 if (_voiceRecordBar->isActive()) {
4304 _voiceRecordBar->showDiscardBox(std::move(continueCallback));
4305 return true;
4306 }
4307 return false;
4308 }
4309
toggleTabbedSelectorMode()4310 void HistoryWidget::toggleTabbedSelectorMode() {
4311 if (!_peer) {
4312 return;
4313 }
4314 if (_tabbedPanel) {
4315 if (controller()->canShowThirdSection()
4316 && !controller()->adaptive().isOneColumn()) {
4317 Core::App().settings().setTabbedSelectorSectionEnabled(true);
4318 Core::App().saveSettingsDelayed();
4319 pushTabbedSelectorToThirdSection(
4320 _peer,
4321 Window::SectionShow::Way::ClearStack);
4322 } else {
4323 _tabbedPanel->toggleAnimated();
4324 }
4325 } else {
4326 controller()->closeThirdSection();
4327 }
4328 }
4329
recountChatWidth()4330 void HistoryWidget::recountChatWidth() {
4331 const auto layout = (width() < st::adaptiveChatWideWidth)
4332 ? Window::Adaptive::ChatLayout::Normal
4333 : Window::Adaptive::ChatLayout::Wide;
4334 controller()->adaptive().setChatLayout(layout);
4335 }
4336
moveFieldControls()4337 void HistoryWidget::moveFieldControls() {
4338 auto keyboardHeight = 0;
4339 auto bottom = height();
4340 auto maxKeyboardHeight = computeMaxFieldHeight() - _field->height();
4341 _keyboard->resizeToWidth(width(), maxKeyboardHeight);
4342 if (_kbShown) {
4343 keyboardHeight = qMin(_keyboard->height(), maxKeyboardHeight);
4344 bottom -= keyboardHeight;
4345 _kbScroll->setGeometryToLeft(0, bottom, width(), keyboardHeight);
4346 }
4347
4348 // _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel
4349 // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
4350 // (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages)
4351
4352 auto buttonsBottom = bottom - _attachToggle->height();
4353 auto left = st::historySendRight;
4354 _attachToggle->moveToLeft(left, buttonsBottom); left += _attachToggle->width();
4355 _field->moveToLeft(left, bottom - _field->height() - st::historySendPadding);
4356 auto right = st::historySendRight;
4357 _send->moveToRight(right, buttonsBottom); right += _send->width();
4358 _voiceRecordBar->moveToLeft(0, bottom - _voiceRecordBar->height());
4359 _tabbedSelectorToggle->moveToRight(right, buttonsBottom);
4360 _botKeyboardHide->moveToRight(right, buttonsBottom); right += _botKeyboardHide->width();
4361 _botKeyboardShow->moveToRight(right, buttonsBottom);
4362 _botCommandStart->moveToRight(right, buttonsBottom);
4363 if (_silent) {
4364 _silent->moveToRight(right, buttonsBottom);
4365 }
4366 const auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
4367 if (kbShowShown || _cmdStartShown || _silent) {
4368 right += _botCommandStart->width();
4369 }
4370 if (_scheduled) {
4371 _scheduled->moveToRight(right, buttonsBottom);
4372 right += _scheduled->width();
4373 }
4374 if (_ttlInfo) {
4375 _ttlInfo->move(width() - right - _ttlInfo->width(), buttonsBottom);
4376 }
4377
4378 _fieldBarCancel->moveToRight(0, _field->y() - st::historySendPadding - _fieldBarCancel->height());
4379 if (_inlineResults) {
4380 _inlineResults->moveBottom(_field->y() - st::historySendPadding);
4381 }
4382 if (_tabbedPanel) {
4383 _tabbedPanel->moveBottomRight(buttonsBottom, width());
4384 }
4385
4386 const auto fullWidthButtonRect = myrtlrect(
4387 0,
4388 bottom - _botStart->height(),
4389 width(),
4390 _botStart->height());
4391 _botStart->setGeometry(fullWidthButtonRect);
4392 _unblock->setGeometry(fullWidthButtonRect);
4393 _joinChannel->setGeometry(fullWidthButtonRect);
4394 _muteUnmute->setGeometry(fullWidthButtonRect);
4395 _reportMessages->setGeometry(fullWidthButtonRect);
4396 }
4397
updateFieldSize()4398 void HistoryWidget::updateFieldSize() {
4399 auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
4400 auto fieldWidth = width() - _attachToggle->width() - st::historySendRight;
4401 fieldWidth -= _send->width();
4402 fieldWidth -= _tabbedSelectorToggle->width();
4403 if (kbShowShown) fieldWidth -= _botKeyboardShow->width();
4404 if (_cmdStartShown) fieldWidth -= _botCommandStart->width();
4405 if (_silent) fieldWidth -= _silent->width();
4406 if (_scheduled) fieldWidth -= _scheduled->width();
4407 if (_ttlInfo) fieldWidth -= _ttlInfo->width();
4408
4409 if (_field->width() != fieldWidth) {
4410 _field->resize(fieldWidth, _field->height());
4411 } else {
4412 moveFieldControls();
4413 }
4414 }
4415
clearInlineBot()4416 void HistoryWidget::clearInlineBot() {
4417 if (_inlineBot || _inlineLookingUpBot) {
4418 _inlineBot = nullptr;
4419 _inlineLookingUpBot = false;
4420 inlineBotChanged();
4421 _field->finishAnimating();
4422 }
4423 if (_inlineResults) {
4424 _inlineResults->clearInlineBot();
4425 }
4426 checkFieldAutocomplete();
4427 }
4428
inlineBotChanged()4429 void HistoryWidget::inlineBotChanged() {
4430 bool isInlineBot = showInlineBotCancel();
4431 if (_isInlineBot != isInlineBot) {
4432 _isInlineBot = isInlineBot;
4433 updateFieldPlaceholder();
4434 updateFieldSubmitSettings();
4435 updateControlsVisibility();
4436 }
4437 }
4438
fieldResized()4439 void HistoryWidget::fieldResized() {
4440 moveFieldControls();
4441 updateHistoryGeometry();
4442 updateField();
4443 }
4444
fieldFocused()4445 void HistoryWidget::fieldFocused() {
4446 if (_list) {
4447 _list->clearSelected(true);
4448 }
4449 }
4450
checkFieldAutocomplete()4451 void HistoryWidget::checkFieldAutocomplete() {
4452 if (!_history || _a_show.animating()) {
4453 return;
4454 }
4455
4456 const auto autocomplete = parseMentionHashtagBotCommandQuery();
4457 _fieldAutocomplete->showFiltered(
4458 _peer,
4459 autocomplete.query,
4460 autocomplete.fromStart);
4461 }
4462
updateFieldPlaceholder()4463 void HistoryWidget::updateFieldPlaceholder() {
4464 if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
4465 _field->setPlaceholder(
4466 rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)),
4467 _inlineBot->username.size() + 2);
4468 return;
4469 }
4470
4471 _field->setPlaceholder([&]() -> rpl::producer<QString> {
4472 if (_editMsgId) {
4473 return tr::lng_edit_message_text();
4474 } else if (!_history) {
4475 return tr::lng_message_ph();
4476 } else if ((_kbShown || _keyboard->forceReply())
4477 && !_keyboard->placeholder().isEmpty()) {
4478 return rpl::single(_keyboard->placeholder());
4479 } else if (const auto channel = _history->peer->asChannel()) {
4480 if (channel->isBroadcast()) {
4481 return session().data().notifySilentPosts(channel)
4482 ? tr::lng_broadcast_silent_ph()
4483 : tr::lng_broadcast_ph();
4484 } else if (channel->adminRights() & ChatAdminRight::Anonymous) {
4485 return tr::lng_send_anonymous_ph();
4486 } else {
4487 return tr::lng_message_ph();
4488 }
4489 } else {
4490 return tr::lng_message_ph();
4491 }
4492 }());
4493 updateSendButtonType();
4494 }
4495
showSendingFilesError(const Ui::PreparedList & list) const4496 bool HistoryWidget::showSendingFilesError(
4497 const Ui::PreparedList &list) const {
4498 const auto text = [&] {
4499 const auto error = _peer
4500 ? Data::RestrictionError(
4501 _peer,
4502 ChatRestriction::SendMedia)
4503 : std::nullopt;
4504 if (error) {
4505 return *error;
4506 } else if (!canWriteMessage()) {
4507 return tr::lng_forward_send_files_cant(tr::now);
4508 }
4509 if (_peer->slowmodeApplied() && !list.canBeSentInSlowmode()) {
4510 return tr::lng_slowmode_no_many(tr::now);
4511 } else if (const auto left = _peer->slowmodeSecondsLeft()) {
4512 return tr::lng_slowmode_enabled(
4513 tr::now,
4514 lt_left,
4515 Ui::FormatDurationWords(left));
4516 }
4517 using Error = Ui::PreparedList::Error;
4518 switch (list.error) {
4519 case Error::None: return QString();
4520 case Error::EmptyFile:
4521 case Error::Directory:
4522 case Error::NonLocalUrl: return tr::lng_send_image_empty(
4523 tr::now,
4524 lt_name,
4525 list.errorData);
4526 case Error::TooLargeFile: return tr::lng_send_image_too_large(
4527 tr::now,
4528 lt_name,
4529 list.errorData);
4530 }
4531 return tr::lng_forward_send_files_cant(tr::now);
4532 }();
4533 if (text.isEmpty()) {
4534 return false;
4535 }
4536
4537 Ui::ShowMultilineToast({
4538 .text = { text },
4539 });
4540 return true;
4541 }
4542
confirmSendingFiles(const QStringList & files)4543 bool HistoryWidget::confirmSendingFiles(const QStringList &files) {
4544 return confirmSendingFiles(files, QString());
4545 }
4546
confirmSendingFiles(not_null<const QMimeData * > data)4547 bool HistoryWidget::confirmSendingFiles(not_null<const QMimeData*> data) {
4548 return confirmSendingFiles(data, std::nullopt);
4549 }
4550
confirmSendingFiles(const QStringList & files,const QString & insertTextOnCancel)4551 bool HistoryWidget::confirmSendingFiles(
4552 const QStringList &files,
4553 const QString &insertTextOnCancel) {
4554 return confirmSendingFiles(
4555 Storage::PrepareMediaList(files, st::sendMediaPreviewSize),
4556 insertTextOnCancel);
4557 }
4558
confirmSendingFiles(Ui::PreparedList && list,const QString & insertTextOnCancel)4559 bool HistoryWidget::confirmSendingFiles(
4560 Ui::PreparedList &&list,
4561 const QString &insertTextOnCancel) {
4562 if (showSendingFilesError(list)) {
4563 return false;
4564 }
4565 if (_editMsgId) {
4566 controller()->show(
4567 Box<Ui::InformBox>(tr::lng_edit_caption_attach(tr::now)));
4568 return false;
4569 }
4570
4571 const auto cursor = _field->textCursor();
4572 const auto position = cursor.position();
4573 const auto anchor = cursor.anchor();
4574 const auto text = _field->getTextWithTags();
4575 using SendLimit = SendFilesBox::SendLimit;
4576 auto box = Box<SendFilesBox>(
4577 controller(),
4578 std::move(list),
4579 text,
4580 _peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many,
4581 Api::SendType::Normal,
4582 sendMenuType());
4583 _field->setTextWithTags({});
4584 box->setConfirmedCallback(crl::guard(this, [=](
4585 Ui::PreparedList &&list,
4586 Ui::SendFilesWay way,
4587 TextWithTags &&caption,
4588 Api::SendOptions options,
4589 bool ctrlShiftEnter) {
4590 sendingFilesConfirmed(
4591 std::move(list),
4592 way,
4593 std::move(caption),
4594 options,
4595 ctrlShiftEnter);
4596 }));
4597 box->setCancelledCallback(crl::guard(this, [=] {
4598 _field->setTextWithTags(text);
4599 auto cursor = _field->textCursor();
4600 cursor.setPosition(anchor);
4601 if (position != anchor) {
4602 cursor.setPosition(position, QTextCursor::KeepAnchor);
4603 }
4604 _field->setTextCursor(cursor);
4605 if (!insertTextOnCancel.isEmpty()) {
4606 _field->textCursor().insertText(insertTextOnCancel);
4607 }
4608 }));
4609
4610 Window::ActivateWindow(controller());
4611 const auto shown = controller()->show(std::move(box));
4612 shown->setCloseByOutsideClick(false);
4613
4614 return true;
4615 }
4616
sendingFilesConfirmed(Ui::PreparedList && list,Ui::SendFilesWay way,TextWithTags && caption,Api::SendOptions options,bool ctrlShiftEnter)4617 void HistoryWidget::sendingFilesConfirmed(
4618 Ui::PreparedList &&list,
4619 Ui::SendFilesWay way,
4620 TextWithTags &&caption,
4621 Api::SendOptions options,
4622 bool ctrlShiftEnter) {
4623 Expects(list.filesToProcess.empty());
4624
4625 if (showSendingFilesError(list)) {
4626 return;
4627 }
4628 auto groups = DivideByGroups(
4629 std::move(list),
4630 way,
4631 _peer->slowmodeApplied());
4632 const auto type = way.sendImagesAsPhotos()
4633 ? SendMediaType::Photo
4634 : SendMediaType::File;
4635 auto action = Api::SendAction(_history);
4636 action.replyTo = replyToId();
4637 action.options = options;
4638 action.clearDraft = false;
4639 if ((groups.size() != 1 || !groups.front().sentWithCaption())
4640 && !caption.text.isEmpty()) {
4641 auto message = Api::MessageToSend(_history);
4642 message.textWithTags = base::take(caption);
4643 message.action = action;
4644 session().api().sendMessage(std::move(message));
4645 }
4646 for (auto &group : groups) {
4647 const auto album = (group.type != Ui::AlbumType::None)
4648 ? std::make_shared<SendingAlbum>()
4649 : nullptr;
4650 session().api().sendFiles(
4651 std::move(group.list),
4652 type,
4653 base::take(caption),
4654 album,
4655 action);
4656 }
4657 }
4658
confirmSendingFiles(QImage && image,QByteArray && content,std::optional<bool> overrideSendImagesAsPhotos,const QString & insertTextOnCancel)4659 bool HistoryWidget::confirmSendingFiles(
4660 QImage &&image,
4661 QByteArray &&content,
4662 std::optional<bool> overrideSendImagesAsPhotos,
4663 const QString &insertTextOnCancel) {
4664 if (image.isNull()) {
4665 return false;
4666 }
4667
4668 auto list = Storage::PrepareMediaFromImage(
4669 std::move(image),
4670 std::move(content),
4671 st::sendMediaPreviewSize);
4672 list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
4673 return confirmSendingFiles(std::move(list), insertTextOnCancel);
4674 }
4675
canSendFiles(not_null<const QMimeData * > data) const4676 bool HistoryWidget::canSendFiles(not_null<const QMimeData*> data) const {
4677 if (!canWriteMessage()) {
4678 return false;
4679 } else if (data->hasImage()) {
4680 return true;
4681 } else if (const auto urls = data->urls(); !urls.empty()) {
4682 if (ranges::all_of(urls, &QUrl::isLocalFile)) {
4683 return true;
4684 }
4685 }
4686 return false;
4687 }
4688
confirmSendingFiles(not_null<const QMimeData * > data,std::optional<bool> overrideSendImagesAsPhotos,const QString & insertTextOnCancel)4689 bool HistoryWidget::confirmSendingFiles(
4690 not_null<const QMimeData*> data,
4691 std::optional<bool> overrideSendImagesAsPhotos,
4692 const QString &insertTextOnCancel) {
4693 if (!canWriteMessage()) {
4694 return false;
4695 }
4696
4697 const auto hasImage = data->hasImage();
4698
4699 if (const auto urls = data->urls(); !urls.empty()) {
4700 auto list = Storage::PrepareMediaList(
4701 urls,
4702 st::sendMediaPreviewSize);
4703 if (list.error != Ui::PreparedList::Error::NonLocalUrl) {
4704 if (list.error == Ui::PreparedList::Error::None
4705 || !hasImage) {
4706 const auto emptyTextOnCancel = QString();
4707 list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
4708 confirmSendingFiles(std::move(list), emptyTextOnCancel);
4709 return true;
4710 }
4711 }
4712 }
4713
4714 if (hasImage) {
4715 auto image = qvariant_cast<QImage>(data->imageData());
4716 if (!image.isNull()) {
4717 confirmSendingFiles(
4718 std::move(image),
4719 QByteArray(),
4720 overrideSendImagesAsPhotos,
4721 insertTextOnCancel);
4722 return true;
4723 }
4724 }
4725 return false;
4726 }
4727
uploadFile(const QByteArray & fileContent,SendMediaType type)4728 void HistoryWidget::uploadFile(
4729 const QByteArray &fileContent,
4730 SendMediaType type) {
4731 if (!canWriteMessage()) return;
4732
4733 auto action = Api::SendAction(_history);
4734 action.replyTo = replyToId();
4735 session().api().sendFile(fileContent, type, action);
4736 }
4737
handleHistoryChange(not_null<const History * > history)4738 void HistoryWidget::handleHistoryChange(not_null<const History*> history) {
4739 if (_list && (_history == history || _migrated == history)) {
4740 handlePendingHistoryUpdate();
4741 updateBotKeyboard();
4742 if (!_scroll->isHidden()) {
4743 const auto unblock = isBlocked();
4744 const auto botStart = isBotStart();
4745 const auto joinChannel = isJoinChannel();
4746 const auto muteUnmute = isMuteUnmute();
4747 const auto reportMessages = isReportMessages();
4748 const auto update = false
4749 || (_reportMessages->isHidden() == reportMessages)
4750 || (!reportMessages && _unblock->isHidden() == unblock)
4751 || (!reportMessages
4752 && !unblock
4753 && _botStart->isHidden() == botStart)
4754 || (!reportMessages
4755 && !unblock
4756 && !botStart
4757 && _joinChannel->isHidden() == joinChannel)
4758 || (!reportMessages
4759 && !unblock
4760 && !botStart
4761 && !joinChannel
4762 && _muteUnmute->isHidden() == muteUnmute);
4763 if (update) {
4764 updateControlsVisibility();
4765 updateControlsGeometry();
4766 }
4767 }
4768 }
4769 }
4770
grabForShowAnimation(const Window::SectionSlideParams & params)4771 QPixmap HistoryWidget::grabForShowAnimation(
4772 const Window::SectionSlideParams ¶ms) {
4773 if (params.withTopBarShadow) {
4774 _topShadow->hide();
4775 }
4776 _inGrab = true;
4777 updateControlsGeometry();
4778 auto result = Ui::GrabWidget(this);
4779 _inGrab = false;
4780 updateControlsGeometry();
4781 if (params.withTopBarShadow) {
4782 _topShadow->show();
4783 }
4784 return result;
4785 }
4786
skipItemRepaint()4787 bool HistoryWidget::skipItemRepaint() {
4788 auto ms = crl::now();
4789 if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
4790 return false;
4791 }
4792 _updateHistoryItems.callOnce(
4793 _lastScrolled + kSkipRepaintWhileScrollMs - ms);
4794 return true;
4795 }
4796
updateHistoryItemsByTimer()4797 void HistoryWidget::updateHistoryItemsByTimer() {
4798 if (!_list) {
4799 return;
4800 }
4801
4802 auto ms = crl::now();
4803 if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
4804 _list->update();
4805 } else {
4806 _updateHistoryItems.callOnce(
4807 _lastScrolled + kSkipRepaintWhileScrollMs - ms);
4808 }
4809 }
4810
handlePendingHistoryUpdate()4811 void HistoryWidget::handlePendingHistoryUpdate() {
4812 if (hasPendingResizedItems() || _updateHistoryGeometryRequired) {
4813 updateHistoryGeometry();
4814 _list->update();
4815 }
4816 }
4817
resizeEvent(QResizeEvent * e)4818 void HistoryWidget::resizeEvent(QResizeEvent *e) {
4819 //updateTabbedSelectorSectionShown();
4820 recountChatWidth();
4821 updateControlsGeometry();
4822 }
4823
updateControlsGeometry()4824 void HistoryWidget::updateControlsGeometry() {
4825 _topBar->resizeToWidth(width());
4826 _topBar->moveToLeft(0, 0);
4827 _voiceRecordBar->resizeToWidth(width());
4828
4829 moveFieldControls();
4830
4831 const auto groupCallTop = _topBar->bottomNoMargins();
4832 if (_groupCallBar) {
4833 _groupCallBar->move(0, groupCallTop);
4834 _groupCallBar->resizeToWidth(width());
4835 }
4836 const auto requestsTop = groupCallTop + (_groupCallBar ? _groupCallBar->height() : 0);
4837 if (_requestsBar) {
4838 _requestsBar->move(0, requestsTop);
4839 _requestsBar->resizeToWidth(width());
4840 }
4841 const auto pinnedBarTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0);
4842 if (_pinnedBar) {
4843 _pinnedBar->move(0, pinnedBarTop);
4844 _pinnedBar->resizeToWidth(width());
4845 }
4846 const auto contactStatusTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0);
4847 if (_contactStatus) {
4848 _contactStatus->move(0, contactStatusTop);
4849 }
4850 const auto scrollAreaTop = contactStatusTop + (_contactStatus ? _contactStatus->height() : 0);
4851 if (_scroll->y() != scrollAreaTop) {
4852 _scroll->moveToLeft(0, scrollAreaTop);
4853 _fieldAutocomplete->setBoundings(_scroll->geometry());
4854 if (_supportAutocomplete) {
4855 _supportAutocomplete->setBoundings(_scroll->geometry());
4856 }
4857 }
4858
4859 updateHistoryGeometry(false, false, { ScrollChangeAdd, _topDelta });
4860
4861 updateFieldSize();
4862
4863 updateHistoryDownPosition();
4864
4865 if (_membersDropdown) {
4866 _membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
4867 }
4868
4869 const auto isOneColumn = controller()->adaptive().isOneColumn();
4870 const auto isThreeColumn = controller()->adaptive().isThreeColumn();
4871 const auto topShadowLeft = (isOneColumn || _inGrab)
4872 ? 0
4873 : st::lineWidth;
4874 const auto topShadowRight = (isThreeColumn && !_inGrab && _peer)
4875 ? st::lineWidth
4876 : 0;
4877 _topShadow->setGeometryToLeft(
4878 topShadowLeft,
4879 _topBar->bottomNoMargins(),
4880 width() - topShadowLeft - topShadowRight,
4881 st::lineWidth);
4882 }
4883
itemRemoved(not_null<const HistoryItem * > item)4884 void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
4885 if (item == _replyEditMsg && _editMsgId) {
4886 cancelEdit();
4887 }
4888 if (item == _replyEditMsg && _replyToId) {
4889 cancelReply();
4890 }
4891 while (item == _replyReturn) {
4892 calcNextReplyReturn();
4893 }
4894 if (_kbReplyTo && item == _kbReplyTo) {
4895 toggleKeyboard();
4896 _kbReplyTo = nullptr;
4897 }
4898 auto found = ranges::find(_toForward.items, item);
4899 if (found != _toForward.items.end()) {
4900 _toForward.items.erase(found);
4901 updateForwardingTexts();
4902 if (_toForward.items.empty()) {
4903 updateControlsVisibility();
4904 updateControlsGeometry();
4905 }
4906 }
4907 const auto i = _itemRevealAnimations.find(item);
4908 if (i != end(_itemRevealAnimations)) {
4909 _itemRevealAnimations.erase(i);
4910 revealItemsCallback();
4911 }
4912 const auto j = _itemRevealPending.find(item);
4913 if (j != _itemRevealPending.end()) {
4914 _itemRevealPending.erase(j);
4915 }
4916 }
4917
itemEdited(not_null<HistoryItem * > item)4918 void HistoryWidget::itemEdited(not_null<HistoryItem*> item) {
4919 if (item.get() == _replyEditMsg) {
4920 updateReplyEditTexts(true);
4921 }
4922 }
4923
replyToId() const4924 MsgId HistoryWidget::replyToId() const {
4925 return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
4926 }
4927
countInitialScrollTop()4928 int HistoryWidget::countInitialScrollTop() {
4929 if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem)) {
4930 return _list->historyScrollTop();
4931 } else if (_showAtMsgId
4932 && (IsServerMsgId(_showAtMsgId)
4933 || IsServerMsgId(-_showAtMsgId))) {
4934 const auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
4935 const auto itemTop = _list->itemTop(item);
4936 if (itemTop < 0) {
4937 setMsgId(0);
4938 return countInitialScrollTop();
4939 } else {
4940 const auto view = item->mainView();
4941 Assert(view != nullptr);
4942
4943 enqueueMessageHighlight(view);
4944 const auto result = itemTopForHighlight(view);
4945 createUnreadBarIfBelowVisibleArea(result);
4946 return result;
4947 }
4948 } else if (_showAtMsgId == ShowAtTheEndMsgId) {
4949 return ScrollMax;
4950 } else if (const auto top = unreadBarTop()) {
4951 return *top;
4952 } else {
4953 _history->calculateFirstUnreadMessage();
4954 return countAutomaticScrollTop();
4955 }
4956 }
4957
createUnreadBarIfBelowVisibleArea(int withScrollTop)4958 void HistoryWidget::createUnreadBarIfBelowVisibleArea(int withScrollTop) {
4959 Expects(_history != nullptr);
4960
4961 if (_history->unreadBar()) {
4962 return;
4963 }
4964 _history->calculateFirstUnreadMessage();
4965 if (const auto unread = _history->firstUnreadMessage()) {
4966 if (_list->itemTop(unread) > withScrollTop) {
4967 createUnreadBarAndResize();
4968 }
4969 }
4970 }
4971
createUnreadBarAndResize()4972 void HistoryWidget::createUnreadBarAndResize() {
4973 if (!_history->firstUnreadMessage()) {
4974 return;
4975 }
4976 const auto was = base::take(_historyInited);
4977 _history->addUnreadBar();
4978 if (hasPendingResizedItems()) {
4979 updateListSize();
4980 }
4981 _historyInited = was;
4982 }
4983
countAutomaticScrollTop()4984 int HistoryWidget::countAutomaticScrollTop() {
4985 Expects(_history != nullptr);
4986 Expects(_list != nullptr);
4987
4988 if (const auto unread = _history->firstUnreadMessage()) {
4989 const auto firstUnreadTop = _list->itemTop(unread);
4990 const auto possibleUnreadBarTop = _scroll->scrollTopMax()
4991 + HistoryView::UnreadBar::height()
4992 - HistoryView::UnreadBar::marginTop();
4993 if (firstUnreadTop < possibleUnreadBarTop) {
4994 createUnreadBarAndResize();
4995 if (_history->unreadBar() != nullptr) {
4996 setMsgId(ShowAtUnreadMsgId);
4997 return countInitialScrollTop();
4998 }
4999 }
5000 }
5001 return ScrollMax;
5002 }
5003
updateHistoryGeometry(bool initial,bool loadedDown,const ScrollChange & change)5004 void HistoryWidget::updateHistoryGeometry(
5005 bool initial,
5006 bool loadedDown,
5007 const ScrollChange &change) {
5008 const auto guard = gsl::finally([&] {
5009 _itemRevealPending.clear();
5010 });
5011 if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) {
5012 return;
5013 }
5014 if (_firstLoadRequest || _a_show.animating()) {
5015 _updateHistoryGeometryRequired = true;
5016 return; // scrollTopMax etc are not working after recountHistoryGeometry()
5017 }
5018
5019 auto newScrollHeight = height() - _topBar->height();
5020 if (_pinnedBar) {
5021 newScrollHeight -= _pinnedBar->height();
5022 }
5023 if (_groupCallBar) {
5024 newScrollHeight -= _groupCallBar->height();
5025 }
5026 if (_requestsBar) {
5027 newScrollHeight -= _requestsBar->height();
5028 }
5029 if (_contactStatus) {
5030 newScrollHeight -= _contactStatus->height();
5031 }
5032 if (isChoosingTheme()) {
5033 newScrollHeight -= _chooseTheme->height();
5034 } else if (!editingMessage()
5035 && (isBlocked()
5036 || isBotStart()
5037 || isJoinChannel()
5038 || isMuteUnmute()
5039 || isReportMessages())) {
5040 newScrollHeight -= _unblock->height();
5041 } else {
5042 if (editingMessage() || _canSendMessages) {
5043 newScrollHeight -= (_field->height() + 2 * st::historySendPadding);
5044 } else if (writeRestriction().has_value()) {
5045 newScrollHeight -= _unblock->height();
5046 }
5047 if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) {
5048 newScrollHeight -= st::historyReplyHeight;
5049 }
5050 if (_kbShown) {
5051 newScrollHeight -= _kbScroll->height();
5052 }
5053 }
5054 if (newScrollHeight <= 0) {
5055 return;
5056 }
5057 const auto wasScrollTop = _scroll->scrollTop();
5058 const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax());
5059 const auto needResize = (_scroll->width() != width())
5060 || (_scroll->height() != newScrollHeight);
5061 if (needResize) {
5062 _scroll->resize(width(), newScrollHeight);
5063 // on initial updateListSize we didn't put the _scroll->scrollTop correctly yet
5064 // so visibleAreaUpdated() call will erase it with the new (undefined) value
5065 if (!initial) {
5066 visibleAreaUpdated();
5067 }
5068
5069 _fieldAutocomplete->setBoundings(_scroll->geometry());
5070 if (_supportAutocomplete) {
5071 _supportAutocomplete->setBoundings(_scroll->geometry());
5072 }
5073 if (!_historyDownShown.animating()) {
5074 // _historyDown is a child widget of _scroll, not me.
5075 _historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _historyDown->height() - st::historyToDownPosition.y());
5076 if (!_unreadMentionsShown.animating()) {
5077 // _unreadMentions is a child widget of _scroll, not me.
5078 auto additionalSkip = _historyDownIsShown ? (_historyDown->height() + st::historyUnreadMentionsSkip) : 0;
5079 _unreadMentions->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _unreadMentions->height() - additionalSkip - st::historyToDownPosition.y());
5080 }
5081 }
5082
5083 controller()->floatPlayerAreaUpdated();
5084 }
5085
5086 updateListSize();
5087 _updateHistoryGeometryRequired = false;
5088
5089 auto newScrollTop = 0;
5090 if (initial) {
5091 newScrollTop = countInitialScrollTop();
5092 _historyInited = true;
5093 _scrollToAnimation.stop();
5094 } else if (wasAtBottom && !loadedDown && !_history->unreadBar()) {
5095 newScrollTop = countAutomaticScrollTop();
5096 } else {
5097 newScrollTop = std::min(
5098 _list->historyScrollTop(),
5099 _scroll->scrollTopMax());
5100 if (change.type == ScrollChangeAdd) {
5101 newScrollTop += change.value;
5102 } else if (change.type == ScrollChangeNoJumpToBottom) {
5103 newScrollTop = wasScrollTop;
5104 }
5105 }
5106 const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
5107 synteticScrollToY(toY);
5108 }
5109
revealItemsCallback()5110 void HistoryWidget::revealItemsCallback() {
5111 auto height = 0;
5112 if (!_historyInited) {
5113 _itemRevealAnimations.clear();
5114 }
5115 for (auto i = begin(_itemRevealAnimations)
5116 ; i != end(_itemRevealAnimations);) {
5117 if (!i->second.animation.animating()) {
5118 i = _itemRevealAnimations.erase(i);
5119 } else {
5120 height += anim::interpolate(
5121 i->second.startHeight,
5122 0,
5123 i->second.animation.value(1.));
5124 ++i;
5125 }
5126 }
5127 if (_itemsRevealHeight != height) {
5128 const auto wasScrollTop = _scroll->scrollTop();
5129 const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax());
5130 if (!wasAtBottom) {
5131 height = 0;
5132 _itemRevealAnimations.clear();
5133 }
5134
5135 _itemsRevealHeight = height;
5136 _list->changeItemsRevealHeight(_itemsRevealHeight);
5137
5138 const auto newScrollTop = (wasAtBottom && !_history->unreadBar())
5139 ? countAutomaticScrollTop()
5140 : _list->historyScrollTop();
5141 const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
5142 synteticScrollToY(toY);
5143 }
5144 }
5145
startItemRevealAnimations()5146 void HistoryWidget::startItemRevealAnimations() {
5147 for (const auto &item : base::take(_itemRevealPending)) {
5148 if (const auto view = item->mainView()) {
5149 if (const auto top = _list->itemTop(view); top >= 0) {
5150 if (const auto height = view->height()) {
5151 if (!_itemRevealAnimations.contains(item)) {
5152 auto &animation = _itemRevealAnimations[item];
5153 animation.startHeight = height;
5154 _itemsRevealHeight += height;
5155 animation.animation.start(
5156 [=] { revealItemsCallback(); },
5157 0.,
5158 1.,
5159 HistoryView::ListWidget::kItemRevealDuration,
5160 anim::easeOutCirc);
5161 if (item->out() || _history->peer->isSelf()) {
5162 _list->theme()->rotateComplexGradientBackground();
5163 }
5164 }
5165 }
5166 }
5167 }
5168 }
5169 }
5170
updateListSize()5171 void HistoryWidget::updateListSize() {
5172 Expects(_list != nullptr);
5173
5174 _list->recountHistoryGeometry();
5175 auto washidden = _scroll->isHidden();
5176 if (washidden) {
5177 _scroll->show();
5178 }
5179 startItemRevealAnimations();
5180 _list->setItemsRevealHeight(_itemsRevealHeight);
5181 _list->updateSize();
5182 if (washidden) {
5183 _scroll->hide();
5184 }
5185 _updateHistoryGeometryRequired = true;
5186 }
5187
hasPendingResizedItems() const5188 bool HistoryWidget::hasPendingResizedItems() const {
5189 if (!_list) {
5190 // Based on the crash reports there is a codepath (at least on macOS)
5191 // that leads from _list = _scroll->setOwnedWidget(...) right into
5192 // the HistoryWidget::paintEvent (by sending fake mouse move events
5193 // inside scroll area -> hiding tooltip window -> exposing the main
5194 // window -> syncing it backing store synchronously).
5195 //
5196 // So really we could get here with !_list && (_history != nullptr).
5197 return false;
5198 }
5199 return (_history && _history->hasPendingResizedItems())
5200 || (_migrated && _migrated->hasPendingResizedItems());
5201 }
5202
unreadBarTop() const5203 std::optional<int> HistoryWidget::unreadBarTop() const {
5204 const auto bar = [&]() -> HistoryView::Element* {
5205 if (const auto bar = _migrated ? _migrated->unreadBar() : nullptr) {
5206 return bar;
5207 }
5208 return _history->unreadBar();
5209 }();
5210 if (bar) {
5211 const auto result = _list->itemTop(bar)
5212 + HistoryView::UnreadBar::marginTop();
5213 if (bar->Has<HistoryView::DateBadge>()) {
5214 return result + bar->Get<HistoryView::DateBadge>()->height();
5215 }
5216 return result;
5217 }
5218 return std::nullopt;
5219 }
5220
addMessagesToFront(PeerData * peer,const QVector<MTPMessage> & messages)5221 void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages) {
5222 _list->messagesReceived(peer, messages);
5223 if (!_firstLoadRequest) {
5224 updateHistoryGeometry();
5225 updateBotKeyboard();
5226 }
5227 }
5228
addMessagesToBack(PeerData * peer,const QVector<MTPMessage> & messages)5229 void HistoryWidget::addMessagesToBack(
5230 PeerData *peer,
5231 const QVector<MTPMessage> &messages) {
5232 const auto checkForUnreadStart = [&] {
5233 if (_history->unreadBar() || !_history->trackUnreadMessages()) {
5234 return false;
5235 }
5236 _history->calculateFirstUnreadMessage();
5237 return !_history->firstUnreadMessage();
5238 }();
5239 _list->messagesReceivedDown(peer, messages);
5240 if (checkForUnreadStart) {
5241 _history->calculateFirstUnreadMessage();
5242 createUnreadBarAndResize();
5243 }
5244 if (!_firstLoadRequest) {
5245 updateHistoryGeometry(false, true, { ScrollChangeNoJumpToBottom, 0 });
5246 }
5247 }
5248
updateBotKeyboard(History * h,bool force)5249 void HistoryWidget::updateBotKeyboard(History *h, bool force) {
5250 if (h && h != _history && h != _migrated) {
5251 return;
5252 }
5253
5254 bool changed = false;
5255 bool wasVisible = _kbShown || _kbReplyTo;
5256 if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) {
5257 changed = _keyboard->updateMarkup(nullptr, force);
5258 } else if (_replyToId && _replyEditMsg) {
5259 changed = _keyboard->updateMarkup(_replyEditMsg, force);
5260 } else {
5261 const auto keyboardItem = _history->lastKeyboardId
5262 ? session().data().message(_channel, _history->lastKeyboardId)
5263 : nullptr;
5264 changed = _keyboard->updateMarkup(keyboardItem, force);
5265 }
5266 updateCmdStartShown();
5267 if (!changed) {
5268 return;
5269 }
5270
5271 bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyToId || !_replyEditMsg);
5272 if (hasMarkup || forceReply) {
5273 if (_keyboard->singleUse() && _keyboard->hasMarkup() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) {
5274 _history->lastKeyboardHiddenId = _history->lastKeyboardId;
5275 }
5276 if (!isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) {
5277 if (!_a_show.animating()) {
5278 if (hasMarkup) {
5279 _kbScroll->show();
5280 _tabbedSelectorToggle->hide();
5281 _botKeyboardHide->show();
5282 } else {
5283 _kbScroll->hide();
5284 _tabbedSelectorToggle->show();
5285 _botKeyboardHide->hide();
5286 }
5287 _botKeyboardShow->hide();
5288 _botCommandStart->hide();
5289 }
5290 const auto maxheight = computeMaxFieldHeight();
5291 const auto kbheight = hasMarkup ? qMin(_keyboard->height(), maxheight - (maxheight / 2)) : 0;
5292 _field->setMaxHeight(maxheight - kbheight);
5293 _kbShown = hasMarkup;
5294 _kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply())
5295 ? session().data().message(_keyboard->forMsgId())
5296 : nullptr;
5297 if (_kbReplyTo && !_replyToId) {
5298 updateReplyToName();
5299 updateReplyEditText(_kbReplyTo);
5300 }
5301 } else {
5302 if (!_a_show.animating()) {
5303 _kbScroll->hide();
5304 _tabbedSelectorToggle->show();
5305 _botKeyboardHide->hide();
5306 _botKeyboardShow->show();
5307 _botCommandStart->hide();
5308 }
5309 _field->setMaxHeight(computeMaxFieldHeight());
5310 _kbShown = false;
5311 _kbReplyTo = nullptr;
5312 if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) {
5313 _fieldBarCancel->hide();
5314 updateMouseTracking();
5315 }
5316 }
5317 } else {
5318 if (!_scroll->isHidden()) {
5319 _kbScroll->hide();
5320 _tabbedSelectorToggle->show();
5321 _botKeyboardHide->hide();
5322 _botKeyboardShow->hide();
5323 _botCommandStart->setVisible(!_editMsgId);
5324 }
5325 _field->setMaxHeight(computeMaxFieldHeight());
5326 _kbShown = false;
5327 _kbReplyTo = nullptr;
5328 if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) {
5329 _fieldBarCancel->hide();
5330 updateMouseTracking();
5331 }
5332 }
5333 refreshTopBarActiveChat();
5334 updateFieldPlaceholder();
5335 updateControlsGeometry();
5336 update();
5337 }
5338
botCallbackSent(not_null<HistoryItem * > item)5339 void HistoryWidget::botCallbackSent(not_null<HistoryItem*> item) {
5340 if (!item->isRegular() || _peer != item->history()->peer) {
5341 return;
5342 }
5343
5344 const auto keyId = _keyboard->forMsgId();
5345 const auto lastKeyboardUsed = (keyId == FullMsgId(_channel, item->id))
5346 && (keyId == FullMsgId(_channel, _history->lastKeyboardId));
5347
5348 session().data().requestItemRepaint(item);
5349
5350 if (_replyToId == item->id) {
5351 cancelReply();
5352 }
5353 if (_keyboard->singleUse()
5354 && _keyboard->hasMarkup()
5355 && lastKeyboardUsed) {
5356 if (_kbShown) {
5357 toggleKeyboard(false);
5358 }
5359 _history->lastKeyboardUsed = true;
5360 }
5361 }
5362
computeMaxFieldHeight() const5363 int HistoryWidget::computeMaxFieldHeight() const {
5364 const auto available = height()
5365 - _topBar->height()
5366 - (_contactStatus ? _contactStatus->height() : 0)
5367 - (_pinnedBar ? _pinnedBar->height() : 0)
5368 - (_groupCallBar ? _groupCallBar->height() : 0)
5369 - (_requestsBar ? _requestsBar->height() : 0)
5370 - ((_editMsgId
5371 || replyToId()
5372 || readyToForward()
5373 || (_previewData && _previewData->pendingTill >= 0))
5374 ? st::historyReplyHeight
5375 : 0)
5376 - (2 * st::historySendPadding)
5377 - st::historyReplyHeight; // at least this height for history.
5378 return std::min(st::historyComposeFieldMaxHeight, available);
5379 }
5380
updateHistoryDownPosition()5381 void HistoryWidget::updateHistoryDownPosition() {
5382 // _historyDown is a child widget of _scroll, not me.
5383 auto top = anim::interpolate(0, _historyDown->height() + st::historyToDownPosition.y(), _historyDownShown.value(_historyDownIsShown ? 1. : 0.));
5384 _historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - top);
5385 auto shouldBeHidden = !_historyDownIsShown && !_historyDownShown.animating();
5386 if (shouldBeHidden != _historyDown->isHidden()) {
5387 _historyDown->setVisible(!shouldBeHidden);
5388 }
5389 updateUnreadMentionsPosition();
5390 }
5391
updateHistoryDownVisibility()5392 void HistoryWidget::updateHistoryDownVisibility() {
5393 if (_a_show.animating()) return;
5394
5395 const auto haveUnreadBelowBottom = [&](History *history) {
5396 if (!_list || !history || history->unreadCount() <= 0) {
5397 return false;
5398 }
5399 const auto unread = history->firstUnreadMessage();
5400 if (!unread) {
5401 return false;
5402 }
5403 const auto top = _list->itemTop(unread);
5404 return (top >= _scroll->scrollTop() + _scroll->height());
5405 };
5406 const auto historyDownIsVisible = [&] {
5407 if (!_list || _firstLoadRequest) {
5408 return false;
5409 }
5410 if (_voiceRecordBar->isLockPresent()) {
5411 return false;
5412 }
5413 if (!_history->loadedAtBottom() || _replyReturn) {
5414 return true;
5415 }
5416 const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
5417 if (top < _scroll->scrollTopMax()) {
5418 return true;
5419 }
5420 if (haveUnreadBelowBottom(_history)
5421 || haveUnreadBelowBottom(_migrated)) {
5422 return true;
5423 }
5424 return false;
5425 };
5426 auto historyDownIsShown = historyDownIsVisible();
5427 if (_historyDownIsShown != historyDownIsShown) {
5428 _historyDownIsShown = historyDownIsShown;
5429 _historyDownShown.start([=] { updateHistoryDownPosition(); }, _historyDownIsShown ? 0. : 1., _historyDownIsShown ? 1. : 0., st::historyToDownDuration);
5430 }
5431 }
5432
updateUnreadMentionsPosition()5433 void HistoryWidget::updateUnreadMentionsPosition() {
5434 // _unreadMentions is a child widget of _scroll, not me.
5435 auto right = anim::interpolate(-_unreadMentions->width(), st::historyToDownPosition.x(), _unreadMentionsShown.value(_unreadMentionsIsShown ? 1. : 0.));
5436 auto shift = anim::interpolate(0, _historyDown->height() + st::historyUnreadMentionsSkip, _historyDownShown.value(_historyDownIsShown ? 1. : 0.));
5437 auto top = _scroll->height() - _unreadMentions->height() - st::historyToDownPosition.y() - shift;
5438 _unreadMentions->moveToRight(right, top);
5439 auto shouldBeHidden = !_unreadMentionsIsShown && !_unreadMentionsShown.animating();
5440 if (shouldBeHidden != _unreadMentions->isHidden()) {
5441 _unreadMentions->setVisible(!shouldBeHidden);
5442 }
5443 }
5444
updateUnreadMentionsVisibility()5445 void HistoryWidget::updateUnreadMentionsVisibility() {
5446 if (_a_show.animating()) return;
5447
5448 auto showUnreadMentions = _peer && (_peer->isChat() || _peer->isMegagroup());
5449 if (showUnreadMentions) {
5450 session().api().preloadEnoughUnreadMentions(_history);
5451 }
5452 const auto unreadMentionsIsShown = [&] {
5453 if (!showUnreadMentions || _firstLoadRequest) {
5454 return false;
5455 }
5456 if (_voiceRecordBar->isLockPresent()) {
5457 return false;
5458 }
5459 if (!_history->getUnreadMentionsLoadedCount()) {
5460 return false;
5461 }
5462 // If we have an unheard voice message with the mention
5463 // and our message is the last one, we can't see the status
5464 // (delivered/read) of this message.
5465 // (Except for MacBooks with the TouchPad.)
5466 if (_scroll->scrollTop() == _scroll->scrollTopMax()) {
5467 if (const auto lastMessage = _history->lastMessage()) {
5468 return !lastMessage->from()->isSelf();
5469 }
5470 }
5471 return true;
5472 }();
5473 if (unreadMentionsIsShown) {
5474 _unreadMentions->setUnreadCount(_history->getUnreadMentionsCount());
5475 }
5476 if (_unreadMentionsIsShown != unreadMentionsIsShown) {
5477 _unreadMentionsIsShown = unreadMentionsIsShown;
5478 _unreadMentionsShown.start([=] { updateUnreadMentionsPosition(); }, _unreadMentionsIsShown ? 0. : 1., _unreadMentionsIsShown ? 1. : 0., st::historyToDownDuration);
5479 }
5480 }
5481
mousePressEvent(QMouseEvent * e)5482 void HistoryWidget::mousePressEvent(QMouseEvent *e) {
5483 const auto hasSecondLayer = (_editMsgId
5484 || _replyToId
5485 || readyToForward()
5486 || _kbReplyTo);
5487 _replyForwardPressed = hasSecondLayer && QRect(
5488 0,
5489 _field->y() - st::historySendPadding - st::historyReplyHeight,
5490 st::historyReplySkip,
5491 st::historyReplyHeight).contains(e->pos());
5492 if (_replyForwardPressed && !_fieldBarCancel->isHidden()) {
5493 updateField();
5494 } else if (_inReplyEditForward) {
5495 if (readyToForward()) {
5496 using Options = Data::ForwardOptions;
5497 const auto now = _toForward.options;
5498 const auto count = _toForward.items.size();
5499 const auto dropNames = (now != Options::PreserveInfo);
5500 const auto hasCaptions = [&] {
5501 for (const auto item : _toForward.items) {
5502 if (const auto media = item->media()) {
5503 if (!item->originalText().text.isEmpty()
5504 && media->allowsEditCaption()) {
5505 return true;
5506 }
5507 }
5508 }
5509 return false;
5510 }();
5511 const auto hasOnlyForcedForwardedInfo = [&] {
5512 if (hasCaptions) {
5513 return false;
5514 }
5515 for (const auto item : _toForward.items) {
5516 if (const auto media = item->media()) {
5517 if (!media->forceForwardedInfo()) {
5518 return false;
5519 }
5520 } else {
5521 return false;
5522 }
5523 }
5524 return true;
5525 }();
5526 const auto dropCaptions = (now == Options::NoNamesAndCaptions);
5527 const auto weak = Ui::MakeWeak(this);
5528 const auto changeRecipient = crl::guard(weak, [=] {
5529 if (_toForward.items.empty()) {
5530 return;
5531 }
5532 const auto draft = std::move(_toForward);
5533 session().data().cancelForwarding(_history);
5534 auto list = session().data().itemsToIds(draft.items);
5535 Window::ShowForwardMessagesBox(controller(), {
5536 .ids = session().data().itemsToIds(draft.items),
5537 .options = draft.options,
5538 });
5539 });
5540 if (hasOnlyForcedForwardedInfo) {
5541 changeRecipient();
5542 return;
5543 }
5544 const auto optionsChanged = crl::guard(weak, [=](
5545 Ui::ForwardOptions options) {
5546 const auto newOptions = (options.hasCaptions
5547 && options.dropCaptions)
5548 ? Options::NoNamesAndCaptions
5549 : options.dropNames
5550 ? Options::NoSenderNames
5551 : Options::PreserveInfo;
5552 if (_history && _toForward.options != newOptions) {
5553 _toForward.options = newOptions;
5554 _history->setForwardDraft({
5555 .ids = session().data().itemsToIds(_toForward.items),
5556 .options = newOptions,
5557 });
5558 updateField();
5559 }
5560 });
5561 controller()->show(Box(
5562 Ui::ForwardOptionsBox,
5563 count,
5564 Ui::ForwardOptions{
5565 .dropNames = dropNames,
5566 .hasCaptions = hasCaptions,
5567 .dropCaptions = dropCaptions,
5568 },
5569 optionsChanged,
5570 changeRecipient));
5571 } else {
5572 Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId());
5573 }
5574 }
5575 }
5576
keyPressEvent(QKeyEvent * e)5577 void HistoryWidget::keyPressEvent(QKeyEvent *e) {
5578 if (!_history) return;
5579
5580 const auto commonModifiers = e->modifiers() & kCommonModifiers;
5581 if (e->key() == Qt::Key_Escape) {
5582 e->ignore();
5583 } else if (e->key() == Qt::Key_Back) {
5584 controller()->showBackFromStack();
5585 _cancelRequests.fire({});
5586 } else if (e->key() == Qt::Key_PageDown) {
5587 _scroll->keyPressEvent(e);
5588 } else if (e->key() == Qt::Key_PageUp) {
5589 _scroll->keyPressEvent(e);
5590 } else if (e->key() == Qt::Key_Down && !commonModifiers) {
5591 _scroll->keyPressEvent(e);
5592 } else if (e->key() == Qt::Key_Up && !commonModifiers) {
5593 const auto item = _history
5594 ? _history->lastEditableMessage()
5595 : nullptr;
5596 if (item
5597 && _field->empty()
5598 && !_editMsgId
5599 && !_replyToId) {
5600 editMessage(item);
5601 return;
5602 }
5603 _scroll->keyPressEvent(e);
5604 } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
5605 if (!_botStart->isHidden()) {
5606 sendBotStartCommand();
5607 }
5608 if (!_canSendMessages) {
5609 const auto submitting = Ui::InputField::ShouldSubmit(
5610 Core::App().settings().sendSubmitWay(),
5611 e->modifiers());
5612 if (submitting) {
5613 sendWithModifiers(e->modifiers());
5614 }
5615 }
5616 } else if (e->key() == Qt::Key_O && e->modifiers() == Qt::ControlModifier) {
5617 chooseAttach();
5618 } else {
5619 e->ignore();
5620 }
5621 }
5622
handlePeerMigration()5623 void HistoryWidget::handlePeerMigration() {
5624 const auto current = _peer->migrateToOrMe();
5625 const auto chat = current->migrateFrom();
5626 if (!chat) {
5627 return;
5628 }
5629 const auto channel = current->asChannel();
5630 Assert(channel != nullptr);
5631
5632 if (_peer != channel) {
5633 showHistory(
5634 channel->id,
5635 (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId);
5636 channel->session().api().requestParticipantsCountDelayed(channel);
5637 } else {
5638 _migrated = _history->migrateFrom();
5639 _list->notifyMigrateUpdated();
5640 setupPinnedTracker();
5641 setupGroupCallBar();
5642 setupRequestsBar();
5643 updateHistoryGeometry();
5644 }
5645 const auto from = chat->owner().historyLoaded(chat);
5646 const auto to = channel->owner().historyLoaded(channel);
5647 if (from
5648 && to
5649 && !from->isEmpty()
5650 && (!from->loadedAtBottom() || !to->loadedAtTop())) {
5651 from->clear(History::ClearType::Unload);
5652 }
5653 }
5654
replyToPreviousMessage()5655 bool HistoryWidget::replyToPreviousMessage() {
5656 if (!_history || _editMsgId) {
5657 return false;
5658 }
5659 const auto fullId = FullMsgId(
5660 _history->channelId(),
5661 _replyToId);
5662 if (const auto item = session().data().message(fullId)) {
5663 if (const auto view = item->mainView()) {
5664 if (const auto previousView = view->previousDisplayedInBlocks()) {
5665 const auto previous = previousView->data();
5666 controller()->showPeerHistoryAtItem(previous);
5667 replyToMessage(previous);
5668 return true;
5669 }
5670 }
5671 } else if (const auto previousView = _history->findLastDisplayed()) {
5672 const auto previous = previousView->data();
5673 controller()->showPeerHistoryAtItem(previous);
5674 replyToMessage(previous);
5675 return true;
5676 }
5677 return false;
5678 }
5679
replyToNextMessage()5680 bool HistoryWidget::replyToNextMessage() {
5681 if (!_history || _editMsgId) {
5682 return false;
5683 }
5684 const auto fullId = FullMsgId(
5685 _history->channelId(),
5686 _replyToId);
5687 if (const auto item = session().data().message(fullId)) {
5688 if (const auto view = item->mainView()) {
5689 if (const auto nextView = view->nextDisplayedInBlocks()) {
5690 const auto next = nextView->data();
5691 controller()->showPeerHistoryAtItem(next);
5692 replyToMessage(next);
5693 } else {
5694 clearHighlightMessages();
5695 cancelReply(false);
5696 }
5697 return true;
5698 }
5699 }
5700 return false;
5701 }
5702
showSlowmodeError()5703 bool HistoryWidget::showSlowmodeError() {
5704 const auto text = [&] {
5705 if (const auto left = _peer->slowmodeSecondsLeft()) {
5706 return tr::lng_slowmode_enabled(
5707 tr::now,
5708 lt_left,
5709 Ui::FormatDurationWords(left));
5710 } else if (_peer->slowmodeApplied()) {
5711 if (const auto item = _history->latestSendingMessage()) {
5712 if (const auto view = item->mainView()) {
5713 animatedScrollToItem(item->id);
5714 enqueueMessageHighlight(view);
5715 }
5716 return tr::lng_slowmode_no_many(tr::now);
5717 }
5718 }
5719 return QString();
5720 }();
5721 if (text.isEmpty()) {
5722 return false;
5723 }
5724 Ui::ShowMultilineToast({
5725 .text = { text },
5726 });
5727 return true;
5728 }
5729
fieldTabbed()5730 void HistoryWidget::fieldTabbed() {
5731 if (_supportAutocomplete) {
5732 _supportAutocomplete->activate(_field.data());
5733 } else if (!_fieldAutocomplete->isHidden()) {
5734 _fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
5735 }
5736 }
5737
sendInlineResult(InlineBots::ResultSelected result)5738 void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
5739 if (!_peer || !_peer->canWrite()) {
5740 return;
5741 } else if (showSlowmodeError()) {
5742 return;
5743 }
5744
5745 auto errorText = result.result->getErrorOnSend(_history);
5746 if (!errorText.isEmpty()) {
5747 controller()->show(Box<Ui::InformBox>(errorText));
5748 return;
5749 }
5750
5751 auto action = Api::SendAction(_history);
5752 action.replyTo = replyToId();
5753 action.options = std::move(result.options);
5754 action.generateLocal = true;
5755 session().api().sendInlineResult(result.bot, result.result, action);
5756
5757 clearFieldText();
5758 _saveDraftText = true;
5759 _saveDraftStart = crl::now();
5760 saveDraft();
5761
5762 auto &bots = cRefRecentInlineBots();
5763 const auto index = bots.indexOf(result.bot);
5764 if (index) {
5765 if (index > 0) {
5766 bots.removeAt(index);
5767 } else if (bots.size() >= RecentInlineBotsLimit) {
5768 bots.resize(RecentInlineBotsLimit - 1);
5769 }
5770 bots.push_front(result.bot);
5771 session().local().writeRecentHashtagsAndBots();
5772 }
5773
5774 hideSelectorControlsAnimated();
5775
5776 _field->setFocus();
5777 }
5778
updatePinnedViewer()5779 void HistoryWidget::updatePinnedViewer() {
5780 if (_firstLoadRequest
5781 || _delayedShowAtRequest
5782 || _scroll->isHidden()
5783 || !_history
5784 || !_historyInited) {
5785 return;
5786 }
5787 const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
5788 auto [view, offset] = _list->findViewForPinnedTracking(visibleBottom);
5789 const auto lessThanId = !view
5790 ? (ServerMaxMsgId - 1)
5791 : (view->data()->history() != _history)
5792 ? (view->data()->id + (offset > 0 ? 1 : 0) - ServerMaxMsgId)
5793 : (view->data()->id + (offset > 0 ? 1 : 0));
5794 const auto lastClickedId = !_pinnedClickedId
5795 ? (ServerMaxMsgId - 1)
5796 : (!_migrated || _pinnedClickedId.channel)
5797 ? _pinnedClickedId.msg
5798 : (_pinnedClickedId.msg - ServerMaxMsgId);
5799 if (_pinnedClickedId
5800 && lessThanId <= lastClickedId
5801 && !_scrollToAnimation.animating()) {
5802 _pinnedClickedId = FullMsgId();
5803 }
5804 if (_pinnedClickedId && !_minPinnedId) {
5805 _minPinnedId = Data::ResolveMinPinnedId(
5806 _peer,
5807 _migrated ? _migrated->peer.get() : nullptr);
5808 }
5809 if (_pinnedClickedId && _minPinnedId && _minPinnedId >= _pinnedClickedId) {
5810 // After click on the last pinned message we should the top one.
5811 _pinnedTracker->trackAround(ServerMaxMsgId - 1);
5812 } else {
5813 _pinnedTracker->trackAround(std::min(lessThanId, lastClickedId));
5814 }
5815 }
5816
checkLastPinnedClickedIdReset(int wasScrollTop,int nowScrollTop)5817 void HistoryWidget::checkLastPinnedClickedIdReset(
5818 int wasScrollTop,
5819 int nowScrollTop) {
5820 if (_firstLoadRequest
5821 || _delayedShowAtRequest
5822 || _scroll->isHidden()
5823 || !_history
5824 || !_historyInited) {
5825 return;
5826 }
5827 if (wasScrollTop < nowScrollTop && _pinnedClickedId) {
5828 // User scrolled down.
5829 _pinnedClickedId = FullMsgId();
5830 _minPinnedId = std::nullopt;
5831 updatePinnedViewer();
5832 }
5833 }
5834
setupPinnedTracker()5835 void HistoryWidget::setupPinnedTracker() {
5836 Expects(_history != nullptr);
5837
5838 _pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history);
5839 _pinnedBar = nullptr;
5840 checkPinnedBarState();
5841 }
5842
checkPinnedBarState()5843 void HistoryWidget::checkPinnedBarState() {
5844 Expects(_pinnedTracker != nullptr);
5845
5846 const auto hiddenId = _peer->canPinMessages()
5847 ? MsgId(0)
5848 : session().settings().hiddenPinnedMessageId(_peer->id);
5849 const auto currentPinnedId = Data::ResolveTopPinnedId(
5850 _peer,
5851 _migrated ? _migrated->peer.get() : nullptr);
5852 const auto universalPinnedId = !currentPinnedId
5853 ? int32(0)
5854 : (_migrated && !currentPinnedId.channel)
5855 ? (currentPinnedId.msg - ServerMaxMsgId)
5856 : currentPinnedId.msg;
5857 if (universalPinnedId == hiddenId) {
5858 if (_pinnedBar) {
5859 _pinnedTracker->reset();
5860 auto qobject = base::unique_qptr{
5861 Ui::WrapAsQObject(this, std::move(_pinnedBar)).get()
5862 };
5863 auto destroyer = [this, object = std::move(qobject)]() mutable {
5864 object = nullptr;
5865 updateHistoryGeometry();
5866 updateControlsGeometry();
5867 };
5868 base::call_delayed(
5869 st::defaultMessageBar.duration,
5870 this,
5871 std::move(destroyer));
5872 }
5873 return;
5874 }
5875 if (_pinnedBar || !universalPinnedId) {
5876 return;
5877 }
5878
5879 auto barContent = HistoryView::PinnedBarContent(
5880 &session(),
5881 _pinnedTracker->shownMessageId());
5882 _pinnedBar = std::make_unique<Ui::PinnedBar>(
5883 this,
5884 std::move(barContent));
5885 Info::Profile::SharedMediaCountValue(
5886 _peer,
5887 nullptr,
5888 Storage::SharedMediaType::Pinned
5889 ) | rpl::distinct_until_changed(
5890 ) | rpl::map([=](int count) {
5891 if (_pinnedClickedId) {
5892 _pinnedClickedId = FullMsgId();
5893 _minPinnedId = std::nullopt;
5894 updatePinnedViewer();
5895 }
5896 return (count > 1);
5897 }) | rpl::distinct_until_changed(
5898 ) | rpl::start_with_next([=](bool many) {
5899 refreshPinnedBarButton(many);
5900 }, _pinnedBar->lifetime());
5901
5902 controller()->adaptive().oneColumnValue(
5903 ) | rpl::start_with_next([=](bool one) {
5904 _pinnedBar->setShadowGeometryPostprocess([=](QRect geometry) {
5905 if (!one) {
5906 geometry.setLeft(geometry.left() + st::lineWidth);
5907 }
5908 return geometry;
5909 });
5910 }, _pinnedBar->lifetime());
5911
5912 _pinnedBar->barClicks(
5913 ) | rpl::start_with_next([=] {
5914 const auto id = _pinnedTracker->currentMessageId();
5915 if (const auto item = session().data().message(id.message)) {
5916 Ui::showPeerHistory(item->history()->peer, item->id);
5917 if (const auto group = session().data().groups().find(item)) {
5918 // Hack for the case when a non-first item of an album
5919 // is pinned and we still want the 'show last after first'.
5920 _pinnedClickedId = group->items.front()->fullId();
5921 } else {
5922 _pinnedClickedId = id.message;
5923 }
5924 _minPinnedId = std::nullopt;
5925 updatePinnedViewer();
5926 }
5927 }, _pinnedBar->lifetime());
5928
5929 _pinnedBarHeight = 0;
5930 _pinnedBar->heightValue(
5931 ) | rpl::start_with_next([=](int height) {
5932 _topDelta = _preserveScrollTop ? 0 : (height - _pinnedBarHeight);
5933 _pinnedBarHeight = height;
5934 updateHistoryGeometry();
5935 updateControlsGeometry();
5936 _topDelta = 0;
5937 }, _pinnedBar->lifetime());
5938
5939 orderWidgets();
5940
5941 if (_a_show.animating()) {
5942 _pinnedBar->hide();
5943 }
5944 }
5945
checkMessagesTTL()5946 void HistoryWidget::checkMessagesTTL() {
5947 if (!_peer || !_peer->messagesTTL()) {
5948 if (_ttlInfo) {
5949 _ttlInfo = nullptr;
5950 updateControlsGeometry();
5951 updateControlsVisibility();
5952 }
5953 } else if (!_ttlInfo || _ttlInfo->peer() != _peer) {
5954 _ttlInfo = std::make_unique<HistoryView::Controls::TTLButton>(
5955 this,
5956 _peer);
5957 orderWidgets();
5958 updateControlsGeometry();
5959 updateControlsVisibility();
5960 }
5961 }
5962
setChooseReportMessagesDetails(Ui::ReportReason reason,Fn<void (MessageIdsList)> callback)5963 void HistoryWidget::setChooseReportMessagesDetails(
5964 Ui::ReportReason reason,
5965 Fn<void(MessageIdsList)> callback) {
5966 if (!callback) {
5967 const auto refresh = _chooseForReport && _chooseForReport->active;
5968 _chooseForReport = nullptr;
5969 if (_list) {
5970 _list->clearChooseReportReason();
5971 }
5972 if (refresh) {
5973 clearSelected();
5974 updateControlsVisibility();
5975 updateControlsGeometry();
5976 updateTopBarChooseForReport();
5977 }
5978 } else {
5979 _chooseForReport = std::make_unique<ChooseMessagesForReport>(
5980 ChooseMessagesForReport{
5981 .reason = reason,
5982 .callback = std::move(callback) });
5983 }
5984 }
5985
refreshPinnedBarButton(bool many)5986 void HistoryWidget::refreshPinnedBarButton(bool many) {
5987 const auto close = !many;
5988 auto button = object_ptr<Ui::IconButton>(
5989 this,
5990 close ? st::historyReplyCancel : st::historyPinnedShowAll);
5991 button->clicks(
5992 ) | rpl::start_with_next([=] {
5993 if (close) {
5994 hidePinnedMessage();
5995 } else {
5996 const auto id = _pinnedTracker->currentMessageId();
5997 if (id.message) {
5998 controller()->showSection(
5999 std::make_shared<HistoryView::PinnedMemento>(
6000 _history,
6001 ((!_migrated || id.message.channel)
6002 ? id.message.msg
6003 : (id.message.msg - ServerMaxMsgId))));
6004 }
6005 }
6006 }, button->lifetime());
6007 _pinnedBar->setRightButton(std::move(button));
6008 }
6009
setupGroupCallBar()6010 void HistoryWidget::setupGroupCallBar() {
6011 Expects(_history != nullptr);
6012
6013 const auto peer = _history->peer;
6014 if (!peer->isChannel() && !peer->isChat()) {
6015 _groupCallBar = nullptr;
6016 return;
6017 }
6018 _groupCallBar = std::make_unique<Ui::GroupCallBar>(
6019 this,
6020 HistoryView::GroupCallBarContentByPeer(
6021 peer,
6022 st::historyGroupCallUserpics.size),
6023 Core::App().appDeactivatedValue());
6024
6025 controller()->adaptive().oneColumnValue(
6026 ) | rpl::start_with_next([=](bool one) {
6027 _groupCallBar->setShadowGeometryPostprocess([=](QRect geometry) {
6028 if (!one) {
6029 geometry.setLeft(geometry.left() + st::lineWidth);
6030 }
6031 return geometry;
6032 });
6033 }, _groupCallBar->lifetime());
6034
6035 rpl::merge(
6036 _groupCallBar->barClicks(),
6037 _groupCallBar->joinClicks()
6038 ) | rpl::start_with_next([=] {
6039 const auto peer = _history->peer;
6040 if (peer->groupCall()) {
6041 controller()->startOrJoinGroupCall(peer);
6042 }
6043 }, _groupCallBar->lifetime());
6044
6045 _groupCallBarHeight = 0;
6046 _groupCallBar->heightValue(
6047 ) | rpl::start_with_next([=](int height) {
6048 _topDelta = _preserveScrollTop ? 0 : (height - _groupCallBarHeight);
6049 _groupCallBarHeight = height;
6050 updateHistoryGeometry();
6051 updateControlsGeometry();
6052 _topDelta = 0;
6053 }, _groupCallBar->lifetime());
6054
6055 orderWidgets();
6056
6057 if (_a_show.animating()) {
6058 _groupCallBar->hide();
6059 }
6060 }
6061
setupRequestsBar()6062 void HistoryWidget::setupRequestsBar() {
6063 Expects(_history != nullptr);
6064
6065 const auto peer = _history->peer;
6066 if (!peer->isChannel() && !peer->isChat()) {
6067 _requestsBar = nullptr;
6068 return;
6069 }
6070 _requestsBar = std::make_unique<Ui::RequestsBar>(
6071 this,
6072 HistoryView::RequestsBarContentByPeer(
6073 peer,
6074 st::historyRequestsUserpics.size));
6075
6076 controller()->adaptive().oneColumnValue(
6077 ) | rpl::start_with_next([=](bool one) {
6078 _requestsBar->setShadowGeometryPostprocess([=](QRect geometry) {
6079 if (!one) {
6080 geometry.setLeft(geometry.left() + st::lineWidth);
6081 }
6082 return geometry;
6083 });
6084 }, _requestsBar->lifetime());
6085
6086 _requestsBar->barClicks(
6087 ) | rpl::start_with_next([=] {
6088 RequestsBoxController::Start(controller(), _peer);
6089 }, _requestsBar->lifetime());
6090
6091 _requestsBarHeight = 0;
6092 _requestsBar->heightValue(
6093 ) | rpl::start_with_next([=](int height) {
6094 _topDelta = _preserveScrollTop ? 0 : (height - _requestsBarHeight);
6095 _requestsBarHeight = height;
6096 updateHistoryGeometry();
6097 updateControlsGeometry();
6098 _topDelta = 0;
6099 }, _requestsBar->lifetime());
6100
6101 orderWidgets();
6102
6103 if (_a_show.animating()) {
6104 _requestsBar->hide();
6105 }
6106 }
6107
requestMessageData(MsgId msgId)6108 void HistoryWidget::requestMessageData(MsgId msgId) {
6109 const auto callback = [=](ChannelData *channel, MsgId msgId) {
6110 messageDataReceived(channel, msgId);
6111 };
6112 session().api().requestMessageData(
6113 _peer->asChannel(),
6114 msgId,
6115 crl::guard(this, callback));
6116 }
6117
sendExistingDocument(not_null<DocumentData * > document,Api::SendOptions options)6118 bool HistoryWidget::sendExistingDocument(
6119 not_null<DocumentData*> document,
6120 Api::SendOptions options) {
6121 const auto error = _peer
6122 ? Data::RestrictionError(_peer, ChatRestriction::SendStickers)
6123 : std::nullopt;
6124 if (error) {
6125 controller()->show(
6126 Box<Ui::InformBox>(*error),
6127 Ui::LayerOption::KeepOther);
6128 return false;
6129 } else if (!_peer || !_peer->canWrite()) {
6130 return false;
6131 } else if (showSlowmodeError()) {
6132 return false;
6133 }
6134
6135 auto message = Api::MessageToSend(_history);
6136 message.action.options = std::move(options);
6137 message.action.replyTo = replyToId();
6138 Api::SendExistingDocument(std::move(message), document);
6139
6140 if (_fieldAutocomplete->stickersShown()) {
6141 clearFieldText();
6142 //_saveDraftText = true;
6143 //_saveDraftStart = crl::now();
6144 //saveDraft();
6145 saveCloudDraft(); // won't be needed if SendInlineBotResult will clear the cloud draft
6146 }
6147
6148 hideSelectorControlsAnimated();
6149
6150 _field->setFocus();
6151 return true;
6152 }
6153
sendExistingPhoto(not_null<PhotoData * > photo,Api::SendOptions options)6154 bool HistoryWidget::sendExistingPhoto(
6155 not_null<PhotoData*> photo,
6156 Api::SendOptions options) {
6157 const auto error = _peer
6158 ? Data::RestrictionError(_peer, ChatRestriction::SendMedia)
6159 : std::nullopt;
6160 if (error) {
6161 controller()->show(
6162 Box<Ui::InformBox>(*error),
6163 Ui::LayerOption::KeepOther);
6164 return false;
6165 } else if (!_peer || !_peer->canWrite()) {
6166 return false;
6167 } else if (showSlowmodeError()) {
6168 return false;
6169 }
6170
6171 auto message = Api::MessageToSend(_history);
6172 message.action.replyTo = replyToId();
6173 message.action.options = std::move(options);
6174 Api::SendExistingPhoto(std::move(message), photo);
6175
6176 hideSelectorControlsAnimated();
6177
6178 _field->setFocus();
6179 return true;
6180 }
6181
showInfoTooltip(const TextWithEntities & text,Fn<void ()> hiddenCallback)6182 void HistoryWidget::showInfoTooltip(
6183 const TextWithEntities &text,
6184 Fn<void()> hiddenCallback) {
6185 hideInfoTooltip(anim::type::normal);
6186 _topToast = Ui::Toast::Show(_scroll, Ui::Toast::Config{
6187 .text = text,
6188 .st = &st::historyInfoToast,
6189 .durationMs = CountToastDuration(text),
6190 .multiline = true,
6191 .dark = true,
6192 .slideSide = RectPart::Top,
6193 });
6194 if (const auto strong = _topToast.get()) {
6195 if (hiddenCallback) {
6196 connect(strong->widget(), &QObject::destroyed, hiddenCallback);
6197 }
6198 } else if (hiddenCallback) {
6199 hiddenCallback();
6200 }
6201 }
6202
hideInfoTooltip(anim::type animated)6203 void HistoryWidget::hideInfoTooltip(anim::type animated) {
6204 if (const auto strong = _topToast.get()) {
6205 if (animated == anim::type::normal) {
6206 strong->hideAnimated();
6207 } else {
6208 strong->hide();
6209 }
6210 }
6211 }
6212
setFieldText(const TextWithTags & textWithTags,TextUpdateEvents events,FieldHistoryAction fieldHistoryAction)6213 void HistoryWidget::setFieldText(
6214 const TextWithTags &textWithTags,
6215 TextUpdateEvents events,
6216 FieldHistoryAction fieldHistoryAction) {
6217 _textUpdateEvents = events;
6218 _field->setTextWithTags(textWithTags, fieldHistoryAction);
6219 auto cursor = _field->textCursor();
6220 cursor.movePosition(QTextCursor::End);
6221 _field->setTextCursor(cursor);
6222 _textUpdateEvents = TextUpdateEvent::SaveDraft
6223 | TextUpdateEvent::SendTyping;
6224
6225 previewCancel();
6226 _previewState = Data::PreviewState::Allowed;
6227 }
6228
clearFieldText(TextUpdateEvents events,FieldHistoryAction fieldHistoryAction)6229 void HistoryWidget::clearFieldText(
6230 TextUpdateEvents events,
6231 FieldHistoryAction fieldHistoryAction) {
6232 setFieldText(TextWithTags(), events, fieldHistoryAction);
6233 }
6234
replyToMessage(FullMsgId itemId)6235 void HistoryWidget::replyToMessage(FullMsgId itemId) {
6236 if (const auto item = session().data().message(itemId)) {
6237 replyToMessage(item);
6238 }
6239 }
6240
replyToMessage(not_null<HistoryItem * > item)6241 void HistoryWidget::replyToMessage(not_null<HistoryItem*> item) {
6242 if (!item->isRegular() || !_canSendMessages) {
6243 return;
6244 } else if (item->history() == _migrated) {
6245 if (item->isService()) {
6246 controller()->show(Box<Ui::InformBox>(
6247 tr::lng_reply_cant(tr::now)));
6248 } else {
6249 const auto itemId = item->fullId();
6250 controller()->show(
6251 Box<Ui::ConfirmBox>(
6252 tr::lng_reply_cant_forward(tr::now),
6253 tr::lng_selected_forward(tr::now),
6254 crl::guard(this, [=] {
6255 controller()->content()->setForwardDraft(
6256 _peer->id,
6257 { .ids = { 1, itemId } });
6258 })));
6259 }
6260 return;
6261 }
6262
6263 session().data().cancelForwarding(_history);
6264
6265 if (_editMsgId) {
6266 if (auto localDraft = _history->localDraft()) {
6267 localDraft->msgId = item->id;
6268 } else {
6269 _history->setLocalDraft(std::make_unique<Data::Draft>(
6270 TextWithTags(),
6271 item->id,
6272 MessageCursor(),
6273 Data::PreviewState::Allowed));
6274 }
6275 } else {
6276 _replyEditMsg = item;
6277 _replyToId = item->id;
6278 updateReplyEditText(_replyEditMsg);
6279 updateBotKeyboard();
6280 updateReplyToName();
6281 updateControlsGeometry();
6282 updateField();
6283 refreshTopBarActiveChat();
6284 }
6285
6286 _saveDraftText = true;
6287 _saveDraftStart = crl::now();
6288 saveDraft();
6289
6290 _field->setFocus();
6291 }
6292
editMessage(FullMsgId itemId)6293 void HistoryWidget::editMessage(FullMsgId itemId) {
6294 if (const auto item = session().data().message(itemId)) {
6295 editMessage(item);
6296 }
6297 }
6298
editMessage(not_null<HistoryItem * > item)6299 void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
6300 if (const auto media = item->media()) {
6301 if (media->allowsEditCaption()) {
6302 controller()->show(Box<EditCaptionBox>(controller(), item));
6303 return;
6304 }
6305 } else if (_chooseTheme) {
6306 toggleChooseChatTheme(_peer);
6307 } else if (_voiceRecordBar->isActive()) {
6308 controller()->show(
6309 Box<Ui::InformBox>(tr::lng_edit_caption_voice(tr::now)));
6310 return;
6311 }
6312
6313 if (isRecording()) {
6314 // Just fix some strange inconsistency.
6315 _send->clearState();
6316 }
6317 if (!_editMsgId) {
6318 if (_replyToId || !_field->empty()) {
6319 _history->setLocalDraft(std::make_unique<Data::Draft>(
6320 _field,
6321 _replyToId,
6322 _previewState));
6323 } else {
6324 _history->clearLocalDraft();
6325 }
6326 }
6327
6328 const auto editData = PrepareEditText(item);
6329 const auto cursor = MessageCursor {
6330 int(editData.text.size()),
6331 int(editData.text.size()),
6332 QFIXED_MAX
6333 };
6334 const auto previewPage = [&]() -> WebPageData* {
6335 if (const auto media = item->media()) {
6336 return media->webpage();
6337 }
6338 return nullptr;
6339 }();
6340 const auto previewState = previewPage
6341 ? Data::PreviewState::Allowed
6342 : Data::PreviewState::EmptyOnEdit;
6343 _history->setLocalEditDraft(std::make_unique<Data::Draft>(
6344 editData,
6345 item->id,
6346 cursor,
6347 previewState));
6348 applyDraft();
6349
6350 _previewData = previewPage;
6351 if (_previewData) {
6352 updatePreview();
6353 }
6354
6355 updateBotKeyboard();
6356
6357 if (!_field->isHidden()) _fieldBarCancel->show();
6358 updateFieldPlaceholder();
6359 updateMouseTracking();
6360 updateReplyToName();
6361 updateControlsGeometry();
6362 updateField();
6363
6364 _saveDraftText = true;
6365 _saveDraftStart = crl::now();
6366 saveDraft();
6367
6368 _field->setFocus();
6369 }
6370
hidePinnedMessage()6371 void HistoryWidget::hidePinnedMessage() {
6372 Expects(_pinnedBar != nullptr);
6373
6374 const auto id = _pinnedTracker->currentMessageId();
6375 if (!id.message) {
6376 return;
6377 }
6378 if (_peer->canPinMessages()) {
6379 Window::ToggleMessagePinned(controller(), id.message, false);
6380 } else {
6381 const auto callback = [=] {
6382 if (_pinnedTracker) {
6383 checkPinnedBarState();
6384 }
6385 };
6386 Window::HidePinnedBar(
6387 controller(),
6388 _peer,
6389 crl::guard(this, callback));
6390 }
6391 }
6392
lastForceReplyReplied(const FullMsgId & replyTo) const6393 bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const {
6394 if (replyTo.channel != _channel) {
6395 return false;
6396 }
6397 return _keyboard->forceReply()
6398 && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)
6399 && _keyboard->forMsgId().msg == replyTo.msg;
6400 }
6401
lastForceReplyReplied() const6402 bool HistoryWidget::lastForceReplyReplied() const {
6403 return _keyboard->forceReply()
6404 && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)
6405 && _keyboard->forMsgId().msg == replyToId();
6406 }
6407
cancelReply(bool lastKeyboardUsed)6408 bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
6409 bool wasReply = false;
6410 if (_replyToId) {
6411 wasReply = true;
6412
6413 _replyEditMsg = nullptr;
6414 _replyToId = 0;
6415 mouseMoveEvent(0);
6416 if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) {
6417 _fieldBarCancel->hide();
6418 updateMouseTracking();
6419 }
6420
6421 updateBotKeyboard();
6422 refreshTopBarActiveChat();
6423 updateControlsGeometry();
6424 update();
6425 } else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) {
6426 if (localDraft->msgId) {
6427 if (localDraft->textWithTags.text.isEmpty()) {
6428 _history->clearLocalDraft();
6429 } else {
6430 localDraft->msgId = 0;
6431 }
6432 }
6433 }
6434 if (wasReply) {
6435 _saveDraftText = true;
6436 _saveDraftStart = crl::now();
6437 saveDraft();
6438 }
6439 if (!_editMsgId
6440 && _keyboard->singleUse()
6441 && _keyboard->forceReply()
6442 && lastKeyboardUsed) {
6443 if (_kbReplyTo) {
6444 toggleKeyboard(false);
6445 }
6446 }
6447 return wasReply;
6448 }
6449
cancelReplyAfterMediaSend(bool lastKeyboardUsed)6450 void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) {
6451 if (cancelReply(lastKeyboardUsed)) {
6452 saveCloudDraft();
6453 }
6454 }
6455
countMembersDropdownHeightMax() const6456 int HistoryWidget::countMembersDropdownHeightMax() const {
6457 int result = height() - st::membersInnerDropdown.padding.top() - st::membersInnerDropdown.padding.bottom();
6458 result -= _tabbedSelectorToggle->height();
6459 accumulate_min(result, st::membersInnerHeightMax);
6460 return result;
6461 }
6462
cancelEdit()6463 void HistoryWidget::cancelEdit() {
6464 if (!_editMsgId) {
6465 return;
6466 }
6467
6468 _replyEditMsg = nullptr;
6469 setEditMsgId(0);
6470 _history->clearLocalEditDraft();
6471 applyDraft();
6472
6473 if (_saveEditMsgRequestId) {
6474 _history->session().api().request(_saveEditMsgRequestId).cancel();
6475 _saveEditMsgRequestId = 0;
6476 }
6477
6478 _saveDraftText = true;
6479 _saveDraftStart = crl::now();
6480 saveDraft();
6481
6482 mouseMoveEvent(nullptr);
6483 if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) {
6484 _fieldBarCancel->hide();
6485 updateMouseTracking();
6486 }
6487
6488 auto old = _textUpdateEvents;
6489 _textUpdateEvents = 0;
6490 fieldChanged();
6491 _textUpdateEvents = old;
6492
6493 if (!canWriteMessage()) {
6494 updateControlsVisibility();
6495 }
6496 updateBotKeyboard();
6497 updateFieldPlaceholder();
6498
6499 updateControlsGeometry();
6500 update();
6501 }
6502
cancelFieldAreaState()6503 void HistoryWidget::cancelFieldAreaState() {
6504 Ui::hideLayer();
6505 _replyForwardPressed = false;
6506 if (_previewData && _previewData->pendingTill >= 0) {
6507 _previewState = Data::PreviewState::Cancelled;
6508 previewCancel();
6509
6510 _saveDraftText = true;
6511 _saveDraftStart = crl::now();
6512 saveDraft();
6513 } else if (_editMsgId) {
6514 cancelEdit();
6515 } else if (readyToForward()) {
6516 session().data().cancelForwarding(_history);
6517 } else if (_replyToId) {
6518 cancelReply();
6519 } else if (_kbReplyTo) {
6520 toggleKeyboard();
6521 }
6522 }
6523
previewCancel()6524 void HistoryWidget::previewCancel() {
6525 _api.request(base::take(_previewRequest)).cancel();
6526 _previewData = nullptr;
6527 _previewLinks.clear();
6528 updatePreview();
6529 }
6530
checkPreview()6531 void HistoryWidget::checkPreview() {
6532 const auto previewRestricted = [&] {
6533 return _peer && _peer->amRestricted(ChatRestriction::EmbedLinks);
6534 }();
6535 if (_previewState != Data::PreviewState::Allowed || previewRestricted) {
6536 previewCancel();
6537 return;
6538 }
6539 const auto links = _parsedLinks.join(' ');
6540 if (_previewLinks != links) {
6541 _api.request(base::take(_previewRequest)).cancel();
6542 _previewLinks = links;
6543 if (_previewLinks.isEmpty()) {
6544 if (_previewData && _previewData->pendingTill >= 0) {
6545 previewCancel();
6546 }
6547 } else {
6548 const auto i = _previewCache.constFind(links);
6549 if (i == _previewCache.cend()) {
6550 _previewRequest = _api.request(MTPmessages_GetWebPagePreview(
6551 MTP_flags(0),
6552 MTP_string(links),
6553 MTPVector<MTPMessageEntity>()
6554 )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) {
6555 gotPreview(links, result, requestId);
6556 }).send();
6557 } else if (i.value()) {
6558 _previewData = session().data().webpage(i.value());
6559 updatePreview();
6560 } else if (_previewData && _previewData->pendingTill >= 0) {
6561 previewCancel();
6562 }
6563 }
6564 }
6565 }
6566
requestPreview()6567 void HistoryWidget::requestPreview() {
6568 if (!_previewData
6569 || (_previewData->pendingTill <= 0)
6570 || _previewLinks.isEmpty()) {
6571 return;
6572 }
6573 const auto links = _previewLinks;
6574 _previewRequest = _api.request(MTPmessages_GetWebPagePreview(
6575 MTP_flags(0),
6576 MTP_string(links),
6577 MTPVector<MTPMessageEntity>()
6578 )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) {
6579 gotPreview(links, result, requestId);
6580 }).send();
6581 }
6582
gotPreview(QString links,const MTPMessageMedia & result,mtpRequestId req)6583 void HistoryWidget::gotPreview(
6584 QString links,
6585 const MTPMessageMedia &result,
6586 mtpRequestId req) {
6587 if (req == _previewRequest) {
6588 _previewRequest = 0;
6589 }
6590 if (result.type() == mtpc_messageMediaWebPage) {
6591 const auto &data = result.c_messageMediaWebPage().vwebpage();
6592 const auto page = session().data().processWebpage(data);
6593 _previewCache.insert(links, page->id);
6594 if (page->pendingTill > 0
6595 && page->pendingTill <= base::unixtime::now()) {
6596 page->pendingTill = -1;
6597 }
6598 if (links == _previewLinks
6599 && _previewState == Data::PreviewState::Allowed) {
6600 _previewData = (page->id && page->pendingTill >= 0)
6601 ? page.get()
6602 : nullptr;
6603 updatePreview();
6604 }
6605 session().data().sendWebPageGamePollNotifications();
6606 } else if (result.type() == mtpc_messageMediaEmpty) {
6607 _previewCache.insert(links, 0);
6608 if (links == _previewLinks
6609 && _previewState == Data::PreviewState::Allowed) {
6610 _previewData = nullptr;
6611 updatePreview();
6612 }
6613 }
6614 }
6615
updatePreview()6616 void HistoryWidget::updatePreview() {
6617 _previewTimer.cancel();
6618 if (_previewData && _previewData->pendingTill >= 0) {
6619 _fieldBarCancel->show();
6620 updateMouseTracking();
6621 if (_previewData->pendingTill) {
6622 _previewTitle.setText(
6623 st::msgNameStyle,
6624 tr::lng_preview_loading(tr::now),
6625 Ui::NameTextOptions());
6626 auto linkText = QStringView(_previewLinks).split(' ').at(0).toString();
6627 _previewDescription.setText(
6628 st::messageTextStyle,
6629 TextUtilities::Clean(linkText),
6630 Ui::DialogTextOptions());
6631
6632 const auto timeout = (_previewData->pendingTill - base::unixtime::now());
6633 _previewTimer.callOnce(std::max(timeout, 0) * crl::time(1000));
6634 } else {
6635 auto preview =
6636 HistoryView::TitleAndDescriptionFromWebPage(_previewData);
6637 if (preview.title.isEmpty()) {
6638 if (_previewData->document) {
6639 preview.title = tr::lng_attach_file(tr::now);
6640 } else if (_previewData->photo) {
6641 preview.title = tr::lng_attach_photo(tr::now);
6642 }
6643 }
6644 _previewTitle.setText(
6645 st::msgNameStyle,
6646 preview.title,
6647 Ui::NameTextOptions());
6648 _previewDescription.setText(
6649 st::messageTextStyle,
6650 TextUtilities::Clean(preview.description),
6651 Ui::DialogTextOptions());
6652 }
6653 } else if (!readyToForward() && !replyToId() && !_editMsgId) {
6654 _fieldBarCancel->hide();
6655 updateMouseTracking();
6656 }
6657 updateControlsGeometry();
6658 update();
6659 }
6660
fullInfoUpdated()6661 void HistoryWidget::fullInfoUpdated() {
6662 auto refresh = false;
6663 if (_list) {
6664 auto newCanSendMessages = _peer->canWrite();
6665 if (newCanSendMessages != _canSendMessages) {
6666 _canSendMessages = newCanSendMessages;
6667 if (!_canSendMessages) {
6668 cancelReply();
6669 }
6670 refreshScheduledToggle();
6671 refreshSilentToggle();
6672 refresh = true;
6673 }
6674 checkFieldAutocomplete();
6675 _list->updateBotInfo();
6676
6677 handlePeerUpdate();
6678 checkSuggestToGigagroup();
6679 }
6680 if (updateCmdStartShown()) {
6681 refresh = true;
6682 } else if (!_scroll->isHidden() && _unblock->isHidden() == isBlocked()) {
6683 refresh = true;
6684 }
6685 if (refresh) {
6686 updateControlsVisibility();
6687 updateControlsGeometry();
6688 }
6689 }
6690
handlePeerUpdate()6691 void HistoryWidget::handlePeerUpdate() {
6692 bool resize = false;
6693 updateHistoryGeometry();
6694 if (_peer->isChat() && _peer->asChat()->noParticipantInfo()) {
6695 session().api().requestFullPeer(_peer);
6696 } else if (_peer->isUser()
6697 && (_peer->asUser()->blockStatus() == UserData::BlockStatus::Unknown
6698 || _peer->asUser()->callsStatus() == UserData::CallsStatus::Unknown)) {
6699 session().api().requestFullPeer(_peer);
6700 } else if (auto channel = _peer->asMegagroup()) {
6701 if (!channel->mgInfo->botStatus) {
6702 session().api().requestBots(channel);
6703 }
6704 if (channel->mgInfo->admins.empty()) {
6705 session().api().requestAdmins(channel);
6706 }
6707 }
6708 if (!_a_show.animating()) {
6709 if (_unblock->isHidden() == isBlocked()
6710 || (!isBlocked() && _joinChannel->isHidden() == isJoinChannel())) {
6711 resize = true;
6712 }
6713 bool newCanSendMessages = _peer->canWrite();
6714 if (newCanSendMessages != _canSendMessages) {
6715 _canSendMessages = newCanSendMessages;
6716 if (!_canSendMessages) {
6717 cancelReply();
6718 }
6719 refreshScheduledToggle();
6720 refreshSilentToggle();
6721 resize = true;
6722 }
6723 updateControlsVisibility();
6724 if (resize) {
6725 updateControlsGeometry();
6726 }
6727 }
6728 }
6729
forwardSelected()6730 void HistoryWidget::forwardSelected() {
6731 if (!_list) {
6732 return;
6733 }
6734 const auto weak = Ui::MakeWeak(this);
6735 Window::ShowForwardMessagesBox(controller(), getSelectedItems(), [=] {
6736 if (const auto strong = weak.data()) {
6737 strong->clearSelected();
6738 }
6739 });
6740 }
6741
confirmDeleteSelected()6742 void HistoryWidget::confirmDeleteSelected() {
6743 if (!_list) return;
6744
6745 auto items = _list->getSelectedItems();
6746 if (items.empty()) {
6747 return;
6748 }
6749 const auto weak = Ui::MakeWeak(this);
6750 auto box = Box<DeleteMessagesBox>(&session(), std::move(items));
6751 box->setDeleteConfirmedCallback([=] {
6752 if (const auto strong = weak.data()) {
6753 strong->clearSelected();
6754 }
6755 });
6756 controller()->show(std::move(box));
6757 }
6758
escape()6759 void HistoryWidget::escape() {
6760 if (_chooseForReport) {
6761 controller()->clearChooseReportMessages();
6762 } else if (_nonEmptySelection && _list) {
6763 clearSelected();
6764 } else if (_isInlineBot) {
6765 cancelInlineBot();
6766 } else if (_editMsgId) {
6767 if (_replyEditMsg
6768 && PrepareEditText(_replyEditMsg) != _field->getTextWithTags()) {
6769 controller()->show(Box<Ui::ConfirmBox>(
6770 tr::lng_cancel_edit_post_sure(tr::now),
6771 tr::lng_cancel_edit_post_yes(tr::now),
6772 tr::lng_cancel_edit_post_no(tr::now),
6773 crl::guard(this, [this] {
6774 if (_editMsgId) {
6775 cancelEdit();
6776 Ui::hideLayer();
6777 }
6778 })));
6779 } else {
6780 cancelEdit();
6781 }
6782 } else if (!_fieldAutocomplete->isHidden()) {
6783 _fieldAutocomplete->hideAnimated();
6784 } else if (_replyToId && _field->getTextWithTags().text.isEmpty()) {
6785 cancelReply();
6786 } else if (auto &voice = _voiceRecordBar; voice->isActive()) {
6787 voice->showDiscardBox(nullptr, anim::type::normal);
6788 } else {
6789 _cancelRequests.fire({});
6790 }
6791 }
6792
clearSelected()6793 void HistoryWidget::clearSelected() {
6794 if (_list) {
6795 _list->clearSelected();
6796 }
6797 }
6798
getItemFromHistoryOrMigrated(MsgId genericMsgId) const6799 HistoryItem *HistoryWidget::getItemFromHistoryOrMigrated(MsgId genericMsgId) const {
6800 if (genericMsgId < 0 && -genericMsgId < ServerMaxMsgId && _migrated) {
6801 return session().data().message(_migrated->channelId(), -genericMsgId);
6802 }
6803 return session().data().message(_channel, genericMsgId);
6804 }
6805
getSelectedItems() const6806 MessageIdsList HistoryWidget::getSelectedItems() const {
6807 return _list ? _list->getSelectedItems() : MessageIdsList();
6808 }
6809
updateTopBarChooseForReport()6810 void HistoryWidget::updateTopBarChooseForReport() {
6811 if (_chooseForReport && _chooseForReport->active) {
6812 _topBar->showChooseMessagesForReport(
6813 _chooseForReport->reason);
6814 } else {
6815 _topBar->clearChooseMessagesForReport();
6816 }
6817 updateTopBarSelection();
6818 updateControlsVisibility();
6819 updateControlsGeometry();
6820 }
6821
updateTopBarSelection()6822 void HistoryWidget::updateTopBarSelection() {
6823 if (!_list) {
6824 _topBar->showSelected(HistoryView::TopBarWidget::SelectedState {});
6825 return;
6826 }
6827
6828 auto selectedState = _list->getSelectionState();
6829 _nonEmptySelection = (selectedState.count > 0)
6830 || selectedState.textSelected;
6831 _topBar->showSelected(selectedState);
6832
6833 const auto transparent = Qt::WA_TransparentForMouseEvents;
6834 if (selectedState.count == 0) {
6835 _reportMessages->clearState();
6836 _reportMessages->setAttribute(transparent);
6837 _reportMessages->setColorOverride(st::windowSubTextFg->c);
6838 } else if (_reportMessages->testAttribute(transparent)) {
6839 _reportMessages->setAttribute(transparent, false);
6840 _reportMessages->setColorOverride(std::nullopt);
6841 }
6842 _reportMessages->setText(Ui::Text::Upper(selectedState.count
6843 ? tr::lng_report_messages_count(
6844 tr::now,
6845 lt_count,
6846 selectedState.count)
6847 : tr::lng_report_messages_none(tr::now)));
6848 updateControlsVisibility();
6849 updateHistoryGeometry();
6850 if (!Ui::isLayerShown() && !Core::App().passcodeLocked()) {
6851 if (_nonEmptySelection
6852 || (_list && _list->wasSelectedText())
6853 || isRecording()
6854 || isBotStart()
6855 || isBlocked()
6856 || !_canSendMessages) {
6857 _list->setFocus();
6858 } else {
6859 _field->setFocus();
6860 }
6861 }
6862 _topBar->update();
6863 update();
6864 }
6865
messageDataReceived(ChannelData * channel,MsgId msgId)6866 void HistoryWidget::messageDataReceived(ChannelData *channel, MsgId msgId) {
6867 if (!_peer || _peer->asChannel() != channel || !msgId) {
6868 return;
6869 }
6870 if (_editMsgId == msgId || _replyToId == msgId) {
6871 updateReplyEditTexts(true);
6872 }
6873 }
6874
updateReplyEditText(not_null<HistoryItem * > item)6875 void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
6876 _replyEditMsgText.setText(
6877 st::messageTextStyle,
6878 item->inReplyText(),
6879 Ui::DialogTextOptions());
6880 if (!_field->isHidden() || isRecording()) {
6881 _fieldBarCancel->show();
6882 updateMouseTracking();
6883 }
6884 }
6885
updateReplyEditTexts(bool force)6886 void HistoryWidget::updateReplyEditTexts(bool force) {
6887 if (!force) {
6888 if (_replyEditMsg || (!_editMsgId && !_replyToId)) {
6889 return;
6890 }
6891 }
6892 if (!_replyEditMsg) {
6893 _replyEditMsg = session().data().message(_channel, _editMsgId ? _editMsgId : _replyToId);
6894 }
6895 if (_replyEditMsg) {
6896 updateReplyEditText(_replyEditMsg);
6897 updateBotKeyboard();
6898 updateReplyToName();
6899 updateField();
6900 } else if (force) {
6901 if (_editMsgId) {
6902 cancelEdit();
6903 } else {
6904 cancelReply();
6905 }
6906 }
6907 }
6908
updateForwarding()6909 void HistoryWidget::updateForwarding() {
6910 if (_history) {
6911 _toForward = _history->resolveForwardDraft();
6912 updateForwardingTexts();
6913 } else {
6914 _toForward = {};
6915 }
6916 updateControlsVisibility();
6917 updateControlsGeometry();
6918 }
6919
updateForwardingTexts()6920 void HistoryWidget::updateForwardingTexts() {
6921 int32 version = 0;
6922 QString from, text;
6923 const auto keepNames = (_toForward.options
6924 == Data::ForwardOptions::PreserveInfo);
6925 const auto keepCaptions = (_toForward.options
6926 != Data::ForwardOptions::NoNamesAndCaptions);
6927 if (const auto count = int(_toForward.items.size())) {
6928 auto insertedPeers = base::flat_set<not_null<PeerData*>>();
6929 auto insertedNames = base::flat_set<QString>();
6930 auto fullname = QString();
6931 auto names = std::vector<QString>();
6932 names.reserve(_toForward.items.size());
6933 for (const auto item : _toForward.items) {
6934 if (const auto from = item->senderOriginal()) {
6935 if (!insertedPeers.contains(from)) {
6936 insertedPeers.emplace(from);
6937 names.push_back(from->shortName());
6938 fullname = from->name;
6939 }
6940 version += from->nameVersion;
6941 } else if (const auto info = item->hiddenForwardedInfo()) {
6942 if (!insertedNames.contains(info->name)) {
6943 insertedNames.emplace(info->name);
6944 names.push_back(info->firstName);
6945 fullname = info->name;
6946 }
6947 ++version;
6948 } else {
6949 Unexpected("Corrupt forwarded information in message.");
6950 }
6951 }
6952 if (!keepNames) {
6953 from = tr::lng_forward_sender_names_removed(tr::now);
6954 } else if (names.size() > 2) {
6955 from = tr::lng_forwarding_from(tr::now, lt_count, names.size() - 1, lt_user, names[0]);
6956 } else if (names.size() < 2) {
6957 from = fullname;
6958 } else {
6959 from = tr::lng_forwarding_from_two(tr::now, lt_user, names[0], lt_second_user, names[1]);
6960 }
6961
6962 if (count < 2) {
6963 text = _toForward.items.front()->toPreview({
6964 .hideSender = true,
6965 .hideCaption = !keepCaptions,
6966 .generateImages = false,
6967 }).text;
6968 } else {
6969 text = textcmdLink(
6970 1,
6971 tr::lng_forward_messages(tr::now, lt_count, count));
6972 }
6973 }
6974 _toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions());
6975 _toForwardText.setText(
6976 st::messageTextStyle,
6977 text,
6978 Ui::DialogTextOptions());
6979 _toForwardNameVersion = keepNames ? version : keepCaptions ? -1 : -2;
6980 }
6981
checkForwardingInfo()6982 void HistoryWidget::checkForwardingInfo() {
6983 if (!_toForward.items.empty()) {
6984 const auto keepNames = (_toForward.options
6985 == Data::ForwardOptions::PreserveInfo);
6986 const auto keepCaptions = (_toForward.options
6987 != Data::ForwardOptions::NoNamesAndCaptions);
6988 auto version = keepNames ? 0 : keepCaptions ? -1 : -2;
6989 if (keepNames) {
6990 for (const auto item : _toForward.items) {
6991 if (const auto from = item->senderOriginal()) {
6992 version += from->nameVersion;
6993 } else if (const auto info = item->hiddenForwardedInfo()) {
6994 ++version;
6995 } else {
6996 Unexpected("Corrupt forwarded information in message.");
6997 }
6998 }
6999 }
7000 if (version != _toForwardNameVersion) {
7001 updateForwardingTexts();
7002 }
7003 }
7004 }
7005
updateReplyToName()7006 void HistoryWidget::updateReplyToName() {
7007 if (_editMsgId) {
7008 return;
7009 } else if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) {
7010 return;
7011 }
7012 const auto from = [&] {
7013 const auto item = _replyEditMsg ? _replyEditMsg : _kbReplyTo;
7014 if (const auto from = item->displayFrom()) {
7015 return from;
7016 }
7017 return item->author().get();
7018 }();
7019 _replyToName.setText(
7020 st::msgNameStyle,
7021 from->name,
7022 Ui::NameTextOptions());
7023 _replyToNameVersion = (_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()->nameVersion;
7024 }
7025
updateField()7026 void HistoryWidget::updateField() {
7027 auto fieldAreaTop = _scroll->y() + _scroll->height();
7028 rtlupdate(0, fieldAreaTop, width(), height() - fieldAreaTop);
7029 }
7030
drawField(Painter & p,const QRect & rect)7031 void HistoryWidget::drawField(Painter &p, const QRect &rect) {
7032 auto backy = _field->y() - st::historySendPadding;
7033 auto backh = _field->height() + 2 * st::historySendPadding;
7034 auto hasForward = readyToForward();
7035 auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo;
7036 if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
7037 if (!_editMsgId && drawMsgText && drawMsgText->author()->nameVersion > _replyToNameVersion) {
7038 updateReplyToName();
7039 }
7040 backy -= st::historyReplyHeight;
7041 backh += st::historyReplyHeight;
7042 } else if (hasForward) {
7043 checkForwardingInfo();
7044 backy -= st::historyReplyHeight;
7045 backh += st::historyReplyHeight;
7046 } else if (_previewData && _previewData->pendingTill >= 0) {
7047 backy -= st::historyReplyHeight;
7048 backh += st::historyReplyHeight;
7049 }
7050 auto drawWebPagePreview = (_previewData && _previewData->pendingTill >= 0) && !_replyForwardPressed;
7051 p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
7052 if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
7053 auto replyLeft = st::historyReplySkip;
7054 (_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
7055 if (!drawWebPagePreview) {
7056 if (drawMsgText) {
7057 if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) {
7058 if (const auto image = drawMsgText->media()->replyPreview()) {
7059 auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
7060 p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small));
7061 }
7062 replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
7063 }
7064 p.setPen(st::historyReplyNameFg);
7065 if (_editMsgId) {
7066 paintEditHeader(p, rect, replyLeft, backy);
7067 } else {
7068 _replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
7069 }
7070 p.setPen(st::historyComposeAreaFg);
7071 p.setTextPalette(st::historyComposeAreaPalette);
7072 _replyEditMsgText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
7073 p.restoreTextPalette();
7074 } else {
7075 p.setFont(st::msgDateFont);
7076 p.setPen(st::historyComposeAreaFgService);
7077 p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()));
7078 }
7079 }
7080 } else if (hasForward) {
7081 auto forwardLeft = st::historyReplySkip;
7082 st::historyForwardIcon.paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
7083 if (!drawWebPagePreview) {
7084 const auto firstItem = _toForward.items.front();
7085 const auto firstMedia = firstItem->media();
7086 const auto preview = (_toForward.items.size() < 2 && firstMedia && firstMedia->hasReplyPreview())
7087 ? firstMedia->replyPreview()
7088 : nullptr;
7089 if (preview) {
7090 auto to = QRect(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
7091 if (preview->width() == preview->height()) {
7092 p.drawPixmap(to.x(), to.y(), preview->pix());
7093 } else {
7094 auto from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width());
7095 p.drawPixmap(to, preview->pix(), from);
7096 }
7097 forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
7098 }
7099 p.setPen(st::historyReplyNameFg);
7100 _toForwardFrom.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
7101 p.setPen(st::historyComposeAreaFg);
7102 p.setTextPalette(st::historyComposeAreaPalette);
7103 _toForwardText.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
7104 p.restoreTextPalette();
7105 }
7106 }
7107 if (drawWebPagePreview) {
7108 const auto textTop = backy + st::msgReplyPadding.top();
7109 auto previewLeft = st::historyReplySkip + st::webPageLeft;
7110 p.fillRect(
7111 st::historyReplySkip,
7112 textTop,
7113 st::webPageBar,
7114 st::msgReplyBarSize.height(),
7115 st::msgInReplyBarColor);
7116
7117 const auto to = QRect(
7118 previewLeft,
7119 textTop,
7120 st::msgReplyBarSize.height(),
7121 st::msgReplyBarSize.height());
7122 if (HistoryView::DrawWebPageDataPreview(p, _previewData, to)) {
7123 previewLeft += st::msgReplyBarSize.height()
7124 + st::msgReplyBarSkip
7125 - st::msgReplyBarSize.width()
7126 - st::msgReplyBarPos.x();
7127 }
7128 p.setPen(st::historyReplyNameFg);
7129 const auto elidedWidth = width()
7130 - previewLeft
7131 - _fieldBarCancel->width()
7132 - st::msgReplyPadding.right();
7133
7134 _previewTitle.drawElided(
7135 p,
7136 previewLeft,
7137 textTop,
7138 elidedWidth);
7139 p.setPen(st::historyComposeAreaFg);
7140 _previewDescription.drawElided(
7141 p,
7142 previewLeft,
7143 textTop + st::msgServiceNameFont->height,
7144 elidedWidth);
7145 }
7146 }
7147
drawRestrictedWrite(Painter & p,const QString & error)7148 void HistoryWidget::drawRestrictedWrite(Painter &p, const QString &error) {
7149 auto rect = myrtlrect(0, height() - _unblock->height(), width(), _unblock->height());
7150 p.fillRect(rect, st::historyReplyBg);
7151
7152 p.setFont(st::normalFont);
7153 p.setPen(st::windowSubTextFg);
7154 p.drawText(rect.marginsRemoved(QMargins(st::historySendPadding, 0, st::historySendPadding, 0)), error, style::al_center);
7155 }
7156
paintEditHeader(Painter & p,const QRect & rect,int left,int top) const7157 void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int top) const {
7158 if (!rect.intersects(myrtlrect(left, top, width() - left, st::normalFont->height))) {
7159 return;
7160 }
7161
7162 p.setFont(st::msgServiceNameFont);
7163 p.drawTextLeft(left, top + st::msgReplyPadding.top(), width(), tr::lng_edit_message(tr::now));
7164
7165 if (!_replyEditMsg
7166 || _replyEditMsg->history()->peer->canEditMessagesIndefinitely()) {
7167 return;
7168 }
7169
7170 QString editTimeLeftText;
7171 int updateIn = -1;
7172 auto timeSinceMessage = ItemDateTime(_replyEditMsg).msecsTo(QDateTime::currentDateTime());
7173 auto editTimeLeft = (session().serverConfig().editTimeLimit * 1000LL) - timeSinceMessage;
7174 if (editTimeLeft < 2) {
7175 editTimeLeftText = qsl("0:00");
7176 } else if (editTimeLeft > kDisplayEditTimeWarningMs) {
7177 updateIn = static_cast<int>(qMin(editTimeLeft - kDisplayEditTimeWarningMs, qint64(kFullDayInMs)));
7178 } else {
7179 updateIn = static_cast<int>(editTimeLeft % 1000);
7180 if (!updateIn) {
7181 updateIn = 1000;
7182 }
7183 ++updateIn;
7184
7185 editTimeLeft = (editTimeLeft - 1) / 1000; // seconds
7186 editTimeLeftText = qsl("%1:%2").arg(editTimeLeft / 60).arg(editTimeLeft % 60, 2, 10, QChar('0'));
7187 }
7188
7189 // Restart timer only if we are sure that we've painted the whole timer.
7190 if (rect.contains(myrtlrect(left, top, width() - left, st::normalFont->height)) && updateIn > 0) {
7191 _updateEditTimeLeftDisplay.callOnce(updateIn);
7192 }
7193
7194 if (!editTimeLeftText.isEmpty()) {
7195 p.setFont(st::normalFont);
7196 p.setPen(st::historyComposeAreaFgService);
7197 p.drawText(left + st::msgServiceNameFont->width(tr::lng_edit_message(tr::now)) + st::normalFont->spacew, top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, editTimeLeftText);
7198 }
7199 }
7200
7201 //
7202 //void HistoryWidget::drawPinnedBar(Painter &p) {
7203 // //if (_pinnedBar->msg) {
7204 // // const auto media = _pinnedBar->msg->media();
7205 // // if (media && media->hasReplyPreview()) {
7206 // // if (const auto image = media->replyPreview()) {
7207 // // QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
7208 // // p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small));
7209 // // }
7210 // // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
7211 // // }
7212 // //}
7213 //}
7214
paintShowAnimationFrame()7215 bool HistoryWidget::paintShowAnimationFrame() {
7216 auto progress = _a_show.value(1.);
7217 if (!_a_show.animating()) {
7218 return false;
7219 }
7220
7221 Painter p(this);
7222 auto animationWidth = width();
7223 auto retina = cIntRetinaFactor();
7224 auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft);
7225 auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress);
7226 auto coordOver = fromLeft ? anim::interpolate(0, animationWidth, progress) : anim::interpolate(animationWidth, 0, progress);
7227 auto shadow = fromLeft ? (1. - progress) : progress;
7228 if (coordOver > 0) {
7229 p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, height() * retina));
7230 p.setOpacity(shadow);
7231 p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg);
7232 p.setOpacity(1);
7233 }
7234 p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, 0, _cacheOver.width(), height() * retina));
7235 p.setOpacity(shadow);
7236 st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height()));
7237 return true;
7238 }
7239
paintEvent(QPaintEvent * e)7240 void HistoryWidget::paintEvent(QPaintEvent *e) {
7241 if (paintShowAnimationFrame()) {
7242 return;
7243 }
7244 if (Ui::skipPaintEvent(this, e)) {
7245 return;
7246 }
7247 if (hasPendingResizedItems()) {
7248 updateListSize();
7249 }
7250
7251 Window::SectionWidget::PaintBackground(
7252 controller(),
7253 _list ? _list->theme().get() : controller()->defaultChatTheme().get(),
7254 this,
7255 e->rect());
7256
7257 Painter p(this);
7258 const auto clip = e->rect();
7259 if (_list) {
7260 if (!_field->isHidden() || isRecording()) {
7261 drawField(p, clip);
7262 } else if (const auto error = writeRestriction()) {
7263 drawRestrictedWrite(p, *error);
7264 }
7265 } else {
7266 const auto w = st::msgServiceFont->width(tr::lng_willbe_history(tr::now))
7267 + st::msgPadding.left()
7268 + st::msgPadding.right();
7269 const auto h = st::msgServiceFont->height
7270 + st::msgServicePadding.top()
7271 + st::msgServicePadding.bottom();
7272 const auto tr = QRect(
7273 (width() - w) / 2,
7274 st::msgServiceMargin.top() + (height()
7275 - _field->height()
7276 - 2 * st::historySendPadding
7277 - h
7278 - st::msgServiceMargin.top()
7279 - st::msgServiceMargin.bottom()) / 2,
7280 w,
7281 h);
7282 const auto st = controller()->chatStyle();
7283 HistoryView::ServiceMessagePainter::PaintBubble(p, st, tr);
7284
7285 p.setPen(st->msgServiceFg());
7286 p.setFont(st::msgServiceFont->f);
7287 p.drawTextLeft(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top(), width(), tr::lng_willbe_history(tr::now));
7288
7289 //AssertIsDebug();
7290 //Ui::EmptyUserpic::PaintRepliesMessages(p, width() / 4, width() / 4, width(), width() / 2);
7291 }
7292 }
7293
historyRect() const7294 QRect HistoryWidget::historyRect() const {
7295 return _scroll->geometry();
7296 }
7297
clampMousePosition(QPoint point)7298 QPoint HistoryWidget::clampMousePosition(QPoint point) {
7299 if (point.x() < 0) {
7300 point.setX(0);
7301 } else if (point.x() >= _scroll->width()) {
7302 point.setX(_scroll->width() - 1);
7303 }
7304 if (point.y() < _scroll->scrollTop()) {
7305 point.setY(_scroll->scrollTop());
7306 } else if (point.y() >= _scroll->scrollTop() + _scroll->height()) {
7307 point.setY(_scroll->scrollTop() + _scroll->height() - 1);
7308 }
7309 return point;
7310 }
7311
touchScroll(const QPoint & delta)7312 bool HistoryWidget::touchScroll(const QPoint &delta) {
7313 int32 scTop = _scroll->scrollTop(), scMax = _scroll->scrollTopMax();
7314 const auto scNew = std::clamp(scTop - delta.y(), 0, scMax);
7315 if (scNew == scTop) return false;
7316
7317 _scroll->scrollToY(scNew);
7318 return true;
7319 }
7320
synteticScrollToY(int y)7321 void HistoryWidget::synteticScrollToY(int y) {
7322 _synteticScrollEvent = true;
7323 if (_scroll->scrollTop() == y) {
7324 visibleAreaUpdated();
7325 } else {
7326 _scroll->scrollToY(y);
7327 }
7328 _synteticScrollEvent = false;
7329 }
7330
~HistoryWidget()7331 HistoryWidget::~HistoryWidget() {
7332 if (_history) {
7333 // Saving a draft on account switching.
7334 saveFieldToHistoryLocalDraft();
7335 session().api().saveDraftToCloudDelayed(_history);
7336
7337 clearAllLoadRequests();
7338 unregisterDraftSources();
7339 }
7340 setTabbedPanel(nullptr);
7341 }
7342