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 &params) {
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 &params) {
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 &params) {
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