5725 lines
		
	
	
	
		
			183 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			5725 lines
		
	
	
	
		
			183 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop version of Telegram messaging app, see https://telegram.org
 | |
| 
 | |
| Telegram Desktop is free software: you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation, either version 3 of the License, or
 | |
| (at your option) any later version.
 | |
| 
 | |
| It is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| GNU General Public License for more details.
 | |
| 
 | |
| In addition, as a special exception, the copyright holders give permission
 | |
| to link the code of portions of this program with the OpenSSL library.
 | |
| 
 | |
| Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | |
| Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 | |
| */
 | |
| #include "mainwidget.h"
 | |
| 
 | |
| #include "styles/style_dialogs.h"
 | |
| #include "styles/style_history.h"
 | |
| #include "ui/special_buttons.h"
 | |
| #include "ui/widgets/buttons.h"
 | |
| #include "ui/widgets/shadow.h"
 | |
| #include "window/section_memento.h"
 | |
| #include "window/section_widget.h"
 | |
| #include "window/top_bar_widget.h"
 | |
| #include "data/data_drafts.h"
 | |
| #include "ui/widgets/dropdown_menu.h"
 | |
| #include "chat_helpers/message_field.h"
 | |
| #include "observer_peer.h"
 | |
| #include "apiwrap.h"
 | |
| #include "dialogswidget.h"
 | |
| #include "historywidget.h"
 | |
| #include "history/history_service_layout.h"
 | |
| #include "overviewwidget.h"
 | |
| #include "lang.h"
 | |
| #include "boxes/add_contact_box.h"
 | |
| #include "storage/file_upload.h"
 | |
| #include "messenger.h"
 | |
| #include "application.h"
 | |
| #include "mainwindow.h"
 | |
| #include "inline_bots/inline_bot_layout_item.h"
 | |
| #include "boxes/confirm_box.h"
 | |
| #include "boxes/sticker_set_box.h"
 | |
| #include "boxes/contacts_box.h"
 | |
| #include "boxes/download_path_box.h"
 | |
| #include "boxes/confirm_phone_box.h"
 | |
| #include "boxes/share_box.h"
 | |
| #include "storage/localstorage.h"
 | |
| #include "shortcuts.h"
 | |
| #include "media/media_audio.h"
 | |
| #include "media/player/media_player_panel.h"
 | |
| #include "media/player/media_player_widget.h"
 | |
| #include "media/player/media_player_volume_controller.h"
 | |
| #include "media/player/media_player_instance.h"
 | |
| #include "media/player/media_player_float.h"
 | |
| #include "base/qthelp_regex.h"
 | |
| #include "base/qthelp_url.h"
 | |
| #include "window/themes/window_theme.h"
 | |
| #include "window/player_wrap_widget.h"
 | |
| #include "styles/style_boxes.h"
 | |
| #include "mtproto/dc_options.h"
 | |
| #include "core/file_utilities.h"
 | |
| #include "boxes/calendar_box.h"
 | |
| #include "auth_session.h"
 | |
| #include "window/notifications_manager.h"
 | |
| #include "window/window_controller.h"
 | |
| #include "calls/calls_instance.h"
 | |
| #include "calls/calls_top_bar.h"
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kSaveFloatPlayerPositionTimeoutMs = TimeMs(1000);
 | |
| 
 | |
| MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) {
 | |
| 	switch (type) {
 | |
| 	case OverviewPhotos: return MTP_inputMessagesFilterPhotos();
 | |
| 	case OverviewVideos: return MTP_inputMessagesFilterVideo();
 | |
| 	case OverviewMusicFiles: return MTP_inputMessagesFilterMusic();
 | |
| 	case OverviewFiles: return MTP_inputMessagesFilterDocument();
 | |
| 	case OverviewVoiceFiles: return MTP_inputMessagesFilterVoice();
 | |
| 	case OverviewRoundVoiceFiles: return MTP_inputMessagesFilterRoundVoice();
 | |
| 	case OverviewGIFs: return MTP_inputMessagesFilterGif();
 | |
| 	case OverviewLinks: return MTP_inputMessagesFilterUrl();
 | |
| 	case OverviewChatPhotos: return MTP_inputMessagesFilterChatPhotos();
 | |
| 	default: return MTP_inputMessagesFilterEmpty();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| StackItemSection::StackItemSection(std::unique_ptr<Window::SectionMemento> &&memento) : StackItem(nullptr)
 | |
| , _memento(std::move(memento)) {
 | |
| }
 | |
| 
 | |
| StackItemSection::~StackItemSection() {
 | |
| }
 | |
| 
 | |
| template <typename ToggleCallback, typename DraggedCallback>
 | |
| MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback toggle, DraggedCallback dragged)
 | |
| : animationSide(RectPart::Right)
 | |
| , column(Window::Column::Second)
 | |
| , corner(RectPart::TopRight)
 | |
| , widget(parent, item, [this, toggle = std::move(toggle)](bool visible) {
 | |
| 	toggle(this, visible);
 | |
| }, [this, dragged = std::move(dragged)](bool closed) {
 | |
| 	dragged(this, closed);
 | |
| }) {
 | |
| }
 | |
| 
 | |
| MainWidget::MainWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller) : TWidget(parent)
 | |
| , _controller(controller)
 | |
| , _dialogsWidth(st::dialogsWidthMin)
 | |
| , _sideShadow(this, st::shadowFg)
 | |
| , _sideResizeArea(this)
 | |
| , _dialogs(this, _controller)
 | |
| , _history(this, _controller)
 | |
| , _playerPlaylist(this, Media::Player::Panel::Layout::OnlyPlaylist)
 | |
| , _playerPanel(this, Media::Player::Panel::Layout::Full) {
 | |
| 	Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived));
 | |
| 	Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail));
 | |
| 
 | |
| 	_ptsWaiter.setRequesting(true);
 | |
| 	updateScrollColors();
 | |
| 
 | |
| 	connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled()));
 | |
| 	connect(this, SIGNAL(dialogsUpdated()), _dialogs, SLOT(onListScroll()));
 | |
| 	connect(_history, SIGNAL(cancelled()), _dialogs, SLOT(activate()));
 | |
| 	connect(this, SIGNAL(peerPhotoChanged(PeerData*)), this, SIGNAL(dialogsUpdated()));
 | |
| 	connect(&noUpdatesTimer, SIGNAL(timeout()), this, SLOT(mtpPing()));
 | |
| 	connect(&_onlineTimer, SIGNAL(timeout()), this, SLOT(updateOnline()));
 | |
| 	connect(&_onlineUpdater, SIGNAL(timeout()), this, SLOT(updateOnlineDisplay()));
 | |
| 	connect(&_idleFinishTimer, SIGNAL(timeout()), this, SLOT(checkIdleFinish()));
 | |
| 	connect(&_bySeqTimer, SIGNAL(timeout()), this, SLOT(getDifference()));
 | |
| 	connect(&_byPtsTimer, SIGNAL(timeout()), this, SLOT(onGetDifferenceTimeByPts()));
 | |
| 	connect(&_byMinChannelTimer, SIGNAL(timeout()), this, SLOT(getDifference()));
 | |
| 	connect(&_failDifferenceTimer, SIGNAL(timeout()), this, SLOT(onGetDifferenceTimeAfterFail()));
 | |
| 	connect(this, SIGNAL(peerUpdated(PeerData*)), _history, SLOT(peerUpdated(PeerData*)));
 | |
| 	connect(_history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId)));
 | |
| 	connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings()));
 | |
| 	subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) {
 | |
| 		if (audioId.type() != AudioMsgId::Type::Video) {
 | |
| 			handleAudioUpdate(audioId);
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(AuthSession::Current().calls().currentCallChanged(), [this](Calls::Call *call) { setCurrentCall(call); });
 | |
| 	subscribe(AuthSession::Current().api().fullPeerUpdated(), [this](PeerData *peer) {
 | |
| 		emit peerUpdated(peer);
 | |
| 	});
 | |
| 	subscribe(_controller->dialogsListFocused(), [this](bool) {
 | |
| 		updateDialogsWidthAnimated();
 | |
| 	});
 | |
| 	subscribe(_controller->dialogsListDisplayForced(), [this](bool) {
 | |
| 		updateDialogsWidthAnimated();
 | |
| 	});
 | |
| 	subscribe(_controller->dialogsWidthRatio(), [this](float64) {
 | |
| 		updateControlsGeometry();
 | |
| 	});
 | |
| 	subscribe(_controller->floatPlayerAreaUpdated(), [this] {
 | |
| 		checkFloatPlayerVisibility();
 | |
| 	});
 | |
| 
 | |
| 	QCoreApplication::instance()->installEventFilter(this);
 | |
| 
 | |
| 	connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted()));
 | |
| 	connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement()));
 | |
| 
 | |
| 	_webPageOrGameUpdater.setSingleShot(true);
 | |
| 	connect(&_webPageOrGameUpdater, SIGNAL(timeout()), this, SLOT(webPagesOrGamesUpdate()));
 | |
| 
 | |
| 	_sideResizeArea->setCursor(style::cur_sizehor);
 | |
| 
 | |
| 	using Update = Window::Theme::BackgroundUpdate;
 | |
| 	subscribe(Window::Theme::Background(), [this](const Update &update) {
 | |
| 		if (update.type == Update::Type::New || update.type == Update::Type::Changed) {
 | |
| 			clearCachedBackground();
 | |
| 		}
 | |
| 	});
 | |
| 	connect(&_cacheBackgroundTimer, SIGNAL(timeout()), this, SLOT(onCacheBackground()));
 | |
| 
 | |
| 	_playerPanel->setPinCallback([this] { switchToFixedPlayer(); });
 | |
| 	_playerPanel->setCloseCallback([this] { closeBothPlayers(); });
 | |
| 	subscribe(Media::Player::instance()->titleButtonOver(), [this](bool over) {
 | |
| 		if (over) {
 | |
| 			_playerPanel->showFromOther();
 | |
| 		} else {
 | |
| 			_playerPanel->hideFromOther();
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(Media::Player::instance()->playerWidgetOver(), [this](bool over) {
 | |
| 		if (over) {
 | |
| 			if (_playerPlaylist->isHidden()) {
 | |
| 				auto position = mapFromGlobal(QCursor::pos()).x();
 | |
| 				auto bestPosition = _playerPlaylist->bestPositionFor(position);
 | |
| 				if (rtl()) bestPosition = position + 2 * (position - bestPosition) - _playerPlaylist->width();
 | |
| 				updateMediaPlaylistPosition(bestPosition);
 | |
| 			}
 | |
| 			_playerPlaylist->showFromOther();
 | |
| 		} else {
 | |
| 			_playerPlaylist->hideFromOther();
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(Media::Player::instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
 | |
| 		if (type == AudioMsgId::Type::Voice) {
 | |
| 			auto songState = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
 | |
| 			if (!songState.id || IsStoppedOrStopping(songState.state)) {
 | |
| 				closeBothPlayers();
 | |
| 			}
 | |
| 		}
 | |
| 	});
 | |
| 	subscribe(Media::Player::instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
 | |
| 		if (type == AudioMsgId::Type::Voice) {
 | |
| 			checkCurrentFloatPlayer();
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); });
 | |
| 
 | |
| 	auto observeEvents = Notify::PeerUpdate::Flag::SharedMediaChanged;
 | |
| 	subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) {
 | |
| 		mediaOverviewUpdated(update);
 | |
| 	}));
 | |
| 
 | |
| 	_dialogs->show();
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		_history->hide();
 | |
| 	} else {
 | |
| 		_history->show();
 | |
| 	}
 | |
| 
 | |
| 	orderWidgets();
 | |
| 
 | |
| 	_sideResizeArea->installEventFilter(this);
 | |
| 
 | |
| #ifndef TDESKTOP_DISABLE_AUTOUPDATE
 | |
| 	Sandbox::startUpdateCheck();
 | |
| #endif // !TDESKTOP_DISABLE_AUTOUPDATE
 | |
| }
 | |
| 
 | |
| void MainWidget::checkCurrentFloatPlayer() {
 | |
| 	auto state = Media::Player::instance()->current(AudioMsgId::Type::Voice);
 | |
| 	auto fullId = state.contextId();
 | |
| 	auto last = currentFloatPlayer();
 | |
| 	if (!last || last->widget->detached() || last->widget->item()->fullId() != fullId) {
 | |
| 		if (last) {
 | |
| 			last->widget->detach();
 | |
| 		}
 | |
| 		if (auto item = App::histItemById(fullId)) {
 | |
| 			if (auto media = item->getMedia()) {
 | |
| 				if (auto document = media->getDocument()) {
 | |
| 					if (document->isRoundVideo()) {
 | |
| 						_playerFloats.push_back(std::make_unique<Float>(this, item, [this](gsl::not_null<Float*> instance, bool visible) {
 | |
| 							instance->hiddenByWidget = !visible;
 | |
| 							toggleFloatPlayer(instance);
 | |
| 						}, [this](gsl::not_null<Float*> instance, bool closed) {
 | |
| 							finishFloatPlayerDrag(instance, closed);
 | |
| 						}));
 | |
| 						currentFloatPlayer()->column = AuthSession::Current().data().floatPlayerColumn();
 | |
| 						currentFloatPlayer()->corner = AuthSession::Current().data().floatPlayerCorner();
 | |
| 						checkFloatPlayerVisibility();
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::toggleFloatPlayer(gsl::not_null<Float*> instance) {
 | |
| 	auto visible = !instance->hiddenByHistory && !instance->hiddenByWidget && !instance->widget->detached();
 | |
| 	if (instance->visible != visible) {
 | |
| 		instance->widget->resetMouseState();
 | |
| 		instance->visible = visible;
 | |
| 		if (!instance->visibleAnimation.animating() && !instance->hiddenByDrag) {
 | |
| 			auto finalRect = QRect(getFloatPlayerPosition(instance), instance->widget->size());
 | |
| 			instance->animationSide = getFloatPlayerSide(finalRect.center());
 | |
| 		}
 | |
| 		instance->visibleAnimation.start([this, instance] {
 | |
| 			updateFloatPlayerPosition(instance);
 | |
| 		}, visible ? 0. : 1., visible ? 1. : 0., st::slideDuration, visible ? anim::easeOutCirc : anim::linear);
 | |
| 		updateFloatPlayerPosition(instance);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::checkFloatPlayerVisibility() {
 | |
| 	auto instance = currentFloatPlayer();
 | |
| 	if (!instance) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (_history->isHidden() || _history->isItemCompletelyHidden(instance->widget->item())) {
 | |
| 		instance->hiddenByHistory = false;
 | |
| 	} else {
 | |
| 		instance->hiddenByHistory = true;
 | |
| 	}
 | |
| 	toggleFloatPlayer(instance);
 | |
| 	updateFloatPlayerPosition(instance);
 | |
| }
 | |
| 
 | |
| void MainWidget::updateFloatPlayerPosition(gsl::not_null<Float*> instance) {
 | |
| 	auto visible = instance->visibleAnimation.current(instance->visible ? 1. : 0.);
 | |
| 	if (visible == 0. && !instance->visible) {
 | |
| 		instance->widget->hide();
 | |
| 		if (instance->widget->detached()) {
 | |
| 			InvokeQueued(instance->widget, [this, instance] {
 | |
| 				removeFloatPlayer(instance);
 | |
| 			});
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!instance->widget->dragged()) {
 | |
| 		if (instance->widget->isHidden()) {
 | |
| 			instance->widget->show();
 | |
| 		}
 | |
| 
 | |
| 		auto dragged = instance->draggedAnimation.current(1.);
 | |
| 		auto position = QPoint();
 | |
| 		if (instance->hiddenByDrag) {
 | |
| 			instance->widget->setOpacity(instance->widget->countOpacityByParent());
 | |
| 			position = getFloatPlayerHiddenPosition(instance->dragFrom, instance->widget->size(), instance->animationSide);
 | |
| 		} else {
 | |
| 			instance->widget->setOpacity(visible * visible);
 | |
| 			position = getFloatPlayerPosition(instance);
 | |
| 			if (visible < 1.) {
 | |
| 				auto hiddenPosition = getFloatPlayerHiddenPosition(position, instance->widget->size(), instance->animationSide);
 | |
| 				position.setX(anim::interpolate(hiddenPosition.x(), position.x(), visible));
 | |
| 				position.setY(anim::interpolate(hiddenPosition.y(), position.y(), visible));
 | |
| 			}
 | |
| 		}
 | |
| 		if (dragged < 1.) {
 | |
| 			position.setX(anim::interpolate(instance->dragFrom.x(), position.x(), dragged));
 | |
| 			position.setY(anim::interpolate(instance->dragFrom.y(), position.y(), dragged));
 | |
| 		}
 | |
| 		instance->widget->move(position);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| QPoint MainWidget::getFloatPlayerHiddenPosition(QPoint position, QSize size, RectPart side) const {
 | |
| 	switch (side) {
 | |
| 	case RectPart::Left: return QPoint(-size.width(), position.y());
 | |
| 	case RectPart::Top: return QPoint(position.x(), -size.height());
 | |
| 	case RectPart::Right: return QPoint(width(), position.y());
 | |
| 	case RectPart::Bottom: return QPoint(position.x(), height());
 | |
| 	}
 | |
| 	Unexpected("Bad side in MainWidget::getFloatPlayerHiddenPosition().");
 | |
| }
 | |
| 
 | |
| QPoint MainWidget::getFloatPlayerPosition(gsl::not_null<Float*> instance) const {
 | |
| 	auto column = instance->column;
 | |
| 	auto section = getFloatPlayerSection(&column);
 | |
| 	auto rect = section->rectForFloatPlayer(column, instance->column);
 | |
| 	auto position = rect.topLeft();
 | |
| 	if (IsBottomCorner(instance->corner)) {
 | |
| 		position.setY(position.y() + rect.height() - instance->widget->height());
 | |
| 	}
 | |
| 	if (IsRightCorner(instance->corner)) {
 | |
| 		position.setX(position.x() + rect.width() - instance->widget->width());
 | |
| 	}
 | |
| 	return mapFromGlobal(position);
 | |
| }
 | |
| 
 | |
| RectPart MainWidget::getFloatPlayerSide(QPoint center) const {
 | |
| 	auto left = qAbs(center.x());
 | |
| 	auto right = qAbs(width() - center.x());
 | |
| 	auto top = qAbs(center.y());
 | |
| 	auto bottom = qAbs(height() - center.y());
 | |
| 	if (left < right && left < top && left < bottom) {
 | |
| 		return RectPart::Left;
 | |
| 	} else if (right < top && right < bottom) {
 | |
| 		return RectPart::Right;
 | |
| 	} else if (top < bottom) {
 | |
| 		return RectPart::Top;
 | |
| 	}
 | |
| 	return RectPart::Bottom;
 | |
| }
 | |
| 
 | |
| void MainWidget::removeFloatPlayer(gsl::not_null<Float*> instance) {
 | |
| 	auto widget = std::move(instance->widget);
 | |
| 	auto i = std::find_if(_playerFloats.begin(), _playerFloats.end(), [instance](auto &item) {
 | |
| 		return (item.get() == instance);
 | |
| 	});
 | |
| 	t_assert(i != _playerFloats.end());
 | |
| 	_playerFloats.erase(i);
 | |
| 
 | |
| 	// ~QWidget() can call HistoryInner::enterEvent() which can
 | |
| 	// lead to repaintHistoryItem() and we'll have an instance
 | |
| 	// in _playerFloats with destroyed widget. So we destroy the
 | |
| 	// instance first and only after that destroy the widget.
 | |
| 	widget.destroy();
 | |
| }
 | |
| 
 | |
| Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null<Window::Column*> column) const {
 | |
| 	if (!Adaptive::Normal()) {
 | |
| 		*column = Adaptive::OneColumn() ? Window::Column::First : Window::Column::Second;
 | |
| 		if (Adaptive::OneColumn() && selectingPeer()) {
 | |
| 			return _dialogs;
 | |
| 		} else if (_overview) {
 | |
| 			return _overview;
 | |
| 		} else if (_wideSection) {
 | |
| 			return _wideSection;
 | |
| 		} else if (!Adaptive::OneColumn() || _history->peer()) {
 | |
| 			return _history;
 | |
| 		}
 | |
| 		return _dialogs;
 | |
| 	}
 | |
| 	if (*column == Window::Column::First) {
 | |
| 		return _dialogs;
 | |
| 	}
 | |
| 	*column = Window::Column::Second;
 | |
| 	if (_overview) {
 | |
| 		return _overview;
 | |
| 	} else if (_wideSection) {
 | |
| 		return _wideSection;
 | |
| 	}
 | |
| 	return _history;
 | |
| }
 | |
| 
 | |
| void MainWidget::updateFloatPlayerColumnCorner(QPoint center) {
 | |
| 	Expects(!_playerFloats.empty());
 | |
| 	auto size = _playerFloats.back()->widget->size();
 | |
| 	auto min = INT_MAX;
 | |
| 	auto column = AuthSession::Current().data().floatPlayerColumn();
 | |
| 	auto corner = AuthSession::Current().data().floatPlayerCorner();
 | |
| 	auto checkSection = [this, center, size, &min, &column, &corner](Window::AbstractSectionWidget *widget, Window::Column myColumn, Window::Column playerColumn) {
 | |
| 		auto rect = mapFromGlobal(widget->rectForFloatPlayer(myColumn, playerColumn));
 | |
| 		auto left = rect.x() + (size.width() / 2);
 | |
| 		auto right = rect.x() + rect.width() - (size.width() / 2);
 | |
| 		auto top = rect.y() + (size.height() / 2);
 | |
| 		auto bottom = rect.y() + rect.height() - (size.height() / 2);
 | |
| 		auto checkCorner = [this, playerColumn, &min, &column, &corner](int distance, RectPart checked) {
 | |
| 			if (min > distance) {
 | |
| 				min = distance;
 | |
| 				column = playerColumn;
 | |
| 				corner = checked;
 | |
| 			}
 | |
| 		};
 | |
| 		checkCorner((QPoint(left, top) - center).manhattanLength(), RectPart::TopLeft);
 | |
| 		checkCorner((QPoint(right, top) - center).manhattanLength(), RectPart::TopRight);
 | |
| 		checkCorner((QPoint(left, bottom) - center).manhattanLength(), RectPart::BottomLeft);
 | |
| 		checkCorner((QPoint(right, bottom) - center).manhattanLength(), RectPart::BottomRight);
 | |
| 	};
 | |
| 
 | |
| 	if (!Adaptive::Normal()) {
 | |
| 		if (Adaptive::OneColumn() && selectingPeer()) {
 | |
| 			checkSection(_dialogs, Window::Column::First, Window::Column::First);
 | |
| 		} else if (_overview) {
 | |
| 			checkSection(_overview, Window::Column::Second, Window::Column::Second);
 | |
| 		} else if (_wideSection) {
 | |
| 			checkSection(_wideSection, Window::Column::Second, Window::Column::Second);
 | |
| 		} else if (!Adaptive::OneColumn() || _history->peer()) {
 | |
| 			checkSection(_history, Window::Column::Second, Window::Column::Second);
 | |
| 			checkSection(_history, Window::Column::Second, Window::Column::Third);
 | |
| 		} else {
 | |
| 			checkSection(_dialogs, Window::Column::First, Window::Column::First);
 | |
| 		}
 | |
| 	} else {
 | |
| 		checkSection(_dialogs, Window::Column::First, Window::Column::First);
 | |
| 		if (_overview) {
 | |
| 			checkSection(_overview, Window::Column::Second, Window::Column::Second);
 | |
| 		} else if (_wideSection) {
 | |
| 			checkSection(_wideSection, Window::Column::Second, Window::Column::Second);
 | |
| 		} else {
 | |
| 			checkSection(_history, Window::Column::Second, Window::Column::Second);
 | |
| 			checkSection(_history, Window::Column::Second, Window::Column::Third);
 | |
| 		}
 | |
| 	}
 | |
| 	if (AuthSession::Current().data().floatPlayerColumn() != column) {
 | |
| 		AuthSession::Current().data().setFloatPlayerColumn(column);
 | |
| 		AuthSession::Current().saveDataDelayed(kSaveFloatPlayerPositionTimeoutMs);
 | |
| 	}
 | |
| 	if (AuthSession::Current().data().floatPlayerCorner() != corner) {
 | |
| 		AuthSession::Current().data().setFloatPlayerCorner(corner);
 | |
| 		AuthSession::Current().saveDataDelayed(kSaveFloatPlayerPositionTimeoutMs);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::finishFloatPlayerDrag(gsl::not_null<Float*> instance, bool closed) {
 | |
| 	instance->dragFrom = instance->widget->pos();
 | |
| 	auto center = instance->widget->geometry().center();
 | |
| 	if (closed) {
 | |
| 		instance->hiddenByDrag = true;
 | |
| 		instance->animationSide = getFloatPlayerSide(center);
 | |
| 	}
 | |
| 	updateFloatPlayerColumnCorner(center);
 | |
| 	instance->column = AuthSession::Current().data().floatPlayerColumn();
 | |
| 	instance->corner = AuthSession::Current().data().floatPlayerCorner();
 | |
| 
 | |
| 	instance->draggedAnimation.finish();
 | |
| 	instance->draggedAnimation.start([this, instance] { updateFloatPlayerPosition(instance); }, 0., 1., st::slideDuration, anim::sineInOut);
 | |
| 	updateFloatPlayerPosition(instance);
 | |
| 
 | |
| 	if (closed) {
 | |
| 		if (auto item = instance->widget->item()) {
 | |
| 			auto voiceData = Media::Player::instance()->current(AudioMsgId::Type::Voice);
 | |
| 			if (_player && voiceData.contextId() == item->fullId()) {
 | |
| 				_player->entity()->stopAndClose();
 | |
| 			}
 | |
| 		}
 | |
| 		instance->widget->detach();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) {
 | |
| 	PeerData *p = App::peer(peer);
 | |
| 	if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) {
 | |
| 		Ui::show(Box<InformBox>(lang(lng_forward_cant)));
 | |
| 		return false;
 | |
| 	}
 | |
| 	_history->cancelReply();
 | |
| 	_toForward.clear();
 | |
| 	if (what == ForwardSelectedMessages) {
 | |
| 		if (_overview) {
 | |
| 			_overview->fillSelectedItems(_toForward, false);
 | |
| 		} else {
 | |
| 			_history->fillSelectedItems(_toForward, false);
 | |
| 		}
 | |
| 	} else {
 | |
| 		HistoryItem *item = 0;
 | |
| 		if (what == ForwardContextMessage) {
 | |
| 			item = App::contextItem();
 | |
| 		} else if (what == ForwardPressedMessage) {
 | |
| 			item = App::pressedItem();
 | |
| 		} else if (what == ForwardPressedLinkMessage) {
 | |
| 			item = App::pressedLinkItem();
 | |
| 		}
 | |
| 		if (item && item->toHistoryMessage() && item->id > 0) {
 | |
| 			_toForward.insert(item->id, item);
 | |
| 		}
 | |
| 	}
 | |
| 	updateForwardingItemRemovedSubscription();
 | |
| 	updateForwardingTexts();
 | |
| 	Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
 | |
| 	_history->onClearSelected();
 | |
| 	_history->updateForwarding();
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QString &text) {
 | |
| 	PeerData *p = App::peer(peer);
 | |
| 	if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) {
 | |
| 		Ui::show(Box<InformBox>(lang(lng_share_cant)));
 | |
| 		return false;
 | |
| 	}
 | |
| 	History *h = App::history(peer);
 | |
| 	TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() };
 | |
| 	MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX };
 | |
| 	h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
 | |
| 	h->clearEditDraft();
 | |
| 	bool opened = _history->peer() && (_history->peer()->id == peer);
 | |
| 	if (opened) {
 | |
| 		_history->applyDraft();
 | |
| 	} else {
 | |
| 		Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery) {
 | |
| 	PeerData *p = App::peer(peer);
 | |
| 	if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) {
 | |
| 		Ui::show(Box<InformBox>(lang(lng_inline_switch_cant)));
 | |
| 		return false;
 | |
| 	}
 | |
| 	History *h = App::history(peer);
 | |
| 	TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() };
 | |
| 	MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX };
 | |
| 	h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
 | |
| 	h->clearEditDraft();
 | |
| 	bool opened = _history->peer() && (_history->peer()->id == peer);
 | |
| 	if (opened) {
 | |
| 		_history->applyDraft();
 | |
| 	} else {
 | |
| 		Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool MainWidget::hasForwardingItems() {
 | |
| 	return !_toForward.isEmpty();
 | |
| }
 | |
| 
 | |
| void MainWidget::fillForwardingInfo(Text *&from, Text *&text, bool &serviceColor, ImagePtr &preview) {
 | |
| 	if (_toForward.isEmpty()) return;
 | |
| 	int32 version = 0;
 | |
| 	for (SelectedItemSet::const_iterator i = _toForward.cbegin(), e = _toForward.cend(); i != e; ++i) {
 | |
| 		version += i.value()->authorOriginal()->nameVersion;
 | |
| 	}
 | |
| 	if (version != _toForwardNameVersion) {
 | |
| 		updateForwardingTexts();
 | |
| 	}
 | |
| 	from = &_toForwardFrom;
 | |
| 	text = &_toForwardText;
 | |
| 	serviceColor = (_toForward.size() > 1) || _toForward.cbegin().value()->getMedia() || _toForward.cbegin().value()->serviceMsg();
 | |
| 	if (_toForward.size() < 2 && _toForward.cbegin().value()->getMedia() && _toForward.cbegin().value()->getMedia()->hasReplyPreview()) {
 | |
| 		preview = _toForward.cbegin().value()->getMedia()->replyPreview();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateForwardingTexts() {
 | |
| 	int32 version = 0;
 | |
| 	QString from, text;
 | |
| 	if (!_toForward.isEmpty()) {
 | |
| 		QMap<PeerData*, bool> fromUsersMap;
 | |
| 		QVector<PeerData*> fromUsers;
 | |
| 		fromUsers.reserve(_toForward.size());
 | |
| 		for (SelectedItemSet::const_iterator i = _toForward.cbegin(), e = _toForward.cend(); i != e; ++i) {
 | |
| 			PeerData *from = i.value()->authorOriginal();
 | |
| 			if (!fromUsersMap.contains(from)) {
 | |
| 				fromUsersMap.insert(from, true);
 | |
| 				fromUsers.push_back(from);
 | |
| 			}
 | |
| 			version += from->nameVersion;
 | |
| 		}
 | |
| 		if (fromUsers.size() > 2) {
 | |
| 			from = lng_forwarding_from(lt_user, fromUsers.at(0)->shortName(), lt_count, fromUsers.size() - 1);
 | |
| 		} else if (fromUsers.size() < 2) {
 | |
| 			from = fromUsers.at(0)->name;
 | |
| 		} else {
 | |
| 			from = lng_forwarding_from_two(lt_user, fromUsers.at(0)->shortName(), lt_second_user, fromUsers.at(1)->shortName());
 | |
| 		}
 | |
| 
 | |
| 		if (_toForward.size() < 2) {
 | |
| 			text = _toForward.cbegin().value()->inReplyText();
 | |
| 		} else {
 | |
| 			text = lng_forward_messages(lt_count, _toForward.size());
 | |
| 		}
 | |
| 	}
 | |
| 	_toForwardFrom.setText(st::msgNameStyle, from, _textNameOptions);
 | |
| 	_toForwardText.setText(st::messageTextStyle, textClean(text), _textDlgOptions);
 | |
| 	_toForwardNameVersion = version;
 | |
| }
 | |
| 
 | |
| void MainWidget::updateForwardingItemRemovedSubscription() {
 | |
| 	if (_toForward.isEmpty()) {
 | |
| 		unsubscribe(_forwardingItemRemovedSubscription);
 | |
| 		_forwardingItemRemovedSubscription = 0;
 | |
| 	} else if (!_forwardingItemRemovedSubscription) {
 | |
| 		_forwardingItemRemovedSubscription = subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) {
 | |
| 			auto i = _toForward.find(item->id);
 | |
| 			if (i == _toForward.cend() || i.value() != item) {
 | |
| 				i = _toForward.find(item->id - ServerMaxMsgId);
 | |
| 			}
 | |
| 			if (i != _toForward.cend() && i.value() == item) {
 | |
| 				_toForward.erase(i);
 | |
| 				updateForwardingItemRemovedSubscription();
 | |
| 				updateForwardingTexts();
 | |
| 			}
 | |
| 		});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::cancelForwarding() {
 | |
| 	if (_toForward.isEmpty()) return;
 | |
| 
 | |
| 	_toForward.clear();
 | |
| 	_history->cancelForwarding();
 | |
| 	updateForwardingItemRemovedSubscription();
 | |
| }
 | |
| 
 | |
| void MainWidget::finishForwarding(History *history, bool silent) {
 | |
| 	if (!history) return;
 | |
| 
 | |
| 	if (!_toForward.isEmpty()) {
 | |
| 		bool genClientSideMessage = (_toForward.size() < 2);
 | |
| 		PeerData *forwardFrom = 0;
 | |
| 		App::main()->readServerHistory(history);
 | |
| 
 | |
| 		auto flags = MTPDmessage::Flags(0);
 | |
| 		auto sendFlags = MTPmessages_ForwardMessages::Flags(0);
 | |
| 		bool channelPost = history->peer->isChannel() && !history->peer->isMegagroup();
 | |
| 		bool showFromName = !channelPost || history->peer->asChannel()->addsSignature();
 | |
| 		bool silentPost = channelPost && silent;
 | |
| 		if (channelPost) {
 | |
| 			flags |= MTPDmessage::Flag::f_views;
 | |
| 			flags |= MTPDmessage::Flag::f_post;
 | |
| 		}
 | |
| 		if (showFromName) {
 | |
| 			flags |= MTPDmessage::Flag::f_from_id;
 | |
| 		}
 | |
| 		if (silentPost) {
 | |
| 			sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
 | |
| 		}
 | |
| 
 | |
| 		QVector<MTPint> ids;
 | |
| 		QVector<MTPlong> randomIds;
 | |
| 		ids.reserve(_toForward.size());
 | |
| 		randomIds.reserve(_toForward.size());
 | |
| 		for (SelectedItemSet::const_iterator i = _toForward.cbegin(), e = _toForward.cend(); i != e; ++i) {
 | |
| 			uint64 randomId = rand_value<uint64>();
 | |
| 			if (genClientSideMessage) {
 | |
| 				FullMsgId newId(peerToChannel(history->peer->id), clientMsgId());
 | |
| 				auto msg = static_cast<HistoryMessage*>(_toForward.cbegin().value());
 | |
| 				auto messageFromId = showFromName ? AuthSession::CurrentUserId() : 0;
 | |
| 				history->addNewForwarded(newId.msg, flags, date(MTP_int(unixtime())), messageFromId, msg);
 | |
| 				App::historyRegRandom(randomId, newId);
 | |
| 			}
 | |
| 			if (forwardFrom != i.value()->history()->peer) {
 | |
| 				if (forwardFrom) {
 | |
| 					history->sendRequestId = MTP::send(MTPmessages_ForwardMessages(MTP_flags(sendFlags), forwardFrom->input, MTP_vector<MTPint>(ids), MTP_vector<MTPlong>(randomIds), history->peer->input), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, history->sendRequestId);
 | |
| 					ids.resize(0);
 | |
| 					randomIds.resize(0);
 | |
| 				}
 | |
| 				forwardFrom = i.value()->history()->peer;
 | |
| 			}
 | |
| 			ids.push_back(MTP_int(i.value()->id));
 | |
| 			randomIds.push_back(MTP_long(randomId));
 | |
| 		}
 | |
| 		history->sendRequestId = MTP::send(MTPmessages_ForwardMessages(MTP_flags(sendFlags), forwardFrom->input, MTP_vector<MTPint>(ids), MTP_vector<MTPlong>(randomIds), history->peer->input), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, history->sendRequestId);
 | |
| 
 | |
| 		if (_history->peer() == history->peer) {
 | |
| 			_history->peerMessagesUpdated();
 | |
| 		}
 | |
| 
 | |
| 		cancelForwarding();
 | |
| 	}
 | |
| 
 | |
| 	historyToDown(history);
 | |
| 	dialogsToUp();
 | |
| 	_history->peerMessagesUpdated(history->peer->id);
 | |
| }
 | |
| 
 | |
| void MainWidget::webPageUpdated(WebPageData *data) {
 | |
| 	_webPagesUpdated.insert(data->id);
 | |
| 	_webPageOrGameUpdater.start(0);
 | |
| }
 | |
| 
 | |
| void MainWidget::gameUpdated(GameData *data) {
 | |
| 	_gamesUpdated.insert(data->id);
 | |
| 	_webPageOrGameUpdater.start(0);
 | |
| }
 | |
| 
 | |
| void MainWidget::webPagesOrGamesUpdate() {
 | |
| 	_webPageOrGameUpdater.stop();
 | |
| 	if (!_webPagesUpdated.isEmpty()) {
 | |
| 		auto &items = App::webPageItems();
 | |
| 		for_const (auto webPageId, _webPagesUpdated) {
 | |
| 			auto j = items.constFind(App::webPage(webPageId));
 | |
| 			if (j != items.cend()) {
 | |
| 				for_const (auto item, j.value()) {
 | |
| 					item->setPendingInitDimensions();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		_webPagesUpdated.clear();
 | |
| 	}
 | |
| 	if (!_gamesUpdated.isEmpty()) {
 | |
| 		auto &items = App::gameItems();
 | |
| 		for_const (auto gameId, _gamesUpdated) {
 | |
| 			auto j = items.constFind(App::game(gameId));
 | |
| 			if (j != items.cend()) {
 | |
| 				for_const (auto item, j.value()) {
 | |
| 					item->setPendingInitDimensions();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		_gamesUpdated.clear();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateMutedIn(int32 seconds) {
 | |
| 	if (seconds > 86400) seconds = 86400;
 | |
| 	int32 ms = seconds * 1000;
 | |
| 	if (_updateMutedTimer.isActive() && _updateMutedTimer.remainingTime() <= ms) return;
 | |
| 	_updateMutedTimer.start(ms);
 | |
| }
 | |
| 
 | |
| void MainWidget::updateStickers() {
 | |
| 	_history->updateStickers();
 | |
| }
 | |
| 
 | |
| void MainWidget::onUpdateMuted() {
 | |
| 	App::updateMuted();
 | |
| }
 | |
| 
 | |
| void MainWidget::onShareContact(const PeerId &peer, UserData *contact) {
 | |
| 	_history->onShareContact(peer, contact);
 | |
| }
 | |
| 
 | |
| bool MainWidget::onSendPaths(const PeerId &peer) {
 | |
| 	Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
 | |
| 	return _history->confirmSendingFiles(cSendPaths());
 | |
| }
 | |
| 
 | |
| void MainWidget::onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data) {
 | |
| 	if (data->hasFormat(qsl("application/x-td-forward-selected"))) {
 | |
| 		onForward(peer, ForwardSelectedMessages);
 | |
| 	} else if (data->hasFormat(qsl("application/x-td-forward-pressed-link"))) {
 | |
| 		onForward(peer, ForwardPressedLinkMessage);
 | |
| 	} else if (data->hasFormat(qsl("application/x-td-forward-pressed"))) {
 | |
| 		onForward(peer, ForwardPressedMessage);
 | |
| 	} else {
 | |
| 		Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
 | |
| 		_history->confirmSendingFiles(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::isItemVisible(HistoryItem *item) {
 | |
| 	if (isHidden() || _a_show.animating()) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	return _history->isItemVisible(item);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_botCommandsChanged(UserData *bot) {
 | |
| 	_history->notify_botCommandsChanged(bot);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_inlineBotRequesting(bool requesting) {
 | |
| 	_history->notify_inlineBotRequesting(requesting);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_replyMarkupUpdated(const HistoryItem *item) {
 | |
| 	_history->notify_replyMarkupUpdated(item);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) {
 | |
| 	_history->notify_inlineKeyboardMoved(item, oldKeyboardTop, newKeyboardTop);
 | |
| }
 | |
| 
 | |
| bool MainWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) {
 | |
| 	return _history->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_userIsBotChanged(UserData *bot) {
 | |
| 	_history->notify_userIsBotChanged(bot);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) {
 | |
| 	if (!user) return;
 | |
| 
 | |
| 	_dialogs->notify_userIsContactChanged(user, fromThisApp);
 | |
| 
 | |
| 	const SharedContactItems &items(App::sharedContactItems());
 | |
| 	SharedContactItems::const_iterator i = items.constFind(peerToUser(user->id));
 | |
| 	if (i != items.cend()) {
 | |
| 		for_const (auto item, i.value()) {
 | |
| 			item->setPendingInitDimensions();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (user->contact > 0 && fromThisApp) {
 | |
| 		Ui::showPeerHistory(user->id, ShowAtTheEndMsgId);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_migrateUpdated(PeerData *peer) {
 | |
| 	_history->notify_migrateUpdated(peer);
 | |
| }
 | |
| 
 | |
| void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) {
 | |
| 	_history->ui_repaintHistoryItem(item);
 | |
| 	if (item->history()->lastMsg == item) {
 | |
| 		item->history()->updateChatListEntry();
 | |
| 	}
 | |
| 	_playerPlaylist->ui_repaintHistoryItem(item);
 | |
| 	_playerPanel->ui_repaintHistoryItem(item);
 | |
| 	if (_overview) _overview->ui_repaintHistoryItem(item);
 | |
| 	if (auto last = currentFloatPlayer()) {
 | |
| 		last->widget->ui_repaintHistoryItem(item);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_historyItemLayoutChanged(const HistoryItem *item) {
 | |
| 	_history->notify_historyItemLayoutChanged(item);
 | |
| 	if (_overview) _overview->notify_historyItemLayoutChanged(item);
 | |
| 	if (auto last = currentFloatPlayer()) {
 | |
| 		last->widget->ui_repaintHistoryItem(item);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_historyMuteUpdated(History *history) {
 | |
| 	_dialogs->notify_historyMuteUpdated(history);
 | |
| }
 | |
| 
 | |
| void MainWidget::notify_handlePendingHistoryUpdate() {
 | |
| 	_history->notify_handlePendingHistoryUpdate();
 | |
| }
 | |
| 
 | |
| bool MainWidget::cmd_search() {
 | |
| 	if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
 | |
| 	return _history->cmd_search();
 | |
| }
 | |
| 
 | |
| bool MainWidget::cmd_next_chat() {
 | |
| 	if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
 | |
| 	return _history->cmd_next_chat();
 | |
| }
 | |
| 
 | |
| bool MainWidget::cmd_previous_chat() {
 | |
| 	if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
 | |
| 	return _history->cmd_previous_chat();
 | |
| }
 | |
| 
 | |
| void MainWidget::noHider(HistoryHider *destroyed) {
 | |
| 	if (_hider == destroyed) {
 | |
| 		_hider = nullptr;
 | |
| 		if (Adaptive::OneColumn()) {
 | |
| 			if (_forwardConfirm) {
 | |
| 				_forwardConfirm->closeBox();
 | |
| 				_forwardConfirm = nullptr;
 | |
| 			}
 | |
| 			onHistoryShown(_history->history(), _history->msgId());
 | |
| 			if (_wideSection || _overview || (_history->peer() && _history->peer()->id)) {
 | |
| 				auto animationParams = ([this] {
 | |
| 					if (_overview) {
 | |
| 						return prepareOverviewAnimation();
 | |
| 					} else if (_wideSection) {
 | |
| 						return prepareWideSectionAnimation(_wideSection);
 | |
| 					}
 | |
| 					return prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0);
 | |
| 				})();
 | |
| 				_dialogs->hide();
 | |
| 				if (_overview) {
 | |
| 					_overview->showAnimated(Window::SlideDirection::FromRight, animationParams);
 | |
| 				} else if (_wideSection) {
 | |
| 					_wideSection->showAnimated(Window::SlideDirection::FromRight, animationParams);
 | |
| 				} else {
 | |
| 					_history->showAnimated(Window::SlideDirection::FromRight, animationParams);
 | |
| 				}
 | |
| 				checkFloatPlayerVisibility();
 | |
| 			}
 | |
| 		} else {
 | |
| 			if (_forwardConfirm) {
 | |
| 				_forwardConfirm->deleteLater();
 | |
| 				_forwardConfirm = nullptr;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::hiderLayer(object_ptr<HistoryHider> h) {
 | |
| 	if (App::passcoded()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	_hider = std::move(h);
 | |
| 	connect(_hider, SIGNAL(forwarded()), _dialogs, SLOT(onCancelSearch()));
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		dialogsToUp();
 | |
| 
 | |
| 		_hider->hide();
 | |
| 		auto animationParams = prepareDialogsAnimation();
 | |
| 
 | |
| 		onHistoryShown(0, 0);
 | |
| 		if (_overview) {
 | |
| 			_overview->hide();
 | |
| 		} else if (_wideSection) {
 | |
| 			_wideSection->hide();
 | |
| 		} else {
 | |
| 			_history->hide();
 | |
| 		}
 | |
| 		if (_dialogs->isHidden()) {
 | |
| 			_dialogs->show();
 | |
| 			updateControlsGeometry();
 | |
| 			_dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams);
 | |
| 		}
 | |
| 	} else {
 | |
| 		_hider->show();
 | |
| 		updateControlsGeometry();
 | |
| 		_dialogs->activate();
 | |
| 	}
 | |
| 	checkFloatPlayerVisibility();
 | |
| }
 | |
| 
 | |
| void MainWidget::forwardLayer(int forwardSelected) {
 | |
| 	hiderLayer((forwardSelected < 0) ? object_ptr<HistoryHider>(this) : object_ptr<HistoryHider>(this, forwardSelected > 0));
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteLayer(int selectedCount) {
 | |
| 	if (selectedCount) {
 | |
| 		auto forDelete = true;
 | |
| 		SelectedItemSet selected;
 | |
| 		if (_overview) {
 | |
| 			_overview->fillSelectedItems(selected, forDelete);
 | |
| 		} else {
 | |
| 			_history->fillSelectedItems(selected, forDelete);
 | |
| 		}
 | |
| 		if (!selected.isEmpty()) {
 | |
| 			Ui::show(Box<DeleteMessagesBox>(selected));
 | |
| 		}
 | |
| 	} else if (auto item = App::contextItem()) {
 | |
| 		auto suggestModerateActions = !_overview;
 | |
| 		Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::cancelUploadLayer() {
 | |
| 	auto item = App::contextItem();
 | |
| 	if (!item) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	App::uploader()->pause(item->fullId());
 | |
| 	Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [this] {
 | |
| 		if (_overview) {
 | |
| 			_overview->deleteContextItem(false);
 | |
| 		} else {
 | |
| 			_history->deleteContextItem(false);
 | |
| 		}
 | |
| 		App::uploader()->unpause();
 | |
| 	}), base::lambda_guarded(this, [] {
 | |
| 		App::uploader()->unpause();
 | |
| 	})));
 | |
| }
 | |
| 
 | |
| void MainWidget::deletePhotoLayer(PhotoData *photo) {
 | |
| 	if (!photo) return;
 | |
| 	Ui::show(Box<ConfirmBox>(lang(lng_delete_photo_sure), lang(lng_box_delete), base::lambda_guarded(this, [this, photo] {
 | |
| 		Ui::hideLayer();
 | |
| 
 | |
| 		auto me = App::self();
 | |
| 		if (!me) return;
 | |
| 
 | |
| 		if (me->photoId == photo->id) {
 | |
| 			App::app()->peerClearPhoto(me->id);
 | |
| 		} else if (photo->peer && !photo->peer->isUser() && photo->peer->photoId == photo->id) {
 | |
| 			App::app()->peerClearPhoto(photo->peer->id);
 | |
| 		} else {
 | |
| 			for (int i = 0, l = me->photos.size(); i != l; ++i) {
 | |
| 				if (me->photos.at(i) == photo) {
 | |
| 					me->photos.removeAt(i);
 | |
| 					MTP::send(MTPphotos_DeletePhotos(MTP_vector<MTPInputPhoto>(1, MTP_inputPhoto(MTP_long(photo->id), MTP_long(photo->access)))));
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	})));
 | |
| }
 | |
| 
 | |
| void MainWidget::shareContactLayer(UserData *contact) {
 | |
| 	hiderLayer(object_ptr<HistoryHider>(this, contact));
 | |
| }
 | |
| 
 | |
| void MainWidget::shareUrlLayer(const QString &url, const QString &text) {
 | |
| 	// Don't allow to insert an inline bot query by share url link.
 | |
| 	if (url.trimmed().startsWith('@')) {
 | |
| 		return;
 | |
| 	}
 | |
| 	hiderLayer(object_ptr<HistoryHider>(this, url, text));
 | |
| }
 | |
| 
 | |
| void MainWidget::inlineSwitchLayer(const QString &botAndQuery) {
 | |
| 	hiderLayer(object_ptr<HistoryHider>(this, botAndQuery));
 | |
| }
 | |
| 
 | |
| bool MainWidget::selectingPeer(bool withConfirm) const {
 | |
| 	return _hider ? (withConfirm ? _hider->withConfirm() : true) : false;
 | |
| }
 | |
| 
 | |
| bool MainWidget::selectingPeerForInlineSwitch() {
 | |
| 	return selectingPeer() ? !_hider->botAndQuery().isEmpty() : false;
 | |
| }
 | |
| 
 | |
| void MainWidget::offerPeer(PeerId peer) {
 | |
| 	Ui::hideLayer();
 | |
| 	if (_hider->offerPeer(peer) && Adaptive::OneColumn()) {
 | |
| 		_forwardConfirm = Ui::show(Box<ConfirmBox>(_hider->offeredText(), lang(lng_forward_send), base::lambda_guarded(this, [this] {
 | |
| 			_hider->forward();
 | |
| 			if (_forwardConfirm) _forwardConfirm->closeBox();
 | |
| 			if (_hider) _hider->offerPeer(0);
 | |
| 		}), base::lambda_guarded(this, [this] {
 | |
| 			if (_hider && _forwardConfirm) _hider->offerPeer(0);
 | |
| 		})));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::dialogsActivate() {
 | |
| 	_dialogs->activate();
 | |
| }
 | |
| 
 | |
| DragState MainWidget::getDragState(const QMimeData *mime) {
 | |
| 	return _history->getDragState(mime);
 | |
| }
 | |
| 
 | |
| bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	if (error.type() == qstr("USER_NOT_PARTICIPANT") || error.type() == qstr("CHAT_ID_INVALID") || error.type() == qstr("PEER_ID_INVALID")) { // left this chat already
 | |
| 		deleteConversation(peer);
 | |
| 		return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteHistoryAfterLeave(PeerData *peer, const MTPUpdates &updates) {
 | |
| 	sentUpdatesReceived(updates);
 | |
| 	deleteConversation(peer);
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteHistoryPart(DeleteHistoryRequest request, const MTPmessages_AffectedHistory &result) {
 | |
| 	auto peer = request.peer;
 | |
| 
 | |
| 	const auto &d(result.c_messages_affectedHistory());
 | |
| 	if (peer && peer->isChannel()) {
 | |
| 		if (peer->asChannel()->ptsUpdated(d.vpts.v, d.vpts_count.v)) {
 | |
| 			peer->asChannel()->ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (ptsUpdated(d.vpts.v, d.vpts_count.v)) {
 | |
| 			ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	int32 offset = d.voffset.v;
 | |
| 	if (offset <= 0) {
 | |
| 		cRefReportSpamStatuses().remove(peer->id);
 | |
| 		Local::writeReportSpamStatuses();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto flags = MTPmessages_DeleteHistory::Flags(0);
 | |
| 	if (request.justClearHistory) {
 | |
| 		flags |= MTPmessages_DeleteHistory::Flag::f_just_clear;
 | |
| 	}
 | |
| 	MTP::send(MTPmessages_DeleteHistory(MTP_flags(flags), peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, request));
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteMessages(PeerData *peer, const QVector<MTPint> &ids, bool forEveryone) {
 | |
| 	if (peer->isChannel()) {
 | |
| 		MTP::send(MTPchannels_DeleteMessages(peer->asChannel()->inputChannel, MTP_vector<MTPint>(ids)), rpcDone(&MainWidget::messagesAffected, peer));
 | |
| 	} else {
 | |
| 		auto flags = MTPmessages_DeleteMessages::Flags(0);
 | |
| 		if (forEveryone) {
 | |
| 			flags |= MTPmessages_DeleteMessages::Flag::f_revoke;
 | |
| 		}
 | |
| 		MTP::send(MTPmessages_DeleteMessages(MTP_flags(flags), MTP_vector<MTPint>(ids)), rpcDone(&MainWidget::messagesAffected, peer));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::deletedContact(UserData *user, const MTPcontacts_Link &result) {
 | |
| 	auto &d(result.c_contacts_link());
 | |
| 	App::feedUsers(MTP_vector<MTPUser>(1, d.vuser));
 | |
| 	App::feedUserLink(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link);
 | |
| }
 | |
| 
 | |
| void MainWidget::removeDialog(History *history) {
 | |
| 	_dialogs->removeDialog(history);
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteConversation(PeerData *peer, bool deleteHistory) {
 | |
| 	if (activePeer() == peer) {
 | |
| 		Ui::showChatsList();
 | |
| 	}
 | |
| 	if (auto history = App::historyLoaded(peer->id)) {
 | |
| 		history->setPinnedDialog(false);
 | |
| 		removeDialog(history);
 | |
| 		if (peer->isMegagroup() && peer->asChannel()->mgInfo->migrateFromPtr) {
 | |
| 			if (auto migrated = App::historyLoaded(peer->asChannel()->mgInfo->migrateFromPtr->id)) {
 | |
| 				if (migrated->lastMsg) { // return initial dialog
 | |
| 					migrated->setLastMessage(migrated->lastMsg);
 | |
| 				} else {
 | |
| 					checkPeerHistory(migrated->peer);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		history->clear();
 | |
| 		history->newLoaded = true;
 | |
| 		history->oldLoaded = deleteHistory;
 | |
| 	}
 | |
| 	if (peer->isChannel()) {
 | |
| 		peer->asChannel()->ptsWaitingForShortPoll(-1);
 | |
| 	}
 | |
| 	if (deleteHistory) {
 | |
| 		DeleteHistoryRequest request = { peer, false };
 | |
| 		MTP::send(MTPmessages_DeleteHistory(MTP_flags(0), peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, request));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteAndExit(ChatData *chat) {
 | |
| 	PeerData *peer = chat;
 | |
| 	MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, App::self()->inputUser), rpcDone(&MainWidget::deleteHistoryAfterLeave, peer), rpcFail(&MainWidget::leaveChatFailed, peer));
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteAllFromUser(ChannelData *channel, UserData *from) {
 | |
| 	t_assert(channel != nullptr && from != nullptr);
 | |
| 
 | |
| 	QVector<MsgId> toDestroy;
 | |
| 	if (auto history = App::historyLoaded(channel->id)) {
 | |
| 		for_const (auto block, history->blocks) {
 | |
| 			for_const (auto item, block->items) {
 | |
| 				if (item->from() == from && item->canDelete()) {
 | |
| 					toDestroy.push_back(item->id);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		for_const (auto &msgId, toDestroy) {
 | |
| 			if (auto item = App::histItemById(peerToChannel(channel->id), msgId)) {
 | |
| 				item->destroy();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	MTP::send(MTPchannels_DeleteUserHistory(channel->inputChannel, from->inputUser), rpcDone(&MainWidget::deleteAllFromUserPart, { channel, from }));
 | |
| }
 | |
| 
 | |
| void MainWidget::deleteAllFromUserPart(DeleteAllFromUserParams params, const MTPmessages_AffectedHistory &result) {
 | |
| 	const auto &d(result.c_messages_affectedHistory());
 | |
| 	if (params.channel->ptsUpdated(d.vpts.v, d.vpts_count.v)) {
 | |
| 		params.channel->ptsApplySkippedUpdates();
 | |
| 	}
 | |
| 
 | |
| 	int32 offset = d.voffset.v;
 | |
| 	if (offset > 0) {
 | |
| 		MTP::send(MTPchannels_DeleteUserHistory(params.channel->inputChannel, params.from->inputUser), rpcDone(&MainWidget::deleteAllFromUserPart, params));
 | |
| 	} else if (History *h = App::historyLoaded(params.channel)) {
 | |
| 		if (!h->lastMsg) {
 | |
| 			checkPeerHistory(params.channel);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::clearHistory(PeerData *peer) {
 | |
| 	if (History *h = App::historyLoaded(peer->id)) {
 | |
| 		if (h->lastMsg) {
 | |
| 			Local::addSavedPeer(h->peer, h->lastMsg->date);
 | |
| 		}
 | |
| 		h->clear();
 | |
| 		h->newLoaded = h->oldLoaded = true;
 | |
| 	}
 | |
| 	auto flags = MTPmessages_DeleteHistory::Flag::f_just_clear;
 | |
| 	DeleteHistoryRequest request = { peer, true };
 | |
| 	MTP::send(MTPmessages_DeleteHistory(MTP_flags(flags), peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, request));
 | |
| }
 | |
| 
 | |
| void MainWidget::addParticipants(PeerData *chatOrChannel, const QVector<UserData*> &users) {
 | |
| 	if (chatOrChannel->isChat()) {
 | |
| 		auto chat = chatOrChannel->asChat();
 | |
| 		for_const (auto user, users) {
 | |
| 			MTP::send(MTPmessages_AddChatUser(chat->inputChat, user->inputUser, MTP_int(ForwardOnAdd)), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::addParticipantFail, { user, chat }), 0, 5);
 | |
| 		}
 | |
| 	} else if (chatOrChannel->isChannel()) {
 | |
| 		QVector<MTPInputUser> inputUsers;
 | |
| 		inputUsers.reserve(qMin(users.size(), int(MaxUsersPerInvite)));
 | |
| 		for (QVector<UserData*>::const_iterator i = users.cbegin(), e = users.cend(); i != e; ++i) {
 | |
| 			inputUsers.push_back((*i)->inputUser);
 | |
| 			if (inputUsers.size() == MaxUsersPerInvite) {
 | |
| 				MTP::send(MTPchannels_InviteToChannel(chatOrChannel->asChannel()->inputChannel, MTP_vector<MTPInputUser>(inputUsers)), rpcDone(&MainWidget::inviteToChannelDone, chatOrChannel->asChannel()), rpcFail(&MainWidget::addParticipantsFail, chatOrChannel->asChannel()), 0, 5);
 | |
| 				inputUsers.clear();
 | |
| 			}
 | |
| 		}
 | |
| 		if (!inputUsers.isEmpty()) {
 | |
| 			MTP::send(MTPchannels_InviteToChannel(chatOrChannel->asChannel()->inputChannel, MTP_vector<MTPInputUser>(inputUsers)), rpcDone(&MainWidget::inviteToChannelDone, chatOrChannel->asChannel()), rpcFail(&MainWidget::addParticipantsFail, chatOrChannel->asChannel()), 0, 5);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::addParticipantFail(UserAndPeer data, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	QString text = lang(lng_failed_add_participant);
 | |
| 	if (error.type() == qstr("USER_LEFT_CHAT")) { // trying to return a user who has left
 | |
| 	} else if (error.type() == qstr("USER_KICKED")) { // trying to return a user who was kicked by admin
 | |
| 		text = lang(lng_cant_invite_banned);
 | |
| 	} else if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
 | |
| 		text = lang(lng_cant_invite_privacy);
 | |
| 	} else if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { // trying to return user who does not have me in contacts
 | |
| 		text = lang(lng_failed_add_not_mutual);
 | |
| 	} else if (error.type() == qstr("USER_ALREADY_PARTICIPANT") && data.user->botInfo) {
 | |
| 		text = lang(lng_bot_already_in_group);
 | |
| 	} else if (error.type() == qstr("PEER_FLOOD")) {
 | |
| 		text = PeerFloodErrorText((data.peer->isChat() || data.peer->isMegagroup()) ? PeerFloodType::InviteGroup : PeerFloodType::InviteChannel);
 | |
| 	}
 | |
| 	Ui::show(Box<InformBox>(text));
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| bool MainWidget::addParticipantsFail(ChannelData *channel, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	QString text = lang(lng_failed_add_participant);
 | |
| 	if (error.type() == qstr("USER_LEFT_CHAT")) { // trying to return banned user to his group
 | |
| 	} else if (error.type() == qstr("USER_KICKED")) { // trying to return a user who was kicked by admin
 | |
| 		text = lang(lng_cant_invite_banned);
 | |
| 	} else if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
 | |
| 		text = lang(channel->isMegagroup() ? lng_cant_invite_privacy : lng_cant_invite_privacy_channel);
 | |
| 	} else if (error.type() == qstr("USER_NOT_MUTUAL_CONTACT")) { // trying to return user who does not have me in contacts
 | |
| 		text = lang(channel->isMegagroup() ? lng_failed_add_not_mutual : lng_failed_add_not_mutual_channel);
 | |
| 	} else if (error.type() == qstr("PEER_FLOOD")) {
 | |
| 		text = PeerFloodErrorText(PeerFloodType::InviteGroup);
 | |
| 	}
 | |
| 	Ui::show(Box<InformBox>(text));
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void MainWidget::kickParticipant(ChatData *chat, UserData *user) {
 | |
| 	MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, user->inputUser), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::kickParticipantFail, chat));
 | |
| 	Ui::showPeerHistory(chat->id, ShowAtTheEndMsgId);
 | |
| }
 | |
| 
 | |
| bool MainWidget::kickParticipantFail(ChatData *chat, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	error.type();
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void MainWidget::checkPeerHistory(PeerData *peer) {
 | |
| 	MTP::send(MTPmessages_GetHistory(peer->input, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::checkedHistory, peer));
 | |
| }
 | |
| 
 | |
| void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &result) {
 | |
| 	const QVector<MTPMessage> *v = 0;
 | |
| 	switch (result.type()) {
 | |
| 	case mtpc_messages_messages: {
 | |
| 		auto &d(result.c_messages_messages());
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		v = &d.vmessages.v;
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_messages_messagesSlice: {
 | |
| 		auto &d(result.c_messages_messagesSlice());
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		v = &d.vmessages.v;
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_messages_channelMessages: {
 | |
| 		auto &d(result.c_messages_channelMessages());
 | |
| 		if (peer && peer->isChannel()) {
 | |
| 			peer->asChannel()->ptsReceived(d.vpts.v);
 | |
| 		} else {
 | |
| 			LOG(("API Error: received messages.channelMessages when no channel was passed! (MainWidget::checkedHistory)"));
 | |
| 		}
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		v = &d.vmessages.v;
 | |
| 	} break;
 | |
| 	}
 | |
| 	if (!v) return;
 | |
| 
 | |
| 	if (v->isEmpty()) {
 | |
| 		if (peer->isChat() && !peer->asChat()->haveLeft()) {
 | |
| 			auto h = App::historyLoaded(peer->id);
 | |
| 			if (h) Local::addSavedPeer(peer, h->lastMsgDate);
 | |
| 		} else if (peer->isChannel()) {
 | |
| 			if (peer->asChannel()->inviter > 0 && peer->asChannel()->amIn()) {
 | |
| 				if (auto from = App::userLoaded(peer->asChannel()->inviter)) {
 | |
| 					auto h = App::history(peer->id);
 | |
| 					h->clear(true);
 | |
| 					h->addNewerSlice(QVector<MTPMessage>());
 | |
| 					h->asChannelHistory()->insertJoinedMessage(true);
 | |
| 					_history->peerMessagesUpdated(h->peer->id);
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			deleteConversation(peer, false);
 | |
| 		}
 | |
| 	} else {
 | |
| 		History *h = App::history(peer->id);
 | |
| 		if (!h->lastMsg) {
 | |
| 			h->addNewMessage((*v)[0], NewMessageLast);
 | |
| 		}
 | |
| 		if (!h->lastMsgDate.isNull() && h->loadedAtBottom()) {
 | |
| 			if (peer->isChannel() && peer->asChannel()->inviter > 0 && h->lastMsgDate <= peer->asChannel()->inviteDate && peer->asChannel()->amIn()) {
 | |
| 				if (UserData *from = App::userLoaded(peer->asChannel()->inviter)) {
 | |
| 					h->asChannelHistory()->insertJoinedMessage(true);
 | |
| 					_history->peerMessagesUpdated(h->peer->id);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::sendMessageFail(const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	if (error.type() == qstr("PEER_FLOOD")) {
 | |
| 		Ui::show(Box<InformBox>(PeerFloodErrorText(PeerFloodType::Send)));
 | |
| 		return true;
 | |
| 	} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
 | |
| 		auto link = textcmdLink(Messenger::Instance().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info));
 | |
| 		Ui::show(Box<InformBox>(lng_error_public_groups_denied(lt_more_info, link)));
 | |
| 		return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void MainWidget::onCacheBackground() {
 | |
| 	if (Window::Theme::Background()->tile()) {
 | |
| 		auto &bg = Window::Theme::Background()->pixmapForTiled();
 | |
| 
 | |
| 		auto result = QImage(_willCacheFor.width() * cIntRetinaFactor(), _willCacheFor.height() * cIntRetinaFactor(), QImage::Format_RGB32);
 | |
|         result.setDevicePixelRatio(cRetinaFactor());
 | |
| 		{
 | |
| 			QPainter p(&result);
 | |
| 			auto left = 0;
 | |
| 			auto top = 0;
 | |
| 			auto right = _willCacheFor.width();
 | |
| 			auto bottom = _willCacheFor.height();
 | |
| 			auto w = bg.width() / cRetinaFactor();
 | |
| 			auto h = bg.height() / cRetinaFactor();
 | |
| 			auto sx = 0;
 | |
| 			auto sy = 0;
 | |
| 			auto cx = qCeil(_willCacheFor.width() / w);
 | |
| 			auto cy = qCeil(_willCacheFor.height() / h);
 | |
| 			for (int i = sx; i < cx; ++i) {
 | |
| 				for (int j = sy; j < cy; ++j) {
 | |
| 					p.drawPixmap(QPointF(i * w, j * h), bg);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		_cachedX = 0;
 | |
| 		_cachedY = 0;
 | |
| 		_cachedBackground = App::pixmapFromImageInPlace(std::move(result));
 | |
| 	} else {
 | |
| 		auto &bg = Window::Theme::Background()->pixmap();
 | |
| 
 | |
| 		QRect to, from;
 | |
| 		Window::Theme::ComputeBackgroundRects(_willCacheFor, bg.size(), to, from);
 | |
| 		_cachedX = to.x();
 | |
| 		_cachedY = to.y();
 | |
| 		_cachedBackground = App::pixmapFromImageInPlace(bg.toImage().copy(from).scaled(to.width() * cIntRetinaFactor(), to.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
 | |
| 		_cachedBackground.setDevicePixelRatio(cRetinaFactor());
 | |
| 	}
 | |
| 	_cachedFor = _willCacheFor;
 | |
| }
 | |
| 
 | |
| void MainWidget::forwardSelectedItems() {
 | |
| 	if (_overview) {
 | |
| 		_overview->onForwardSelected();
 | |
| 	} else {
 | |
| 		_history->onForwardSelected();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::confirmDeleteSelectedItems() {
 | |
| 	if (_overview) {
 | |
| 		_overview->confirmDeleteSelectedItems();
 | |
| 	} else {
 | |
| 		_history->confirmDeleteSelectedItems();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::clearSelectedItems() {
 | |
| 	if (_overview) {
 | |
| 		_overview->onClearSelected();
 | |
| 	} else {
 | |
| 		_history->onClearSelected();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| Dialogs::IndexedList *MainWidget::contactsList() {
 | |
| 	return _dialogs->contactsList();
 | |
| }
 | |
| 
 | |
| Dialogs::IndexedList *MainWidget::dialogsList() {
 | |
| 	return _dialogs->dialogsList();
 | |
| }
 | |
| 
 | |
| Dialogs::IndexedList *MainWidget::contactsNoDialogsList() {
 | |
| 	return _dialogs->contactsNoDialogsList();
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| QString parseCommandFromMessage(History *history, const QString &message) {
 | |
| 	if (history->peer->id != peerFromUser(ServiceUserId)) {
 | |
| 		return QString();
 | |
| 	}
 | |
| 	if (message.size() < 3 || message.at(0) != '*' || message.at(message.size() - 1) != '*') {
 | |
| 		return QString();
 | |
| 	}
 | |
| 	QString command = message.mid(1, message.size() - 2);
 | |
| 	QStringList commands;
 | |
| 	commands.push_back(qsl("new_version_text"));
 | |
| 	commands.push_back(qsl("all_new_version_texts"));
 | |
| 	if (commands.indexOf(command) < 0) {
 | |
| 		return QString();
 | |
| 	}
 | |
| 	return command;
 | |
| }
 | |
| 
 | |
| void executeParsedCommand(const QString &command) {
 | |
| 	if (command.isEmpty() || !App::wnd()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (command == qsl("new_version_text")) {
 | |
| 		App::wnd()->serviceNotificationLocal(langNewVersionText());
 | |
| 	} else if (command == qsl("all_new_version_texts")) {
 | |
| 		for (int i = 0; i < languageCount; ++i) {
 | |
| 			App::wnd()->serviceNotificationLocal(langNewVersionTextForLang(i));
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| } // namespace
 | |
| 
 | |
| void MainWidget::sendMessage(const MessageToSend &message) {
 | |
| 	auto history = message.history;
 | |
| 	auto &textWithTags = message.textWithTags;
 | |
| 
 | |
| 	readServerHistory(history);
 | |
| 	_history->fastShowAtEnd(history);
 | |
| 
 | |
| 	if (!history || !_history->canSendMessages(history->peer)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	saveRecentHashtags(textWithTags.text);
 | |
| 
 | |
| 	EntitiesInText sendingEntities, leftEntities = ConvertTextTagsToEntities(textWithTags.tags);
 | |
| 	auto prepareFlags = itemTextOptions(history, App::self()).flags;
 | |
| 	QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
 | |
| 
 | |
| 	QString command = parseCommandFromMessage(history, textWithTags.text);
 | |
| 	HistoryItem *lastMessage = nullptr;
 | |
| 
 | |
| 	MsgId replyTo = (message.replyTo < 0) ? _history->replyToId() : message.replyTo;
 | |
| 	while (command.isEmpty() && textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | |
| 		FullMsgId newId(peerToChannel(history->peer->id), clientMsgId());
 | |
| 		uint64 randomId = rand_value<uint64>();
 | |
| 
 | |
| 		trimTextWithEntities(sendingText, &sendingEntities);
 | |
| 
 | |
| 		App::historyRegRandom(randomId, newId);
 | |
| 		App::historyRegSentData(randomId, history->peer->id, sendingText);
 | |
| 
 | |
| 		MTPstring msgText(MTP_string(sendingText));
 | |
| 		auto flags = newMessageFlags(history->peer) | MTPDmessage::Flag::f_entities; // unread, out
 | |
| 		auto sendFlags = MTPmessages_SendMessage::Flags(0);
 | |
| 		if (replyTo) {
 | |
| 			flags |= MTPDmessage::Flag::f_reply_to_msg_id;
 | |
| 			sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id;
 | |
| 		}
 | |
| 		MTPMessageMedia media = MTP_messageMediaEmpty();
 | |
| 		if (message.webPageId == CancelledWebPageId) {
 | |
| 			sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
 | |
| 		} else if (message.webPageId) {
 | |
| 			auto page = App::webPage(message.webPageId);
 | |
| 			media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill)));
 | |
| 			flags |= MTPDmessage::Flag::f_media;
 | |
| 		}
 | |
| 		bool channelPost = history->peer->isChannel() && !history->peer->isMegagroup();
 | |
| 		bool showFromName = !channelPost || history->peer->asChannel()->addsSignature();
 | |
| 		bool silentPost = channelPost && message.silent;
 | |
| 		if (channelPost) {
 | |
| 			flags |= MTPDmessage::Flag::f_views;
 | |
| 			flags |= MTPDmessage::Flag::f_post;
 | |
| 		}
 | |
| 		if (showFromName) {
 | |
| 			flags |= MTPDmessage::Flag::f_from_id;
 | |
| 		}
 | |
| 		if (silentPost) {
 | |
| 			sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
 | |
| 		}
 | |
| 		auto localEntities = linksToMTP(sendingEntities);
 | |
| 		auto sentEntities = linksToMTP(sendingEntities, true);
 | |
| 		if (!sentEntities.v.isEmpty()) {
 | |
| 			sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
 | |
| 		}
 | |
| 		if (message.clearDraft) {
 | |
| 			sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
 | |
| 			history->clearCloudDraft();
 | |
| 		}
 | |
| 		auto messageFromId = showFromName ? AuthSession::CurrentUserId() : 0;
 | |
| 		lastMessage = history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(messageFromId), peerToMTP(history->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread);
 | |
| 		history->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_flags(sendFlags), history->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, history->sendRequestId);
 | |
| 	}
 | |
| 
 | |
| 	history->lastSentMsg = lastMessage;
 | |
| 
 | |
| 	finishForwarding(history, message.silent);
 | |
| 
 | |
| 	executeParsedCommand(command);
 | |
| }
 | |
| 
 | |
| void MainWidget::saveRecentHashtags(const QString &text) {
 | |
| 	bool found = false;
 | |
| 	QRegularExpressionMatch m;
 | |
| 	RecentHashtagPack recent(cRecentWriteHashtags());
 | |
| 	for (int32 i = 0, next = 0; (m = reHashtag().match(text, i)).hasMatch(); i = next) {
 | |
| 		i = m.capturedStart();
 | |
| 		next = m.capturedEnd();
 | |
| 		if (m.hasMatch()) {
 | |
| 			if (!m.capturedRef(1).isEmpty()) {
 | |
| 				++i;
 | |
| 			}
 | |
| 			if (!m.capturedRef(2).isEmpty()) {
 | |
| 				--next;
 | |
| 			}
 | |
| 		}
 | |
| 		if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) {
 | |
| 			Local::readRecentHashtagsAndBots();
 | |
| 			recent = cRecentWriteHashtags();
 | |
| 		}
 | |
| 		found = true;
 | |
| 		incrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
 | |
| 	}
 | |
| 	if (found) {
 | |
| 		cSetRecentWriteHashtags(recent);
 | |
| 		Local::writeRecentHashtagsAndBots();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::readServerHistory(History *history, ReadServerHistoryChecks checks) {
 | |
| 	if (!history) return;
 | |
| 	if (checks == ReadServerHistoryChecks::OnlyIfUnread && !history->unreadCount()) return;
 | |
| 
 | |
| 	auto peer = history->peer;
 | |
| 	MsgId upTo = history->inboxRead(0);
 | |
| 	if (auto channel = peer->asChannel()) {
 | |
| 		if (!channel->amIn()) {
 | |
| 			return; // no read request for channels that I didn't koin
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (_readRequests.contains(peer)) {
 | |
| 		auto i = _readRequestsPending.find(peer);
 | |
| 		if (i == _readRequestsPending.cend()) {
 | |
| 			_readRequestsPending.insert(peer, upTo);
 | |
| 		} else if (i.value() < upTo) {
 | |
| 			i.value() = upTo;
 | |
| 		}
 | |
| 	} else {
 | |
| 		sendReadRequest(peer, upTo);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::unreadCountChanged(History *history) {
 | |
| 	_history->unreadCountChanged(history);
 | |
| }
 | |
| 
 | |
| TimeMs MainWidget::animActiveTimeStart(const HistoryItem *msg) const {
 | |
| 	return _history->animActiveTimeStart(msg);
 | |
| }
 | |
| 
 | |
| void MainWidget::stopAnimActive() {
 | |
| 	_history->stopAnimActive();
 | |
| }
 | |
| 
 | |
| void MainWidget::sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) {
 | |
| 	_history->sendBotCommand(peer, bot, cmd, replyTo);
 | |
| }
 | |
| 
 | |
| void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
 | |
| 	_history->hideSingleUseKeyboard(peer, replyTo);
 | |
| }
 | |
| 
 | |
| void MainWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col) {
 | |
| 	_history->app_sendBotCallback(button, msg, row, col);
 | |
| }
 | |
| 
 | |
| bool MainWidget::insertBotCommand(const QString &cmd) {
 | |
| 	return _history->insertBotCommand(cmd);
 | |
| }
 | |
| 
 | |
| void MainWidget::searchMessages(const QString &query, PeerData *inPeer) {
 | |
| 	App::wnd()->hideMediaview();
 | |
| 	_dialogs->searchMessages(query, inPeer);
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		Ui::showChatsList();
 | |
| 	} else {
 | |
| 		_dialogs->activate();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::preloadOverview(PeerData *peer, MediaOverviewType type) {
 | |
| 	auto filter = typeToMediaFilter(type);
 | |
| 	if (filter.type() == mtpc_inputMessagesFilterEmpty) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	auto history = App::history(peer->id);
 | |
| 	if (history->overviewCountLoaded(type) || _overviewPreload[type].contains(peer)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	_overviewPreload[type].insert(peer, MTP::send(MTPmessages_Search(MTP_flags(0), peer->input, MTP_string(""), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::overviewPreloaded, peer), rpcFail(&MainWidget::overviewFailed, peer), 0, 10));
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::overviewPreloaded(PeerData *peer, const MTPmessages_Messages &result, mtpRequestId req) {
 | |
| 	MediaOverviewType type = OverviewCount;
 | |
| 	for (int32 i = 0; i < OverviewCount; ++i) {
 | |
| 		OverviewsPreload::iterator j = _overviewPreload[i].find(peer);
 | |
| 		if (j != _overviewPreload[i].end() && j.value() == req) {
 | |
| 			type = MediaOverviewType(i);
 | |
| 			_overviewPreload[i].erase(j);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (type == OverviewCount) return;
 | |
| 
 | |
| 	App::history(peer->id)->overviewSliceDone(type, result, true);
 | |
| 
 | |
| 	Notify::mediaOverviewUpdated(peer, type);
 | |
| }
 | |
| 
 | |
| void MainWidget::mediaOverviewUpdated(const Notify::PeerUpdate &update) {
 | |
| 	auto peer = update.peer;
 | |
| 	if (_overview && (_overview->peer() == peer || _overview->peer()->migrateFrom() == peer)) {
 | |
| 		_overview->mediaOverviewUpdated(update);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::changingMsgId(HistoryItem *row, MsgId newId) {
 | |
| 	if (_overview) _overview->changingMsgId(row, newId);
 | |
| }
 | |
| 
 | |
| void MainWidget::itemEdited(HistoryItem *item) {
 | |
| 	if (_history->peer() == item->history()->peer || (_history->peer() && _history->peer() == item->history()->peer->migrateTo())) {
 | |
| 		_history->itemEdited(item);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::overviewFailed(PeerData *peer, const RPCError &error, mtpRequestId req) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	MediaOverviewType type = OverviewCount;
 | |
| 	for (int32 i = 0; i < OverviewCount; ++i) {
 | |
| 		OverviewsPreload::iterator j = _overviewPreload[i].find(peer);
 | |
| 		if (j != _overviewPreload[i].end() && j.value() == req) {
 | |
| 			_overviewPreload[i].erase(j);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many) {
 | |
| 	if (_overviewLoad[type].constFind(peer) != _overviewLoad[type].cend()) return;
 | |
| 
 | |
| 	auto history = App::history(peer->id);
 | |
| 	if (history->overviewLoaded(type)) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto minId = history->overviewMinId(type);
 | |
| 	auto limit = (many || history->overview[type].size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage;
 | |
| 	auto filter = typeToMediaFilter(type);
 | |
| 	if (filter.type() == mtpc_inputMessagesFilterEmpty) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	_overviewLoad[type].insert(peer, MTP::send(MTPmessages_Search(MTP_flags(0), peer->input, MTPstring(), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(minId), MTP_int(limit)), rpcDone(&MainWidget::overviewLoaded, history)));
 | |
| }
 | |
| 
 | |
| void MainWidget::checkLastUpdate(bool afterSleep) {
 | |
| 	auto n = getms(true);
 | |
| 	if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? NoUpdatesAfterSleepTimeout : NoUpdatesTimeout)) {
 | |
| 		_lastUpdateTime = n;
 | |
| 		MTP::ping();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::overviewLoaded(History *history, const MTPmessages_Messages &result, mtpRequestId req) {
 | |
| 	OverviewsPreload::iterator it;
 | |
| 	MediaOverviewType type = OverviewCount;
 | |
| 	for (int32 i = 0; i < OverviewCount; ++i) {
 | |
| 		it = _overviewLoad[i].find(history->peer);
 | |
| 		if (it != _overviewLoad[i].cend()) {
 | |
| 			type = MediaOverviewType(i);
 | |
| 			_overviewLoad[i].erase(it);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (type == OverviewCount) return;
 | |
| 
 | |
| 	history->overviewSliceDone(type, result);
 | |
| 
 | |
| 	Notify::mediaOverviewUpdated(history->peer, type);
 | |
| }
 | |
| 
 | |
| void MainWidget::sendReadRequest(PeerData *peer, MsgId upTo) {
 | |
| 	if (peer->isChannel()) {
 | |
| 		_readRequests.insert(peer, qMakePair(MTP::send(MTPchannels_ReadHistory(peer->asChannel()->inputChannel, MTP_int(upTo)), rpcDone(&MainWidget::channelReadDone, peer), rpcFail(&MainWidget::readRequestFail, peer)), upTo));
 | |
| 	} else {
 | |
| 		_readRequests.insert(peer, qMakePair(MTP::send(MTPmessages_ReadHistory(peer->input, MTP_int(upTo)), rpcDone(&MainWidget::historyReadDone, peer), rpcFail(&MainWidget::readRequestFail, peer)), upTo));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::channelReadDone(PeerData *peer, const MTPBool &result) {
 | |
| 	readRequestDone(peer);
 | |
| }
 | |
| 
 | |
| void MainWidget::historyReadDone(PeerData *peer, const MTPmessages_AffectedMessages &result) {
 | |
| 	messagesAffected(peer, result);
 | |
| 	readRequestDone(peer);
 | |
| }
 | |
| 
 | |
| bool MainWidget::readRequestFail(PeerData *peer, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	readRequestDone(peer);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void MainWidget::readRequestDone(PeerData *peer) {
 | |
| 	_readRequests.remove(peer);
 | |
| 	ReadRequestsPending::iterator i = _readRequestsPending.find(peer);
 | |
| 	if (i != _readRequestsPending.cend()) {
 | |
| 		sendReadRequest(peer, i.value());
 | |
| 		_readRequestsPending.erase(i);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::messagesAffected(PeerData *peer, const MTPmessages_AffectedMessages &result) {
 | |
| 	const auto &d(result.c_messages_affectedMessages());
 | |
| 	if (peer && peer->isChannel()) {
 | |
| 		if (peer->asChannel()->ptsUpdated(d.vpts.v, d.vpts_count.v)) {
 | |
| 			peer->asChannel()->ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (ptsUpdated(d.vpts.v, d.vpts_count.v)) {
 | |
| 			ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	}
 | |
| 	if (History *h = App::historyLoaded(peer ? peer->id : 0)) {
 | |
| 		if (!h->lastMsg) {
 | |
| 			checkPeerHistory(peer);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) {
 | |
| 	Ui::showPeerHistory(peerId, showAtMsgId, way);
 | |
| }
 | |
| 
 | |
| void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) {
 | |
| 	if (HistoryItem *item = App::histItemById(channelId, msgId)) {
 | |
| 		if (HistoryMedia *media = item->getMedia()) {
 | |
| 			media->playInline(true);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
 | |
| 	using State = Media::Player::State;
 | |
| 	auto state = Media::Player::mixer()->currentState(audioId.type());
 | |
| 	if (state.id == audioId && state.state == State::StoppedAtStart) {
 | |
| 		state.state = State::Stopped;
 | |
| 		Media::Player::mixer()->clearStoppedAtStart(audioId);
 | |
| 
 | |
| 		auto document = audioId.audio();
 | |
| 		auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData);
 | |
| 		if (!filepath.isEmpty()) {
 | |
| 			if (documentIsValidMediaFile(filepath)) {
 | |
| 				File::Launch(filepath);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (state.id == audioId && (audioId.type() == AudioMsgId::Type::Song || audioId.type() == AudioMsgId::Type::Voice)) {
 | |
| 		if (!Media::Player::IsStoppedOrStopping(state.state)) {
 | |
| 			createPlayer();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (auto item = App::histItemById(audioId.contextId())) {
 | |
| 		Ui::repaintHistoryItem(item);
 | |
| 		item->audioTrackUpdated();
 | |
| 	}
 | |
| 	if (auto items = InlineBots::Layout::documentItems()) {
 | |
| 		for (auto item : items->value(audioId.audio())) {
 | |
| 			item->update();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::switchToPanelPlayer() {
 | |
| 	if (_playerUsingPanel) return;
 | |
| 	_playerUsingPanel = true;
 | |
| 
 | |
| 	_player->hideAnimated();
 | |
| 	_playerVolume.destroyDelayed();
 | |
| 	_playerPlaylist->hideIgnoringEnterEvents();
 | |
| 
 | |
| 	Media::Player::instance()->usePanelPlayer().notify(true, true);
 | |
| }
 | |
| 
 | |
| void MainWidget::switchToFixedPlayer() {
 | |
| 	if (!_playerUsingPanel) return;
 | |
| 	_playerUsingPanel = false;
 | |
| 
 | |
| 	if (!_player) {
 | |
| 		createPlayer();
 | |
| 	} else {
 | |
| 		_player->showAnimated();
 | |
| 		if (!_playerVolume) {
 | |
| 			_playerVolume.create(this);
 | |
| 			_player->entity()->volumeWidgetCreated(_playerVolume);
 | |
| 			updateMediaPlayerPosition();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	Media::Player::instance()->usePanelPlayer().notify(false, true);
 | |
| 	_playerPanel->hideIgnoringEnterEvents();
 | |
| }
 | |
| 
 | |
| void MainWidget::closeBothPlayers() {
 | |
| 	if (_playerUsingPanel) {
 | |
| 		_playerUsingPanel = false;
 | |
| 		_player.destroyDelayed();
 | |
| 	} else {
 | |
| 		_player->hideAnimated();
 | |
| 	}
 | |
| 	_playerVolume.destroyDelayed();
 | |
| 
 | |
| 	Media::Player::instance()->usePanelPlayer().notify(false, true);
 | |
| 	_playerPanel->hideIgnoringEnterEvents();
 | |
| 	_playerPlaylist->hideIgnoringEnterEvents();
 | |
| 	Media::Player::instance()->stop(AudioMsgId::Type::Voice);
 | |
| 	Media::Player::instance()->stop(AudioMsgId::Type::Song);
 | |
| 
 | |
| 	Shortcuts::disableMediaShortcuts();
 | |
| }
 | |
| 
 | |
| void MainWidget::createPlayer() {
 | |
| 	if (_playerUsingPanel) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!_player) {
 | |
| 		_player.create(this, [this] { playerHeightUpdated(); });
 | |
| 		_player->entity()->setCloseCallback([this] { closeBothPlayers(); });
 | |
| 		_playerVolume.create(this);
 | |
| 		_player->entity()->volumeWidgetCreated(_playerVolume);
 | |
| 		orderWidgets();
 | |
| 		if (_a_show.animating()) {
 | |
| 			_player->showFast();
 | |
| 			_player->hide();
 | |
| 			Shortcuts::enableMediaShortcuts();
 | |
| 		} else {
 | |
| 			_player->hideFast();
 | |
| 		}
 | |
| 	}
 | |
| 	if (_player && _player->isHiddenOrHiding()) {
 | |
| 		if (!_a_show.animating()) {
 | |
| 			_player->showAnimated();
 | |
| 			_playerHeight = _contentScrollAddToY = _player->contentHeight();
 | |
| 			updateControlsGeometry();
 | |
| 			Shortcuts::enableMediaShortcuts();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::playerHeightUpdated() {
 | |
| 	auto playerHeight = _player->contentHeight();
 | |
| 	if (playerHeight != _playerHeight) {
 | |
| 		_contentScrollAddToY += playerHeight - _playerHeight;
 | |
| 		_playerHeight = playerHeight;
 | |
| 		updateControlsGeometry();
 | |
| 	}
 | |
| 	if (!_playerHeight && _player->isHidden()) {
 | |
| 		auto state = Media::Player::mixer()->currentState(Media::Player::instance()->getActiveType());
 | |
| 		if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) {
 | |
| 			_playerVolume.destroyDelayed();
 | |
| 			_player.destroyDelayed();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::setCurrentCall(Calls::Call *call) {
 | |
| 	_currentCall = call;
 | |
| 	if (_currentCall) {
 | |
| 		subscribe(_currentCall->stateChanged(), [this](Calls::Call::State state) {
 | |
| 			using State = Calls::Call::State;
 | |
| 			if (state == State::Established) {
 | |
| 				createCallTopBar();
 | |
| 			} else {
 | |
| 				destroyCallTopBar();
 | |
| 			}
 | |
| 		});
 | |
| 	} else {
 | |
| 		destroyCallTopBar();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::createCallTopBar() {
 | |
| 	Expects(_currentCall != nullptr);
 | |
| 	_callTopBar.create(this, object_ptr<Calls::TopBar>(this, _currentCall), style::margins(0, 0, 0, 0), [this] { callTopBarHeightUpdated(); });
 | |
| 	orderWidgets();
 | |
| 	if (_a_show.animating()) {
 | |
| 		_callTopBar->showFast();
 | |
| 		_callTopBar->hide();
 | |
| 	} else {
 | |
| 		_callTopBar->hideFast();
 | |
| 		_callTopBar->showAnimated();
 | |
| 		_callTopBarHeight = _contentScrollAddToY = _callTopBar->height();
 | |
| 		updateControlsGeometry();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::destroyCallTopBar() {
 | |
| 	if (_callTopBar) {
 | |
| 		_callTopBar->hideAnimated();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::callTopBarHeightUpdated() {
 | |
| 	auto callTopBarHeight = _callTopBar ? _callTopBar->height() : 0;
 | |
| 	if (!callTopBarHeight && !_currentCall) {
 | |
| 		_callTopBar.destroyDelayed();
 | |
| 	}
 | |
| 	if (callTopBarHeight != _callTopBarHeight) {
 | |
| 		_contentScrollAddToY += callTopBarHeight - _callTopBarHeight;
 | |
| 		_callTopBarHeight = callTopBarHeight;
 | |
| 		updateControlsGeometry();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::documentLoadProgress(FileLoader *loader) {
 | |
| 	if (auto documentId = loader ? loader->objId() : 0) {
 | |
| 		documentLoadProgress(App::document(documentId));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::documentLoadProgress(DocumentData *document) {
 | |
| 	if (document->loaded()) {
 | |
| 		document->performActionOnLoad();
 | |
| 	}
 | |
| 
 | |
| 	auto &items = App::documentItems();
 | |
| 	auto i = items.constFind(document);
 | |
| 	if (i != items.cend()) {
 | |
| 		for_const (auto item, i.value()) {
 | |
| 			Ui::repaintHistoryItem(item);
 | |
| 		}
 | |
| 	}
 | |
| 	App::wnd()->documentUpdated(document);
 | |
| 
 | |
| 	if (!document->loaded() && document->song()) {
 | |
| 		Media::Player::instance()->documentLoadProgress(document);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::documentLoadFailed(FileLoader *loader, bool started) {
 | |
| 	auto documentId = loader ? loader->objId() : 0;
 | |
| 	if (!documentId) return;
 | |
| 
 | |
| 	auto document = App::document(documentId);
 | |
| 	if (started) {
 | |
| 		auto failedFileName = loader->fileName();
 | |
| 		Ui::show(Box<ConfirmBox>(lang(lng_download_finish_failed), base::lambda_guarded(this, [this, document, failedFileName] {
 | |
| 			Ui::hideLayer();
 | |
| 			if (document) document->save(failedFileName);
 | |
| 		})));
 | |
| 	} else {
 | |
| 		Ui::show(Box<ConfirmBox>(lang(lng_download_path_failed), lang(lng_download_path_settings), base::lambda_guarded(this, [this] {
 | |
| 			Global::SetDownloadPath(QString());
 | |
| 			Global::SetDownloadPathBookmark(QByteArray());
 | |
| 			Ui::show(Box<DownloadPathBox>());
 | |
| 			Global::RefDownloadPathChanged().notify();
 | |
| 		})));
 | |
| 	}
 | |
| 
 | |
| 	if (document) {
 | |
| 		if (document->loading()) document->cancel();
 | |
| 		document->status = FileDownloadFailed;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::inlineResultLoadProgress(FileLoader *loader) {
 | |
| 	//InlineBots::Result *result = InlineBots::resultFromLoader(loader);
 | |
| 	//if (!result) return;
 | |
| 
 | |
| 	//result->loaded();
 | |
| 
 | |
| 	//Ui::repaintInlineItem();
 | |
| }
 | |
| 
 | |
| void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) {
 | |
| 	//InlineBots::Result *result = InlineBots::resultFromLoader(loader);
 | |
| 	//if (!result) return;
 | |
| 
 | |
| 	//result->loaded();
 | |
| 
 | |
| 	//Ui::repaintInlineItem();
 | |
| }
 | |
| 
 | |
| void MainWidget::mediaMarkRead(DocumentData *data) {
 | |
| 	const DocumentItems &items(App::documentItems());
 | |
| 	DocumentItems::const_iterator i = items.constFind(data);
 | |
| 	if (i != items.cend()) {
 | |
| 		mediaMarkRead(i.value());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::mediaMarkRead(const HistoryItemsMap &items) {
 | |
| 	QVector<MTPint> markedIds;
 | |
| 	markedIds.reserve(items.size());
 | |
| 	for_const (auto item, items) {
 | |
| 		if (!item->out() && item->isMediaUnread()) {
 | |
| 			item->markMediaRead();
 | |
| 			if (item->id > 0) {
 | |
| 				markedIds.push_back(MTP_int(item->id));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (!markedIds.isEmpty()) {
 | |
| 		MTP::send(MTPmessages_ReadMessageContents(MTP_vector<MTPint>(markedIds)), rpcDone(&MainWidget::messagesAffected, (PeerData*)0));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateOnlineDisplay() {
 | |
| 	if (this != App::main()) return;
 | |
| 	_history->updateOnlineDisplay();
 | |
| }
 | |
| 
 | |
| void MainWidget::onSendFileConfirm(const FileLoadResultPtr &file) {
 | |
| 	_history->sendFileConfirmed(file);
 | |
| }
 | |
| 
 | |
| bool MainWidget::onSendSticker(DocumentData *document) {
 | |
| 	return _history->onStickerSend(document);
 | |
| }
 | |
| 
 | |
| void MainWidget::dialogsCancelled() {
 | |
| 	if (_hider) {
 | |
| 		_hider->startHide();
 | |
| 		noHider(_hider);
 | |
| 	}
 | |
| 	_history->activate();
 | |
| }
 | |
| 
 | |
| void MainWidget::serviceNotification(const TextWithEntities &message, const MTPMessageMedia &media, int32 date) {
 | |
| 	auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | MTPDmessage_ClientFlag::f_clientside_unread;
 | |
| 	QString sendingText, leftText = message.text;
 | |
| 	EntitiesInText sendingEntities, leftEntities = message.entities;
 | |
| 	HistoryItem *item = nullptr;
 | |
| 	while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | |
| 		MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities);
 | |
| 		item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(AuthSession::CurrentUserId())), MTPnullFwdHeader, MTPint(), MTPint(), MTP_int(date), MTP_string(sendingText), media, MTPnullMarkup, localEntities, MTPint(), MTPint()), NewMessageUnread);
 | |
| 	}
 | |
| 	if (item) {
 | |
| 		_history->peerMessagesUpdated(item->history()->peer->id);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::serviceHistoryDone(const MTPmessages_Messages &msgs) {
 | |
| 	switch (msgs.type()) {
 | |
| 	case mtpc_messages_messages: {
 | |
| 		auto &d(msgs.c_messages_messages());
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		App::feedMsgs(d.vmessages, NewMessageLast);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_messages_messagesSlice: {
 | |
| 		auto &d(msgs.c_messages_messagesSlice());
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		App::feedMsgs(d.vmessages, NewMessageLast);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_messages_channelMessages: {
 | |
| 		auto &d(msgs.c_messages_channelMessages());
 | |
| 		LOG(("API Error: received messages.channelMessages! (MainWidget::serviceHistoryDone)"));
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		App::feedMsgs(d.vmessages, NewMessageLast);
 | |
| 	} break;
 | |
| 	}
 | |
| 
 | |
| 	App::wnd()->showDelayedServiceMsgs();
 | |
| }
 | |
| 
 | |
| bool MainWidget::serviceHistoryFail(const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	App::wnd()->showDelayedServiceMsgs();
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| bool MainWidget::isIdle() const {
 | |
| 	return _isIdle;
 | |
| }
 | |
| 
 | |
| void MainWidget::clearCachedBackground() {
 | |
| 	_cachedBackground = QPixmap();
 | |
| 	_cacheBackgroundTimer.stop();
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| QPixmap MainWidget::cachedBackground(const QRect &forRect, int &x, int &y) {
 | |
| 	if (!_cachedBackground.isNull() && forRect == _cachedFor) {
 | |
| 		x = _cachedX;
 | |
| 		y = _cachedY;
 | |
| 		return _cachedBackground;
 | |
| 	}
 | |
| 	if (_willCacheFor != forRect || !_cacheBackgroundTimer.isActive()) {
 | |
| 		_willCacheFor = forRect;
 | |
| 		_cacheBackgroundTimer.start(CacheBackgroundTimeout);
 | |
| 	}
 | |
| 	return QPixmap();
 | |
| }
 | |
| 
 | |
| void MainWidget::updateScrollColors() {
 | |
| 	_history->updateScrollColors();
 | |
| }
 | |
| 
 | |
| void MainWidget::setChatBackground(const App::WallPaper &wp) {
 | |
| 	_background = std::make_unique<App::WallPaper>(wp);
 | |
| 	_background->full->loadEvenCancelled();
 | |
| 	checkChatBackground();
 | |
| }
 | |
| 
 | |
| bool MainWidget::chatBackgroundLoading() {
 | |
| 	return (_background != nullptr);
 | |
| }
 | |
| 
 | |
| float64 MainWidget::chatBackgroundProgress() const {
 | |
| 	if (_background) {
 | |
| 		return _background->full->progress();
 | |
| 	}
 | |
| 	return 1.;
 | |
| }
 | |
| 
 | |
| void MainWidget::checkChatBackground() {
 | |
| 	if (_background) {
 | |
| 		if (_background->full->loaded()) {
 | |
| 			if (_background->full->isNull()) {
 | |
| 				Window::Theme::Background()->setImage(Window::Theme::kDefaultBackground);
 | |
| 			} else if (false
 | |
| 				|| _background->id == Window::Theme::kInitialBackground
 | |
| 				|| _background->id == Window::Theme::kDefaultBackground) {
 | |
| 				Window::Theme::Background()->setImage(_background->id);
 | |
| 			} else {
 | |
| 				Window::Theme::Background()->setImage(_background->id, _background->full->pix().toImage());
 | |
| 			}
 | |
| 			_background = nullptr;
 | |
| 			QTimer::singleShot(0, this, SLOT(update()));
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ImagePtr MainWidget::newBackgroundThumb() {
 | |
| 	return _background ? _background->thumb : ImagePtr();
 | |
| }
 | |
| 
 | |
| void MainWidget::messageDataReceived(ChannelData *channel, MsgId msgId) {
 | |
| 	_history->messageDataReceived(channel, msgId);
 | |
| }
 | |
| 
 | |
| void MainWidget::updateBotKeyboard(History *h) {
 | |
| 	_history->updateBotKeyboard(h);
 | |
| }
 | |
| 
 | |
| void MainWidget::pushReplyReturn(HistoryItem *item) {
 | |
| 	_history->pushReplyReturn(item);
 | |
| }
 | |
| 
 | |
| void MainWidget::setInnerFocus() {
 | |
| 	if (_hider || !_history->peer()) {
 | |
| 		if (_hider && _hider->wasOffered()) {
 | |
| 			_hider->setFocus();
 | |
| 		} else if (!_hider && _overview) {
 | |
| 			_overview->activate();
 | |
| 		} else if (!_hider && _wideSection) {
 | |
| 			_wideSection->setInnerFocus();
 | |
| 		} else if (!_hider && _thirdSection) {
 | |
| 			_thirdSection->setInnerFocus();
 | |
| 		} else {
 | |
| 			dialogsActivate();
 | |
| 		}
 | |
| 	} else if (_overview) {
 | |
| 		_overview->activate();
 | |
| 	} else if (_wideSection) {
 | |
| 		_wideSection->setInnerFocus();
 | |
| 	} else if (_thirdSection) {
 | |
| 		_thirdSection->setInnerFocus();
 | |
| 	} else {
 | |
| 		_history->setInnerFocus();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::scheduleViewIncrement(HistoryItem *item) {
 | |
| 	PeerData *peer = item->history()->peer;
 | |
| 	ViewsIncrement::iterator i = _viewsIncremented.find(peer);
 | |
| 	if (i != _viewsIncremented.cend()) {
 | |
| 		if (i.value().contains(item->id)) return;
 | |
| 	} else {
 | |
| 		i = _viewsIncremented.insert(peer, ViewsIncrementMap());
 | |
| 	}
 | |
| 	i.value().insert(item->id, true);
 | |
| 	ViewsIncrement::iterator j = _viewsToIncrement.find(peer);
 | |
| 	if (j == _viewsToIncrement.cend()) {
 | |
| 		j = _viewsToIncrement.insert(peer, ViewsIncrementMap());
 | |
| 		_viewsIncrementTimer.start(SendViewsTimeout);
 | |
| 	}
 | |
| 	j.value().insert(item->id, true);
 | |
| }
 | |
| 
 | |
| void MainWidget::fillPeerMenu(PeerData *peer, base::lambda<QAction*(const QString &text, base::lambda<void()> handler)> callback, bool pinToggle) {
 | |
| 	if (pinToggle) {
 | |
| 		auto isPinned = false;
 | |
| 		if (auto history = App::historyLoaded(peer)) {
 | |
| 			isPinned = history->isPinnedDialog();
 | |
| 		}
 | |
| 		auto pinSubscription = MakeShared<base::Subscription>();
 | |
| 		auto pinAction = callback(lang(isPinned ? lng_context_unpin_from_top : lng_context_pin_to_top), [peer, pinSubscription] {
 | |
| 			auto history = App::history(peer);
 | |
| 			auto isPinned = !history->isPinnedDialog();
 | |
| 			if (isPinned && App::histories().pinnedCount() >= Global::PinnedDialogsCountMax()) {
 | |
| 				// Some old chat, that was converted to supergroup, maybe is still pinned.
 | |
| 				auto findWastedPin = []() -> History* {
 | |
| 					auto order = App::histories().getPinnedOrder();
 | |
| 					for_const (auto pinned, order) {
 | |
| 						if (pinned->peer->isChat()
 | |
| 							&& pinned->peer->asChat()->isDeactivated()
 | |
| 							&& !pinned->inChatList(Dialogs::Mode::All)) {
 | |
| 							return pinned;
 | |
| 						}
 | |
| 					}
 | |
| 					return nullptr;
 | |
| 				};
 | |
| 				if (auto wasted = findWastedPin()) {
 | |
| 					wasted->setPinnedDialog(false);
 | |
| 					history->setPinnedDialog(isPinned);
 | |
| 					App::histories().savePinnedToServer();
 | |
| 				} else {
 | |
| 					Ui::show(Box<InformBox>(lng_error_pinned_max(lt_count, Global::PinnedDialogsCountMax())));
 | |
| 				}
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			history->setPinnedDialog(isPinned);
 | |
| 			auto flags = MTPmessages_ToggleDialogPin::Flags(0);
 | |
| 			if (isPinned) {
 | |
| 				flags |= MTPmessages_ToggleDialogPin::Flag::f_pinned;
 | |
| 			}
 | |
| 			MTP::send(MTPmessages_ToggleDialogPin(MTP_flags(flags), peer->input));
 | |
| 			if (isPinned) {
 | |
| 				if (auto main = App::main()) {
 | |
| 					main->dialogsToUp();
 | |
| 				}
 | |
| 			}
 | |
| 		});
 | |
| 		auto pinChangedHandler = Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::PinnedChanged, [pinAction, peer](const Notify::PeerUpdate &update) {
 | |
| 			if (update.peer != peer) return;
 | |
| 			pinAction->setText(lang(App::history(peer)->isPinnedDialog() ? lng_context_unpin_from_top : lng_context_pin_to_top));
 | |
| 		});
 | |
| 		*pinSubscription = Notify::PeerUpdated().add_subscription(std::move(pinChangedHandler));
 | |
| 	}
 | |
| 	callback(lang((peer->isChat() || peer->isMegagroup()) ? lng_context_view_group : (peer->isUser() ? lng_context_view_profile : lng_context_view_channel)), [peer] {
 | |
| 		Ui::showPeerProfile(peer);
 | |
| 	});
 | |
| 	auto muteSubscription = MakeShared<base::Subscription>();
 | |
| 	auto muteAction = callback(lang(peer->isMuted() ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray), [peer, muteSubscription] {
 | |
| 		App::main()->updateNotifySetting(peer, peer->isMuted() ? NotifySettingSetNotify : NotifySettingSetMuted);
 | |
| 	});
 | |
| 	auto muteChangedHandler = Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::NotificationsEnabled, [muteAction, peer](const Notify::PeerUpdate &update) {
 | |
| 		if (update.peer != peer) return;
 | |
| 		muteAction->setText(lang(peer->isMuted() ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray));
 | |
| 	});
 | |
| 	*muteSubscription = Notify::PeerUpdated().add_subscription(std::move(muteChangedHandler));
 | |
| 
 | |
| 	callback(lang(lng_profile_search_messages), [peer] {
 | |
| 		App::main()->searchInPeer(peer);
 | |
| 	});
 | |
| 
 | |
| 	auto clearHistoryHandler = [peer] {
 | |
| 		auto text = peer->isUser() ? lng_sure_delete_history(lt_contact, peer->name) : lng_sure_delete_group_history(lt_group, peer->name);
 | |
| 		Ui::show(Box<ConfirmBox>(text, lang(lng_box_delete), st::attentionBoxButton, [peer] {
 | |
| 			if (!App::main()) return;
 | |
| 
 | |
| 			Ui::hideLayer();
 | |
| 			App::main()->clearHistory(peer);
 | |
| 		}));
 | |
| 	};
 | |
| 	auto deleteAndLeaveHandler = [peer] {
 | |
| 		auto warningText = peer->isUser() ? lng_sure_delete_history(lt_contact, peer->name) :
 | |
| 			peer->isChat() ? lng_sure_delete_and_exit(lt_group, peer->name) :
 | |
| 			lang(peer->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel);
 | |
| 		auto confirmText = lang(peer->isUser() ? lng_box_delete : lng_box_leave);
 | |
| 		auto &confirmStyle = peer->isChannel() ? st::defaultBoxButton : st::attentionBoxButton;
 | |
| 		Ui::show(Box<ConfirmBox>(warningText, confirmText, confirmStyle, [peer] {
 | |
| 			if (!App::main()) return;
 | |
| 
 | |
| 			Ui::hideLayer();
 | |
| 			Ui::showChatsList();
 | |
| 			if (peer->isUser()) {
 | |
| 				App::main()->deleteConversation(peer);
 | |
| 			} else if (peer->isChat()) {
 | |
| 				MTP::send(MTPmessages_DeleteChatUser(peer->asChat()->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, peer), App::main()->rpcFail(&MainWidget::leaveChatFailed, peer));
 | |
| 			} else if (peer->isChannel()) {
 | |
| 				if (peer->migrateFrom()) {
 | |
| 					App::main()->deleteConversation(peer->migrateFrom());
 | |
| 				}
 | |
| 				MTP::send(MTPchannels_LeaveChannel(peer->asChannel()->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived));
 | |
| 			}
 | |
| 		}));
 | |
| 	};
 | |
| 	if (auto user = peer->asUser()) {
 | |
| 		callback(lang(lng_profile_delete_conversation), std::move(deleteAndLeaveHandler));
 | |
| 		callback(lang(lng_profile_clear_history), std::move(clearHistoryHandler));
 | |
| 		if (!user->isInaccessible() && user != App::self()) {
 | |
| 			auto blockSubscription = MakeShared<base::Subscription>();
 | |
| 			auto blockAction = callback(lang(user->isBlocked() ? (user->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (user->botInfo ? lng_profile_block_bot : lng_profile_block_user)), [user, blockSubscription] {
 | |
| 				auto willBeBlocked = !user->isBlocked();
 | |
| 				auto handler = ::rpcDone([user, willBeBlocked](const MTPBool &result) {
 | |
| 					user->setBlockStatus(willBeBlocked ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
 | |
| 					emit App::main()->peerUpdated(user);
 | |
| 				});
 | |
| 				if (willBeBlocked) {
 | |
| 					MTP::send(MTPcontacts_Block(user->inputUser), std::move(handler));
 | |
| 				} else {
 | |
| 					MTP::send(MTPcontacts_Unblock(user->inputUser), std::move(handler));
 | |
| 				}
 | |
| 			});
 | |
| 			auto blockChangedHandler = Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [blockAction, peer](const Notify::PeerUpdate &update) {
 | |
| 				if (update.peer != peer) return;
 | |
| 				blockAction->setText(lang(peer->asUser()->isBlocked() ? (peer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (peer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)));
 | |
| 			});
 | |
| 			*blockSubscription = Notify::PeerUpdated().add_subscription(std::move(blockChangedHandler));
 | |
| 
 | |
| 			if (user->blockStatus() == UserData::BlockStatus::Unknown) {
 | |
| 				App::api()->requestFullPeer(user);
 | |
| 			}
 | |
| 		}
 | |
| 	} else if (peer->isChat()) {
 | |
| 		callback(lang(lng_profile_clear_and_exit), std::move(deleteAndLeaveHandler));
 | |
| 		callback(lang(lng_profile_clear_history), std::move(clearHistoryHandler));
 | |
| 	} else if (peer->isChannel() && peer->asChannel()->amIn() && !peer->asChannel()->amCreator()) {
 | |
| 		callback(lang(peer->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel), std::move(deleteAndLeaveHandler));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::onViewsIncrement() {
 | |
| 	for (ViewsIncrement::iterator i = _viewsToIncrement.begin(); i != _viewsToIncrement.cend();) {
 | |
| 		if (_viewsIncrementRequests.contains(i.key())) {
 | |
| 			++i;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		QVector<MTPint> ids;
 | |
| 		ids.reserve(i.value().size());
 | |
| 		for (ViewsIncrementMap::const_iterator j = i.value().cbegin(), end = i.value().cend(); j != end; ++j) {
 | |
| 			ids.push_back(MTP_int(j.key()));
 | |
| 		}
 | |
| 		mtpRequestId req = MTP::send(MTPmessages_GetMessagesViews(i.key()->input, MTP_vector<MTPint>(ids), MTP_bool(true)), rpcDone(&MainWidget::viewsIncrementDone, ids), rpcFail(&MainWidget::viewsIncrementFail), 0, 5);
 | |
| 		_viewsIncrementRequests.insert(i.key(), req);
 | |
| 		i = _viewsToIncrement.erase(i);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::viewsIncrementDone(QVector<MTPint> ids, const MTPVector<MTPint> &result, mtpRequestId req) {
 | |
| 	auto &v = result.v;
 | |
| 	if (ids.size() == v.size()) {
 | |
| 		for (ViewsIncrementRequests::iterator i = _viewsIncrementRequests.begin(); i != _viewsIncrementRequests.cend(); ++i) {
 | |
| 			if (i.value() == req) {
 | |
| 				PeerData *peer = i.key();
 | |
| 				ChannelId channel = peerToChannel(peer->id);
 | |
| 				for (int32 j = 0, l = ids.size(); j < l; ++j) {
 | |
| 					if (HistoryItem *item = App::histItemById(channel, ids.at(j).v)) {
 | |
| 						item->setViewsCount(v.at(j).v);
 | |
| 					}
 | |
| 				}
 | |
| 				_viewsIncrementRequests.erase(i);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (!_viewsToIncrement.isEmpty() && !_viewsIncrementTimer.isActive()) {
 | |
| 		_viewsIncrementTimer.start(SendViewsTimeout);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::viewsIncrementFail(const RPCError &error, mtpRequestId req) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	for (ViewsIncrementRequests::iterator i = _viewsIncrementRequests.begin(); i != _viewsIncrementRequests.cend(); ++i) {
 | |
| 		if (i.value() == req) {
 | |
| 			_viewsIncrementRequests.erase(i);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (!_viewsToIncrement.isEmpty() && !_viewsIncrementTimer.isActive()) {
 | |
| 		_viewsIncrementTimer.start(SendViewsTimeout);
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void MainWidget::createDialog(History *history) {
 | |
| 	_dialogs->createDialog(history);
 | |
| }
 | |
| 
 | |
| void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) {
 | |
| 	if (selectingPeer()) {
 | |
| 		offerPeer(peerId);
 | |
| 	} else {
 | |
| 		Ui::showPeerHistory(peerId, showAtMsgId);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::clearBotStartToken(PeerData *peer) {
 | |
| 	if (peer && peer->isUser() && peer->asUser()->botInfo) {
 | |
| 		peer->asUser()->botInfo->startToken = QString();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateAfterDrag() {
 | |
| 	if (_overview) {
 | |
| 		_overview->updateAfterDrag();
 | |
| 	} else {
 | |
| 		_history->updateAfterDrag();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::ctrlEnterSubmitUpdated() {
 | |
| 	_history->updateFieldSubmitSettings();
 | |
| }
 | |
| 
 | |
| void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) {
 | |
| 	if (auto peer = App::peerLoaded(peerId)) {
 | |
| 		if (peer->migrateTo()) {
 | |
| 			peer = peer->migrateTo();
 | |
| 			peerId = peer->id;
 | |
| 			if (showAtMsgId > 0) showAtMsgId = -showAtMsgId;
 | |
| 		}
 | |
| 		QString restriction = peer->restrictionReason();
 | |
| 		if (!restriction.isEmpty()) {
 | |
| 			Ui::showChatsList();
 | |
| 			Ui::show(Box<InformBox>(restriction));
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_controller->dialogsListFocused().set(false, true);
 | |
| 	_a_dialogsWidth.finish();
 | |
| 
 | |
| 	bool back = (way == Ui::ShowWay::Backward || !peerId);
 | |
| 	bool foundInStack = !peerId;
 | |
| 	if (foundInStack || (way == Ui::ShowWay::ClearStack)) {
 | |
| 		for_const (auto &item, _stack) {
 | |
| 			clearBotStartToken(item->peer);
 | |
| 		}
 | |
| 		_stack.clear();
 | |
| 	} else {
 | |
| 		for (auto i = 0, s = int(_stack.size()); i < s; ++i) {
 | |
| 			if (_stack.at(i)->type() == HistoryStackItem && _stack.at(i)->peer->id == peerId) {
 | |
| 				foundInStack = true;
 | |
| 				while (int(_stack.size()) > i + 1) {
 | |
| 					clearBotStartToken(_stack.back()->peer);
 | |
| 					_stack.pop_back();
 | |
| 				}
 | |
| 				_stack.pop_back();
 | |
| 				if (!back) {
 | |
| 					back = true;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (auto historyPeer = _history->peer()) {
 | |
| 			if (way == Ui::ShowWay::Forward && historyPeer->id == peerId) {
 | |
| 				way = Ui::ShowWay::ClearStack;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	dlgUpdated();
 | |
| 	if (back || (way == Ui::ShowWay::ClearStack)) {
 | |
| 		_peerInStack = nullptr;
 | |
| 		_msgIdInStack = 0;
 | |
| 	} else {
 | |
| 		saveSectionInStack();
 | |
| 	}
 | |
| 	dlgUpdated();
 | |
| 
 | |
| 	auto wasActivePeer = activePeer();
 | |
| 
 | |
| 	Ui::hideSettingsAndLayer();
 | |
| 	if (_hider) {
 | |
| 		_hider->startHide();
 | |
| 		_hider = nullptr;
 | |
| 	}
 | |
| 
 | |
| 	auto animatedShow = [this, peerId, back, way] {
 | |
| 		if (_a_show.animating() || App::passcoded()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		if (!peerId) {
 | |
| 			if (Adaptive::OneColumn()) {
 | |
| 				return true;
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		if (back || way == Ui::ShowWay::Forward) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		if (_history->isHidden() && (_wideSection || _overview || Adaptive::OneColumn())) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		return false;
 | |
| 	};
 | |
| 
 | |
| 	// Qt bug workaround: QWidget::render() for an arbitrary widget calls
 | |
| 	// sendPendingMoveAndResizeEvents(true, true) for the whole window,
 | |
| 	// which does something like:
 | |
| 	//
 | |
| 	// setAttribute(Qt::WA_UpdatesDisabled);
 | |
| 	// sendEvent(QResizeEvent);
 | |
| 	// setAttribute(Qt::WA_UpdatesDisabled, false);
 | |
| 	//
 | |
| 	// So if we create TabbedSection widget in HistoryWidget::resizeEvent()
 | |
| 	// it will get an enabled Qt::WA_UpdatesDisabled from its parent and it
 | |
| 	// will never be rendered, because no one will ever remove that attribute.
 | |
| 	//
 | |
| 	// So we force HistoryWidget::resizeEvent() here, without WA_UpdatesDisabled.
 | |
| 	myEnsureResized(_history);
 | |
| 
 | |
| 	auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams();
 | |
| 
 | |
| 	if (_history->peer() && _history->peer()->id != peerId && way != Ui::ShowWay::Forward) {
 | |
| 		clearBotStartToken(_history->peer());
 | |
| 	}
 | |
| 	_history->showHistory(peerId, showAtMsgId);
 | |
| 
 | |
| 	auto noPeer = !_history->peer();
 | |
| 	auto onlyDialogs = noPeer && Adaptive::OneColumn();
 | |
| 	if (_wideSection || _overview) {
 | |
| 		if (_wideSection) {
 | |
| 			_wideSection->hide();
 | |
| 			_wideSection->deleteLater();
 | |
| 			_wideSection = nullptr;
 | |
| 		}
 | |
| 		if (_overview) {
 | |
| 			_overview->hide();
 | |
| 			_overview->clear();
 | |
| 			_overview->deleteLater();
 | |
| 			_overview->rpcClear();
 | |
| 			_overview = nullptr;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	updateControlsGeometry();
 | |
| 	if (onlyDialogs) {
 | |
| 		_history->hide();
 | |
| 		if (!_a_show.animating()) {
 | |
| 			if (animationParams) {
 | |
| 				auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight;
 | |
| 				_dialogs->showAnimated(direction, animationParams);
 | |
| 			} else {
 | |
| 				_dialogs->showFast();
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (!noPeer && wasActivePeer != activePeer()) {
 | |
| 			if (activePeer()->isChannel()) {
 | |
| 				activePeer()->asChannel()->ptsWaitingForShortPoll(WaitForChannelGetDifference);
 | |
| 			}
 | |
| 			_viewsIncremented.remove(activePeer());
 | |
| 		}
 | |
| 		if (Adaptive::OneColumn() && !_dialogs->isHidden()) _dialogs->hide();
 | |
| 		if (!_a_show.animating()) {
 | |
| 			if (!animationParams.oldContentCache.isNull()) {
 | |
| 				_history->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams);
 | |
| 			} else {
 | |
| 				_history->show();
 | |
| 				if (App::wnd()) {
 | |
| 					QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	//if (wasActivePeer && wasActivePeer->isChannel() && activePeer() != wasActivePeer) {
 | |
| 	//	wasActivePeer->asChannel()->ptsWaitingForShortPoll(false);
 | |
| 	//}
 | |
| 
 | |
| 	if (!_dialogs->isHidden()) {
 | |
| 		if (!back) {
 | |
| 			_dialogs->scrollToPeer(peerId, showAtMsgId);
 | |
| 		}
 | |
| 		_dialogs->update();
 | |
| 	}
 | |
| 
 | |
| 	checkFloatPlayerVisibility();
 | |
| }
 | |
| 
 | |
| PeerData *MainWidget::ui_getPeerForMouseAction() {
 | |
| 	return _history->ui_getPeerForMouseAction();
 | |
| }
 | |
| 
 | |
| void MainWidget::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) {
 | |
| 	if (selectingPeer()) {
 | |
| 		outPeer = 0;
 | |
| 		outMsg = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 	_dialogs->peerBefore(inPeer, inMsg, outPeer, outMsg);
 | |
| }
 | |
| 
 | |
| void MainWidget::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) {
 | |
| 	if (selectingPeer()) {
 | |
| 		outPeer = 0;
 | |
| 		outMsg = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 	_dialogs->peerAfter(inPeer, inMsg, outPeer, outMsg);
 | |
| }
 | |
| 
 | |
| PeerData *MainWidget::historyPeer() {
 | |
| 	return _history->peer();
 | |
| }
 | |
| 
 | |
| PeerData *MainWidget::peer() {
 | |
| 	return _overview ? _overview->peer() : _history->peer();
 | |
| }
 | |
| 
 | |
| PeerData *MainWidget::activePeer() {
 | |
| 	return _history->peer() ? _history->peer() : _peerInStack;
 | |
| }
 | |
| 
 | |
| MsgId MainWidget::activeMsgId() {
 | |
| 	return _history->peer() ? _history->msgId() : _msgIdInStack;
 | |
| }
 | |
| 
 | |
| PeerData *MainWidget::overviewPeer() {
 | |
| 	return _overview ? _overview->peer() : 0;
 | |
| }
 | |
| 
 | |
| bool MainWidget::showMediaTypeSwitch() const {
 | |
| 	return _overview ? _overview->showMediaTypeSwitch() : false;
 | |
| }
 | |
| 
 | |
| void MainWidget::saveSectionInStack() {
 | |
| 	if (_overview) {
 | |
| 		_stack.push_back(std::make_unique<StackItemOverview>(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop()));
 | |
| 	} else if (_wideSection) {
 | |
| 		_stack.push_back(std::make_unique<StackItemSection>(_wideSection->createMemento()));
 | |
| 	} else if (_history->peer()) {
 | |
| 		_peerInStack = _history->peer();
 | |
| 		_msgIdInStack = _history->msgId();
 | |
| 		_stack.push_back(std::make_unique<StackItemHistory>(_peerInStack, _msgIdInStack, _history->replyReturns()));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool back, int32 lastScrollTop) {
 | |
| 	if (peer->migrateTo()) {
 | |
| 		peer = peer->migrateTo();
 | |
| 	}
 | |
| 
 | |
| 	Ui::hideSettingsAndLayer();
 | |
| 	if (_overview && _overview->peer() == peer) {
 | |
| 		if (_overview->type() != type) {
 | |
| 			_overview->switchType(type);
 | |
| 		} else if (type == OverviewMusicFiles) { // hack for player
 | |
| 			showBackFromStack();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	_controller->dialogsListFocused().set(false, true);
 | |
| 	_a_dialogsWidth.finish();
 | |
| 
 | |
| 	auto animatedShow = [this] {
 | |
| 		if (_a_show.animating() || App::passcoded()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		if (Adaptive::OneColumn() || isSectionShown()) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		return false;
 | |
| 	};
 | |
| 	auto animationParams = animatedShow() ? prepareOverviewAnimation() : Window::SectionSlideParams();
 | |
| 	if (!back) {
 | |
| 		saveSectionInStack();
 | |
| 	}
 | |
| 
 | |
| 	setFocus(); // otherwise dialogs widget could be focused.
 | |
| 	if (_overview) {
 | |
| 		_overview->hide();
 | |
| 		_overview->clear();
 | |
| 		_overview->deleteLater();
 | |
| 		_overview->rpcClear();
 | |
| 	}
 | |
| 	if (_wideSection) {
 | |
| 		_wideSection->hide();
 | |
| 		_wideSection->deleteLater();
 | |
| 		_wideSection = nullptr;
 | |
| 	}
 | |
| 	_overview.create(this, _controller, peer, type);
 | |
| 	updateControlsGeometry();
 | |
| 
 | |
| 	// Send a fake update.
 | |
| 	Notify::PeerUpdate update(peer);
 | |
| 	update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged;
 | |
| 	update.mediaTypesMask |= (1 << type);
 | |
| 	mediaOverviewUpdated(update);
 | |
| 
 | |
| 	_overview->setLastScrollTop(lastScrollTop);
 | |
| 	if (!animationParams.oldContentCache.isNull()) {
 | |
| 		_overview->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams);
 | |
| 	} else {
 | |
| 		_overview->fastShow();
 | |
| 	}
 | |
| 	_history->finishAnimation();
 | |
| 	if (back) {
 | |
| 		clearBotStartToken(_history->peer());
 | |
| 	}
 | |
| 	_history->showHistory(0, 0);
 | |
| 	_history->hide();
 | |
| 	if (Adaptive::OneColumn()) _dialogs->hide();
 | |
| 
 | |
| 	checkFloatPlayerVisibility();
 | |
| 	orderWidgets();
 | |
| }
 | |
| 
 | |
| void MainWidget::showWideSection(const Window::SectionMemento &memento) {
 | |
| 	Ui::hideSettingsAndLayer();
 | |
| 	if (_wideSection && _wideSection->showInternal(&memento)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	showNewWideSection(&memento, false, true);
 | |
| }
 | |
| 
 | |
| Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarShadow, bool willHaveTabbedSection) {
 | |
| 	Window::SectionSlideParams result;
 | |
| 	result.withTopBarShadow = willHaveTopBarShadow;
 | |
| 	result.withTabbedSection = willHaveTabbedSection;
 | |
| 	if (selectingPeer() && Adaptive::OneColumn()) {
 | |
| 		result.withTopBarShadow = false;
 | |
| 	} else if (_wideSection) {
 | |
| 		if (!_wideSection->hasTopBarShadow()) {
 | |
| 			result.withTopBarShadow = false;
 | |
| 		}
 | |
| 	} else if (!_overview && !_history->peer()) {
 | |
| 		result.withTopBarShadow = false;
 | |
| 	}
 | |
| 	if ((selectingPeer() && Adaptive::OneColumn()) || !_history->peer()) {
 | |
| 		result.withTabbedSection = false;
 | |
| 	}
 | |
| 
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		instance->widget->hide();
 | |
| 	}
 | |
| 	if (_player) {
 | |
| 		_player->hideShadow();
 | |
| 	}
 | |
| 	auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden();
 | |
| 	if (playerVolumeVisible) {
 | |
| 		_playerVolume->hide();
 | |
| 	}
 | |
| 	auto playerPanelVisible = !_playerPanel->isHidden();
 | |
| 	if (playerPanelVisible) {
 | |
| 		_playerPanel->hide();
 | |
| 	}
 | |
| 	auto playerPlaylistVisible = !_playerPlaylist->isHidden();
 | |
| 	if (playerPlaylistVisible) {
 | |
| 		_playerPlaylist->hide();
 | |
| 	}
 | |
| 
 | |
| 	auto sectionTop = getSectionTop();
 | |
| 	if (selectingPeer() && Adaptive::OneColumn()) {
 | |
| 		result.oldContentCache = myGrab(this, QRect(0, sectionTop, _dialogsWidth, height() - sectionTop));
 | |
| 	} else if (_wideSection) {
 | |
| 		result.oldContentCache = _wideSection->grabForShowAnimation(result);
 | |
| 	} else {
 | |
| 		if (result.withTopBarShadow) {
 | |
| 			if (_overview) _overview->grapWithoutTopBarShadow();
 | |
| 			_history->grapWithoutTopBarShadow();
 | |
| 		} else {
 | |
| 			if (_overview) _overview->grabStart();
 | |
| 			_history->grabStart();
 | |
| 		}
 | |
| 		if (Adaptive::OneColumn()) {
 | |
| 			result.oldContentCache = myGrab(this, QRect(0, sectionTop, _dialogsWidth, height() - sectionTop));
 | |
| 		} else {
 | |
| 			_sideShadow->hide();
 | |
| 			result.oldContentCache = myGrab(this, QRect(_dialogsWidth, sectionTop, width() - _dialogsWidth, height() - sectionTop));
 | |
| 			_sideShadow->show();
 | |
| 		}
 | |
| 		if (_overview) _overview->grabFinish();
 | |
| 		_history->grabFinish();
 | |
| 	}
 | |
| 
 | |
| 	if (playerVolumeVisible) {
 | |
| 		_playerVolume->show();
 | |
| 	}
 | |
| 	if (playerPanelVisible) {
 | |
| 		_playerPanel->show();
 | |
| 	}
 | |
| 	if (playerPlaylistVisible) {
 | |
| 		_playerPlaylist->show();
 | |
| 	}
 | |
| 	if (_player) {
 | |
| 		_player->showShadow();
 | |
| 	}
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		if (instance->visible) {
 | |
| 			instance->widget->show();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| Window::SectionSlideParams MainWidget::prepareWideSectionAnimation(Window::SectionWidget *section) {
 | |
| 	return prepareShowAnimation(section->hasTopBarShadow(), false);
 | |
| }
 | |
| 
 | |
| Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) {
 | |
| 	return prepareShowAnimation(historyPeerId != 0, historyPeerId != 0);
 | |
| }
 | |
| 
 | |
| Window::SectionSlideParams MainWidget::prepareOverviewAnimation() {
 | |
| 	return prepareShowAnimation(true, false);
 | |
| }
 | |
| 
 | |
| Window::SectionSlideParams MainWidget::prepareDialogsAnimation() {
 | |
| 	return prepareShowAnimation(false, false);
 | |
| }
 | |
| 
 | |
| void MainWidget::showNewWideSection(const Window::SectionMemento *memento, bool back, bool saveInStack) {
 | |
| 	QPixmap animCache;
 | |
| 
 | |
| 	_controller->dialogsListFocused().set(false, true);
 | |
| 	_a_dialogsWidth.finish();
 | |
| 
 | |
| 	auto sectionTop = getSectionTop();
 | |
| 	auto newWideGeometry = QRect(_history->x(), sectionTop, _history->width(), height() - sectionTop);
 | |
| 	auto newWideSection = memento->createWidget(this, _controller, newWideGeometry);
 | |
| 	auto animatedShow = [this] {
 | |
| 		if (_a_show.animating() || App::passcoded()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		if (Adaptive::OneColumn() || isSectionShown()) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		return false;
 | |
| 	};
 | |
| 	auto animationParams = animatedShow() ? prepareWideSectionAnimation(newWideSection) : Window::SectionSlideParams();
 | |
| 
 | |
| 	if (saveInStack) {
 | |
| 		saveSectionInStack();
 | |
| 	}
 | |
| 
 | |
| 	setFocus(); // otherwise dialogs widget could be focused.
 | |
| 	if (_overview) {
 | |
| 		_overview->hide();
 | |
| 		_overview->clear();
 | |
| 		_overview->deleteLater();
 | |
| 		_overview->rpcClear();
 | |
| 		_overview = nullptr;
 | |
| 	}
 | |
| 	if (_wideSection) {
 | |
| 		_wideSection->hide();
 | |
| 		_wideSection->deleteLater();
 | |
| 		_wideSection = nullptr;
 | |
| 	}
 | |
| 	_wideSection = std::move(newWideSection);
 | |
| 	updateControlsGeometry();
 | |
| 	_history->finishAnimation();
 | |
| 	_history->showHistory(0, 0);
 | |
| 	_history->hide();
 | |
| 	if (Adaptive::OneColumn()) _dialogs->hide();
 | |
| 
 | |
| 	if (animationParams) {
 | |
| 		auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight;
 | |
| 		_wideSection->showAnimated(direction, animationParams);
 | |
| 	} else {
 | |
| 		_wideSection->showFast();
 | |
| 	}
 | |
| 
 | |
| 	checkFloatPlayerVisibility();
 | |
| 	orderWidgets();
 | |
| }
 | |
| 
 | |
| bool MainWidget::isSectionShown() const {
 | |
| 	return _wideSection || _overview || _history->peer();
 | |
| }
 | |
| 
 | |
| bool MainWidget::stackIsEmpty() const {
 | |
| 	return _stack.empty();
 | |
| }
 | |
| 
 | |
| void MainWidget::showBackFromStack() {
 | |
| 	if (selectingPeer()) return;
 | |
| 	if (_stack.empty()) {
 | |
| 		Ui::showChatsList();
 | |
| 		if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
 | |
| 		return;
 | |
| 	}
 | |
| 	auto item = std::move(_stack.back());
 | |
| 	_stack.pop_back();
 | |
| 	if (auto currentHistoryPeer = _history->peer()) {
 | |
| 		clearBotStartToken(currentHistoryPeer);
 | |
| 	}
 | |
| 	if (item->type() == HistoryStackItem) {
 | |
| 		dlgUpdated();
 | |
| 		_peerInStack = nullptr;
 | |
| 		_msgIdInStack = 0;
 | |
| 		for (auto i = _stack.size(); i > 0;) {
 | |
| 			if (_stack[--i]->type() == HistoryStackItem) {
 | |
| 				auto historyItem = static_cast<StackItemHistory*>(_stack[i].get());
 | |
| 				_peerInStack = historyItem->peer;
 | |
| 				_msgIdInStack = historyItem->msgId;
 | |
| 				dlgUpdated();
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		auto historyItem = static_cast<StackItemHistory*>(item.get());
 | |
| 		Ui::showPeerHistory(historyItem->peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Backward);
 | |
| 		_history->setReplyReturns(historyItem->peer->id, historyItem->replyReturns);
 | |
| 	} else if (item->type() == SectionStackItem) {
 | |
| 		auto sectionItem = static_cast<StackItemSection*>(item.get());
 | |
| 		showNewWideSection(sectionItem->memento(), true, false);
 | |
| 	} else if (item->type() == OverviewStackItem) {
 | |
| 		auto overviewItem = static_cast<StackItemOverview*>(item.get());
 | |
| 		showMediaOverview(overviewItem->peer, overviewItem->mediaType, true, overviewItem->lastScrollTop);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::orderWidgets() {
 | |
| 	_dialogs->raise();
 | |
| 	if (_callTopBar) {
 | |
| 		_callTopBar->raise();
 | |
| 	}
 | |
| 	if (_player) {
 | |
| 		_player->raise();
 | |
| 	}
 | |
| 	if (_playerVolume) {
 | |
| 		_playerVolume->raise();
 | |
| 	}
 | |
| 	_sideShadow->raise();
 | |
| 	_sideResizeArea->raise();
 | |
| 	_playerPlaylist->raise();
 | |
| 	_playerPanel->raise();
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		instance->widget->raise();
 | |
| 	}
 | |
| 	if (_hider) _hider->raise();
 | |
| }
 | |
| 
 | |
| QRect MainWidget::historyRect() const {
 | |
| 	QRect r(_history->historyRect());
 | |
| 	r.moveLeft(r.left() + _history->x());
 | |
| 	r.moveTop(r.top() + _history->y());
 | |
| 	return r;
 | |
| }
 | |
| 
 | |
| QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) {
 | |
| 	QPixmap result;
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		instance->widget->hide();
 | |
| 	}
 | |
| 	if (_player) {
 | |
| 		_player->hideShadow();
 | |
| 	}
 | |
| 	auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden();
 | |
| 	if (playerVolumeVisible) {
 | |
| 		_playerVolume->hide();
 | |
| 	}
 | |
| 	auto playerPanelVisible = !_playerPanel->isHidden();
 | |
| 	if (playerPanelVisible) {
 | |
| 		_playerPanel->hide();
 | |
| 	}
 | |
| 	auto playerPlaylistVisible = !_playerPlaylist->isHidden();
 | |
| 	if (playerPlaylistVisible) {
 | |
| 		_playerPlaylist->hide();
 | |
| 	}
 | |
| 
 | |
| 	auto sectionTop = getSectionTop();
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		result = myGrab(this, QRect(0, sectionTop, _dialogsWidth, height() - sectionTop));
 | |
| 	} else {
 | |
| 		_sideShadow->hide();
 | |
| 		result = myGrab(this, QRect(_dialogsWidth, sectionTop, width() - _dialogsWidth, height() - sectionTop));
 | |
| 		_sideShadow->show();
 | |
| 	}
 | |
| 	if (playerVolumeVisible) {
 | |
| 		_playerVolume->show();
 | |
| 	}
 | |
| 	if (playerPanelVisible) {
 | |
| 		_playerPanel->show();
 | |
| 	}
 | |
| 	if (playerPlaylistVisible) {
 | |
| 		_playerPlaylist->show();
 | |
| 	}
 | |
| 	if (_player) {
 | |
| 		_player->showShadow();
 | |
| 	}
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		if (instance->visible) {
 | |
| 			instance->widget->show();
 | |
| 		}
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void MainWidget::dlgUpdated() {
 | |
| 	if (_peerInStack) {
 | |
| 		_dialogs->dlgUpdated(_peerInStack, _msgIdInStack);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) {
 | |
| 	if (row) {
 | |
| 		_dialogs->dlgUpdated(list, row);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::dlgUpdated(PeerData *peer, MsgId msgId) {
 | |
| 	if (!peer) return;
 | |
| 	if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) {
 | |
| 		_dialogs->dlgUpdated(peer->migrateFrom(), -msgId);
 | |
| 	} else {
 | |
| 		_dialogs->dlgUpdated(peer, msgId);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::showJumpToDate(PeerData *peer, QDate requestedDate) {
 | |
| 	Expects(peer != nullptr);
 | |
| 	auto currentPeerDate = [peer] {
 | |
| 		if (auto history = App::historyLoaded(peer)) {
 | |
| 			if (history->scrollTopItem) {
 | |
| 				return history->scrollTopItem->date.date();
 | |
| 			} else if (history->loadedAtTop() && !history->isEmpty() && history->peer->migrateFrom()) {
 | |
| 				if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) {
 | |
| 					if (migrated->scrollTopItem) {
 | |
| 						// We're up in the migrated history.
 | |
| 						// So current date is the date of first message here.
 | |
| 						return history->blocks.front()->items.front()->date.date();
 | |
| 					}
 | |
| 				}
 | |
| 			} else if (!history->lastMsgDate.isNull()) {
 | |
| 				return history->lastMsgDate.date();
 | |
| 			}
 | |
| 		}
 | |
| 		return QDate::currentDate();
 | |
| 	};
 | |
| 	auto maxPeerDate = [peer] {
 | |
| 		if (auto history = App::historyLoaded(peer)) {
 | |
| 			if (!history->lastMsgDate.isNull()) {
 | |
| 				return history->lastMsgDate.date();
 | |
| 			}
 | |
| 		}
 | |
| 		return QDate::currentDate();
 | |
| 	};
 | |
| 	auto minPeerDate = [peer] {
 | |
| 		if (auto history = App::historyLoaded(peer)) {
 | |
| 			if (history->loadedAtTop()) {
 | |
| 				if (history->isEmpty()) {
 | |
| 					return QDate::currentDate();
 | |
| 				}
 | |
| 				return history->blocks.front()->items.front()->date.date();
 | |
| 			}
 | |
| 		}
 | |
| 		return QDate(2013, 8, 1); // Telegram was launched in August 2013 :)
 | |
| 	};
 | |
| 	auto highlighted = requestedDate.isNull() ? currentPeerDate() : requestedDate;
 | |
| 	auto month = highlighted;
 | |
| 	auto box = Box<CalendarBox>(month, highlighted, [this, peer](const QDate &date) { jumpToDate(peer, date); });
 | |
| 	box->setMinDate(minPeerDate());
 | |
| 	box->setMaxDate(maxPeerDate());
 | |
| 	Ui::show(std::move(box));
 | |
| }
 | |
| 
 | |
| void MainWidget::jumpToDate(PeerData *peer, const QDate &date) {
 | |
| 	// API returns a message with date <= offset_date.
 | |
| 	// So we request a message with offset_date = desired_date - 1 and add_offset = -1.
 | |
| 	// This should give us the first message with date >= desired_date.
 | |
| 	auto offset_date = static_cast<int>(QDateTime(date).toTime_t()) - 1;
 | |
| 	auto add_offset = -1;
 | |
| 	auto limit = 1;
 | |
| 	auto flags = MTPmessages_Search::Flags(0);
 | |
| 	auto request = MTPmessages_GetHistory(peer->input, MTP_int(0), MTP_int(offset_date), MTP_int(add_offset), MTP_int(limit), MTP_int(0), MTP_int(0));
 | |
| 	MTP::send(request, ::rpcDone([peer](const MTPmessages_Messages &result) {
 | |
| 		auto getMessagesList = [&result, peer]() -> const QVector<MTPMessage>* {
 | |
| 			auto handleMessages = [](auto &messages) {
 | |
| 				App::feedUsers(messages.vusers);
 | |
| 				App::feedChats(messages.vchats);
 | |
| 				return &messages.vmessages.v;
 | |
| 			};
 | |
| 			switch (result.type()) {
 | |
| 			case mtpc_messages_messages: return handleMessages(result.c_messages_messages());
 | |
| 			case mtpc_messages_messagesSlice: return handleMessages(result.c_messages_messagesSlice());
 | |
| 			case mtpc_messages_channelMessages: {
 | |
| 				auto &messages = result.c_messages_channelMessages();
 | |
| 				if (peer && peer->isChannel()) {
 | |
| 					peer->asChannel()->ptsReceived(messages.vpts.v);
 | |
| 				} else {
 | |
| 					LOG(("API Error: received messages.channelMessages when no channel was passed! (MainWidget::showJumpToDate)"));
 | |
| 				}
 | |
| 				return handleMessages(messages);
 | |
| 			} break;
 | |
| 			}
 | |
| 			return nullptr;
 | |
| 		};
 | |
| 
 | |
| 		if (auto list = getMessagesList()) {
 | |
| 			App::feedMsgs(*list, NewMessageExisting);
 | |
| 			for (auto &message : *list) {
 | |
| 				auto id = idFromMessage(message);
 | |
| 				Ui::showPeerHistory(peer, id);
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 		Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
 | |
| 	}));
 | |
| }
 | |
| 
 | |
| void MainWidget::windowShown() {
 | |
| 	_history->windowShown();
 | |
| }
 | |
| 
 | |
| void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result) {
 | |
| 	feedUpdates(result, randomId);
 | |
| }
 | |
| 
 | |
| bool MainWidget::deleteChannelFailed(const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	//if (error.type() == qstr("CHANNEL_TOO_LARGE")) {
 | |
| 	//	Ui::show(Box<InformBox>(lang(lng_cant_delete_channel)));
 | |
| 	//}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::inviteToChannelDone(ChannelData *channel, const MTPUpdates &updates) {
 | |
| 	sentUpdatesReceived(updates);
 | |
| 	App::api()->requestParticipantsCountDelayed(channel);
 | |
| }
 | |
| 
 | |
| void MainWidget::historyToDown(History *history) {
 | |
| 	_history->historyToDown(history);
 | |
| }
 | |
| 
 | |
| void MainWidget::dialogsToUp() {
 | |
| 	_dialogs->dialogsToUp();
 | |
| }
 | |
| 
 | |
| void MainWidget::newUnreadMsg(History *history, HistoryItem *item) {
 | |
| 	_history->newUnreadMsg(history, item);
 | |
| }
 | |
| 
 | |
| void MainWidget::markActiveHistoryAsRead() {
 | |
| 	_history->historyWasRead(ReadServerHistoryChecks::OnlyIfUnread);
 | |
| }
 | |
| 
 | |
| void MainWidget::historyCleared(History *history) {
 | |
| 	_history->historyCleared(history);
 | |
| }
 | |
| 
 | |
| void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) {
 | |
| 	_showBack = back;
 | |
| 	(_showBack ? _cacheOver : _cacheUnder) = bgAnimCache;
 | |
| 
 | |
| 	_a_show.finish();
 | |
| 
 | |
| 	showAll();
 | |
| 	(_showBack ? _cacheUnder : _cacheOver) = myGrab(this);
 | |
| 	hideAll();
 | |
| 
 | |
| 	_a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition());
 | |
| 
 | |
| 	show();
 | |
| }
 | |
| 
 | |
| void MainWidget::animationCallback() {
 | |
| 	update();
 | |
| 	if (!_a_show.animating()) {
 | |
| 		_cacheUnder = _cacheOver = QPixmap();
 | |
| 
 | |
| 		showAll();
 | |
| 		activate();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::paintEvent(QPaintEvent *e) {
 | |
| 	if (_background) checkChatBackground();
 | |
| 
 | |
| 	Painter p(this);
 | |
| 	auto progress = _a_show.current(getms(), 1.);
 | |
| 	if (_a_show.animating()) {
 | |
| 		auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress);
 | |
| 		auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress);
 | |
| 		auto shadow = _showBack ? (1. - progress) : progress;
 | |
| 		if (coordOver > 0) {
 | |
| 			p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor()));
 | |
| 			p.setOpacity(shadow);
 | |
| 			p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg);
 | |
| 			p.setOpacity(1);
 | |
| 		}
 | |
| 		p.drawPixmap(coordOver, 0, _cacheOver);
 | |
| 		p.setOpacity(shadow);
 | |
| 		st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height()));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int MainWidget::getSectionTop() const {
 | |
| 	return _callTopBarHeight + _playerHeight;
 | |
| }
 | |
| 
 | |
| void MainWidget::hideAll() {
 | |
| 	_dialogs->hide();
 | |
| 	_history->hide();
 | |
| 	if (_wideSection) {
 | |
| 		_wideSection->hide();
 | |
| 	}
 | |
| 	if (_overview) {
 | |
| 		_overview->hide();
 | |
| 	}
 | |
| 	_sideShadow->hide();
 | |
| 	if (_player) {
 | |
| 		_player->hide();
 | |
| 		_playerHeight = 0;
 | |
| 	}
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		instance->widget->hide();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::showAll() {
 | |
| 	if (cPasswordRecovered()) {
 | |
| 		cSetPasswordRecovered(false);
 | |
| 		Ui::show(Box<InformBox>(lang(lng_signin_password_removed)));
 | |
| 	}
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		_sideShadow->hide();
 | |
| 		if (_hider) {
 | |
| 			_hider->hide();
 | |
| 			if (!_forwardConfirm && _hider->wasOffered()) {
 | |
| 				_forwardConfirm = Ui::show(Box<ConfirmBox>(_hider->offeredText(), lang(lng_forward_send), base::lambda_guarded(this, [this] {
 | |
| 					_hider->forward();
 | |
| 					if (_forwardConfirm) _forwardConfirm->closeBox();
 | |
| 					if (_hider) _hider->offerPeer(0);
 | |
| 				}), base::lambda_guarded(this, [this] {
 | |
| 					if (_hider && _forwardConfirm) _hider->offerPeer(0);
 | |
| 				})), ForceFastShowLayer);
 | |
| 			}
 | |
| 		}
 | |
| 		if (selectingPeer()) {
 | |
| 			_dialogs->showFast();
 | |
| 			_history->hide();
 | |
| 			if (_overview) _overview->hide();
 | |
| 			if (_wideSection) _wideSection->hide();
 | |
| 		} else if (_overview) {
 | |
| 			_overview->show();
 | |
| 		} else if (_wideSection) {
 | |
| 			_wideSection->show();
 | |
| 		} else if (_history->peer()) {
 | |
| 			_history->show();
 | |
| 			_history->updateControlsGeometry();
 | |
| 		} else {
 | |
| 			_dialogs->showFast();
 | |
| 			_history->hide();
 | |
| 		}
 | |
| 		if (!selectingPeer()) {
 | |
| 			if (_wideSection) {
 | |
| 				_dialogs->hide();
 | |
| 			} else if (isSectionShown()) {
 | |
| 				_dialogs->hide();
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		_sideShadow->show();
 | |
| 		if (_hider) {
 | |
| 			_hider->show();
 | |
| 			if (_forwardConfirm) {
 | |
| 				_forwardConfirm = nullptr;
 | |
| 				Ui::hideLayer(true);
 | |
| 				if (_hider->wasOffered()) {
 | |
| 					_hider->setFocus();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		_dialogs->showFast();
 | |
| 		if (_overview) {
 | |
| 			_overview->show();
 | |
| 		} else if (_wideSection) {
 | |
| 			_wideSection->show();
 | |
| 		} else {
 | |
| 			_history->show();
 | |
| 			_history->updateControlsGeometry();
 | |
| 		}
 | |
| 	}
 | |
| 	if (_player) {
 | |
| 		_player->show();
 | |
| 		_playerHeight = _player->contentHeight();
 | |
| 	}
 | |
| 	updateControlsGeometry();
 | |
| 	if (auto instance = currentFloatPlayer()) {
 | |
| 		checkFloatPlayerVisibility();
 | |
| 		if (instance->visible) {
 | |
| 			instance->widget->show();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	App::wnd()->checkHistoryActivation();
 | |
| }
 | |
| 
 | |
| void MainWidget::resizeEvent(QResizeEvent *e) {
 | |
| 	updateControlsGeometry();
 | |
| }
 | |
| 
 | |
| void MainWidget::updateControlsGeometry() {
 | |
| 	updateWindowAdaptiveLayout();
 | |
| 	if (!Adaptive::SmallColumn()) {
 | |
| 		_a_dialogsWidth.finish();
 | |
| 	}
 | |
| 	if (!_a_dialogsWidth.animating()) {
 | |
| 		_dialogs->stopWidthAnimation();
 | |
| 	}
 | |
| 	auto sectionTop = getSectionTop();
 | |
| 	auto dialogsWidth = qRound(_a_dialogsWidth.current(_dialogsWidth));
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		if (_callTopBar) {
 | |
| 			_callTopBar->resizeToWidth(dialogsWidth);
 | |
| 			_callTopBar->moveToLeft(0, 0);
 | |
| 		}
 | |
| 		if (_player) {
 | |
| 			_player->resizeToWidth(dialogsWidth);
 | |
| 			_player->moveToLeft(0, _callTopBarHeight);
 | |
| 		}
 | |
| 		_dialogs->setGeometry(0, sectionTop, dialogsWidth, height() - sectionTop);
 | |
| 		_history->setGeometry(0, sectionTop, dialogsWidth, height() - sectionTop);
 | |
| 		if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height());
 | |
| 	} else {
 | |
| 		accumulate_min(dialogsWidth, width() - st::windowMinWidth);
 | |
| 		auto sectionWidth = width() - dialogsWidth;
 | |
| 
 | |
| 		_dialogs->setGeometryToLeft(0, 0, dialogsWidth, height());
 | |
| 		_sideShadow->setGeometryToLeft(dialogsWidth, 0, st::lineWidth, height());
 | |
| 		if (_callTopBar) {
 | |
| 			_callTopBar->resizeToWidth(sectionWidth);
 | |
| 			_callTopBar->moveToLeft(dialogsWidth, 0);
 | |
| 		}
 | |
| 		if (_player) {
 | |
| 			_player->resizeToWidth(sectionWidth);
 | |
| 			_player->moveToLeft(dialogsWidth, _callTopBarHeight);
 | |
| 		}
 | |
| 		_history->setGeometryToLeft(dialogsWidth, sectionTop, sectionWidth, height() - sectionTop);
 | |
| 		if (_hider) {
 | |
| 			_hider->setGeometryToLeft(dialogsWidth, 0, sectionWidth, height());
 | |
| 		}
 | |
| 	}
 | |
| 	_sideResizeArea->setGeometryToLeft(_history->x(), 0, st::historyResizeWidth, height());
 | |
| 	auto isSideResizeAreaVisible = [this] {
 | |
| 		if (width() < st::windowMinWidth + st::dialogsWidthMin) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		if (Adaptive::OneColumn() && !isSectionShown()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		return true;
 | |
| 	};
 | |
| 	_sideResizeArea->setVisible(isSideResizeAreaVisible());
 | |
| 	if (_wideSection) {
 | |
| 		auto wideSectionGeometry = QRect(_history->x(), sectionTop, _history->width(), height() - sectionTop);
 | |
| 		_wideSection->setGeometryWithTopMoved(wideSectionGeometry, _contentScrollAddToY);
 | |
| 	}
 | |
| 	if (_overview) _overview->setGeometry(_history->geometry());
 | |
| 	updateMediaPlayerPosition();
 | |
| 	updateMediaPlaylistPosition(_playerPlaylist->x());
 | |
| 	_contentScrollAddToY = 0;
 | |
| 	for (auto &instance : _playerFloats) {
 | |
| 		updateFloatPlayerPosition(instance.get());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateDialogsWidthAnimated() {
 | |
| 	if (!Adaptive::SmallColumn()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	auto dialogsWidth = _dialogsWidth;
 | |
| 	updateWindowAdaptiveLayout();
 | |
| 	if (Adaptive::SmallColumn() && (_dialogsWidth != dialogsWidth || _a_dialogsWidth.animating())) {
 | |
| 		_dialogs->startWidthAnimation();
 | |
| 		_a_dialogsWidth.start([this] { updateControlsGeometry(); }, dialogsWidth, _dialogsWidth, st::dialogsWidthDuration, anim::easeOutCirc);
 | |
| 		updateControlsGeometry();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateMediaPlayerPosition() {
 | |
| 	_playerPanel->moveToRight(0, 0);
 | |
| 	if (_player && _playerVolume) {
 | |
| 		auto relativePosition = _player->entity()->getPositionForVolumeWidget();
 | |
| 		auto playerMargins = _playerVolume->getMargin();
 | |
| 		_playerVolume->moveToLeft(_player->x() + relativePosition.x() - playerMargins.left(), _player->y() + relativePosition.y() - playerMargins.top());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateMediaPlaylistPosition(int x) {
 | |
| 	if (_player) {
 | |
| 		auto playlistLeft = x;
 | |
| 		auto playlistWidth = _playerPlaylist->width();
 | |
| 		auto playlistTop = _player->y() + _player->height();
 | |
| 		auto rightEdge = width();
 | |
| 		if (playlistLeft + playlistWidth > rightEdge) {
 | |
| 			playlistLeft = rightEdge - playlistWidth;
 | |
| 		} else if (playlistLeft < 0) {
 | |
| 			playlistLeft = 0;
 | |
| 		}
 | |
| 		_playerPlaylist->move(playlistLeft, playlistTop);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int MainWidget::contentScrollAddToY() const {
 | |
| 	return _contentScrollAddToY;
 | |
| }
 | |
| 
 | |
| void MainWidget::keyPressEvent(QKeyEvent *e) {
 | |
| }
 | |
| 
 | |
| bool MainWidget::eventFilter(QObject *o, QEvent *e) {
 | |
| 	if (o == _sideResizeArea) {
 | |
| 		auto mouseLeft = [this, e] {
 | |
| 			return mapFromGlobal(static_cast<QMouseEvent*>(e)->globalPos()).x();
 | |
| 		};
 | |
| 		if (e->type() == QEvent::MouseButtonPress && static_cast<QMouseEvent*>(e)->button() == Qt::LeftButton) {
 | |
| 			_resizingSide = true;
 | |
| 			_resizingSideShift = mouseLeft() - (Adaptive::OneColumn() ? 0 : _dialogsWidth);
 | |
| 		} else if (e->type() == QEvent::MouseButtonRelease) {
 | |
| 			_resizingSide = false;
 | |
| 			if (!Adaptive::OneColumn()) {
 | |
| 				_controller->dialogsWidthRatio().set(float64(_dialogsWidth) / width(), true);
 | |
| 			}
 | |
| 			Local::writeUserSettings();
 | |
| 		} else if (e->type() == QEvent::MouseMove && _resizingSide) {
 | |
| 			auto newWidth = mouseLeft() - _resizingSideShift;
 | |
| 			accumulate_max(newWidth, _controller->dialogsSmallColumnWidth());
 | |
| 			_controller->dialogsWidthRatio().set(float64(newWidth) / width(), true);
 | |
| 		}
 | |
| 	} else if (e->type() == QEvent::FocusIn) {
 | |
| 		if (auto widget = qobject_cast<QWidget*>(o)) {
 | |
| 			if (_history == widget || _history->isAncestorOf(widget)
 | |
| 				|| (_overview && (_overview == widget || _overview->isAncestorOf(widget)))
 | |
| 				|| (_wideSection && (_wideSection == widget || _wideSection->isAncestorOf(widget)))) {
 | |
| 				_controller->dialogsListFocused().set(false);
 | |
| 			} else if (_dialogs == widget || _dialogs->isAncestorOf(widget)) {
 | |
| 				_controller->dialogsListFocused().set(true);
 | |
| 			}
 | |
| 		}
 | |
| 	} else if (e->type() == QEvent::MouseButtonPress) {
 | |
| 		if (static_cast<QMouseEvent*>(e)->button() == Qt::BackButton) {
 | |
| 			showBackFromStack();
 | |
| 			return true;
 | |
| 		}
 | |
| 	} else if (e->type() == QEvent::Wheel && !_playerFloats.empty()) {
 | |
| 		for (auto &instance : _playerFloats) {
 | |
| 			if (instance->widget == o) {
 | |
| 				auto column = instance->column;
 | |
| 				auto section = getFloatPlayerSection(&column);
 | |
| 				return section->wheelEventFromFloatPlayer(e, column, instance->column);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return TWidget::eventFilter(o, e);
 | |
| }
 | |
| 
 | |
| void MainWidget::handleAdaptiveLayoutUpdate() {
 | |
| 	showAll();
 | |
| 	_sideShadow->setVisible(!Adaptive::OneColumn());
 | |
| 	if (_player) {
 | |
| 		_player->updateAdaptiveLayout();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateWindowAdaptiveLayout() {
 | |
| 	auto layout = _controller->computeColumnLayout();
 | |
| 	auto dialogsWidthRatio = _controller->dialogsWidthRatio().value();
 | |
| 
 | |
| 	// Check if we are in a single-column layout in a wide enough window
 | |
| 	// for the normal layout. If so, switch to the normal layout.
 | |
| 	if (layout.windowLayout == Adaptive::WindowLayout::OneColumn) {
 | |
| 		auto chatWidth = layout.chatWidth;
 | |
| 		if (AuthSession::Current().data().tabbedSelectorSectionEnabled()
 | |
| 			&& chatWidth >= _history->minimalWidthForTabbedSelectorSection()) {
 | |
| 			chatWidth -= _history->tabbedSelectorSectionWidth();
 | |
| 		}
 | |
| 		if (chatWidth >= st::dialogsWidthMin + st::windowMinWidth) {
 | |
| 			// Switch layout back to normal in a wide enough window.
 | |
| 			layout.windowLayout = Adaptive::WindowLayout::Normal;
 | |
| 			layout.dialogsWidth = st::dialogsWidthMin;
 | |
| 			layout.chatWidth = layout.bodyWidth - layout.dialogsWidth;
 | |
| 			dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Check if we are going to create the third column and shrink the
 | |
| 	// dialogs widget to provide a wide enough chat history column.
 | |
| 	// Don't shrink the column on the first call, when window is inited.
 | |
| 	if (layout.windowLayout == Adaptive::WindowLayout::Normal
 | |
| 		&& _controller->window()->positionInited()) {
 | |
| 		auto chatWidth = layout.chatWidth;
 | |
| 		if (_history->willSwitchToTabbedSelectorWithWidth(chatWidth)) {
 | |
| 			auto thirdColumnWidth = _history->tabbedSelectorSectionWidth();
 | |
| 			auto twoColumnsWidth = (layout.bodyWidth - thirdColumnWidth);
 | |
| 			auto sameRatioChatWidth = twoColumnsWidth - qRound(dialogsWidthRatio * twoColumnsWidth);
 | |
| 			auto desiredChatWidth = qMax(sameRatioChatWidth, HistoryLayout::WideChatWidth());
 | |
| 			chatWidth -= thirdColumnWidth;
 | |
| 			auto extendChatBy = desiredChatWidth - chatWidth;
 | |
| 			accumulate_min(extendChatBy, layout.dialogsWidth - st::dialogsWidthMin);
 | |
| 			if (extendChatBy > 0) {
 | |
| 				layout.dialogsWidth -= extendChatBy;
 | |
| 				layout.chatWidth += extendChatBy;
 | |
| 				dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_controller->dialogsWidthRatio().set(dialogsWidthRatio, true);
 | |
| 
 | |
| 	_dialogsWidth = layout.dialogsWidth;
 | |
| 	if (layout.windowLayout != Global::AdaptiveWindowLayout()) {
 | |
| 		Global::SetAdaptiveWindowLayout(layout.windowLayout);
 | |
| 		Adaptive::Changed().notify(true);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::paintTopBar(Painter &p, int decreaseWidth, TimeMs ms) {
 | |
| 	if (_overview) {
 | |
| 		return _overview->paintTopBar(p, decreaseWidth);
 | |
| 	} else if (!_wideSection) {
 | |
| 		return _history->paintTopBar(p, decreaseWidth, ms);
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| QRect MainWidget::getMembersShowAreaGeometry() const {
 | |
| 	if (!_overview && !_wideSection) {
 | |
| 		return _history->getMembersShowAreaGeometry();
 | |
| 	}
 | |
| 	return QRect();
 | |
| }
 | |
| 
 | |
| void MainWidget::setMembersShowAreaActive(bool active) {
 | |
| 	if (!active || (!_overview && !_wideSection)) {
 | |
| 		_history->setMembersShowAreaActive(active);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int MainWidget::backgroundFromY() const {
 | |
| 	return -getSectionTop();
 | |
| }
 | |
| 
 | |
| void MainWidget::onHistoryShown(History *history, MsgId atMsgId) {
 | |
| 	updateControlsGeometry();
 | |
| 	dlgUpdated(history ? history->peer : nullptr, atMsgId);
 | |
| }
 | |
| 
 | |
| void MainWidget::searchInPeer(PeerData *peer) {
 | |
| 	_dialogs->searchInPeer(peer);
 | |
| 	if (Adaptive::OneColumn()) {
 | |
| 		dialogsToUp();
 | |
| 		Ui::showChatsList();
 | |
| 	} else {
 | |
| 		_dialogs->activate();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::onUpdateNotifySettings() {
 | |
| 	if (this != App::main()) return;
 | |
| 	while (!updateNotifySettingPeers.isEmpty()) {
 | |
| 		PeerData *peer = *updateNotifySettingPeers.begin();
 | |
| 		updateNotifySettingPeers.erase(updateNotifySettingPeers.begin());
 | |
| 
 | |
| 		if (peer->notify == UnknownNotifySettings || peer->notify == EmptyNotifySettings) {
 | |
| 			peer->notify = new NotifySettings();
 | |
| 		}
 | |
| 		MTP::send(MTPaccount_UpdateNotifySettings(MTP_inputNotifyPeer(peer->input), MTP_inputPeerNotifySettings(MTP_flags(mtpCastFlags(peer->notify->flags)), MTP_int(peer->notify->mute), MTP_string(peer->notify->sound))), RPCResponseHandler(), 0, updateNotifySettingPeers.isEmpty() ? 0 : 10);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::feedUpdateVector(const MTPVector<MTPUpdate> &updates, bool skipMessageIds) {
 | |
| 	for_const (auto &update, updates.v) {
 | |
| 		if (skipMessageIds && update.type() == mtpc_updateMessageID) continue;
 | |
| 		feedUpdate(update);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
 | |
| 	for_const (auto &update, updates.v) {
 | |
| 		if (update.type() == mtpc_updateMessageID) {
 | |
| 			feedUpdate(update);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::updateFail(const RPCError &e) {
 | |
| 	App::logOutDelayed();
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::updSetState(int32 pts, int32 date, int32 qts, int32 seq) {
 | |
| 	if (pts) {
 | |
| 		_ptsWaiter.init(pts);
 | |
| 	}
 | |
| 	if (updDate < date && !_byMinChannelTimer.isActive()) {
 | |
| 		updDate = date;
 | |
| 	}
 | |
| 	if (qts && updQts < qts) {
 | |
| 		updQts = qts;
 | |
| 	}
 | |
| 	if (seq && seq != updSeq) {
 | |
| 		updSeq = seq;
 | |
| 		if (_bySeqTimer.isActive()) _bySeqTimer.stop();
 | |
| 		for (QMap<int32, MTPUpdates>::iterator i = _bySeqUpdates.begin(); i != _bySeqUpdates.end();) {
 | |
| 			int32 s = i.key();
 | |
| 			if (s <= seq + 1) {
 | |
| 				MTPUpdates v = i.value();
 | |
| 				i = _bySeqUpdates.erase(i);
 | |
| 				if (s == seq + 1) {
 | |
| 					return feedUpdates(v);
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (!_bySeqTimer.isActive()) _bySeqTimer.start(WaitForSkippedTimeout);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff) {
 | |
| 	_channelFailDifferenceTimeout.remove(channel);
 | |
| 
 | |
| 	int32 timeout = 0;
 | |
| 	bool isFinal = true;
 | |
| 	switch (diff.type()) {
 | |
| 	case mtpc_updates_channelDifferenceEmpty: {
 | |
| 		auto &d = diff.c_updates_channelDifferenceEmpty();
 | |
| 		if (d.has_timeout()) timeout = d.vtimeout.v;
 | |
| 		isFinal = d.is_final();
 | |
| 		channel->ptsInit(d.vpts.v);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updates_channelDifferenceTooLong: {
 | |
| 		auto &d = diff.c_updates_channelDifferenceTooLong();
 | |
| 
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		auto h = App::historyLoaded(channel->id);
 | |
| 		if (h) {
 | |
| 			h->setNotLoadedAtBottom();
 | |
| 		}
 | |
| 		App::feedMsgs(d.vmessages, NewMessageLast);
 | |
| 		if (h) {
 | |
| 			if (auto item = App::histItemById(peerToChannel(channel->id), d.vtop_message.v)) {
 | |
| 				h->setLastMessage(item);
 | |
| 			}
 | |
| 			if (d.vunread_count.v >= h->unreadCount()) {
 | |
| 				h->setUnreadCount(d.vunread_count.v);
 | |
| 				h->inboxReadBefore = d.vread_inbox_max_id.v + 1;
 | |
| 			}
 | |
| 			if (_history->peer() == channel) {
 | |
| 				_history->updateHistoryDownVisibility();
 | |
| 				_history->preloadHistoryIfNeeded();
 | |
| 			}
 | |
| 			h->asChannelHistory()->getRangeDifference();
 | |
| 		}
 | |
| 
 | |
| 		if (d.has_timeout()) timeout = d.vtimeout.v;
 | |
| 		isFinal = d.is_final();
 | |
| 		channel->ptsInit(d.vpts.v);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updates_channelDifference: {
 | |
| 		auto &d = diff.c_updates_channelDifference();
 | |
| 
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 
 | |
| 		_handlingChannelDifference = true;
 | |
| 		feedMessageIds(d.vother_updates);
 | |
| 
 | |
| 		// feed messages and groups, copy from App::feedMsgs
 | |
| 		auto h = App::history(channel->id);
 | |
| 		auto &vmsgs = d.vnew_messages.v;
 | |
| 		QMap<uint64, int> msgsIds;
 | |
| 		for (int i = 0, l = vmsgs.size(); i < l; ++i) {
 | |
| 			auto &msg = vmsgs[i];
 | |
| 			switch (msg.type()) {
 | |
| 			case mtpc_message: {
 | |
| 				const auto &d(msg.c_message());
 | |
| 				if (App::checkEntitiesAndViewsUpdate(d)) { // new message, index my forwarded messages to links _overview, already in blocks
 | |
| 					LOG(("Skipping message, because it is already in blocks!"));
 | |
| 				} else {
 | |
| 					msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i + 1);
 | |
| 				}
 | |
| 			} break;
 | |
| 			case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i + 1); break;
 | |
| 			case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i + 1); break;
 | |
| 			}
 | |
| 		}
 | |
| 		for_const (auto msgIndex, msgsIds) {
 | |
| 			if (msgIndex > 0) { // add message
 | |
| 				auto &msg = vmsgs.at(msgIndex - 1);
 | |
| 				if (channel->id != peerFromMessage(msg)) {
 | |
| 					LOG(("API Error: message with invalid peer returned in channelDifference, channelId: %1, peer: %2").arg(peerToChannel(channel->id)).arg(peerFromMessage(msg)));
 | |
| 					continue; // wtf
 | |
| 				}
 | |
| 				h->addNewMessage(msg, NewMessageUnread);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		feedUpdateVector(d.vother_updates, true);
 | |
| 		_handlingChannelDifference = false;
 | |
| 
 | |
| 		if (d.has_timeout()) timeout = d.vtimeout.v;
 | |
| 		isFinal = d.is_final();
 | |
| 		channel->ptsInit(d.vpts.v);
 | |
| 	} break;
 | |
| 	}
 | |
| 
 | |
| 	channel->ptsSetRequesting(false);
 | |
| 
 | |
| 	if (!isFinal) {
 | |
| 		MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 		getChannelDifference(channel);
 | |
| 	} else if (activePeer() == channel) {
 | |
| 		channel->ptsWaitingForShortPoll(timeout ? (timeout * 1000) : WaitForChannelGetDifference);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff) {
 | |
| 	int32 nextRequestPts = 0;
 | |
| 	bool isFinal = true;
 | |
| 	switch (diff.type()) {
 | |
| 	case mtpc_updates_channelDifferenceEmpty: {
 | |
| 		const auto &d(diff.c_updates_channelDifferenceEmpty());
 | |
| 		nextRequestPts = d.vpts.v;
 | |
| 		isFinal = d.is_final();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updates_channelDifferenceTooLong: {
 | |
| 		const auto &d(diff.c_updates_channelDifferenceTooLong());
 | |
| 
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 
 | |
| 		nextRequestPts = d.vpts.v;
 | |
| 		isFinal = d.is_final();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updates_channelDifference: {
 | |
| 		const auto &d(diff.c_updates_channelDifference());
 | |
| 
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 
 | |
| 		_handlingChannelDifference = true;
 | |
| 		feedMessageIds(d.vother_updates);
 | |
| 		App::feedMsgs(d.vnew_messages, NewMessageUnread);
 | |
| 		feedUpdateVector(d.vother_updates, true);
 | |
| 		_handlingChannelDifference = false;
 | |
| 
 | |
| 		nextRequestPts = d.vpts.v;
 | |
| 		isFinal = d.is_final();
 | |
| 	} break;
 | |
| 	}
 | |
| 
 | |
| 	if (!isFinal) {
 | |
| 		if (History *h = App::historyLoaded(channel->id)) {
 | |
| 			MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received, validating history part }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 			h->asChannelHistory()->getRangeDifferenceNext(nextRequestPts);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	LOG(("RPC Error in getChannelDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
 | |
| 	failDifferenceStartTimerFor(channel);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::gotState(const MTPupdates_State &state) {
 | |
| 	auto &d = state.c_updates_state();
 | |
| 	updSetState(d.vpts.v, d.vdate.v, d.vqts.v, d.vseq.v);
 | |
| 
 | |
| 	_lastUpdateTime = getms(true);
 | |
| 	noUpdatesTimer.start(NoUpdatesTimeout);
 | |
| 	_ptsWaiter.setRequesting(false);
 | |
| 
 | |
| 	_dialogs->loadDialogs();
 | |
| 	updateOnline();
 | |
| }
 | |
| 
 | |
| void MainWidget::gotDifference(const MTPupdates_Difference &difference) {
 | |
| 	_failDifferenceTimeout = 1;
 | |
| 
 | |
| 	switch (difference.type()) {
 | |
| 	case mtpc_updates_differenceEmpty: {
 | |
| 		auto &d = difference.c_updates_differenceEmpty();
 | |
| 		updSetState(_ptsWaiter.current(), d.vdate.v, updQts, d.vseq.v);
 | |
| 
 | |
| 		_lastUpdateTime = getms(true);
 | |
| 		noUpdatesTimer.start(NoUpdatesTimeout);
 | |
| 
 | |
| 		_ptsWaiter.setRequesting(false);
 | |
| 	} break;
 | |
| 	case mtpc_updates_differenceSlice: {
 | |
| 		auto &d = difference.c_updates_differenceSlice();
 | |
| 		feedDifference(d.vusers, d.vchats, d.vnew_messages, d.vother_updates);
 | |
| 
 | |
| 		auto &s = d.vintermediate_state.c_updates_state();
 | |
| 		updSetState(s.vpts.v, s.vdate.v, s.vqts.v, s.vseq.v);
 | |
| 
 | |
| 		_ptsWaiter.setRequesting(false);
 | |
| 
 | |
| 		MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 		getDifference();
 | |
| 	} break;
 | |
| 	case mtpc_updates_difference: {
 | |
| 		auto &d = difference.c_updates_difference();
 | |
| 		feedDifference(d.vusers, d.vchats, d.vnew_messages, d.vother_updates);
 | |
| 
 | |
| 		gotState(d.vstate);
 | |
| 	} break;
 | |
| 	case mtpc_updates_differenceTooLong: {
 | |
| 		auto &d = difference.c_updates_differenceTooLong();
 | |
| 		LOG(("API Error: updates.differenceTooLong is not supported by Telegram Desktop!"));
 | |
| 	} break;
 | |
| 	};
 | |
| }
 | |
| 
 | |
| bool MainWidget::getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, TimeMs &curTime) {
 | |
| 	if (channel) {
 | |
| 		if (ms <= 0) {
 | |
| 			ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel);
 | |
| 			if (i != channelCurTime.cend()) {
 | |
| 				channelCurTime.erase(i);
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		} else {
 | |
| 			auto when = getms(true) + ms;
 | |
| 			ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel);
 | |
| 			if (i != channelCurTime.cend()) {
 | |
| 				if (i.value() > when) {
 | |
| 					i.value() = when;
 | |
| 				} else {
 | |
| 					return false;
 | |
| 				}
 | |
| 			} else {
 | |
| 				channelCurTime.insert(channel, when);
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (ms <= 0) {
 | |
| 			if (curTime) {
 | |
| 				curTime = 0;
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		} else {
 | |
| 			auto when = getms(true) + ms;
 | |
| 			if (!curTime || curTime > when) {
 | |
| 				curTime = when;
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::ptsWaiterStartTimerFor(ChannelData *channel, int32 ms) {
 | |
| 	if (getDifferenceTimeChanged(channel, ms, _channelGetDifferenceTimeByPts, _getDifferenceTimeByPts)) {
 | |
| 		onGetDifferenceTimeByPts();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::failDifferenceStartTimerFor(ChannelData *channel) {
 | |
| 	int32 ms = 0;
 | |
| 	ChannelFailDifferenceTimeout::iterator i;
 | |
| 	if (channel) {
 | |
| 		i = _channelFailDifferenceTimeout.find(channel);
 | |
| 		if (i == _channelFailDifferenceTimeout.cend()) {
 | |
| 			i = _channelFailDifferenceTimeout.insert(channel, 1);
 | |
| 		}
 | |
| 		ms = i.value() * 1000;
 | |
| 	} else {
 | |
| 		ms = _failDifferenceTimeout * 1000;
 | |
| 	}
 | |
| 	if (getDifferenceTimeChanged(channel, ms, _channelGetDifferenceTimeAfterFail, _getDifferenceTimeAfterFail)) {
 | |
| 		onGetDifferenceTimeAfterFail();
 | |
| 	}
 | |
| 	if (channel) {
 | |
| 		if (i.value() < 64) i.value() *= 2;
 | |
| 	} else {
 | |
| 		if (_failDifferenceTimeout < 64) _failDifferenceTimeout *= 2;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::ptsUpdated(int32 pts, int32 ptsCount) { // return false if need to save that update and apply later
 | |
| 	return _ptsWaiter.updated(0, pts, ptsCount);
 | |
| }
 | |
| 
 | |
| bool MainWidget::ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates) {
 | |
| 	return _ptsWaiter.updated(0, pts, ptsCount, updates);
 | |
| }
 | |
| 
 | |
| bool MainWidget::ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdate &update) {
 | |
| 	return _ptsWaiter.updated(0, pts, ptsCount, update);
 | |
| }
 | |
| 
 | |
| void MainWidget::ptsApplySkippedUpdates() {
 | |
| 	return _ptsWaiter.applySkippedUpdates(0);
 | |
| }
 | |
| 
 | |
| void MainWidget::feedDifference(const MTPVector<MTPUser> &users, const MTPVector<MTPChat> &chats, const MTPVector<MTPMessage> &msgs, const MTPVector<MTPUpdate> &other) {
 | |
| 	AuthSession::Current().checkAutoLock();
 | |
| 	App::feedUsers(users);
 | |
| 	App::feedChats(chats);
 | |
| 	feedMessageIds(other);
 | |
| 	App::feedMsgs(msgs, NewMessageUnread);
 | |
| 	feedUpdateVector(other, true);
 | |
| 	_history->peerMessagesUpdated();
 | |
| }
 | |
| 
 | |
| bool MainWidget::failDifference(const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	LOG(("RPC Error in getDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
 | |
| 	failDifferenceStartTimerFor(0);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::onGetDifferenceTimeByPts() {
 | |
| 	auto now = getms(true), wait = 0LL;
 | |
| 	if (_getDifferenceTimeByPts) {
 | |
| 		if (_getDifferenceTimeByPts > now) {
 | |
| 			wait = _getDifferenceTimeByPts - now;
 | |
| 		} else {
 | |
| 			getDifference();
 | |
| 		}
 | |
| 	}
 | |
| 	for (ChannelGetDifferenceTime::iterator i = _channelGetDifferenceTimeByPts.begin(); i != _channelGetDifferenceTimeByPts.cend();) {
 | |
| 		if (i.value() > now) {
 | |
| 			wait = wait ? qMin(wait, i.value() - now) : (i.value() - now);
 | |
| 			++i;
 | |
| 		} else {
 | |
| 			getChannelDifference(i.key(), ChannelDifferenceRequest::PtsGapOrShortPoll);
 | |
| 			i = _channelGetDifferenceTimeByPts.erase(i);
 | |
| 		}
 | |
| 	}
 | |
| 	if (wait) {
 | |
| 		_byPtsTimer.start(wait);
 | |
| 	} else {
 | |
| 		_byPtsTimer.stop();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::onGetDifferenceTimeAfterFail() {
 | |
| 	auto now = getms(true), wait = 0LL;
 | |
| 	if (_getDifferenceTimeAfterFail) {
 | |
| 		if (_getDifferenceTimeAfterFail > now) {
 | |
| 			wait = _getDifferenceTimeAfterFail - now;
 | |
| 		} else {
 | |
| 			_ptsWaiter.setRequesting(false);
 | |
| 			MTP_LOG(0, ("getDifference { force - after get difference failed }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 			getDifference();
 | |
| 		}
 | |
| 	}
 | |
| 	for (auto i = _channelGetDifferenceTimeAfterFail.begin(); i != _channelGetDifferenceTimeAfterFail.cend();) {
 | |
| 		if (i.value() > now) {
 | |
| 			wait = wait ? qMin(wait, i.value() - now) : (i.value() - now);
 | |
| 			++i;
 | |
| 		} else {
 | |
| 			getChannelDifference(i.key(), ChannelDifferenceRequest::AfterFail);
 | |
| 			i = _channelGetDifferenceTimeAfterFail.erase(i);
 | |
| 		}
 | |
| 	}
 | |
| 	if (wait) {
 | |
| 		_failDifferenceTimer.start(wait);
 | |
| 	} else {
 | |
| 		_failDifferenceTimer.stop();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::getDifference() {
 | |
| 	if (this != App::main()) return;
 | |
| 
 | |
| 	_getDifferenceTimeByPts = 0;
 | |
| 
 | |
| 	if (requestingDifference()) return;
 | |
| 
 | |
| 	_bySeqUpdates.clear();
 | |
| 	_bySeqTimer.stop();
 | |
| 
 | |
| 	noUpdatesTimer.stop();
 | |
| 	_getDifferenceTimeAfterFail = 0;
 | |
| 
 | |
| 	_ptsWaiter.setRequesting(true);
 | |
| 
 | |
| 	MTP::send(MTPupdates_GetDifference(MTP_flags(0), MTP_int(_ptsWaiter.current()), MTPint(), MTP_int(updDate), MTP_int(updQts)), rpcDone(&MainWidget::gotDifference), rpcFail(&MainWidget::failDifference));
 | |
| }
 | |
| 
 | |
| void MainWidget::getChannelDifference(ChannelData *channel, ChannelDifferenceRequest from) {
 | |
| 	if (this != App::main() || !channel) return;
 | |
| 
 | |
| 	if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) {
 | |
| 		_channelGetDifferenceTimeByPts.remove(channel);
 | |
| 	}
 | |
| 
 | |
| 	if (!channel->ptsInited() || channel->ptsRequesting()) return;
 | |
| 
 | |
| 	if (from != ChannelDifferenceRequest::AfterFail) {
 | |
| 		_channelGetDifferenceTimeAfterFail.remove(channel);
 | |
| 	}
 | |
| 
 | |
| 	channel->ptsSetRequesting(true);
 | |
| 
 | |
| 	auto filter = MTP_channelMessagesFilterEmpty();
 | |
| 	auto flags = qFlags(MTPupdates_GetChannelDifference::Flag::f_force);
 | |
| 	if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) {
 | |
| 		if (!channel->ptsWaitingForSkipped()) {
 | |
| 			flags = 0; // No force flag when requesting for short poll.
 | |
| 		}
 | |
| 	}
 | |
| 	MTP::send(MTPupdates_GetChannelDifference(MTP_flags(flags), channel->inputChannel, filter, MTP_int(channel->pts()), MTP_int(MTPChannelGetDifferenceLimit)), rpcDone(&MainWidget::gotChannelDifference, channel), rpcFail(&MainWidget::failChannelDifference, channel));
 | |
| }
 | |
| 
 | |
| void MainWidget::mtpPing() {
 | |
| 	MTP::ping();
 | |
| }
 | |
| 
 | |
| void MainWidget::start(const MTPUser *self) {
 | |
| 	if (!self) {
 | |
| 		MTP::send(MTPusers_GetUsers(MTP_vector<MTPInputUser>(1, MTP_inputUserSelf())), rpcDone(&MainWidget::startWithSelf));
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!AuthSession::Current().validateSelf(*self)) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	Local::readSavedPeers();
 | |
| 	cSetOtherOnline(0);
 | |
| 	if (auto user = App::feedUsers(MTP_vector<MTPUser>(1, *self))) {
 | |
| 		user->loadUserpic();
 | |
| 	}
 | |
| 
 | |
| 	MTP::send(MTPupdates_GetState(), rpcDone(&MainWidget::gotState));
 | |
| 	update();
 | |
| 
 | |
| 	_started = true;
 | |
| 	App::wnd()->sendServiceHistoryRequest();
 | |
| 	Local::readInstalledStickers();
 | |
| 	Local::readFeaturedStickers();
 | |
| 	Local::readRecentStickers();
 | |
| 	Local::readSavedGifs();
 | |
| 	_history->start();
 | |
| 
 | |
| 	checkStartUrl();
 | |
| }
 | |
| 
 | |
| bool MainWidget::started() {
 | |
| 	return _started;
 | |
| }
 | |
| 
 | |
| void MainWidget::checkStartUrl() {
 | |
| 	if (!cStartUrl().isEmpty() && App::self() && !App::passcoded()) {
 | |
| 		auto url = cStartUrl();
 | |
| 		cSetStartUrl(QString());
 | |
| 
 | |
| 		openLocalUrl(url);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::openLocalUrl(const QString &url) {
 | |
| 	auto urlTrimmed = url.trimmed();
 | |
| 	if (urlTrimmed.size() > 8192) urlTrimmed = urlTrimmed.mid(0, 8192);
 | |
| 
 | |
| 	if (!urlTrimmed.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	auto command = urlTrimmed.midRef(qstr("tg://").size());
 | |
| 
 | |
| 	using namespace qthelp;
 | |
| 	auto matchOptions = RegExOption::CaseInsensitive;
 | |
| 	if (auto joinChatMatch = regex_match(qsl("^join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), command, matchOptions)) {
 | |
| 		joinGroupByHash(joinChatMatch->captured(1));
 | |
| 	} else if (auto stickerSetMatch = regex_match(qsl("^addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), command, matchOptions)) {
 | |
| 		stickersBox(MTP_inputStickerSetShortName(MTP_string(stickerSetMatch->captured(1))));
 | |
| 	} else if (auto shareUrlMatch = regex_match(qsl("^msg_url/?\\?(.+)(#|$)"), command, matchOptions)) {
 | |
| 		auto params = url_parse_params(shareUrlMatch->captured(1), UrlParamNameTransform::ToLower);
 | |
| 		auto url = params.value(qsl("url"));
 | |
| 		if (!url.isEmpty()) {
 | |
| 			shareUrlLayer(url, params.value("text"));
 | |
| 		}
 | |
| 	} else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)(#|$)"), command, matchOptions)) {
 | |
| 		auto params = url_parse_params(confirmPhoneMatch->captured(1), UrlParamNameTransform::ToLower);
 | |
| 		auto phone = params.value(qsl("phone"));
 | |
| 		auto hash = params.value(qsl("hash"));
 | |
| 		if (!phone.isEmpty() && !hash.isEmpty()) {
 | |
| 			ConfirmPhoneBox::start(phone, hash);
 | |
| 		}
 | |
| 	} else if (auto usernameMatch = regex_match(qsl("^resolve/?\\?(.+)(#|$)"), command, matchOptions)) {
 | |
| 		auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower);
 | |
| 		auto domain = params.value(qsl("domain"));
 | |
| 		if (regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
 | |
| 			auto start = qsl("start");
 | |
| 			auto startToken = params.value(start);
 | |
| 			if (startToken.isEmpty()) {
 | |
| 				start = qsl("startgroup");
 | |
| 				startToken = params.value(start);
 | |
| 				if (startToken.isEmpty()) {
 | |
| 					start = QString();
 | |
| 				}
 | |
| 			}
 | |
| 			auto post = (start == qsl("startgroup")) ? ShowAtProfileMsgId : ShowAtUnreadMsgId;
 | |
| 			auto postParam = params.value(qsl("post"));
 | |
| 			if (auto postId = postParam.toInt()) {
 | |
| 				post = postId;
 | |
| 			}
 | |
| 			auto gameParam = params.value(qsl("game"));
 | |
| 			if (!gameParam.isEmpty() && regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), gameParam, matchOptions)) {
 | |
| 				startToken = gameParam;
 | |
| 				post = ShowAtGameShareMsgId;
 | |
| 			}
 | |
| 			openPeerByName(domain, post, startToken);
 | |
| 		}
 | |
| 	} else if (auto shareGameScoreMatch = regex_match(qsl("^share_game_score/?\\?(.+)(#|$)"), command, matchOptions)) {
 | |
| 		auto params = url_parse_params(shareGameScoreMatch->captured(1), UrlParamNameTransform::ToLower);
 | |
| 		shareGameScoreByHash(params.value(qsl("hash")));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QString &startToken) {
 | |
| 	App::wnd()->hideMediaview();
 | |
| 
 | |
| 	PeerData *peer = App::peerByName(username);
 | |
| 	if (peer) {
 | |
| 		if (msgId == ShowAtGameShareMsgId) {
 | |
| 			if (peer->isUser() && peer->asUser()->botInfo && !startToken.isEmpty()) {
 | |
| 				peer->asUser()->botInfo->shareGameShortName = startToken;
 | |
| 				Ui::show(Box<ContactsBox>(peer->asUser()));
 | |
| 			} else {
 | |
| 				Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
 | |
| 			}
 | |
| 		} else if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
 | |
| 			if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) {
 | |
| 				peer->asUser()->botInfo->startGroupToken = startToken;
 | |
| 				Ui::show(Box<ContactsBox>(peer->asUser()));
 | |
| 			} else if (peer->isUser() && peer->asUser()->botInfo) {
 | |
| 				// Always open bot chats, even from mention links.
 | |
| 				Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
 | |
| 			} else {
 | |
| 				Ui::showPeerProfile(peer);
 | |
| 			}
 | |
| 		} else {
 | |
| 			if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups
 | |
| 				msgId = ShowAtUnreadMsgId;
 | |
| 			}
 | |
| 			if (peer->isUser() && peer->asUser()->botInfo) {
 | |
| 				peer->asUser()->botInfo->startToken = startToken;
 | |
| 				if (peer == _history->peer()) {
 | |
| 					_history->updateControlsVisibility();
 | |
| 					_history->updateControlsGeometry();
 | |
| 				}
 | |
| 			}
 | |
| 			Ui::showPeerHistoryAsync(peer->id, msgId, Ui::ShowWay::Forward);
 | |
| 		}
 | |
| 	} else {
 | |
| 		MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(msgId, startToken)), rpcFail(&MainWidget::usernameResolveFail, username));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::joinGroupByHash(const QString &hash) {
 | |
| 	App::wnd()->hideMediaview();
 | |
| 	MTP::send(MTPmessages_CheckChatInvite(MTP_string(hash)), rpcDone(&MainWidget::inviteCheckDone, hash), rpcFail(&MainWidget::inviteCheckFail));
 | |
| }
 | |
| 
 | |
| void MainWidget::stickersBox(const MTPInputStickerSet &set) {
 | |
| 	App::wnd()->hideMediaview();
 | |
| 	auto box = Ui::show(Box<StickerSetBox>(set));
 | |
| 	connect(box, SIGNAL(installed(uint64)), this, SLOT(onStickersInstalled(uint64)));
 | |
| }
 | |
| 
 | |
| void MainWidget::onStickersInstalled(uint64 setId) {
 | |
| 	_history->stickersInstalled(setId);
 | |
| }
 | |
| 
 | |
| void MainWidget::onSelfParticipantUpdated(ChannelData *channel) {
 | |
| 	auto history = App::historyLoaded(channel->id);
 | |
| 	if (_updatedChannels.contains(channel)) {
 | |
| 		_updatedChannels.remove(channel);
 | |
| 		if ((history ? history : App::history(channel->id))->isEmpty()) {
 | |
| 			checkPeerHistory(channel);
 | |
| 		} else {
 | |
| 			history->asChannelHistory()->checkJoinedMessage(true);
 | |
| 			_history->peerMessagesUpdated(channel->id);
 | |
| 		}
 | |
| 	} else if (history) {
 | |
| 		history->asChannelHistory()->checkJoinedMessage();
 | |
| 		_history->peerMessagesUpdated(channel->id);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::contentOverlapped(const QRect &globalRect) {
 | |
| 	return (_history->contentOverlapped(globalRect)
 | |
| 			|| (_overview && _overview->contentOverlapped(globalRect))
 | |
| 			|| _playerPanel->overlaps(globalRect)
 | |
| 			|| _playerPlaylist->overlaps(globalRect)
 | |
| 			|| (_playerVolume && _playerVolume->overlaps(globalRect)));
 | |
| }
 | |
| 
 | |
| void MainWidget::usernameResolveDone(QPair<MsgId, QString> msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result) {
 | |
| 	Ui::hideLayer();
 | |
| 	if (result.type() != mtpc_contacts_resolvedPeer) return;
 | |
| 
 | |
| 	const auto &d(result.c_contacts_resolvedPeer());
 | |
| 	App::feedUsers(d.vusers);
 | |
| 	App::feedChats(d.vchats);
 | |
| 	PeerId peerId = peerFromMTP(d.vpeer);
 | |
| 	if (!peerId) return;
 | |
| 
 | |
| 	PeerData *peer = App::peer(peerId);
 | |
| 	MsgId msgId = msgIdAndStartToken.first;
 | |
| 	QString startToken = msgIdAndStartToken.second;
 | |
| 	if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
 | |
| 		if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) {
 | |
| 			peer->asUser()->botInfo->startGroupToken = startToken;
 | |
| 			Ui::show(Box<ContactsBox>(peer->asUser()));
 | |
| 		} else if (peer->isUser() && peer->asUser()->botInfo) {
 | |
| 			// Always open bot chats, even from mention links.
 | |
| 			Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
 | |
| 		} else {
 | |
| 			Ui::showPeerProfile(peer);
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups
 | |
| 			msgId = ShowAtUnreadMsgId;
 | |
| 		}
 | |
| 		if (peer->isUser() && peer->asUser()->botInfo) {
 | |
| 			peer->asUser()->botInfo->startToken = startToken;
 | |
| 			if (peer == _history->peer()) {
 | |
| 				_history->updateControlsVisibility();
 | |
| 				_history->updateControlsGeometry();
 | |
| 			}
 | |
| 		}
 | |
| 		Ui::showPeerHistory(peer->id, msgId, Ui::ShowWay::Forward);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::usernameResolveFail(QString name, const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	if (error.code() == 400) {
 | |
| 		Ui::show(Box<InformBox>(lng_username_not_found(lt_user, name)));
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) {
 | |
| 	switch (invite.type()) {
 | |
| 	case mtpc_chatInvite: {
 | |
| 		auto &d(invite.c_chatInvite());
 | |
| 
 | |
| 		QVector<UserData*> participants;
 | |
| 		if (d.has_participants()) {
 | |
| 			auto &v = d.vparticipants.v;
 | |
| 			participants.reserve(v.size());
 | |
| 			for_const (auto &user, v) {
 | |
| 				if (auto feededUser = App::feedUser(user)) {
 | |
| 					participants.push_back(feededUser);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		_inviteHash = hash;
 | |
| 		Ui::show(Box<ConfirmInviteBox>(qs(d.vtitle), d.vphoto, d.vparticipants_count.v, participants));
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_chatInviteAlready: {
 | |
| 		const auto &d(invite.c_chatInviteAlready());
 | |
| 		PeerData *chat = App::feedChats(MTP_vector<MTPChat>(1, d.vchat));
 | |
| 		if (chat) {
 | |
| 			Ui::showPeerHistory(chat->id, ShowAtUnreadMsgId);
 | |
| 		}
 | |
| 	} break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::inviteCheckFail(const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	if (error.code() == 400) {
 | |
| 		Ui::show(Box<InformBox>(lang(lng_group_invite_bad_link)));
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::onInviteImport() {
 | |
| 	if (_inviteHash.isEmpty()) return;
 | |
| 	MTP::send(MTPmessages_ImportChatInvite(MTP_string(_inviteHash)), rpcDone(&MainWidget::inviteImportDone), rpcFail(&MainWidget::inviteImportFail));
 | |
| }
 | |
| 
 | |
| void MainWidget::inviteImportDone(const MTPUpdates &updates) {
 | |
| 	App::main()->sentUpdatesReceived(updates);
 | |
| 
 | |
| 	Ui::hideLayer();
 | |
| 	const QVector<MTPChat> *v = 0;
 | |
| 	switch (updates.type()) {
 | |
| 	case mtpc_updates: v = &updates.c_updates().vchats.v; break;
 | |
| 	case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.v; break;
 | |
| 	default: LOG(("API Error: unexpected update cons %1 (MainWidget::inviteImportDone)").arg(updates.type())); break;
 | |
| 	}
 | |
| 	if (v && !v->isEmpty()) {
 | |
| 		if (v->front().type() == mtpc_chat) {
 | |
| 			Ui::showPeerHistory(peerFromChat(v->front().c_chat().vid.v), ShowAtTheEndMsgId);
 | |
| 		} else if (v->front().type() == mtpc_channel) {
 | |
| 			Ui::showPeerHistory(peerFromChannel(v->front().c_channel().vid.v), ShowAtTheEndMsgId);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool MainWidget::inviteImportFail(const RPCError &error) {
 | |
| 	if (MTP::isDefaultHandledError(error)) return false;
 | |
| 
 | |
| 	if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
 | |
| 		Ui::show(Box<InformBox>(lang(lng_join_channel_error)));
 | |
| 	} else if (error.code() == 400) {
 | |
| 		Ui::show(Box<InformBox>(lang(error.type() == qstr("USERS_TOO_MUCH") ? lng_group_invite_no_room : lng_group_invite_bad_link)));
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void MainWidget::startWithSelf(const MTPVector<MTPUser> &users) {
 | |
| 	auto &v = users.v;
 | |
| 	if (v.isEmpty()) {
 | |
| 		LOG(("Auth Error: self user not received."));
 | |
| 		return App::logOutDelayed();
 | |
| 	}
 | |
| 	start(&v[0]);
 | |
| }
 | |
| 
 | |
| void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *h) {
 | |
| 	PeerData *updatePeer = nullptr;
 | |
| 	bool changed = false;
 | |
| 	switch (settings.type()) {
 | |
| 	case mtpc_peerNotifySettingsEmpty:
 | |
| 		switch (peer.type()) {
 | |
| 		case mtpc_notifyAll: globalNotifyAllPtr = EmptyNotifySettings; break;
 | |
| 		case mtpc_notifyUsers: globalNotifyUsersPtr = EmptyNotifySettings; break;
 | |
| 		case mtpc_notifyChats: globalNotifyChatsPtr = EmptyNotifySettings; break;
 | |
| 		case mtpc_notifyPeer: {
 | |
| 			if ((updatePeer = App::peerLoaded(peerFromMTP(peer.c_notifyPeer().vpeer)))) {
 | |
| 				changed = (updatePeer->notify != EmptyNotifySettings);
 | |
| 				if (changed) {
 | |
| 					if (updatePeer->notify != UnknownNotifySettings) {
 | |
| 						delete updatePeer->notify;
 | |
| 					}
 | |
| 					updatePeer->notify = EmptyNotifySettings;
 | |
| 					App::unregMuted(updatePeer);
 | |
| 					if (!h) h = App::history(updatePeer->id);
 | |
| 					h->setMute(false);
 | |
| 				}
 | |
| 			}
 | |
| 		} break;
 | |
| 		}
 | |
| 	break;
 | |
| 	case mtpc_peerNotifySettings: {
 | |
| 		const auto &d(settings.c_peerNotifySettings());
 | |
| 		NotifySettingsPtr setTo = UnknownNotifySettings;
 | |
| 		switch (peer.type()) {
 | |
| 		case mtpc_notifyAll: setTo = globalNotifyAllPtr = &globalNotifyAll; break;
 | |
| 		case mtpc_notifyUsers: setTo = globalNotifyUsersPtr = &globalNotifyUsers; break;
 | |
| 		case mtpc_notifyChats: setTo = globalNotifyChatsPtr = &globalNotifyChats; break;
 | |
| 		case mtpc_notifyPeer: {
 | |
| 			if ((updatePeer = App::peerLoaded(peerFromMTP(peer.c_notifyPeer().vpeer)))) {
 | |
| 				if (updatePeer->notify == UnknownNotifySettings || updatePeer->notify == EmptyNotifySettings) {
 | |
| 					changed = true;
 | |
| 					updatePeer->notify = new NotifySettings();
 | |
| 				}
 | |
| 				setTo = updatePeer->notify;
 | |
| 			}
 | |
| 		} break;
 | |
| 		}
 | |
| 		if (setTo == UnknownNotifySettings) break;
 | |
| 
 | |
| 		auto sound = qs(d.vsound);
 | |
| 		changed = (setTo->flags != d.vflags.v) || (setTo->mute != d.vmute_until.v) || (setTo->sound != sound);
 | |
| 		if (changed) {
 | |
| 			setTo->flags = d.vflags.v;
 | |
| 			setTo->mute = d.vmute_until.v;
 | |
| 			setTo->sound = sound;
 | |
| 			if (updatePeer) {
 | |
| 				if (!h) h = App::history(updatePeer->id);
 | |
| 				int32 changeIn = 0;
 | |
| 				if (isNotifyMuted(setTo, &changeIn)) {
 | |
| 					AuthSession::Current().notifications().clearFromHistory(h);
 | |
| 					h->setMute(true);
 | |
| 					App::regMuted(updatePeer, changeIn);
 | |
| 				} else {
 | |
| 					h->setMute(false);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 	}
 | |
| 
 | |
| 	if (updatePeer) {
 | |
| 		if (_history->peer() == updatePeer) {
 | |
| 			_history->updateNotifySettings();
 | |
| 		}
 | |
| 		if (changed) {
 | |
| 			Notify::peerUpdatedDelayed(updatePeer, Notify::PeerUpdate::Flag::NotificationsEnabled);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateNotifySetting(PeerData *peer, NotifySettingStatus notify, SilentNotifiesStatus silent) {
 | |
| 	if (notify == NotifySettingDontChange && silent == SilentNotifiesDontChange) return;
 | |
| 
 | |
| 	updateNotifySettingPeers.insert(peer);
 | |
| 	int32 muteFor = 86400 * 365;
 | |
| 	if (peer->notify == EmptyNotifySettings) {
 | |
| 		if (notify == NotifySettingSetMuted || silent == SilentNotifiesSetSilent) {
 | |
| 			peer->notify = new NotifySettings();
 | |
| 		}
 | |
| 	} else if (peer->notify == UnknownNotifySettings) {
 | |
| 		peer->notify = new NotifySettings();
 | |
| 	}
 | |
| 	if (peer->notify != EmptyNotifySettings && peer->notify != UnknownNotifySettings) {
 | |
| 		if (notify != NotifySettingDontChange) {
 | |
| 			if ((notify != NotifySettingSetMuted) && peer->notify->sound.isEmpty()) {
 | |
| 				peer->notify->sound = qsl("default");
 | |
| 			}
 | |
| 			peer->notify->mute = (notify == NotifySettingSetMuted) ? (unixtime() + muteFor) : 0;
 | |
| 		}
 | |
| 		if (silent == SilentNotifiesSetSilent) {
 | |
| 			peer->notify->flags |= MTPDpeerNotifySettings::Flag::f_silent;
 | |
| 		} else if (silent == SilentNotifiesSetNotify) {
 | |
| 			peer->notify->flags &= ~MTPDpeerNotifySettings::Flag::f_silent;
 | |
| 		}
 | |
| 	}
 | |
| 	if (notify != NotifySettingDontChange) {
 | |
| 		if (notify == NotifySettingSetMuted) {
 | |
| 			App::regMuted(peer, muteFor + 1);
 | |
| 		} else {
 | |
| 			App::unregMuted(peer);
 | |
| 		}
 | |
| 		App::history(peer->id)->setMute(notify == NotifySettingSetMuted);
 | |
| 	}
 | |
| 	if (_history->peer() == peer) _history->updateNotifySettings();
 | |
| 	updateNotifySettingTimer.start(NotifySettingSaveTimeout);
 | |
| }
 | |
| 
 | |
| void MainWidget::incrementSticker(DocumentData *sticker) {
 | |
| 	if (!sticker || !sticker->sticker()) return;
 | |
| 	if (sticker->sticker()->set.type() == mtpc_inputStickerSetEmpty) return;
 | |
| 
 | |
| 	bool writeRecentStickers = false;
 | |
| 	auto &sets = Global::RefStickerSets();
 | |
| 	auto it = sets.find(Stickers::CloudRecentSetId);
 | |
| 	if (it == sets.cend()) {
 | |
| 		if (it == sets.cend()) {
 | |
| 			it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_recent_stickers), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special)));
 | |
| 		} else {
 | |
| 			it->title = lang(lng_recent_stickers);
 | |
| 		}
 | |
| 	}
 | |
| 	auto index = it->stickers.indexOf(sticker);
 | |
| 	if (index > 0) {
 | |
| 		it->stickers.removeAt(index);
 | |
| 	}
 | |
| 	if (index) {
 | |
| 		it->stickers.push_front(sticker);
 | |
| 		writeRecentStickers = true;
 | |
| 	}
 | |
| 
 | |
| 	// Remove that sticker from old recent, now it is in cloud recent stickers.
 | |
| 	bool writeOldRecent = false;
 | |
| 	auto &recent = cGetRecentStickers();
 | |
| 	for (auto i = recent.begin(), e = recent.end(); i != e; ++i) {
 | |
| 		if (i->first == sticker) {
 | |
| 			writeOldRecent = true;
 | |
| 			recent.erase(i);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	while (!recent.isEmpty() && it->stickers.size() + recent.size() > Global::StickersRecentLimit()) {
 | |
| 		writeOldRecent = true;
 | |
| 		recent.pop_back();
 | |
| 	}
 | |
| 
 | |
| 	if (writeOldRecent) {
 | |
| 		Local::writeUserSettings();
 | |
| 	}
 | |
| 
 | |
| 	// Remove that sticker from custom stickers, now it is in cloud recent stickers.
 | |
| 	bool writeInstalledStickers = false;
 | |
| 	auto custom = sets.find(Stickers::CustomSetId);
 | |
| 	if (custom != sets.cend()) {
 | |
| 		int removeIndex = custom->stickers.indexOf(sticker);
 | |
| 		if (removeIndex >= 0) {
 | |
| 			custom->stickers.removeAt(removeIndex);
 | |
| 			if (custom->stickers.isEmpty()) {
 | |
| 				sets.erase(custom);
 | |
| 			}
 | |
| 			writeInstalledStickers = true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (writeInstalledStickers) {
 | |
| 		Local::writeInstalledStickers();
 | |
| 	}
 | |
| 	if (writeRecentStickers) {
 | |
| 		Local::writeRecentStickers();
 | |
| 	}
 | |
| 	_history->updateRecentStickers();
 | |
| }
 | |
| 
 | |
| void MainWidget::activate() {
 | |
| 	if (_a_show.animating()) return;
 | |
| 	if (!_wideSection && !_overview) {
 | |
| 		if (_hider) {
 | |
| 			if (_hider->wasOffered()) {
 | |
| 				_hider->setFocus();
 | |
| 			} else {
 | |
| 				_dialogs->activate();
 | |
| 			}
 | |
|         } else if (App::wnd() && !Ui::isLayerShown()) {
 | |
| 			if (!cSendPaths().isEmpty()) {
 | |
| 				forwardLayer(-1);
 | |
| 			} else if (_history->peer()) {
 | |
| 				_history->activate();
 | |
| 			} else {
 | |
| 				_dialogs->activate();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	App::wnd()->fixOrder();
 | |
| }
 | |
| 
 | |
| void MainWidget::destroyData() {
 | |
| 	_history->destroyData();
 | |
| 	_dialogs->destroyData();
 | |
| }
 | |
| 
 | |
| void MainWidget::updateOnlineDisplayIn(int32 msecs) {
 | |
| 	_onlineUpdater.start(msecs);
 | |
| }
 | |
| 
 | |
| bool MainWidget::isActive() const {
 | |
| 	return !_isIdle && isVisible() && !_a_show.animating();
 | |
| }
 | |
| 
 | |
| bool MainWidget::doWeReadServerHistory() const {
 | |
| 	return isActive() && !_wideSection && !_overview && _history->doWeReadServerHistory();
 | |
| }
 | |
| 
 | |
| bool MainWidget::lastWasOnline() const {
 | |
| 	return _lastWasOnline;
 | |
| }
 | |
| 
 | |
| TimeMs MainWidget::lastSetOnline() const {
 | |
| 	return _lastSetOnline;
 | |
| }
 | |
| 
 | |
| int32 MainWidget::dlgsWidth() const {
 | |
| 	return _dialogs->width();
 | |
| }
 | |
| 
 | |
| MainWidget::~MainWidget() {
 | |
| 	if (App::main() == this) _history->showHistory(0, 0);
 | |
| 
 | |
| 	if (HistoryHider *hider = _hider) {
 | |
| 		_hider = nullptr;
 | |
| 		delete hider;
 | |
| 	}
 | |
| 	Messenger::Instance().mtp()->clearGlobalHandlers();
 | |
| }
 | |
| 
 | |
| void MainWidget::updateOnline(bool gotOtherOffline) {
 | |
| 	if (this != App::main()) return;
 | |
| 	AuthSession::Current().checkAutoLock();
 | |
| 
 | |
| 	bool isOnline = App::wnd()->isActive();
 | |
| 	int updateIn = Global::OnlineUpdatePeriod();
 | |
| 	if (isOnline) {
 | |
| 		auto idle = psIdleTime();
 | |
| 		if (idle >= Global::OfflineIdleTimeout()) {
 | |
| 			isOnline = false;
 | |
| 			if (!_isIdle) {
 | |
| 				_isIdle = true;
 | |
| 				_idleFinishTimer.start(900);
 | |
| 			}
 | |
| 		} else {
 | |
| 			updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle));
 | |
| 		}
 | |
| 	}
 | |
| 	auto ms = getms(true);
 | |
| 	if (isOnline != _lastWasOnline || (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms) || (isOnline && gotOtherOffline)) {
 | |
| 		if (_onlineRequest) {
 | |
| 			MTP::cancel(_onlineRequest);
 | |
| 			_onlineRequest = 0;
 | |
| 		}
 | |
| 
 | |
| 		_lastWasOnline = isOnline;
 | |
| 		_lastSetOnline = ms;
 | |
| 		_onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline)));
 | |
| 
 | |
| 		if (App::self()) {
 | |
| 			App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1);
 | |
| 			Notify::peerUpdatedDelayed(App::self(), Notify::PeerUpdate::Flag::UserOnlineChanged);
 | |
| 		}
 | |
| 		if (!isOnline) { // Went offline, so we need to save message draft to the cloud.
 | |
| 			saveDraftToCloud();
 | |
| 		}
 | |
| 
 | |
| 		_lastSetOnline = ms;
 | |
| 
 | |
| 		updateOnlineDisplay();
 | |
| 	} else if (isOnline) {
 | |
| 		updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms));
 | |
| 	}
 | |
| 	_onlineTimer.start(updateIn);
 | |
| }
 | |
| 
 | |
| void MainWidget::saveDraftToCloud() {
 | |
| 	_history->saveFieldToHistoryLocalDraft();
 | |
| 
 | |
| 	auto peer = _history->peer();
 | |
| 	if (auto history = App::historyLoaded(peer)) {
 | |
| 		writeDrafts(history);
 | |
| 
 | |
| 		auto localDraft = history->localDraft();
 | |
| 		auto cloudDraft = history->cloudDraft();
 | |
| 		if (!Data::draftsAreEqual(localDraft, cloudDraft)) {
 | |
| 			App::api()->saveDraftToCloudDelayed(history);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::applyCloudDraft(History *history) {
 | |
| 	_history->applyCloudDraft(history);
 | |
| }
 | |
| 
 | |
| void MainWidget::writeDrafts(History *history) {
 | |
| 	Local::MessageDraft storedLocalDraft, storedEditDraft;
 | |
| 	MessageCursor localCursor, editCursor;
 | |
| 	if (auto localDraft = history->localDraft()) {
 | |
| 		if (!Data::draftsAreEqual(localDraft, history->cloudDraft())) {
 | |
| 			storedLocalDraft = Local::MessageDraft(localDraft->msgId, localDraft->textWithTags, localDraft->previewCancelled);
 | |
| 			localCursor = localDraft->cursor;
 | |
| 		}
 | |
| 	}
 | |
| 	if (auto editDraft = history->editDraft()) {
 | |
| 		storedEditDraft = Local::MessageDraft(editDraft->msgId, editDraft->textWithTags, editDraft->previewCancelled);
 | |
| 		editCursor = editDraft->cursor;
 | |
| 	}
 | |
| 	Local::writeDrafts(history->peer->id, storedLocalDraft, storedEditDraft);
 | |
| 	Local::writeDraftCursors(history->peer->id, localCursor, editCursor);
 | |
| }
 | |
| 
 | |
| void MainWidget::checkIdleFinish() {
 | |
| 	if (this != App::main()) return;
 | |
| 	if (psIdleTime() < Global::OfflineIdleTimeout()) {
 | |
| 		_idleFinishTimer.stop();
 | |
| 		_isIdle = false;
 | |
| 		updateOnline();
 | |
| 		if (App::wnd()) App::wnd()->checkHistoryActivation();
 | |
| 	} else {
 | |
| 		_idleFinishTimer.start(900);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
 | |
| 	if (end <= from) return;
 | |
| 
 | |
| 	AuthSession::Current().checkAutoLock();
 | |
| 
 | |
| 	if (mtpTypeId(*from) == mtpc_new_session_created) {
 | |
| 		try {
 | |
| 			MTPNewSession newSession;
 | |
| 			newSession.read(from, end);
 | |
| 		} catch (mtpErrorUnexpected &) {
 | |
| 		}
 | |
| 		updSeq = 0;
 | |
| 		MTP_LOG(0, ("getDifference { after new_session_created }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 		return getDifference();
 | |
| 	} else {
 | |
| 		try {
 | |
| 			MTPUpdates updates;
 | |
| 			updates.read(from, end);
 | |
| 
 | |
| 			_lastUpdateTime = getms(true);
 | |
| 			noUpdatesTimer.start(NoUpdatesTimeout);
 | |
| 			if (!requestingDifference()) {
 | |
| 				feedUpdates(updates);
 | |
| 			}
 | |
| 		} catch (mtpErrorUnexpected &) { // just some other type
 | |
| 		}
 | |
| 	}
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| bool fwdInfoDataLoaded(const MTPMessageFwdHeader &header) {
 | |
| 	if (header.type() != mtpc_messageFwdHeader) {
 | |
| 		return true;
 | |
| 	}
 | |
| 	auto &info = header.c_messageFwdHeader();
 | |
| 	if (info.has_channel_id()) {
 | |
| 		if (!App::channelLoaded(peerFromChannel(info.vchannel_id))) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		if (info.has_from_id() && !App::user(peerFromUser(info.vfrom_id), PeerData::MinimalLoaded)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (info.has_from_id() && !App::userLoaded(peerFromUser(info.vfrom_id))) {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool mentionUsersLoaded(const MTPVector<MTPMessageEntity> &entities) {
 | |
| 	for_const (auto &entity, entities.v) {
 | |
| 		auto type = entity.type();
 | |
| 		if (type == mtpc_messageEntityMentionName) {
 | |
| 			if (!App::userLoaded(peerFromUser(entity.c_messageEntityMentionName().vuser_id))) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		} else if (type == mtpc_inputMessageEntityMentionName) {
 | |
| 			auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id;
 | |
| 			if (inputUser.type() == mtpc_inputUser) {
 | |
| 				if (!App::userLoaded(peerFromUser(inputUser.c_inputUser().vuser_id))) {
 | |
| 					return false;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| enum class DataIsLoadedResult {
 | |
| 	NotLoaded = 0,
 | |
| 	FromNotLoaded = 1,
 | |
| 	MentionNotLoaded = 2,
 | |
| 	Ok = 3,
 | |
| };
 | |
| DataIsLoadedResult allDataLoadedForMessage(const MTPMessage &msg) {
 | |
| 	switch (msg.type()) {
 | |
| 	case mtpc_message: {
 | |
| 		const MTPDmessage &d(msg.c_message());
 | |
| 		if (!d.is_post() && d.has_from_id()) {
 | |
| 			if (!App::userLoaded(peerFromUser(d.vfrom_id))) {
 | |
| 				return DataIsLoadedResult::FromNotLoaded;
 | |
| 			}
 | |
| 		}
 | |
| 		if (d.has_via_bot_id()) {
 | |
| 			if (!App::userLoaded(peerFromUser(d.vvia_bot_id))) {
 | |
| 				return DataIsLoadedResult::NotLoaded;
 | |
| 			}
 | |
| 		}
 | |
| 		if (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from)) {
 | |
| 			return DataIsLoadedResult::NotLoaded;
 | |
| 		}
 | |
| 		if (d.has_entities() && !mentionUsersLoaded(d.ventities)) {
 | |
| 			return DataIsLoadedResult::MentionNotLoaded;
 | |
| 		}
 | |
| 	} break;
 | |
| 	case mtpc_messageService: {
 | |
| 		const MTPDmessageService &d(msg.c_messageService());
 | |
| 		if (!d.is_post() && d.has_from_id()) {
 | |
| 			if (!App::userLoaded(peerFromUser(d.vfrom_id))) {
 | |
| 				return DataIsLoadedResult::FromNotLoaded;
 | |
| 			}
 | |
| 		}
 | |
| 		switch (d.vaction.type()) {
 | |
| 		case mtpc_messageActionChatAddUser: {
 | |
| 			for_const (const MTPint &userId, d.vaction.c_messageActionChatAddUser().vusers.v) {
 | |
| 				if (!App::userLoaded(peerFromUser(userId))) {
 | |
| 					return DataIsLoadedResult::NotLoaded;
 | |
| 				}
 | |
| 			}
 | |
| 		} break;
 | |
| 		case mtpc_messageActionChatJoinedByLink: {
 | |
| 			if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatJoinedByLink().vinviter_id))) {
 | |
| 				return DataIsLoadedResult::NotLoaded;
 | |
| 			}
 | |
| 		} break;
 | |
| 		case mtpc_messageActionChatDeleteUser: {
 | |
| 			if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatDeleteUser().vuser_id))) {
 | |
| 				return DataIsLoadedResult::NotLoaded;
 | |
| 			}
 | |
| 		} break;
 | |
| 		}
 | |
| 	} break;
 | |
| 	}
 | |
| 	return DataIsLoadedResult::Ok;
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
 | |
| 	switch (updates.type()) {
 | |
| 	case mtpc_updates: {
 | |
| 		auto &d = updates.c_updates();
 | |
| 		if (d.vseq.v) {
 | |
| 			if (d.vseq.v <= updSeq) return;
 | |
| 			if (d.vseq.v > updSeq + 1) {
 | |
| 				_bySeqUpdates.insert(d.vseq.v, updates);
 | |
| 				return _bySeqTimer.start(WaitForSkippedTimeout);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		feedUpdateVector(d.vupdates);
 | |
| 
 | |
| 		updSetState(0, d.vdate.v, updQts, d.vseq.v);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updatesCombined: {
 | |
| 		auto &d = updates.c_updatesCombined();
 | |
| 		if (d.vseq_start.v) {
 | |
| 			if (d.vseq_start.v <= updSeq) return;
 | |
| 			if (d.vseq_start.v > updSeq + 1) {
 | |
| 				_bySeqUpdates.insert(d.vseq_start.v, updates);
 | |
| 				return _bySeqTimer.start(WaitForSkippedTimeout);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		App::feedUsers(d.vusers);
 | |
| 		App::feedChats(d.vchats);
 | |
| 		feedUpdateVector(d.vupdates);
 | |
| 
 | |
| 		updSetState(0, d.vdate.v, updQts, d.vseq.v);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateShort: {
 | |
| 		auto &d = updates.c_updateShort();
 | |
| 		feedUpdate(d.vupdate);
 | |
| 
 | |
| 		updSetState(0, d.vdate.v, updQts, updSeq);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateShortMessage: {
 | |
| 		auto &d = updates.c_updateShortMessage();
 | |
| 		if (!App::userLoaded(d.vuser_id.v)
 | |
| 			|| (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))
 | |
| 			|| (d.has_entities() && !mentionUsersLoaded(d.ventities))
 | |
| 			|| (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from))) {
 | |
| 			MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 			return getDifference();
 | |
| 		}
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, updates)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
 | |
| 		auto item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), d.vid, d.is_out() ? MTP_int(AuthSession::CurrentUserId()) : d.vuser_id, MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(AuthSession::CurrentUserId())), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint()), NewMessageUnread);
 | |
| 		if (item) {
 | |
| 			_history->peerMessagesUpdated(item->history()->peer->id);
 | |
| 		}
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 
 | |
| 		updSetState(0, d.vdate.v, updQts, updSeq);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateShortChatMessage: {
 | |
| 		auto &d = updates.c_updateShortChatMessage();
 | |
| 		bool noFrom = !App::userLoaded(d.vfrom_id.v);
 | |
| 		if (!App::chatLoaded(d.vchat_id.v)
 | |
| 			|| noFrom
 | |
| 			|| (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))
 | |
| 			|| (d.has_entities() && !mentionUsersLoaded(d.ventities))
 | |
| 			|| (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from))) {
 | |
| 			MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 			if (noFrom && App::api()) App::api()->requestFullPeer(App::chatLoaded(d.vchat_id.v));
 | |
| 			return getDifference();
 | |
| 		}
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, updates)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
 | |
| 		auto item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint()), NewMessageUnread);
 | |
| 		if (item) {
 | |
| 			_history->peerMessagesUpdated(item->history()->peer->id);
 | |
| 		}
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 
 | |
| 		updSetState(0, d.vdate.v, updQts, updSeq);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateShortSentMessage: {
 | |
| 		auto &d = updates.c_updateShortSentMessage();
 | |
| 		if (randomId) {
 | |
| 			PeerId peerId = 0;
 | |
| 			QString text;
 | |
| 			App::histSentDataByItem(randomId, peerId, text);
 | |
| 
 | |
| 			feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date
 | |
| 			if (peerId) {
 | |
| 				if (auto item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
 | |
| 					if (d.has_entities() && !mentionUsersLoaded(d.ventities)) {
 | |
| 						AuthSession::Current().api().requestMessageData(item->history()->peer->asChannel(), item->id, ApiWrap::RequestMessageDataCallback());
 | |
| 					}
 | |
| 					auto entities = d.has_entities() ? entitiesFromMTP(d.ventities.v) : EntitiesInText();
 | |
| 					item->setText({ text, entities });
 | |
| 					item->updateMedia(d.has_media() ? (&d.vmedia) : nullptr);
 | |
| 					item->addToOverview(AddToOverviewNew);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, updates)) {
 | |
| 			return;
 | |
| 		}
 | |
| 		// update before applying skipped
 | |
| 		ptsApplySkippedUpdates();
 | |
| 
 | |
| 		updSetState(0, d.vdate.v, updQts, updSeq);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updatesTooLong: {
 | |
| 		MTP_LOG(0, ("getDifference { good - updatesTooLong received }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 		return getDifference();
 | |
| 	} break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MainWidget::feedUpdate(const MTPUpdate &update) {
 | |
| 	switch (update.type()) {
 | |
| 	case mtpc_updateNewMessage: {
 | |
| 		auto &d = update.c_updateNewMessage();
 | |
| 
 | |
| 		DataIsLoadedResult isDataLoaded = allDataLoadedForMessage(d.vmessage);
 | |
| 		if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) {
 | |
| 			MTP_LOG(0, ("getDifference { good - after not all data loaded in updateNewMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 
 | |
| 			// This can be if this update was created by grouping
 | |
| 			// some short message update into an updates vector.
 | |
| 			return getDifference();
 | |
| 		}
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		bool needToAdd = true;
 | |
| 		if (d.vmessage.type() == mtpc_message) { // index forwarded messages to links _overview
 | |
| 			if (App::checkEntitiesAndViewsUpdate(d.vmessage.c_message())) { // already in blocks
 | |
| 				LOG(("Skipping message, because it is already in blocks!"));
 | |
| 				needToAdd = false;
 | |
| 			}
 | |
| 		}
 | |
| 		if (needToAdd) {
 | |
| 			if (auto item = App::histories().addNewMessage(d.vmessage, NewMessageUnread)) {
 | |
| 				_history->peerMessagesUpdated(item->history()->peer->id);
 | |
| 			}
 | |
| 		}
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateMessageID: {
 | |
| 		auto &d = update.c_updateMessageID();
 | |
| 		auto msg = App::histItemByRandom(d.vrandom_id.v);
 | |
| 		if (msg.msg) {
 | |
| 			if (auto msgRow = App::histItemById(msg)) {
 | |
| 				if (App::histItemById(msg.channel, d.vid.v)) {
 | |
| 					auto history = msgRow->history();
 | |
| 					auto wasLast = (history->lastMsg == msgRow);
 | |
| 					msgRow->destroy();
 | |
| 					if (wasLast && !history->lastMsg) {
 | |
| 						checkPeerHistory(history->peer);
 | |
| 					}
 | |
| 					_history->peerMessagesUpdated();
 | |
| 				} else {
 | |
| 					App::historyUnregItem(msgRow);
 | |
| 					if (App::wnd()) App::wnd()->changingMsgId(msgRow, d.vid.v);
 | |
| 					msgRow->setId(d.vid.v);
 | |
| 					if (msgRow->history()->peer->isSelf()) {
 | |
| 						msgRow->history()->unregSendAction(App::self());
 | |
| 					}
 | |
| 					App::historyRegItem(msgRow);
 | |
| 					Ui::repaintHistoryItem(msgRow);
 | |
| 				}
 | |
| 			}
 | |
| 			App::historyUnregRandom(d.vrandom_id.v);
 | |
| 		}
 | |
| 		App::historyUnregSentData(d.vrandom_id.v);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateReadMessagesContents: {
 | |
| 		auto &d = update.c_updateReadMessagesContents();
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		auto &v = d.vmessages.v;
 | |
| 		for (int32 i = 0, l = v.size(); i < l; ++i) {
 | |
| 			if (auto item = App::histItemById(NoChannel, v.at(i).v)) {
 | |
| 				if (item->isMediaUnread()) {
 | |
| 					item->markMediaRead();
 | |
| 					Ui::repaintHistoryItem(item);
 | |
| 
 | |
| 					if (item->out() && item->history()->peer->isUser()) {
 | |
| 						auto when = requestingDifference() ? 0 : unixtime();
 | |
| 						item->history()->peer->asUser()->madeAction(when);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateReadHistoryInbox: {
 | |
| 		auto &d = update.c_updateReadHistoryInbox();
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		App::feedInboxRead(peerFromMTP(d.vpeer), d.vmax_id.v);
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateReadHistoryOutbox: {
 | |
| 		auto &d = update.c_updateReadHistoryOutbox();
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		auto peerId = peerFromMTP(d.vpeer);
 | |
| 		auto when = requestingDifference() ? 0 : unixtime();
 | |
| 		App::feedOutboxRead(peerId, d.vmax_id.v, when);
 | |
| 		if (_history->peer() && _history->peer()->id == peerId) {
 | |
| 			_history->update();
 | |
| 		}
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateWebPage: {
 | |
| 		auto &d = update.c_updateWebPage();
 | |
| 
 | |
| 		// update web page anyway
 | |
| 		App::feedWebPage(d.vwebpage);
 | |
| 		_history->updatePreview();
 | |
| 		webPagesOrGamesUpdate();
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateDeleteMessages: {
 | |
| 		auto &d = update.c_updateDeleteMessages();
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		App::feedWereDeleted(NoChannel, d.vmessages.v);
 | |
| 		_history->peerMessagesUpdated();
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateUserTyping: {
 | |
| 		auto &d = update.c_updateUserTyping();
 | |
| 		auto history = App::historyLoaded(peerFromUser(d.vuser_id));
 | |
| 		auto user = App::userLoaded(d.vuser_id.v);
 | |
| 		if (history && user) {
 | |
| 			auto when = requestingDifference() ? 0 : unixtime();
 | |
| 			App::histories().regSendAction(history, user, d.vaction, when);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChatUserTyping: {
 | |
| 		auto &d = update.c_updateChatUserTyping();
 | |
| 		History *history = 0;
 | |
| 		if (auto chat = App::chatLoaded(d.vchat_id.v)) {
 | |
| 			history = App::historyLoaded(chat->id);
 | |
| 		} else if (auto channel = App::channelLoaded(d.vchat_id.v)) {
 | |
| 			history = App::historyLoaded(channel->id);
 | |
| 		}
 | |
| 		auto user = (d.vuser_id.v == AuthSession::CurrentUserId()) ? nullptr : App::userLoaded(d.vuser_id.v);
 | |
| 		if (history && user) {
 | |
| 			auto when = requestingDifference() ? 0 : unixtime();
 | |
| 			App::histories().regSendAction(history, user, d.vaction, when);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChatParticipants: {
 | |
| 		App::feedParticipants(update.c_updateChatParticipants().vparticipants, true, false);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChatParticipantAdd: {
 | |
| 		App::feedParticipantAdd(update.c_updateChatParticipantAdd(), false);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChatParticipantDelete: {
 | |
| 		App::feedParticipantDelete(update.c_updateChatParticipantDelete(), false);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChatAdmins: {
 | |
| 		App::feedChatAdmins(update.c_updateChatAdmins(), false);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChatParticipantAdmin: {
 | |
| 		App::feedParticipantAdmin(update.c_updateChatParticipantAdmin(), false);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateUserStatus: {
 | |
| 		auto &d = update.c_updateUserStatus();
 | |
| 		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | |
| 			switch (d.vstatus.type()) {
 | |
| 			case mtpc_userStatusEmpty: user->onlineTill = 0; break;
 | |
| 			case mtpc_userStatusRecently:
 | |
| 				if (user->onlineTill > -10) { // don't modify pseudo-online
 | |
| 					user->onlineTill = -2;
 | |
| 				}
 | |
| 			break;
 | |
| 			case mtpc_userStatusLastWeek: user->onlineTill = -3; break;
 | |
| 			case mtpc_userStatusLastMonth: user->onlineTill = -4; break;
 | |
| 			case mtpc_userStatusOffline: user->onlineTill = d.vstatus.c_userStatusOffline().vwas_online.v; break;
 | |
| 			case mtpc_userStatusOnline: user->onlineTill = d.vstatus.c_userStatusOnline().vexpires.v; break;
 | |
| 			}
 | |
| 			App::markPeerUpdated(user);
 | |
| 			Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged);
 | |
| 		}
 | |
| 		if (d.vuser_id.v == AuthSession::CurrentUserId()) {
 | |
| 			if (d.vstatus.type() == mtpc_userStatusOffline || d.vstatus.type() == mtpc_userStatusEmpty) {
 | |
| 				updateOnline(true);
 | |
| 				if (d.vstatus.type() == mtpc_userStatusOffline) {
 | |
| 					cSetOtherOnline(d.vstatus.c_userStatusOffline().vwas_online.v);
 | |
| 				}
 | |
| 			} else if (d.vstatus.type() == mtpc_userStatusOnline) {
 | |
| 				cSetOtherOnline(d.vstatus.c_userStatusOnline().vexpires.v);
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateUserName: {
 | |
| 		auto &d = update.c_updateUserName();
 | |
| 		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | |
| 			if (user->contact <= 0) {
 | |
| 				user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
 | |
| 			} else {
 | |
| 				user->setName(textOneLine(user->firstName), textOneLine(user->lastName), user->nameOrPhone, textOneLine(qs(d.vusername)));
 | |
| 			}
 | |
| 			App::markPeerUpdated(user);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateUserPhoto: {
 | |
| 		auto &d = update.c_updateUserPhoto();
 | |
| 		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | |
| 			user->setPhoto(d.vphoto);
 | |
| 			user->loadUserpic();
 | |
| 			if (mtpIsTrue(d.vprevious)) {
 | |
| 				user->photosCount = -1;
 | |
| 				user->photos.clear();
 | |
| 			} else {
 | |
| 				if (user->photoId && user->photoId != UnknownPeerPhotoId) {
 | |
| 					if (user->photosCount > 0) ++user->photosCount;
 | |
| 					user->photos.push_front(App::photo(user->photoId));
 | |
| 				} else {
 | |
| 					user->photosCount = -1;
 | |
| 					user->photos.clear();
 | |
| 				}
 | |
| 			}
 | |
| 			App::markPeerUpdated(user);
 | |
| 			Notify::mediaOverviewUpdated(user, OverviewCount);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateContactRegistered: {
 | |
| 		auto &d = update.c_updateContactRegistered();
 | |
| 		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | |
| 			if (App::history(user->id)->loadedAtBottom()) {
 | |
| 				App::history(user->id)->addNewService(clientMsgId(), date(d.vdate), lng_action_user_registered(lt_from, user->name), 0);
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateContactLink: {
 | |
| 		auto &d = update.c_updateContactLink();
 | |
| 		App::feedUserLink(d.vuser_id, d.vmy_link, d.vforeign_link);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateNotifySettings: {
 | |
| 		auto &d = update.c_updateNotifySettings();
 | |
| 		applyNotifySetting(d.vpeer, d.vnotify_settings);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateDcOptions: {
 | |
| 		auto &d = update.c_updateDcOptions();
 | |
| 		Messenger::Instance().dcOptions()->addFromList(d.vdc_options);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateConfig: {
 | |
| 		Messenger::Instance().mtp()->configLoadRequest();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateUserPhone: {
 | |
| 		auto &d = update.c_updateUserPhone();
 | |
| 		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | |
| 			auto newPhone = qs(d.vphone);
 | |
| 			if (newPhone != user->phone()) {
 | |
| 				user->setPhone(newPhone);
 | |
| 				user->setName(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone().isEmpty()) ? QString() : App::formatPhone(user->phone()), user->username);
 | |
| 				App::markPeerUpdated(user);
 | |
| 
 | |
| 				Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserPhoneChanged);
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateNewEncryptedMessage: {
 | |
| 		auto &d = update.c_updateNewEncryptedMessage();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateEncryptedChatTyping: {
 | |
| 		auto &d = update.c_updateEncryptedChatTyping();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateEncryption: {
 | |
| 		auto &d = update.c_updateEncryption();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateEncryptedMessagesRead: {
 | |
| 		auto &d = update.c_updateEncryptedMessagesRead();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updatePhoneCall: {
 | |
| 		Calls::Current().handleUpdate(update.c_updatePhoneCall());
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateUserBlocked: {
 | |
| 		auto &d = update.c_updateUserBlocked();
 | |
| 		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | |
| 			user->setBlockStatus(mtpIsTrue(d.vblocked) ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
 | |
| 			App::markPeerUpdated(user);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateServiceNotification: {
 | |
| 		auto &d = update.c_updateServiceNotification();
 | |
| 		if (d.is_popup()) {
 | |
| 			Ui::show(Box<InformBox>(qs(d.vmessage)));
 | |
| 		} else {
 | |
| 			App::wnd()->serviceNotification({ qs(d.vmessage), entitiesFromMTP(d.ventities.v) }, d.vmedia);
 | |
| 			emit App::wnd()->checkNewAuthorization();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updatePrivacy: {
 | |
| 		auto &d = update.c_updatePrivacy();
 | |
| 		App::api()->handlePrivacyChange(d.vkey.type(), d.vrules);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updatePinnedDialogs: {
 | |
| 		auto &d = update.c_updatePinnedDialogs();
 | |
| 		if (d.has_order()) {
 | |
| 			auto allLoaded = true;
 | |
| 			auto &order = d.vorder.v;
 | |
| 			for_const (auto &peer, order) {
 | |
| 				auto peerId = peerFromMTP(peer);
 | |
| 				if (!App::historyLoaded(peerId)) {
 | |
| 					allLoaded = false;
 | |
| 					DEBUG_LOG(("API Error: pinned chat not loaded for peer %1").arg(peerId));
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 			if (allLoaded) {
 | |
| 				App::histories().clearPinned();
 | |
| 				for (auto i = order.size(); i != 0;) {
 | |
| 					auto history = App::historyLoaded(peerFromMTP(order[--i]));
 | |
| 					t_assert(history != nullptr);
 | |
| 					history->setPinnedDialog(true);
 | |
| 				}
 | |
| 			} else {
 | |
| 				_dialogs->loadPinnedDialogs();
 | |
| 			}
 | |
| 		} else {
 | |
| 			_dialogs->loadPinnedDialogs();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateDialogPinned: {
 | |
| 		auto &d = update.c_updateDialogPinned();
 | |
| 		auto peerId = peerFromMTP(d.vpeer);
 | |
| 		if (auto history = App::historyLoaded(peerId)) {
 | |
| 			history->setPinnedDialog(d.is_pinned());
 | |
| 		} else {
 | |
| 			DEBUG_LOG(("API Error: pinned chat not loaded for peer %1").arg(peerId));
 | |
| 			_dialogs->loadPinnedDialogs();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	/////// Channel updates
 | |
| 	case mtpc_updateChannel: {
 | |
| 		auto &d = update.c_updateChannel();
 | |
| 		if (auto channel = App::channelLoaded(d.vchannel_id.v)) {
 | |
| 			App::markPeerUpdated(channel);
 | |
| 			channel->inviter = 0;
 | |
| 			if (!channel->amIn()) {
 | |
| 				deleteConversation(channel, false);
 | |
| 			} else if (!channel->amCreator() && App::history(channel->id)) { // create history
 | |
| 				_updatedChannels.insert(channel, true);
 | |
| 				if (App::api()) App::api()->requestSelfParticipant(channel);
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateNewChannelMessage: {
 | |
| 		auto &d = update.c_updateNewChannelMessage();
 | |
| 		auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage)));
 | |
| 		auto isDataLoaded = allDataLoadedForMessage(d.vmessage);
 | |
| 		if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) {
 | |
| 			MTP_LOG(0, ("getDifference { good - after not all data loaded in updateNewChannelMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
 | |
| 
 | |
| 			// Request last active supergroup participants if the 'from' user was not loaded yet.
 | |
| 			// This will optimize similar getDifference() calls for almost all next messages.
 | |
| 			if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup() && App::api()) {
 | |
| 				if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.isEmpty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) {
 | |
| 					App::api()->requestLastParticipants(channel);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (!_byMinChannelTimer.isActive()) { // getDifference after timeout
 | |
| 				_byMinChannelTimer.start(WaitForSkippedTimeout);
 | |
| 			}
 | |
| 			return;
 | |
| 		}
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | |
| 				return;
 | |
| 			} else if (!channel->ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		bool needToAdd = true;
 | |
| 		if (d.vmessage.type() == mtpc_message) { // index forwarded messages to links _overview
 | |
| 			if (App::checkEntitiesAndViewsUpdate(d.vmessage.c_message())) { // already in blocks
 | |
| 				LOG(("Skipping message, because it is already in blocks!"));
 | |
| 				needToAdd = false;
 | |
| 			}
 | |
| 		}
 | |
| 		if (needToAdd) {
 | |
| 			if (auto item = App::histories().addNewMessage(d.vmessage, NewMessageUnread)) {
 | |
| 				_history->peerMessagesUpdated(item->history()->peer->id);
 | |
| 			}
 | |
| 		}
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			channel->ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateEditChannelMessage: {
 | |
| 		auto &d = update.c_updateEditChannelMessage();
 | |
| 		auto channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage)));
 | |
| 
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | |
| 				return;
 | |
| 			} else if (!channel->ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		App::updateEditedMessage(d.vmessage);
 | |
| 
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			channel->ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateEditMessage: {
 | |
| 		auto &d = update.c_updateEditMessage();
 | |
| 
 | |
| 		if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		App::updateEditedMessage(d.vmessage);
 | |
| 
 | |
| 		ptsApplySkippedUpdates();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChannelPinnedMessage: {
 | |
| 		auto &d = update.c_updateChannelPinnedMessage();
 | |
| 
 | |
| 		if (auto channel = App::channelLoaded(d.vchannel_id.v)) {
 | |
| 			if (channel->isMegagroup()) {
 | |
| 				channel->mgInfo->pinnedMsgId = d.vid.v;
 | |
| 				if (App::api()) {
 | |
| 					App::api()->fullPeerUpdated().notify(channel);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateReadChannelInbox: {
 | |
| 		auto &d = update.c_updateReadChannelInbox();
 | |
| 		App::feedInboxRead(peerFromChannel(d.vchannel_id.v), d.vmax_id.v);
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateReadChannelOutbox: {
 | |
| 		auto &d = update.c_updateReadChannelOutbox();
 | |
| 		auto peerId = peerFromChannel(d.vchannel_id.v);
 | |
| 		auto when = requestingDifference() ? 0 : unixtime();
 | |
| 		App::feedOutboxRead(peerId, d.vmax_id.v, when);
 | |
| 		if (_history->peer() && _history->peer()->id == peerId) {
 | |
| 			_history->update();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChannelWebPage: {
 | |
| 		auto &d = update.c_updateChannelWebPage();
 | |
| 
 | |
| 		// update web page anyway
 | |
| 		App::feedWebPage(d.vwebpage);
 | |
| 		_history->updatePreview();
 | |
| 		webPagesOrGamesUpdate();
 | |
| 
 | |
| 		auto channel = App::channelLoaded(d.vchannel_id.v);
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | |
| 				return;
 | |
| 			} else if (!channel->ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			channel->ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateDeleteChannelMessages: {
 | |
| 		auto &d = update.c_updateDeleteChannelMessages();
 | |
| 		auto channel = App::channelLoaded(d.vchannel_id.v);
 | |
| 
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			if (channel->ptsRequesting()) { // skip global updates while getting channel difference
 | |
| 				return;
 | |
| 			} else if (!channel->ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
 | |
| 				return;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// update before applying skipped
 | |
| 		App::feedWereDeleted(d.vchannel_id.v, d.vmessages.v);
 | |
| 		_history->peerMessagesUpdated();
 | |
| 
 | |
| 		if (channel && !_handlingChannelDifference) {
 | |
| 			channel->ptsApplySkippedUpdates();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChannelTooLong: {
 | |
| 		auto &d = update.c_updateChannelTooLong();
 | |
| 		if (auto channel = App::channelLoaded(d.vchannel_id.v)) {
 | |
| 			if (!d.has_pts() || channel->pts() < d.vpts.v) {
 | |
| 				getChannelDifference(channel);
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateChannelMessageViews: {
 | |
| 		auto &d = update.c_updateChannelMessageViews();
 | |
| 		if (auto item = App::histItemById(d.vchannel_id.v, d.vid.v)) {
 | |
| 			item->setViewsCount(d.vviews.v);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	////// Cloud sticker sets
 | |
| 	case mtpc_updateNewStickerSet: {
 | |
| 		auto &d = update.c_updateNewStickerSet();
 | |
| 		bool writeArchived = false;
 | |
| 		if (d.vstickerset.type() == mtpc_messages_stickerSet) {
 | |
| 			auto &set = d.vstickerset.c_messages_stickerSet();
 | |
| 			if (set.vset.type() == mtpc_stickerSet) {
 | |
| 				auto &s = set.vset.c_stickerSet();
 | |
| 				if (!s.is_masks()) {
 | |
| 					auto &sets = Global::RefStickerSets();
 | |
| 					auto it = sets.find(s.vid.v);
 | |
| 					if (it == sets.cend()) {
 | |
| 						it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed));
 | |
| 					} else {
 | |
| 						it->flags |= MTPDstickerSet::Flag::f_installed;
 | |
| 						if (it->flags & MTPDstickerSet::Flag::f_archived) {
 | |
| 							it->flags &= ~MTPDstickerSet::Flag::f_archived;
 | |
| 							writeArchived = true;
 | |
| 						}
 | |
| 					}
 | |
| 					auto inputSet = MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access));
 | |
| 					auto &v = set.vdocuments.v;
 | |
| 					it->stickers.clear();
 | |
| 					it->stickers.reserve(v.size());
 | |
| 					for (int i = 0, l = v.size(); i < l; ++i) {
 | |
| 						auto doc = App::feedDocument(v.at(i));
 | |
| 						if (!doc || !doc->sticker()) continue;
 | |
| 
 | |
| 						it->stickers.push_back(doc);
 | |
| 						if (doc->sticker()->set.type() != mtpc_inputStickerSetID) {
 | |
| 							doc->sticker()->set = inputSet;
 | |
| 						}
 | |
| 					}
 | |
| 					it->emoji.clear();
 | |
| 					auto &packs = set.vpacks.v;
 | |
| 					for (auto i = 0, l = packs.size(); i != l; ++i) {
 | |
| 						if (packs[i].type() != mtpc_stickerPack) continue;
 | |
| 						auto &pack = packs.at(i).c_stickerPack();
 | |
| 						if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon))) {
 | |
| 							emoji = emoji->original();
 | |
| 							auto &stickers = pack.vdocuments.v;
 | |
| 
 | |
| 							StickerPack p;
 | |
| 							p.reserve(stickers.size());
 | |
| 							for (auto j = 0, c = stickers.size(); j != c; ++j) {
 | |
| 								auto doc = App::document(stickers[j].v);
 | |
| 								if (!doc || !doc->sticker()) continue;
 | |
| 
 | |
| 								p.push_back(doc);
 | |
| 							}
 | |
| 							it->emoji.insert(emoji, p);
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					auto &order(Global::RefStickerSetsOrder());
 | |
| 					int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v);
 | |
| 					if (currentIndex != insertAtIndex) {
 | |
| 						if (currentIndex > 0) {
 | |
| 							order.removeAt(currentIndex);
 | |
| 						}
 | |
| 						order.insert(insertAtIndex, s.vid.v);
 | |
| 					}
 | |
| 
 | |
| 					auto custom = sets.find(Stickers::CustomSetId);
 | |
| 					if (custom != sets.cend()) {
 | |
| 						for (int32 i = 0, l = it->stickers.size(); i < l; ++i) {
 | |
| 							int32 removeIndex = custom->stickers.indexOf(it->stickers.at(i));
 | |
| 							if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
 | |
| 						}
 | |
| 						if (custom->stickers.isEmpty()) {
 | |
| 							sets.erase(custom);
 | |
| 						}
 | |
| 					}
 | |
| 					Local::writeInstalledStickers();
 | |
| 					if (writeArchived) Local::writeArchivedStickers();
 | |
| 					emit stickersUpdated();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateStickerSetsOrder: {
 | |
| 		auto &d = update.c_updateStickerSetsOrder();
 | |
| 		if (!d.is_masks()) {
 | |
| 			auto &order = d.vorder.v;
 | |
| 			auto &sets = Global::StickerSets();
 | |
| 			Stickers::Order result;
 | |
| 			for (int i = 0, l = order.size(); i < l; ++i) {
 | |
| 				if (sets.constFind(order.at(i).v) == sets.cend()) {
 | |
| 					break;
 | |
| 				}
 | |
| 				result.push_back(order.at(i).v);
 | |
| 			}
 | |
| 			if (result.size() != Global::StickerSetsOrder().size() || result.size() != order.size()) {
 | |
| 				Global::SetLastStickersUpdate(0);
 | |
| 				App::main()->updateStickers();
 | |
| 			} else {
 | |
| 				Global::SetStickerSetsOrder(result);
 | |
| 				Local::writeInstalledStickers();
 | |
| 				emit stickersUpdated();
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateStickerSets: {
 | |
| 		Global::SetLastStickersUpdate(0);
 | |
| 		App::main()->updateStickers();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateRecentStickers: {
 | |
| 		Global::SetLastStickersUpdate(0);
 | |
| 		App::main()->updateStickers();
 | |
| 	} break;
 | |
| 
 | |
| 	case mtpc_updateReadFeaturedStickers: {
 | |
| 		// We read some of the featured stickers, perhaps not all of them.
 | |
| 		// Here we don't know what featured sticker sets were read, so we
 | |
| 		// request all of them once again.
 | |
| 		Global::SetLastFeaturedStickersUpdate(0);
 | |
| 		App::main()->updateStickers();
 | |
| 	} break;
 | |
| 
 | |
| 	////// Cloud saved GIFs
 | |
| 	case mtpc_updateSavedGifs: {
 | |
| 		cSetLastSavedGifsUpdate(0);
 | |
| 		App::main()->updateStickers();
 | |
| 	} break;
 | |
| 
 | |
| 	////// Cloud drafts
 | |
| 	case mtpc_updateDraftMessage: {
 | |
| 		auto &peerDraft = update.c_updateDraftMessage();
 | |
| 		auto peerId = peerFromMTP(peerDraft.vpeer);
 | |
| 
 | |
| 		auto &draftMessage = peerDraft.vdraft;
 | |
| 		if (draftMessage.type() == mtpc_draftMessage) {
 | |
| 			auto &draft = draftMessage.c_draftMessage();
 | |
| 			Data::applyPeerCloudDraft(peerId, draft);
 | |
| 		} else {
 | |
| 			Data::clearPeerCloudDraft(peerId);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	}
 | |
| }
 | 
