/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mainwidget.h" #include #include #include #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_web_page.h" #include "data/data_game.h" #include "data/data_peer_values.h" #include "data/data_drafts.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_folder.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "ui/special_buttons.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/toast/toast.h" #include "ui/widgets/dropdown_menu.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "ui/focus_persister.h" #include "ui/resize_area.h" #include "ui/text_options.h" #include "ui/emoji_config.h" #include "window/section_memento.h" #include "window/section_widget.h" #include "window/window_connecting_widget.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "observer_peer.h" #include "apiwrap.h" #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_widget.h" #include "history/history_message.h" #include "history/media/history_media.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" #include "lang/lang_keys.h" #include "lang/lang_cloud_manager.h" #include "boxes/add_contact_box.h" #include "storage/file_upload.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/mute_settings_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/download_path_box.h" #include "boxes/connection_box.h" #include "storage/localstorage.h" #include "media/audio/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 "base/flat_set.h" #include "window/window_top_bar_wrap.h" #include "window/notifications_manager.h" #include "window/window_slide_animation.h" #include "window/window_session_controller.h" #include "window/themes/window_theme.h" #include "window/window_history_hider.h" #include "mtproto/dc_options.h" #include "core/file_utilities.h" #include "core/update_checker.h" #include "core/shortcuts.h" #include "core/application.h" #include "calls/calls_instance.h" #include "calls/calls_top_bar.h" #include "export/export_settings.h" #include "export/view/export_view_top_bar.h" #include "export/view/export_view_panel_controller.h" #include "auth_session.h" #include "support/support_helper.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" #include "styles/style_boxes.h" namespace { constexpr auto kChannelGetDifferenceLimit = 100; // 1s wait after show channel history before sending getChannelDifference. constexpr auto kWaitForChannelGetDifference = crl::time(1000); // If nothing is received in 1 min we ping. constexpr auto kNoUpdatesTimeout = 60 * 1000; // If nothing is received in 1 min when was a sleepmode we ping. constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000); // Send channel views each second. constexpr auto kSendViewsTimeout = crl::time(1000); // Cache background scaled image after 3s. constexpr auto kCacheBackgroundTimeout = 3000; enum class DataIsLoadedResult { NotLoaded = 0, FromNotLoaded = 1, MentionNotLoaded = 2, Ok = 3, }; bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) { return qs(data.vtype).startsWith(qstr("AUTH_KEY_DROP_")); } bool HasForceLogoutNotification(const MTPUpdates &updates) { const auto checkUpdate = [](const MTPUpdate &update) { if (update.type() != mtpc_updateServiceNotification) { return false; } return IsForceLogoutNotification( update.c_updateServiceNotification()); }; const auto checkVector = [&](const MTPVector &list) { for (const auto &update : list.v) { if (checkUpdate(update)) { return true; } } return false; }; switch (updates.type()) { case mtpc_updates: return checkVector(updates.c_updates().vupdates); case mtpc_updatesCombined: return checkVector(updates.c_updatesCombined().vupdates); case mtpc_updateShort: return checkUpdate(updates.c_updateShort().vupdate); } return false; } bool ForwardedInfoDataLoaded( not_null session, const MTPMessageFwdHeader &header) { return header.match([&](const MTPDmessageFwdHeader &data) { if (data.has_channel_id()) { if (!session->data().channelLoaded(data.vchannel_id.v)) { return false; } if (data.has_from_id()) { const auto from = session->data().user(data.vfrom_id.v); if (from->loadedStatus == PeerData::NotLoaded) { return false; } } } else if (data.has_from_id() && !session->data().userLoaded(data.vfrom_id.v)) { return false; } return true; }); } bool MentionUsersLoaded( not_null session, const MTPVector &entities) { for (const auto &entity : entities.v) { auto type = entity.type(); if (type == mtpc_messageEntityMentionName) { if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id.v)) { return false; } } else if (type == mtpc_inputMessageEntityMentionName) { auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id; if (inputUser.type() == mtpc_inputUser) { if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id.v)) { return false; } } } } return true; } DataIsLoadedResult AllDataLoadedForMessage( not_null session, const MTPMessage &message) { return message.match([&](const MTPDmessage &message) { if (!message.is_post() && message.has_from_id()) { if (!session->data().userLoaded(message.vfrom_id.v)) { return DataIsLoadedResult::FromNotLoaded; } } if (message.has_via_bot_id()) { if (!session->data().userLoaded(message.vvia_bot_id.v)) { return DataIsLoadedResult::NotLoaded; } } if (message.has_fwd_from() && !ForwardedInfoDataLoaded(session, message.vfwd_from)) { return DataIsLoadedResult::NotLoaded; } if (message.has_entities() && !MentionUsersLoaded(session, message.ventities)) { return DataIsLoadedResult::MentionNotLoaded; } return DataIsLoadedResult::Ok; }, [&](const MTPDmessageService &message) { if (!message.is_post() && message.has_from_id()) { if (!session->data().userLoaded(message.vfrom_id.v)) { return DataIsLoadedResult::FromNotLoaded; } } return message.vaction.match( [&](const MTPDmessageActionChatAddUser &action) { for (const MTPint &userId : action.vusers.v) { if (!session->data().userLoaded(userId.v)) { return DataIsLoadedResult::NotLoaded; } } return DataIsLoadedResult::Ok; }, [&](const MTPDmessageActionChatJoinedByLink &action) { if (!session->data().userLoaded(action.vinviter_id.v)) { return DataIsLoadedResult::NotLoaded; } return DataIsLoadedResult::Ok; }, [&](const MTPDmessageActionChatDeleteUser &action) { if (!session->data().userLoaded(action.vuser_id.v)) { return DataIsLoadedResult::NotLoaded; } return DataIsLoadedResult::Ok; }, [](const auto &) { return DataIsLoadedResult::Ok; }); }, [](const MTPDmessageEmpty &message) { return DataIsLoadedResult::Ok; }); } } // namespace enum StackItemType { HistoryStackItem, SectionStackItem, }; class StackItem { public: StackItem(PeerData *peer) : _peer(peer) { } PeerData *peer() const { return _peer; } void setThirdSectionMemento( std::unique_ptr &&memento); std::unique_ptr takeThirdSectionMemento() { return std::move(_thirdSectionMemento); } void setThirdSectionWeak(QPointer section) { _thirdSectionWeak = section; } QPointer thirdSectionWeak() const { return _thirdSectionWeak; } virtual StackItemType type() const = 0; virtual ~StackItem() = default; private: PeerData *_peer = nullptr; QPointer _thirdSectionWeak; std::unique_ptr _thirdSectionMemento; }; class StackItemHistory : public StackItem { public: StackItemHistory( not_null history, MsgId msgId, QList replyReturns) : StackItem(history->peer) , history(history) , msgId(msgId) , replyReturns(replyReturns) { } StackItemType type() const override { return HistoryStackItem; } not_null history; MsgId msgId; QList replyReturns; }; class StackItemSection : public StackItem { public: StackItemSection( std::unique_ptr &&memento); StackItemType type() const override { return SectionStackItem; } Window::SectionMemento *memento() const { return _memento.get(); } private: std::unique_ptr _memento; }; void StackItem::setThirdSectionMemento( std::unique_ptr &&memento) { _thirdSectionMemento = std::move(memento); } StackItemSection::StackItemSection( std::unique_ptr &&memento) : StackItem(nullptr) , _memento(std::move(memento)) { } struct MainWidget::SettingBackground { explicit SettingBackground(const Data::WallPaper &data); Data::WallPaper data; base::binary_guard generating; }; MainWidget::SettingBackground::SettingBackground( const Data::WallPaper &data) : data(data) { } MainWidget::MainWidget( QWidget *parent, not_null controller) : RpWidget(parent) , _controller(controller) , _dialogsWidth(st::columnMinimalWidthLeft) , _thirdColumnWidth(st::columnMinimalWidthThird) , _sideShadow(this) , _dialogs(this, _controller) , _history(this, _controller) , _playerPlaylist(this, _controller) , _noUpdatesTimer([=] { sendPing(); }) , _byPtsTimer([=] { getDifferenceByPts(); }) , _bySeqTimer([=] { getDifference(); }) , _byMinChannelTimer([=] { getDifference(); }) , _onlineTimer([=] { updateOnline(); }) , _idleFinishTimer([=] { checkIdleFinish(); }) , _failDifferenceTimer([=] { getDifferenceAfterFail(); }) , _cacheBackgroundTimer([=] { cacheBackground(); }) , _viewsIncrementTimer([=] { viewsIncrement(); }) { _controller->setDefaultFloatPlayerDelegate(floatPlayerDelegate()); _controller->floatPlayerClosed( ) | rpl::start_with_next([=](FullMsgId itemId) { floatPlayerClosed(itemId); }, lifetime()); _ptsWaiter.setRequesting(true); updateScrollColors(); setupConnectingWidget(); connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled())); connect(this, SIGNAL(dialogsUpdated()), _dialogs, SLOT(onListScroll())); connect(_history, &HistoryWidget::cancelled, [=] { handleHistoryBack(); }); Media::Player::instance()->updatedNotifier( ) | rpl::start_with_next([=](const Media::Player::TrackState &state) { handleAudioUpdate(state); }, lifetime()); subscribe(session().calls().currentCallChanged(), [this](Calls::Call *call) { setCurrentCall(call); }); session().data().currentExportView( ) | rpl::start_with_next([=](Export::View::PanelController *view) { setCurrentExportView(view); }, lifetime()); subscribe(_controller->dialogsListFocused(), [this](bool) { updateDialogsWidthAnimated(); }); subscribe(_controller->dialogsListDisplayForced(), [this](bool) { updateDialogsWidthAnimated(); }); rpl::merge( session().settings().dialogsWidthRatioChanges() | rpl::map([] { return rpl::empty_value(); }), session().settings().thirdColumnWidthChanges() | rpl::map([] { return rpl::empty_value(); }) ) | rpl::start_with_next( [this] { updateControlsGeometry(); }, lifetime()); // MSVC BUG + REGRESSION rpl::mappers::tuple :( using namespace rpl::mappers; _controller->activeChatValue( ) | rpl::map([](Dialogs::Key key) { const auto peer = key.peer(); auto canWrite = peer ? Data::CanWriteValue(peer) : rpl::single(false); return std::move( canWrite ) | rpl::map([=](bool can) { return std::make_tuple(key, can); }); }) | rpl::flatten_latest( ) | rpl::start_with_next([this](Dialogs::Key key, bool canWrite) { updateThirdColumnToCurrentChat(key, canWrite); }, lifetime()); QCoreApplication::instance()->installEventFilter(this); 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(); } }); 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) { const auto songState = Media::Player::instance()->getState(AudioMsgId::Type::Song); if (!songState.id || IsStoppedOrStopping(songState.state)) { closeBothPlayers(); } } }); subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); }); _dialogs->show(); if (Adaptive::OneColumn()) { _history->hide(); } else { _history->show(); } orderWidgets(); if (!Core::UpdaterDisabled()) { Core::UpdateChecker checker; checker.start(); } } AuthSession &MainWidget::session() const { return _controller->session(); } void MainWidget::setupConnectingWidget() { using namespace rpl::mappers; _connecting = std::make_unique( this, Window::AdaptiveIsOneColumn() | rpl::map(!_1)); } not_null MainWidget::floatPlayerDelegate() { return static_cast(this); } not_null MainWidget::floatPlayerWidget() { return this; } not_null MainWidget::floatPlayerController() { return _controller; } not_null MainWidget::floatPlayerGetSection( Window::Column column) { if (Adaptive::ThreeColumn()) { if (column == Window::Column::First) { return _dialogs; } else if (column == Window::Column::Second || !_thirdSection) { if (_mainSection) { return _mainSection; } return _history; } return _thirdSection; } else if (Adaptive::Normal()) { if (column == Window::Column::First) { return _dialogs; } else if (_mainSection) { return _mainSection; } return _history; } if (Adaptive::OneColumn() && selectingPeer()) { return _dialogs; } else if (_mainSection) { return _mainSection; } else if (!Adaptive::OneColumn() || _history->peer()) { return _history; } return _dialogs; } void MainWidget::floatPlayerEnumerateSections(Fn widget, Window::Column widgetColumn)> callback) { if (Adaptive::ThreeColumn()) { callback(_dialogs, Window::Column::First); if (_mainSection) { callback(_mainSection, Window::Column::Second); } else { callback(_history, Window::Column::Second); } if (_thirdSection) { callback(_thirdSection, Window::Column::Third); } } else if (Adaptive::Normal()) { callback(_dialogs, Window::Column::First); if (_mainSection) { callback(_mainSection, Window::Column::Second); } else { callback(_history, Window::Column::Second); } } else { if (Adaptive::OneColumn() && selectingPeer()) { callback(_dialogs, Window::Column::First); } else if (_mainSection) { callback(_mainSection, Window::Column::Second); } else if (!Adaptive::OneColumn() || _history->peer()) { callback(_history, Window::Column::Second); } else { callback(_dialogs, Window::Column::First); } } } bool MainWidget::floatPlayerIsVisible(not_null item) { auto isVisible = false; session().data().queryItemVisibility().notify({ item, &isVisible }, true); return isVisible; } void MainWidget::floatPlayerClosed(FullMsgId itemId) { if (_player) { const auto voiceData = Media::Player::instance()->current( AudioMsgId::Type::Voice); if (voiceData.contextId() == itemId) { _player->entity()->stopAndClose(); } } } bool MainWidget::setForwardDraft(PeerId peerId, MessageIdsList &&items) { Expects(peerId != 0); const auto peer = session().data().peer(peerId); const auto error = GetErrorTextForForward( peer, session().data().idsToItems(items)); if (!error.isEmpty()) { Ui::show(Box(error), LayerOption::KeepOther); return false; } peer->owner().history(peer)->setForwardDraft(std::move(items)); _controller->showPeerHistory( peer, SectionShow::Way::Forward, ShowAtUnreadMsgId); _history->cancelReply(); return true; } bool MainWidget::shareUrl( PeerId peerId, const QString &url, const QString &text) { Expects(peerId != 0); const auto peer = session().data().peer(peerId); if (!peer->canWrite()) { Ui::show(Box(lang(lng_share_cant))); return false; } TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() }; MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX }; auto history = peer->owner().history(peer); history->setLocalDraft( std::make_unique(textWithTags, 0, cursor, false)); history->clearEditDraft(); if (_history->peer() == peer) { _history->applyDraft(); } else { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } return true; } void MainWidget::replyToItem(not_null item) { if (_history->peer() == item->history()->peer || _history->peer() == item->history()->peer->migrateTo()) { _history->replyToMessage(item); } } bool MainWidget::inlineSwitchChosen(PeerId peerId, const QString &botAndQuery) { Expects(peerId != 0); const auto peer = session().data().peer(peerId); if (!peer->canWrite()) { Ui::show(Box(lang(lng_inline_switch_cant))); return false; } const auto h = peer->owner().history(peer); TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() }; MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX }; h->setLocalDraft(std::make_unique(textWithTags, 0, cursor, false)); h->clearEditDraft(); const auto opened = _history->peer() && (_history->peer() == peer); if (opened) { _history->applyDraft(); } else { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } return true; } void MainWidget::cancelForwarding(not_null history) { history->setForwardDraft({}); _history->updateForwarding(); } void MainWidget::finishForwarding(not_null history) { auto toForward = history->validateForwardDraft(); if (!toForward.empty()) { auto options = ApiWrap::SendOptions(history); session().api().forwardMessages(std::move(toForward), options); cancelForwarding(history); } session().data().sendHistoryChangeNotifications(); historyToDown(history); dialogsToUp(); } bool MainWidget::sendPaths(PeerId peerId) { Expects(peerId != 0); auto peer = session().data().peer(peerId); if (!peer->canWrite()) { Ui::show(Box(lang(lng_forward_send_files_cant))); return false; } else if (const auto key = Data::RestrictionErrorKey( peer, ChatRestriction::f_send_media)) { Ui::show(Box(lang(*key))); return false; } Ui::showPeerHistory(peer, ShowAtTheEndMsgId); return _history->confirmSendingFiles(cSendPaths()); } void MainWidget::onFilesOrForwardDrop( const PeerId &peerId, const QMimeData *data) { Expects(peerId != 0); if (data->hasFormat(qsl("application/x-td-forward"))) { if (!setForwardDraft(peerId, session().data().takeMimeForwardIds())) { // We've already released the mouse button, so the forwarding is cancelled. if (_hider) { _hider->startHide(); clearHider(_hider); } } } else { auto peer = session().data().peer(peerId); if (!peer->canWrite()) { Ui::show(Box(lang(lng_forward_send_files_cant))); return; } Ui::showPeerHistory(peer, ShowAtTheEndMsgId); _history->confirmSendingFiles(data); } } 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_historyMuteUpdated(History *history) { _dialogs->notify_historyMuteUpdated(history); } void MainWidget::clearHider(not_null instance) { if (_hider != instance) { return; } _hider.release(); if (Adaptive::OneColumn()) { if (_mainSection || (_history->peer() && _history->peer()->id)) { auto animationParams = ([=] { if (_mainSection) { return prepareMainSectionAnimation(_mainSection); } return prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0); })(); _dialogs->hide(); if (_mainSection) { _mainSection->showAnimated(Window::SlideDirection::FromRight, animationParams); } else { _history->showAnimated(Window::SlideDirection::FromRight, animationParams); } floatPlayerCheckVisibility(); } } } void MainWidget::hiderLayer(base::unique_qptr hider) { if (Core::App().locked()) { return; } _hider = std::move(hider); _hider->setParent(this); _hider->hidden( ) | rpl::start_with_next([=, instance = _hider.get()] { clearHider(instance); instance->deleteLater(); }, _hider->lifetime()); _hider->confirmed( ) | rpl::start_with_next([=] { _dialogs->onCancelSearch(); }, _hider->lifetime()); if (Adaptive::OneColumn()) { dialogsToUp(); _hider->hide(); auto animationParams = prepareDialogsAnimation(); if (_mainSection) { _mainSection->hide(); } else { _history->hide(); } if (_dialogs->isHidden()) { _dialogs->show(); updateControlsGeometry(); _dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams); } } else { _hider->show(); updateControlsGeometry(); _dialogs->setInnerFocus(); } floatPlayerCheckVisibility(); } void MainWidget::showForwardLayer(MessageIdsList &&items) { auto callback = [=, items = std::move(items)](PeerId peer) mutable { return setForwardDraft(peer, std::move(items)); }; hiderLayer(base::make_unique_q( this, lang(lng_forward_choose), std::move(callback))); } void MainWidget::showSendPathsLayer() { hiderLayer(base::make_unique_q( this, lang(lng_forward_choose), [=](PeerId peer) { return sendPaths(peer); })); if (_hider) { connect(_hider, &QObject::destroyed, [] { cSetSendPaths(QStringList()); }); } } void MainWidget::cancelUploadLayer(not_null item) { const auto itemId = item->fullId(); session().uploader().pause(itemId); const auto stopUpload = [=] { Ui::hideLayer(); if (const auto item = session().data().message(itemId)) { const auto history = item->history(); if (!IsServerMsgId(itemId.msg)) { item->destroy(); history->requestChatListMessage(); } else { item->returnSavedMedia(); session().uploader().cancel(item->fullId()); } session().data().sendHistoryChangeNotifications(); } session().uploader().unpause(); }; const auto continueUpload = [=] { session().uploader().unpause(); }; Ui::show(Box( lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), stopUpload, continueUpload)); } void MainWidget::deletePhotoLayer(PhotoData *photo) { if (!photo) return; Ui::show(Box(lang(lng_delete_photo_sure), lang(lng_box_delete), crl::guard(this, [=] { session().api().clearPeerPhoto(photo); Ui::hideLayer(); }))); } 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; } auto callback = [=](PeerId peer) { return shareUrl(peer, url, text); }; hiderLayer(base::make_unique_q( this, lang(lng_forward_choose), std::move(callback))); } void MainWidget::inlineSwitchLayer(const QString &botAndQuery) { auto callback = [=](PeerId peer) { return inlineSwitchChosen(peer, botAndQuery); }; hiderLayer(base::make_unique_q( this, lang(lng_inline_switch_choose), std::move(callback))); } bool MainWidget::selectingPeer() const { return _hider ? true : false; } void MainWidget::removeDialog(Dialogs::Key key) { _dialogs->removeDialog(key); } bool MainWidget::sendMessageFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("PEER_FLOOD")) { Ui::show(Box(PeerFloodErrorText(PeerFloodType::Send))); return true; } else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) { const auto link = textcmdLink( Core::App().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info)); const auto text = lng_error_public_groups_denied(lt_more_info, link); Ui::show(Box(text)); return true; } return false; } void MainWidget::cacheBackground() { if (Window::Theme::Background()->colorForFill()) { return; } else 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; } crl::time MainWidget::highlightStartTime(not_null item) const { return _history->highlightStartTime(item); } MsgId MainWidget::currentReplyToIdFor(not_null history) const { if (_history->history() == history) { return _history->replyToId(); } else if (const auto localDraft = history->localDraft()) { return localDraft->msgId; } return 0; } 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( not_null button, not_null msg, int row, int column) { _history->app_sendBotCallback(button, msg, row, column); } bool MainWidget::insertBotCommand(const QString &cmd) { return _history->insertBotCommand(cmd); } void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { _dialogs->searchMessages(query, inChat); if (Adaptive::OneColumn()) { Ui::showChatsList(); } else { _dialogs->setInnerFocus(); } } void MainWidget::itemEdited(not_null item) { if (_history->peer() == item->history()->peer || (_history->peer() && _history->peer() == item->history()->peer->migrateTo())) { _history->itemEdited(item); } } void MainWidget::checkLastUpdate(bool afterSleep) { auto n = crl::now(); if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? kNoUpdatesAfterSleepTimeout : kNoUpdatesTimeout)) { _lastUpdateTime = n; MTP::ping(); } } void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { using State = Media::Player::State; const auto document = state.id.audio(); if (!Media::Player::IsStoppedOrStopping(state.state)) { createPlayer(); } else if (state.state == State::StoppedAtStart) { closeBothPlayers(); } if (const auto item = session().data().message(state.id.contextId())) { session().data().requestItemRepaint(item); } if (const auto items = InlineBots::Layout::documentItems()) { if (const auto i = items->find(document); i != items->end()) { for (const auto item : i->second) { item->update(); } } } } void MainWidget::closeBothPlayers() { if (_player) { _player->hide(anim::type::normal); } _playerVolume.destroyDelayed(); _playerPlaylist->hideIgnoringEnterEvents(); Media::Player::instance()->stop(AudioMsgId::Type::Voice); Media::Player::instance()->stop(AudioMsgId::Type::Song); Shortcuts::ToggleMediaShortcuts(false); } void MainWidget::createPlayer() { if (!_player) { _player.create(this, object_ptr(this)); rpl::merge( _player->heightValue() | rpl::map([] { return true; }), _player->shownValue() ) | rpl::start_with_next( [this] { playerHeightUpdated(); }, _player->lifetime()); _player->entity()->setCloseCallback([this] { closeBothPlayers(); }); _playerVolume.create(this); _player->entity()->volumeWidgetCreated(_playerVolume); orderWidgets(); if (_a_show.animating()) { _player->show(anim::type::instant); _player->setVisible(false); Shortcuts::ToggleMediaShortcuts(true); } else { _player->hide(anim::type::instant); } } if (_player && !_player->toggled()) { if (!_a_show.animating()) { _player->show(anim::type::normal); _playerHeight = _contentScrollAddToY = _player->contentHeight(); updateControlsGeometry(); Shortcuts::ToggleMediaShortcuts(true); } } } void MainWidget::playerHeightUpdated() { if (!_player) { // Player could be already "destroyDelayed", but still handle events. return; } auto playerHeight = _player->contentHeight(); if (playerHeight != _playerHeight) { _contentScrollAddToY += playerHeight - _playerHeight; _playerHeight = playerHeight; updateControlsGeometry(); } if (!_playerHeight && _player->isHidden()) { const auto state = Media::Player::instance()->getState(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(this, _currentCall)); _callTopBar->heightValue( ) | rpl::start_with_next([this](int value) { callTopBarHeightUpdated(value); }, lifetime()); orderWidgets(); if (_a_show.animating()) { _callTopBar->show(anim::type::instant); _callTopBar->setVisible(false); } else { _callTopBar->hide(anim::type::instant); _callTopBar->show(anim::type::normal); _callTopBarHeight = _contentScrollAddToY = _callTopBar->height(); updateControlsGeometry(); } } void MainWidget::destroyCallTopBar() { if (_callTopBar) { _callTopBar->hide(anim::type::normal); } } void MainWidget::callTopBarHeightUpdated(int callTopBarHeight) { if (!callTopBarHeight && !_currentCall) { _callTopBar.destroyDelayed(); } if (callTopBarHeight != _callTopBarHeight) { _contentScrollAddToY += callTopBarHeight - _callTopBarHeight; _callTopBarHeight = callTopBarHeight; updateControlsGeometry(); } } void MainWidget::setCurrentExportView(Export::View::PanelController *view) { _currentExportView = view; if (_currentExportView) { _currentExportView->progressState( ) | rpl::start_with_next([=](Export::View::Content &&data) { if (!data.rows.empty() && data.rows[0].id == Export::View::Content::kDoneId) { LOG(("Export Info: Destroy top bar by Done.")); destroyExportTopBar(); } else if (!_exportTopBar) { LOG(("Export Info: Create top bar by State.")); createExportTopBar(std::move(data)); } else { _exportTopBar->entity()->updateData(std::move(data)); } }, _currentExportView->lifetime()); } else { LOG(("Export Info: Destroy top bar by controller removal.")); destroyExportTopBar(); } } void MainWidget::createExportTopBar(Export::View::Content &&data) { _exportTopBar.create( this, object_ptr(this, std::move(data))); _exportTopBar->entity()->clicks( ) | rpl::start_with_next([=] { if (_currentExportView) { _currentExportView->activatePanel(); } }, _exportTopBar->lifetime()); orderWidgets(); if (_a_show.animating()) { _exportTopBar->show(anim::type::instant); _exportTopBar->setVisible(false); } else { _exportTopBar->hide(anim::type::instant); _exportTopBar->show(anim::type::normal); _exportTopBarHeight = _contentScrollAddToY = _exportTopBar->contentHeight(); updateControlsGeometry(); } rpl::merge( _exportTopBar->heightValue() | rpl::map([] { return true; }), _exportTopBar->shownValue() ) | rpl::start_with_next([=] { exportTopBarHeightUpdated(); }, _exportTopBar->lifetime()); } void MainWidget::destroyExportTopBar() { if (_exportTopBar) { _exportTopBar->hide(anim::type::normal); } } void MainWidget::exportTopBarHeightUpdated() { if (!_exportTopBar) { // Player could be already "destroyDelayed", but still handle events. return; } const auto exportTopBarHeight = _exportTopBar->contentHeight(); if (exportTopBarHeight != _exportTopBarHeight) { _contentScrollAddToY += exportTopBarHeight - _exportTopBarHeight; _exportTopBarHeight = exportTopBarHeight; updateControlsGeometry(); } if (!_exportTopBarHeight && _exportTopBar->isHidden()) { _exportTopBar.destroyDelayed(); } } void MainWidget::documentLoadProgress(FileLoader *loader) { if (const auto documentId = loader ? loader->objId() : 0) { documentLoadProgress(session().data().document(documentId)); } } void MainWidget::documentLoadProgress(DocumentData *document) { session().data().requestDocumentViewRepaint(document); session().documentUpdated.notify(document, true); if (!document->loaded() && document->isAudioFile()) { Media::Player::instance()->documentLoadProgress(document); } } void MainWidget::documentLoadFailed(FileLoader *loader, bool started) { const auto documentId = loader ? loader->objId() : 0; if (!documentId) return; const auto document = session().data().document(documentId); if (started) { const auto origin = loader->fileOrigin(); const auto failedFileName = loader->fileName(); Ui::show(Box(lang(lng_download_finish_failed), crl::guard(this, [=] { Ui::hideLayer(); if (document) { document->save(origin, failedFileName); } }))); } else { // Sometimes we have LOCATION_INVALID error in documents / stickers. // Sometimes FILE_REFERENCE_EXPIRED could not be handled. // //Ui::show(Box(lang(lng_download_path_failed), lang(lng_download_path_settings), crl::guard(this, [=] { // Global::SetDownloadPath(QString()); // Global::SetDownloadPathBookmark(QByteArray()); // Ui::show(Box()); // 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::onSendFileConfirm( const std::shared_ptr &file, const std::optional &oldId) { _history->sendFileConfirmed(file, oldId); } bool MainWidget::onSendSticker(DocumentData *document) { return _history->sendExistingDocument(document); } void MainWidget::dialogsCancelled() { if (_hider) { _hider->startHide(); clearHider(_hider); } _history->activate(); } bool MainWidget::isIdle() const { return _isIdle; } void MainWidget::clearCachedBackground() { _cachedBackground = QPixmap(); _cacheBackgroundTimer.cancel(); 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.callOnce(kCacheBackgroundTimeout); } return QPixmap(); } void MainWidget::updateScrollColors() { _history->updateScrollColors(); } void MainWidget::setChatBackground( const Data::WallPaper &background, QImage &&image) { using namespace Window::Theme; if (isReadyChatBackground(background, image)) { setReadyChatBackground(background, std::move(image)); return; } _background = std::make_unique(background); _background->data.loadDocument(); checkChatBackground(); const auto tile = Data::IsLegacy1DefaultWallPaper(background); using Update = Window::Theme::BackgroundUpdate; Window::Theme::Background()->notify(Update(Update::Type::Start, tile)); } bool MainWidget::isReadyChatBackground( const Data::WallPaper &background, const QImage &image) const { return !image.isNull() || !background.document(); } void MainWidget::setReadyChatBackground( const Data::WallPaper &background, QImage &&image) { using namespace Window::Theme; if (image.isNull() && !background.document() && background.thumbnail() && background.thumbnail()->loaded()) { image = background.thumbnail()->original(); } const auto resetToDefault = image.isNull() && !background.document() && !background.backgroundColor() && !Data::IsLegacy1DefaultWallPaper(background); const auto ready = resetToDefault ? Data::DefaultWallPaper() : background; Background()->set(ready, std::move(image)); const auto tile = Data::IsLegacy1DefaultWallPaper(ready); Background()->setTile(tile); Ui::ForceFullRepaint(this); } bool MainWidget::chatBackgroundLoading() { return (_background != nullptr); } float64 MainWidget::chatBackgroundProgress() const { if (_background) { if (_background->generating) { return 1.; } else if (const auto document = _background->data.document()) { return document->progress(); } else if (const auto thumbnail = _background->data.thumbnail()) { return thumbnail->progress(); } } return 1.; } void MainWidget::checkChatBackground() { if (!_background || _background->generating) { return; } const auto document = _background->data.document(); Assert(document != nullptr); if (!document->loaded()) { return; } const auto generateCallback = [=](QImage &&image) { const auto background = base::take(_background); const auto ready = image.isNull() ? Data::DefaultWallPaper() : background->data; setReadyChatBackground(ready, std::move(image)); }; _background->generating = Data::ReadImageAsync( document, Window::Theme::ProcessBackgroundImage, generateCallback); } Image *MainWidget::newBackgroundThumb() { return _background ? _background->data.thumbnail() : nullptr; } void MainWidget::messageDataReceived(ChannelData *channel, MsgId msgId) { _history->messageDataReceived(channel, msgId); } void MainWidget::updateBotKeyboard(History *h) { _history->updateBotKeyboard(h); } void MainWidget::pushReplyReturn(not_null item) { _history->pushReplyReturn(item); } void MainWidget::setInnerFocus() { if (_hider || !_history->peer()) { if (!_hider && _mainSection) { _mainSection->setInnerFocus(); } else if (!_hider && _thirdSection) { _thirdSection->setInnerFocus(); } else { _dialogs->setInnerFocus(); } } else if (_mainSection) { _mainSection->setInnerFocus(); } else if (_history->peer() || !_thirdSection) { _history->setInnerFocus(); } else { _thirdSection->setInnerFocus(); } } void MainWidget::scheduleViewIncrement(HistoryItem *item) { PeerData *peer = item->history()->peer; auto 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); auto j = _viewsToIncrement.find(peer); if (j == _viewsToIncrement.cend()) { j = _viewsToIncrement.insert(peer, ViewsIncrementMap()); _viewsIncrementTimer.callOnce(kSendViewsTimeout); } j.value().insert(item->id, true); } void MainWidget::viewsIncrement() { for (auto i = _viewsToIncrement.begin(); i != _viewsToIncrement.cend();) { if (_viewsIncrementRequests.contains(i.key())) { ++i; continue; } QVector 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())); } auto req = MTP::send(MTPmessages_GetMessagesViews(i.key()->input, MTP_vector(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 ids, const MTPVector &result, mtpRequestId req) { auto &v = result.v; if (ids.size() == v.size()) { for (auto 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 = session().data().message(channel, ids.at(j).v)) { item->setViewsCount(v.at(j).v); } } _viewsIncrementRequests.erase(i); break; } } } if (!_viewsToIncrement.isEmpty() && !_viewsIncrementTimer.isActive()) { _viewsIncrementTimer.callOnce(kSendViewsTimeout); } } bool MainWidget::viewsIncrementFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; for (auto i = _viewsIncrementRequests.begin(); i != _viewsIncrementRequests.cend(); ++i) { if (i.value() == req) { _viewsIncrementRequests.erase(i); break; } } if (!_viewsToIncrement.isEmpty() && !_viewsIncrementTimer.isActive()) { _viewsIncrementTimer.callOnce(kSendViewsTimeout); } return false; } void MainWidget::refreshDialog(Dialogs::Key key) { _dialogs->refreshDialog(key); } void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) { if (selectingPeer()) { _hider->offerPeer(peerId); } else { Ui::showPeerHistory(peerId, showAtMsgId); } } void MainWidget::clearBotStartToken(PeerData *peer) { if (peer && peer->isUser() && peer->asUser()->isBot()) { peer->asUser()->botInfo->startToken = QString(); } } void MainWidget::ctrlEnterSubmitUpdated() { _history->updateFieldSubmitSettings(); } void MainWidget::ui_showPeerHistory( PeerId peerId, const SectionShow ¶ms, MsgId showAtMsgId) { if (auto peer = session().data().peerLoaded(peerId)) { if (peer->migrateTo()) { peer = peer->migrateTo(); peerId = peer->id; if (showAtMsgId > 0) showAtMsgId = -showAtMsgId; } const auto unavailable = peer->unavailableReason(); if (!unavailable.isEmpty()) { if (params.activation != anim::activation::background) { Ui::show(Box(unavailable)); } return; } } _controller->dialogsListFocused().set(false, true); _a_dialogsWidth.stop(); using Way = SectionShow::Way; auto way = params.way; bool back = (way == Way::Backward || !peerId); bool foundInStack = !peerId; if (foundInStack || (way == Way::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 (const auto activeChat = _controller->activeChatCurrent()) { if (const auto peer = activeChat.peer()) { if (way == Way::Forward && peer->id == peerId) { way = Way::ClearStack; } } } } const auto wasActivePeer = _controller->activeChatCurrent().peer(); if (params.activation != anim::activation::background) { Ui::hideSettingsAndLayer(); } if (_hider) { _hider->startHide(); _hider.release(); } auto animatedShow = [&] { if (_a_show.animating() || Core::App().locked() || (params.animated == anim::type::instant)) { return false; } if (!peerId) { if (Adaptive::OneColumn()) { return _dialogs->isHidden(); } else { return false; } } if (_history->isHidden()) { if (!Adaptive::OneColumn() && way == Way::ClearStack) { return false; } return (_mainSection != nullptr) || (Adaptive::OneColumn() && !_dialogs->isHidden()); } if (back || way == Way::Forward) { return true; } return false; }; auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams(); if (!back && (way != Way::ClearStack)) { // This may modify the current section, for example remove its contents. saveSectionInStack(); } if (_history->peer() && _history->peer()->id != peerId && way != Way::Forward) { clearBotStartToken(_history->peer()); } _history->showHistory(peerId, showAtMsgId); auto noPeer = !_history->peer(); auto onlyDialogs = noPeer && Adaptive::OneColumn(); if (_mainSection) { _mainSection->hide(); _mainSection->deleteLater(); _mainSection = nullptr; } updateControlsGeometry(); if (noPeer) { _controller->setActiveChatEntry(Dialogs::Key()); } 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 { const auto nowActivePeer = _controller->activeChatCurrent().peer(); if (nowActivePeer && nowActivePeer != wasActivePeer) { if (const auto channel = nowActivePeer->asChannel()) { channel->ptsWaitingForShortPoll( kWaitForChannelGetDifference); } _viewsIncremented.remove(nowActivePeer); } 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(); crl::on_main(App::wnd(), [] { App::wnd()->setInnerFocus(); }); } } } if (!_dialogs->isHidden()) { if (!back) { if (const auto history = _history->history()) { _dialogs->scrollToEntry(Dialogs::RowDescriptor( history, FullMsgId(history->channelId(), showAtMsgId))); } } _dialogs->update(); } floatPlayerCheckVisibility(); } PeerData *MainWidget::ui_getPeerForMouseAction() { return _history->ui_getPeerForMouseAction(); } PeerData *MainWidget::peer() { return _history->peer(); } void MainWidget::saveSectionInStack() { if (_mainSection) { if (auto memento = _mainSection->createMemento()) { _stack.push_back(std::make_unique( std::move(memento))); _stack.back()->setThirdSectionWeak(_thirdSection.data()); } } else if (const auto history = _history->history()) { _stack.push_back(std::make_unique( history, _history->msgId(), _history->replyReturns())); _stack.back()->setThirdSectionWeak(_thirdSection.data()); } } void MainWidget::showSection( Window::SectionMemento &&memento, const SectionShow ¶ms) { if (_mainSection && _mainSection->showInternal( &memento, params)) { if (const auto entry = _mainSection->activeChat(); entry.key) { _controller->setActiveChatEntry(entry); } return; // // Now third section handles only its own showSection() requests. // General showSection() should show layer or main_section instead. // //} else if (_thirdSection && _thirdSection->showInternal( // &memento, // params)) { // return; } // If the window was not resized, but we've enabled // tabbedSelectorSectionEnabled or thirdSectionInfoEnabled // we need to update adaptive layout to Adaptive::ThirdColumn(). updateColumnLayout(); showNewSection( std::move(memento), params); } void MainWidget::updateColumnLayout() { updateWindowAdaptiveLayout(); } Window::SectionSlideParams MainWidget::prepareThirdSectionAnimation(Window::SectionWidget *section) { Expects(_thirdSection != nullptr); Window::SectionSlideParams result; result.withTopBarShadow = section->hasTopBarShadow(); if (!_thirdSection->hasTopBarShadow()) { result.withTopBarShadow = false; } floatPlayerHideAll(); auto sectionTop = getThirdSectionTop(); result.oldContentCache = _thirdSection->grabForShowAnimation(result); floatPlayerShowVisible(); return result; } Window::SectionSlideParams MainWidget::prepareShowAnimation( bool willHaveTopBarShadow) { Window::SectionSlideParams result; result.withTopBarShadow = willHaveTopBarShadow; if (selectingPeer() && Adaptive::OneColumn()) { result.withTopBarShadow = false; } else if (_mainSection) { if (!_mainSection->hasTopBarShadow()) { result.withTopBarShadow = false; } } else if (!_history->peer()) { result.withTopBarShadow = false; } floatPlayerHideAll(); if (_player) { _player->hideShadow(); } auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden(); if (playerVolumeVisible) { _playerVolume->hide(); } auto playerPlaylistVisible = !_playerPlaylist->isHidden(); if (playerPlaylistVisible) { _playerPlaylist->hide(); } auto sectionTop = getMainSectionTop(); if (selectingPeer() && Adaptive::OneColumn()) { result.oldContentCache = Ui::GrabWidget(this, QRect( 0, sectionTop, _dialogsWidth, height() - sectionTop)); } else if (_mainSection) { result.oldContentCache = _mainSection->grabForShowAnimation(result); } else if (!Adaptive::OneColumn() || !_history->isHidden()) { result.oldContentCache = _history->grabForShowAnimation(result); } else { result.oldContentCache = Ui::GrabWidget(this, QRect( 0, sectionTop, _dialogsWidth, height() - sectionTop)); } if (playerVolumeVisible) { _playerVolume->show(); } if (playerPlaylistVisible) { _playerPlaylist->show(); } if (_player) { _player->showShadow(); } floatPlayerShowVisible(); return result; } Window::SectionSlideParams MainWidget::prepareMainSectionAnimation(Window::SectionWidget *section) { return prepareShowAnimation(section->hasTopBarShadow()); } Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) { return prepareShowAnimation(historyPeerId != 0); } Window::SectionSlideParams MainWidget::prepareDialogsAnimation() { return prepareShowAnimation(false); } void MainWidget::showNewSection( Window::SectionMemento &&memento, const SectionShow ¶ms) { using Column = Window::Column; auto saveInStack = (params.way == SectionShow::Way::Forward); auto thirdSectionTop = getThirdSectionTop(); auto newThirdGeometry = QRect( width() - st::columnMinimalWidthThird, thirdSectionTop, st::columnMinimalWidthThird, height() - thirdSectionTop); auto newThirdSection = (Adaptive::ThreeColumn() && params.thirdColumn) ? memento.createWidget( this, _controller, Column::Third, newThirdGeometry) : nullptr; if (newThirdSection) { saveInStack = false; } else if (auto layer = memento.createLayer(_controller, rect())) { if (params.activation != anim::activation::background) { Ui::hideLayer(anim::type::instant); } _controller->showSpecialLayer(std::move(layer)); return; } if (params.activation != anim::activation::background) { Ui::hideSettingsAndLayer(); } QPixmap animCache; _controller->dialogsListFocused().set(false, true); _a_dialogsWidth.stop(); auto mainSectionTop = getMainSectionTop(); auto newMainGeometry = QRect( _history->x(), mainSectionTop, _history->width(), height() - mainSectionTop); auto newMainSection = newThirdSection ? nullptr : memento.createWidget( this, _controller, Adaptive::OneColumn() ? Column::First : Column::Second, newMainGeometry); Assert(newMainSection || newThirdSection); auto animatedShow = [&] { if (_a_show.animating() || Core::App().locked() || (params.animated == anim::type::instant) || memento.instant()) { return false; } if (!Adaptive::OneColumn() && params.way == SectionShow::Way::ClearStack) { return false; } else if (Adaptive::OneColumn() || (newThirdSection && _thirdSection) || (newMainSection && isMainSectionShown())) { return true; } return false; }(); auto animationParams = animatedShow ? (newThirdSection ? prepareThirdSectionAnimation(newThirdSection) : prepareMainSectionAnimation(newMainSection)) : Window::SectionSlideParams(); setFocus(); // otherwise dialogs widget could be focused. if (saveInStack) { // This may modify the current section, for example remove its contents. saveSectionInStack(); } auto &settingSection = newThirdSection ? _thirdSection : _mainSection; if (newThirdSection) { _thirdSection = std::move(newThirdSection); if (!_thirdShadow) { _thirdShadow.create(this); _thirdShadow->show(); orderWidgets(); } updateControlsGeometry(); } else { if (_mainSection) { _mainSection->hide(); _mainSection->deleteLater(); _mainSection = nullptr; } _mainSection = std::move(newMainSection); updateControlsGeometry(); _history->finishAnimating(); _history->showHistory(0, 0); _history->hide(); if (Adaptive::OneColumn()) _dialogs->hide(); } if (animationParams) { auto back = (params.way == SectionShow::Way::Backward); auto direction = (back || settingSection->forceAnimateBack()) ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; if (Adaptive::OneColumn()) { _controller->removeLayerBlackout(); } settingSection->showAnimated(direction, animationParams); } else { settingSection->showFast(); } if (settingSection.data() == _mainSection.data()) { if (const auto entry = _mainSection->activeChat(); entry.key) { _controller->setActiveChatEntry(entry); } } floatPlayerCheckVisibility(); orderWidgets(); } void MainWidget::checkMainSectionToLayer() { if (!_mainSection) { return; } Ui::FocusPersister persister(this); if (auto layer = _mainSection->moveContentToLayer(rect())) { dropMainSection(_mainSection); _controller->showSpecialLayer( std::move(layer), anim::type::instant); } } void MainWidget::dropMainSection(Window::SectionWidget *widget) { if (_mainSection != widget) { return; } _mainSection.destroy(); _controller->showBackFromStack( SectionShow( anim::type::instant, anim::activation::background)); } bool MainWidget::isMainSectionShown() const { return _mainSection || _history->peer(); } bool MainWidget::isThirdSectionShown() const { return _thirdSection != nullptr; } bool MainWidget::stackIsEmpty() const { return _stack.empty(); } void MainWidget::showBackFromStack( const SectionShow ¶ms) { if (selectingPeer()) { return; } if (_stack.empty()) { _controller->clearSectionStack(params); crl::on_main(App::wnd(), [] { App::wnd()->setInnerFocus(); }); return; } auto item = std::move(_stack.back()); _stack.pop_back(); if (auto currentHistoryPeer = _history->peer()) { clearBotStartToken(currentHistoryPeer); } _thirdSectionFromStack = item->takeThirdSectionMemento(); if (item->type() == HistoryStackItem) { auto historyItem = static_cast(item.get()); _controller->showPeerHistory( historyItem->peer()->id, params.withWay(SectionShow::Way::Backward), ShowAtUnreadMsgId); _history->setReplyReturns(historyItem->peer()->id, historyItem->replyReturns); } else if (item->type() == SectionStackItem) { auto sectionItem = static_cast(item.get()); showNewSection( std::move(*sectionItem->memento()), params.withWay(SectionShow::Way::Backward)); } if (_thirdSectionFromStack && _thirdSection) { _controller->showSection( std::move(*base::take(_thirdSectionFromStack)), SectionShow( SectionShow::Way::ClearStack, anim::type::instant, anim::activation::background)); } } void MainWidget::orderWidgets() { _dialogs->raise(); if (_player) { _player->raise(); } if (_exportTopBar) { _exportTopBar->raise(); } if (_callTopBar) { _callTopBar->raise(); } if (_playerVolume) { _playerVolume->raise(); } _sideShadow->raise(); if (_thirdShadow) { _thirdShadow->raise(); } if (_firstColumnResizeArea) { _firstColumnResizeArea->raise(); } if (_thirdColumnResizeArea) { _thirdColumnResizeArea->raise(); } _connecting->raise(); _playerPlaylist->raise(); floatPlayerRaiseAll(); 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; floatPlayerHideAll(); if (_player) { _player->hideShadow(); } auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden(); if (playerVolumeVisible) { _playerVolume->hide(); } auto playerPlaylistVisible = !_playerPlaylist->isHidden(); if (playerPlaylistVisible) { _playerPlaylist->hide(); } auto sectionTop = getMainSectionTop(); if (Adaptive::OneColumn()) { result = Ui::GrabWidget(this, QRect( 0, sectionTop, _dialogsWidth, height() - sectionTop)); } else { _sideShadow->hide(); if (_thirdShadow) { _thirdShadow->hide(); } result = Ui::GrabWidget(this, QRect( _dialogsWidth, sectionTop, width() - _dialogsWidth, height() - sectionTop)); _sideShadow->show(); if (_thirdShadow) { _thirdShadow->show(); } } if (playerVolumeVisible) { _playerVolume->show(); } if (playerPlaylistVisible) { _playerPlaylist->show(); } if (_player) { _player->showShadow(); } floatPlayerShowVisible(); return result; } void MainWidget::repaintDialogRow( Dialogs::Mode list, not_null row) { _dialogs->repaintDialogRow(list, row); } void MainWidget::repaintDialogRow(Dialogs::RowDescriptor row) { _dialogs->repaintDialogRow(row); } 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(lang(lng_cant_delete_channel))); //} return true; } void MainWidget::historyToDown(History *history) { _history->historyToDown(history); } void MainWidget::dialogsToUp() { _dialogs->jumpToTop(); } void MainWidget::newUnreadMsg( not_null history, not_null item) { _history->newUnreadMsg(history, item); } void MainWidget::markActiveHistoryAsRead() { if (const auto activeHistory = _history->history()) { session().api().readServerHistory(activeHistory); } } void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { _showBack = back; (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.stop(); showAll(); (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(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.value(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::getMainSectionTop() const { return _callTopBarHeight + _exportTopBarHeight + _playerHeight; } int MainWidget::getThirdSectionTop() const { return 0; } void MainWidget::hideAll() { _dialogs->hide(); _history->hide(); if (_mainSection) { _mainSection->hide(); } if (_thirdSection) { _thirdSection->hide(); } _sideShadow->hide(); if (_thirdShadow) { _thirdShadow->hide(); } if (_player) { _player->setVisible(false); _playerHeight = 0; } floatPlayerHideAll(); } void MainWidget::showAll() { if (cPasswordRecovered()) { cSetPasswordRecovered(false); Ui::show(Box(lang(lng_signin_password_removed))); } if (Adaptive::OneColumn()) { _sideShadow->hide(); if (_hider) { _hider->hide(); } if (selectingPeer()) { _dialogs->showFast(); _history->hide(); if (_mainSection) _mainSection->hide(); } else if (_mainSection) { _mainSection->show(); } else if (_history->peer()) { _history->show(); _history->updateControlsGeometry(); } else { _dialogs->showFast(); _history->hide(); } if (!selectingPeer()) { if (_mainSection) { _dialogs->hide(); } else if (isMainSectionShown()) { _dialogs->hide(); } } } else { _sideShadow->show(); if (_hider) { _hider->show(); } _dialogs->showFast(); if (_mainSection) { _mainSection->show(); } else { _history->show(); _history->updateControlsGeometry(); } if (_thirdSection) { _thirdSection->show(); } if (_thirdShadow) { _thirdShadow->show(); } } if (_player) { _player->setVisible(true); _playerHeight = _player->contentHeight(); } updateControlsGeometry(); floatPlayerCheckVisibility(); floatPlayerShowVisible(); App::wnd()->checkHistoryActivation(); } void MainWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void MainWidget::updateControlsGeometry() { updateWindowAdaptiveLayout(); if (session().settings().dialogsWidthRatio() > 0) { _a_dialogsWidth.stop(); } if (!_a_dialogsWidth.animating()) { _dialogs->stopWidthAnimation(); } if (Adaptive::ThreeColumn()) { if (!_thirdSection && !_controller->takeThirdSectionFromLayer()) { auto params = Window::SectionShow( Window::SectionShow::Way::ClearStack, anim::type::instant, anim::activation::background); if (session().settings().tabbedSelectorSectionEnabled()) { _history->pushTabbedSelectorToThirdSection(params); } else if (session().settings().thirdSectionInfoEnabled()) { const auto active = _controller->activeChatCurrent(); if (const auto peer = active.peer()) { _controller->showSection( Info::Memento::Default(peer), params.withThirdColumn()); } } } } else { _thirdSection.destroy(); _thirdShadow.destroy(); } auto mainSectionTop = getMainSectionTop(); auto dialogsWidth = qRound(_a_dialogsWidth.value(_dialogsWidth)); if (Adaptive::OneColumn()) { if (_callTopBar) { _callTopBar->resizeToWidth(dialogsWidth); _callTopBar->moveToLeft(0, 0); } if (_exportTopBar) { _exportTopBar->resizeToWidth(dialogsWidth); _exportTopBar->moveToLeft(0, _callTopBarHeight); } if (_player) { _player->resizeToWidth(dialogsWidth); _player->moveToLeft(0, _callTopBarHeight + _exportTopBarHeight); } auto mainSectionGeometry = QRect( 0, mainSectionTop, dialogsWidth, height() - mainSectionTop); _dialogs->setGeometry(mainSectionGeometry); _history->setGeometry(mainSectionGeometry); if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height()); } else { auto thirdSectionWidth = _thirdSection ? _thirdColumnWidth : 0; if (_thirdSection) { auto thirdSectionTop = getThirdSectionTop(); _thirdSection->setGeometry( width() - thirdSectionWidth, thirdSectionTop, thirdSectionWidth, height() - thirdSectionTop); } accumulate_min(dialogsWidth, width() - st::columnMinimalWidthMain); auto mainSectionWidth = width() - dialogsWidth - thirdSectionWidth; _dialogs->setGeometryToLeft(0, 0, dialogsWidth, height()); _sideShadow->setGeometryToLeft(dialogsWidth, 0, st::lineWidth, height()); if (_thirdShadow) { _thirdShadow->setGeometryToLeft( width() - thirdSectionWidth - st::lineWidth, 0, st::lineWidth, height()); } if (_callTopBar) { _callTopBar->resizeToWidth(mainSectionWidth); _callTopBar->moveToLeft(dialogsWidth, 0); } if (_exportTopBar) { _exportTopBar->resizeToWidth(mainSectionWidth); _exportTopBar->moveToLeft(dialogsWidth, _callTopBarHeight); } if (_player) { _player->resizeToWidth(mainSectionWidth); _player->moveToLeft( dialogsWidth, _callTopBarHeight + _exportTopBarHeight); } _history->setGeometryToLeft(dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop); if (_hider) { _hider->setGeometryToLeft(dialogsWidth, 0, mainSectionWidth, height()); } } if (_mainSection) { auto mainSectionGeometry = QRect(_history->x(), mainSectionTop, _history->width(), height() - mainSectionTop); _mainSection->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY); } refreshResizeAreas(); updateMediaPlayerPosition(); updateMediaPlaylistPosition(_playerPlaylist->x()); _contentScrollAddToY = 0; floatPlayerUpdatePositions(); } void MainWidget::refreshResizeAreas() { if (!Adaptive::OneColumn()) { ensureFirstColumnResizeAreaCreated(); _firstColumnResizeArea->setGeometryToLeft( _history->x(), 0, st::historyResizeWidth, height()); } else if (_firstColumnResizeArea) { _firstColumnResizeArea.destroy(); } if (Adaptive::ThreeColumn() && _thirdSection) { ensureThirdColumnResizeAreaCreated(); _thirdColumnResizeArea->setGeometryToLeft( _thirdSection->x(), 0, st::historyResizeWidth, height()); } else if (_thirdColumnResizeArea) { _thirdColumnResizeArea.destroy(); } } template void MainWidget::createResizeArea( object_ptr &area, MoveCallback &&moveCallback, FinishCallback &&finishCallback) { area.create(this); area->show(); area->addMoveLeftCallback( std::forward(moveCallback)); area->addMoveFinishedCallback( std::forward(finishCallback)); orderWidgets(); } void MainWidget::ensureFirstColumnResizeAreaCreated() { if (_firstColumnResizeArea) { return; } auto moveLeftCallback = [=](int globalLeft) { auto newWidth = globalLeft - mapToGlobal(QPoint(0, 0)).x(); auto newRatio = (newWidth < st::columnMinimalWidthLeft / 2) ? 0. : float64(newWidth) / width(); session().settings().setDialogsWidthRatio(newRatio); }; auto moveFinishedCallback = [=] { if (Adaptive::OneColumn()) { return; } if (session().settings().dialogsWidthRatio() > 0) { session().settings().setDialogsWidthRatio( float64(_dialogsWidth) / width()); } Local::writeUserSettings(); }; createResizeArea( _firstColumnResizeArea, std::move(moveLeftCallback), std::move(moveFinishedCallback)); } void MainWidget::ensureThirdColumnResizeAreaCreated() { if (_thirdColumnResizeArea) { return; } auto moveLeftCallback = [=](int globalLeft) { auto newWidth = mapToGlobal(QPoint(width(), 0)).x() - globalLeft; session().settings().setThirdColumnWidth(newWidth); }; auto moveFinishedCallback = [=] { if (!Adaptive::ThreeColumn() || !_thirdSection) { return; } session().settings().setThirdColumnWidth(snap( session().settings().thirdColumnWidth(), st::columnMinimalWidthThird, st::columnMaximalWidthThird)); Local::writeUserSettings(); }; createResizeArea( _thirdColumnResizeArea, std::move(moveLeftCallback), std::move(moveFinishedCallback)); } void MainWidget::updateDialogsWidthAnimated() { if (session().settings().dialogsWidthRatio() > 0) { return; } auto dialogsWidth = _dialogsWidth; updateWindowAdaptiveLayout(); if (!session().settings().dialogsWidthRatio() && (_dialogsWidth != dialogsWidth || _a_dialogsWidth.animating())) { _dialogs->startWidthAnimation(); _a_dialogsWidth.start( [this] { updateControlsGeometry(); }, dialogsWidth, _dialogsWidth, st::dialogsWidthDuration, anim::easeOutCirc); updateControlsGeometry(); } } bool MainWidget::saveThirdSectionToStackBack() const { return !_stack.empty() && _thirdSection != nullptr && _stack.back()->thirdSectionWeak() == _thirdSection.data(); } auto MainWidget::thirdSectionForCurrentMainSection( Dialogs::Key key) -> std::unique_ptr { if (_thirdSectionFromStack) { return std::move(_thirdSectionFromStack); } else if (const auto peer = key.peer()) { return std::make_unique( peer->id, Info::Memento::DefaultSection(peer)); //} else if (const auto feed = key.feed()) { // #feed // return std::make_unique( // feed, // Info::Memento::DefaultSection(key)); } Unexpected("Key in MainWidget::thirdSectionForCurrentMainSection()."); } void MainWidget::updateThirdColumnToCurrentChat( Dialogs::Key key, bool canWrite) { auto saveOldThirdSection = [&] { if (saveThirdSectionToStackBack()) { _stack.back()->setThirdSectionMemento( _thirdSection->createMemento()); _thirdSection.destroy(); } }; auto params = Window::SectionShow( Window::SectionShow::Way::ClearStack, anim::type::instant, anim::activation::background); auto switchInfoFast = [&] { saveOldThirdSection(); // // Like in _controller->showPeerInfo() // if (Adaptive::ThreeColumn() && !session().settings().thirdSectionInfoEnabled()) { session().settings().setThirdSectionInfoEnabled(true); session().saveSettingsDelayed(); } _controller->showSection( std::move(*thirdSectionForCurrentMainSection(key)), params.withThirdColumn()); }; auto switchTabbedFast = [&] { saveOldThirdSection(); _history->pushTabbedSelectorToThirdSection(params); }; if (Adaptive::ThreeColumn() && session().settings().tabbedSelectorSectionEnabled() && key) { if (!canWrite) { switchInfoFast(); session().settings().setTabbedSelectorSectionEnabled(true); session().settings().setTabbedReplacedWithInfo(true); } else if (session().settings().tabbedReplacedWithInfo()) { session().settings().setTabbedReplacedWithInfo(false); switchTabbedFast(); } } else { session().settings().setTabbedReplacedWithInfo(false); if (!key) { if (_thirdSection) { _thirdSection.destroy(); _thirdShadow.destroy(); updateControlsGeometry(); } } else if (Adaptive::ThreeColumn() && session().settings().thirdSectionInfoEnabled()) { switchInfoFast(); } } } void MainWidget::updateMediaPlayerPosition() { 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 (e->type() == QEvent::FocusIn) { if (const auto widget = qobject_cast(o)) { if (_history == widget || _history->isAncestorOf(widget) || (_mainSection && (_mainSection == widget || _mainSection->isAncestorOf(widget))) || (_thirdSection && (_thirdSection == widget || _thirdSection->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(e)->button() == Qt::BackButton) { handleHistoryBack(); return true; } } else if (e->type() == QEvent::Wheel) { if (const auto result = floatPlayerFilterWheelEvent(o, e)) { return *result; } } return RpWidget::eventFilter(o, e); } void MainWidget::handleAdaptiveLayoutUpdate() { showAll(); _sideShadow->setVisible(!Adaptive::OneColumn()); if (_player) { _player->updateAdaptiveLayout(); } } void MainWidget::handleHistoryBack() { const auto historyFromFolder = _history->history() ? _history->history()->folder() : nullptr; const auto openedFolder = _controller->openedFolder().current(); if (!openedFolder || historyFromFolder == openedFolder || _dialogs->isHidden()) { _controller->showBackFromStack(); _dialogs->setInnerFocus(); } else { _controller->closeFolder(); } } void MainWidget::updateWindowAdaptiveLayout() { auto layout = _controller->computeColumnLayout(); auto dialogsWidthRatio = session().settings().dialogsWidthRatio(); // 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 (session().settings().tabbedSelectorSectionEnabled() // && chatWidth >= _history->minimalWidthForTabbedSelectorSection()) { // chatWidth -= _history->tabbedSelectorSectionWidth(); //} auto minimalNormalWidth = st::columnMinimalWidthLeft + st::columnMinimalWidthMain; if (chatWidth >= minimalNormalWidth) { // Switch layout back to normal in a wide enough window. layout.windowLayout = Adaptive::WindowLayout::Normal; layout.dialogsWidth = st::columnMinimalWidthLeft; 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::ThreeColumn && _started && _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, HistoryView::WideChatWidth()); // chatWidth -= thirdColumnWidth; // auto extendChatBy = desiredChatWidth - chatWidth; // accumulate_min(extendChatBy, layout.dialogsWidth - st::columnMinimalWidthLeft); // if (extendChatBy > 0) { // layout.dialogsWidth -= extendChatBy; // layout.chatWidth += extendChatBy; // dialogsWidthRatio = float64(layout.dialogsWidth) / layout.bodyWidth; // } //} } session().settings().setDialogsWidthRatio(dialogsWidthRatio); auto useSmallColumnWidth = !Adaptive::OneColumn() && !dialogsWidthRatio && !_controller->forceWideDialogs(); _dialogsWidth = useSmallColumnWidth ? _controller->dialogsSmallColumnWidth() : layout.dialogsWidth; _thirdColumnWidth = layout.thirdWidth; if (layout.windowLayout != Global::AdaptiveWindowLayout()) { Global::SetAdaptiveWindowLayout(layout.windowLayout); Adaptive::Changed().notify(true); } } int MainWidget::backgroundFromY() const { return -getMainSectionTop(); } void MainWidget::searchInChat(Dialogs::Key chat) { if (_controller->openedFolder().current()) { _controller->closeFolder(); } _dialogs->searchInChat(chat); if (Adaptive::OneColumn()) { Ui::showChatsList(); } else { _dialogs->setInnerFocus(); } } void MainWidget::feedUpdateVector( const MTPVector &updates, bool skipMessageIds) { for (const auto &update : updates.v) { if (skipMessageIds && update.type() == mtpc_updateMessageID) { continue; } feedUpdate(update); } session().data().sendHistoryChangeNotifications(); } void MainWidget::feedMessageIds(const MTPVector &updates) { for (const auto &update : updates.v) { if (update.type() == mtpc_updateMessageID) { feedUpdate(update); } } } 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.cancel(); } for (QMap::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.callOnce(PtsWaiter::kWaitForSkippedTimeout); } break; } } } } void MainWidget::gotChannelDifference( ChannelData *channel, const MTPupdates_ChannelDifference &difference) { _channelFailDifferenceTimeout.remove(channel); const auto timeout = difference.match([&](const auto &data) { return data.has_timeout() ? data.vtimeout.v : 0; }); const auto isFinal = difference.match([&](const auto &data) { return data.is_final(); }); difference.match([&](const MTPDupdates_channelDifferenceEmpty &data) { channel->ptsInit(data.vpts.v); }, [&](const MTPDupdates_channelDifferenceTooLong &data) { session().data().processUsers(data.vusers); session().data().processChats(data.vchats); const auto history = session().data().historyLoaded(channel->id); if (history) { history->setNotLoadedAtBottom(); session().api().requestChannelRangeDifference(history); } data.vdialog.match([&](const MTPDdialog &data) { if (data.has_pts()) { channel->ptsInit(data.vpts.v); } }, [&](const MTPDdialogFolder &) { }); session().data().applyDialogs( nullptr, data.vmessages.v, QVector(1, data.vdialog)); if (_history->peer() == channel) { _history->updateHistoryDownVisibility(); _history->preloadHistoryIfNeeded(); } }, [&](const MTPDupdates_channelDifference &data) { feedChannelDifference(data); channel->ptsInit(data.vpts.v); }); channel->ptsSetRequesting(false); if (!isFinal) { MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); getChannelDifference(channel); } else if (_controller->activeChatCurrent().peer() == channel) { channel->ptsWaitingForShortPoll(timeout ? (timeout * crl::time(1000)) : kWaitForChannelGetDifference); } } void MainWidget::feedChannelDifference( const MTPDupdates_channelDifference &data) { session().data().processUsers(data.vusers); session().data().processChats(data.vchats); _handlingChannelDifference = true; feedMessageIds(data.vother_updates); session().data().processMessages( data.vnew_messages, NewMessageType::Unread); feedUpdateVector(data.vother_updates, true); _handlingChannelDifference = false; } 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 = crl::now(); _noUpdatesTimer.callOnce(kNoUpdatesTimeout); _ptsWaiter.setRequesting(false); session().api().requestDialogs(); 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 = crl::now(); _noUpdatesTimer.callOnce(kNoUpdatesTimeout); _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, crl::time &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 = crl::now() + 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 = crl::now() + 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)) { getDifferenceByPts(); } } void MainWidget::failDifferenceStartTimerFor(ChannelData *channel) { auto &timeout = [&]() -> int32& { if (!channel) { return _failDifferenceTimeout; } const auto i = _channelFailDifferenceTimeout.find(channel); return (i == _channelFailDifferenceTimeout.end()) ? _channelFailDifferenceTimeout.insert(channel, 1).value() : i.value(); }(); if (getDifferenceTimeChanged(channel, timeout * 1000, _channelGetDifferenceTimeAfterFail, _getDifferenceTimeAfterFail)) { getDifferenceAfterFail(); } if (timeout < 64) timeout *= 2; } bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates) { return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, updates); } bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update) { return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, update); } bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount) { return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount); } void MainWidget::feedDifference( const MTPVector &users, const MTPVector &chats, const MTPVector &msgs, const MTPVector &other) { session().checkAutoLock(); session().data().processUsers(users); session().data().processChats(chats); feedMessageIds(other); session().data().processMessages(msgs, NewMessageType::Unread); feedUpdateVector(other, true); } 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(nullptr); return true; } void MainWidget::getDifferenceByPts() { auto now = crl::now(), wait = crl::time(0); 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.callOnce(wait); } else { _byPtsTimer.cancel(); } } void MainWidget::getDifferenceAfterFail() { auto now = crl::now(), wait = crl::time(0); 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.callOnce(wait); } else { _failDifferenceTimer.cancel(); } } void MainWidget::getDifference() { if (this != App::main()) return; _getDifferenceTimeByPts = 0; if (requestingDifference()) return; _bySeqUpdates.clear(); _bySeqTimer.cancel(); _noUpdatesTimer.cancel(); _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 = MTPupdates_GetChannelDifference::Flag::f_force | 0; 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(kChannelGetDifferenceLimit)), rpcDone(&MainWidget::gotChannelDifference, channel), rpcFail(&MainWidget::failChannelDifference, channel)); } void MainWidget::sendPing() { MTP::ping(); } void MainWidget::start() { session().api().requestNotifySettings(MTP_inputNotifyUsers()); session().api().requestNotifySettings(MTP_inputNotifyChats()); session().api().requestNotifySettings(MTP_inputNotifyBroadcasts()); cSetOtherOnline(0); session().user()->loadUserpic(); MTP::send(MTPupdates_GetState(), rpcDone(&MainWidget::gotState)); update(); _started = true; Local::readInstalledStickers(); Local::readFeaturedStickers(); Local::readRecentStickers(); Local::readFavedStickers(); Local::readSavedGifs(); if (const auto availableAt = Local::ReadExportSettings().availableAt) { session().data().suggestStartExport(availableAt); } _history->start(); Core::App().checkStartUrl(); } bool MainWidget::started() { return _started; } void MainWidget::openPeerByName( const QString &username, MsgId msgId, const QString &startToken, FullMsgId clickFromMessageId) { Core::App().hideMediaView(); if (const auto peer = session().data().peerByUsername(username)) { if (msgId == ShowAtGameShareMsgId) { if (peer->isUser() && peer->asUser()->botInfo && !startToken.isEmpty()) { peer->asUser()->botInfo->shareGameShortName = startToken; AddBotToGroupBoxController::Start(peer->asUser()); } else { InvokeQueued(this, [this, peer] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward); }); } } else if (msgId == ShowAtProfileMsgId && !peer->isChannel()) { if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) { peer->asUser()->botInfo->startGroupToken = startToken; AddBotToGroupBoxController::Start(peer->asUser()); } else if (peer->isUser() && peer->asUser()->botInfo) { // Always open bot chats, even from mention links. InvokeQueued(this, [this, peer] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward); }); } else { _controller->showPeerInfo(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(); } } const auto returnToId = clickFromMessageId; InvokeQueued(this, [=] { if (const auto returnTo = session().data().message(returnToId)) { if (returnTo->history()->peer == peer) { pushReplyReturn(returnTo); } } _controller->showPeerHistory( peer->id, SectionShow::Way::Forward, msgId); }); } } else { MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(msgId, startToken)), rpcFail(&MainWidget::usernameResolveFail, username)); } } bool MainWidget::contentOverlapped(const QRect &globalRect) { return (_history->contentOverlapped(globalRect) || _playerPlaylist->overlaps(globalRect) || (_playerVolume && _playerVolume->overlaps(globalRect))); } void MainWidget::usernameResolveDone(QPair msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result) { Ui::hideLayer(); if (result.type() != mtpc_contacts_resolvedPeer) return; const auto &d(result.c_contacts_resolvedPeer()); session().data().processUsers(d.vusers); session().data().processChats(d.vchats); PeerId peerId = peerFromMTP(d.vpeer); if (!peerId) return; PeerData *peer = session().data().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; AddBotToGroupBoxController::Start(peer->asUser()); } else if (peer->isUser() && peer->asUser()->botInfo) { // Always open bot chats, even from mention links. InvokeQueued(this, [this, peer] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward); }); } else { _controller->showPeerInfo(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(); } } InvokeQueued(this, [this, peer, msgId] { _controller->showPeerHistory( peer->id, SectionShow::Way::Forward, msgId); }); } } bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.code() == 400) { Ui::show(Box(lng_username_not_found(lt_user, name))); } return true; } void MainWidget::incrementSticker(DocumentData *sticker) { if (!sticker || !sticker->sticker() || sticker->sticker()->set.type() == mtpc_inputStickerSetEmpty) { return; } bool writeRecentStickers = false; auto &sets = session().data().stickerSetsRef(); auto it = sets.find(Stickers::CloudRecentSetId); if (it == sets.cend()) { if (it == sets.cend()) { it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set( Stickers::CloudRecentSetId, uint64(0), lang(lng_recent_stickers), QString(), 0, // count 0, // hash MTPDstickerSet_ClientFlag::f_special | 0, TimeId(0), ImagePtr())); } else { it->title = lang(lng_recent_stickers); } } auto removedFromEmoji = std::vector>(); auto index = it->stickers.indexOf(sticker); if (index > 0) { if (it->dates.empty()) { session().api().requestRecentStickersForce(); } else { Assert(it->dates.size() == it->stickers.size()); it->dates.erase(it->dates.begin() + index); } it->stickers.removeAt(index); for (auto i = it->emoji.begin(); i != it->emoji.end();) { if (const auto index = i->indexOf(sticker); index >= 0) { removedFromEmoji.emplace_back(i.key()); i->removeAt(index); if (i->isEmpty()) { i = it->emoji.erase(i); continue; } } ++i; } } if (index) { if (it->dates.size() == it->stickers.size()) { it->dates.insert(it->dates.begin(), unixtime()); } it->stickers.push_front(sticker); if (const auto emojiList = Stickers::GetEmojiListFromSet(sticker)) { for (const auto emoji : *emojiList) { it->emoji[emoji].push_front(sticker); } } else if (!removedFromEmoji.empty()) { for (const auto emoji : removedFromEmoji) { it->emoji[emoji].push_front(sticker); } } else { session().api().requestRecentStickersForce(); } writeRecentStickers = true; } // Remove that sticker from old recent, now it is in cloud recent stickers. bool writeOldRecent = false; auto &recent = Stickers::GetRecentPack(); 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 (!_mainSection) { if (_hider) { _dialogs->setInnerFocus(); } else if (App::wnd() && !Ui::isLayerShown()) { if (!cSendPaths().isEmpty()) { const auto interpret = qstr("interpret://"); const auto path = cSendPaths()[0]; if (path.startsWith(interpret)) { cSetSendPaths(QStringList()); const auto error = Support::InterpretSendPath(path.mid(interpret.size())); if (!error.isEmpty()) { Ui::show(Box(error)); } } else { showSendPathsLayer(); } } else if (_history->peer()) { _history->activate(); } else { _dialogs->setInnerFocus(); } } } App::wnd()->fixOrder(); } bool MainWidget::isActive() const { return !_isIdle && isVisible() && !_a_show.animating(); } bool MainWidget::doWeReadServerHistory() const { return isActive() && !session().supportMode() && !_mainSection && _history->doWeReadServerHistory(); } bool MainWidget::doWeReadMentions() const { return isActive() && !_mainSection && _history->doWeReadMentions(); } bool MainWidget::lastWasOnline() const { return _lastWasOnline; } crl::time MainWidget::lastSetOnline() const { return _lastSetOnline; } int32 MainWidget::dlgsWidth() const { return _dialogs->width(); } MainWidget::~MainWidget() { if (App::main() == this) { _history->showHistory(0, 0); } } void MainWidget::updateOnline(bool gotOtherOffline) { if (this != App::main()) return; InvokeQueued(this, [=] { session().checkAutoLock(); }); bool isOnline = !App::quitting() && App::wnd()->isActive(); int updateIn = Global::OnlineUpdatePeriod(); Assert(updateIn >= 0); if (isOnline) { const auto idle = crl::now() - Core::App().lastNonIdleTime(); if (idle >= Global::OfflineIdleTimeout()) { isOnline = false; if (!_isIdle) { _isIdle = true; _idleFinishTimer.callOnce(900); } } else { updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle)); Assert(updateIn >= 0); } } auto ms = crl::now(); if (isOnline != _lastWasOnline || (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms) || (isOnline && gotOtherOffline)) { if (_onlineRequest) { MTP::cancel(_onlineRequest); _onlineRequest = 0; } _lastWasOnline = isOnline; _lastSetOnline = ms; if (!App::quitting()) { _onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline))); } else { _onlineRequest = MTP::send( MTPaccount_UpdateStatus(MTP_bool(!isOnline)), rpcDone(&MainWidget::updateStatusDone), rpcFail(&MainWidget::updateStatusFail)); } const auto self = session().user(); self->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); Notify::peerUpdatedDelayed( self, Notify::PeerUpdate::Flag::UserOnlineChanged); if (!isOnline) { // Went offline, so we need to save message draft to the cloud. saveDraftToCloud(); } _lastSetOnline = ms; } else if (isOnline) { updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms)); Assert(updateIn >= 0); } _onlineTimer.callOnce(updateIn); } void MainWidget::updateStatusDone(const MTPBool &result) { Core::App().quitPreventFinished(); } bool MainWidget::updateStatusFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) { return false; } Core::App().quitPreventFinished(); return true; } bool MainWidget::isQuitPrevent() { if (!_lastWasOnline) { return false; } LOG(("MainWidget prevents quit, sending offline status...")); updateOnline(); return true; } void MainWidget::saveDraftToCloud() { _history->saveFieldToHistoryLocalDraft(); const auto peer = _history->peer(); if (const auto history = session().data().historyLoaded(peer)) { writeDrafts(history); const auto localDraft = history->localDraft(); const auto cloudDraft = history->cloudDraft(); if (!Data::draftsAreEqual(localDraft, cloudDraft) && !session().supportMode()) { session().api().saveDraftToCloudDelayed(history); } } } void MainWidget::applyCloudDraft(History *history) { _history->applyCloudDraft(history); } void MainWidget::writeDrafts(History *history) { Local::MessageDraft storedLocalDraft, storedEditDraft; MessageCursor localCursor, editCursor; if (const auto localDraft = history->localDraft()) { if (session().supportMode() || !Data::draftsAreEqual(localDraft, history->cloudDraft())) { storedLocalDraft = Local::MessageDraft( localDraft->msgId, localDraft->textWithTags, localDraft->previewCancelled); localCursor = localDraft->cursor; } } if (const 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 (crl::now() - Core::App().lastNonIdleTime() < Global::OfflineIdleTimeout()) { _idleFinishTimer.cancel(); _isIdle = false; updateOnline(); App::wnd()->checkHistoryActivation(); } else { _idleFinishTimer.callOnce(900); } } void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) { if (end <= from) return; session().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 = crl::now(); _noUpdatesTimer.callOnce(kNoUpdatesTimeout); if (!requestingDifference() || HasForceLogoutNotification(updates)) { feedUpdates(updates); } } catch (mtpErrorUnexpected &) { // just some other type } } update(); } 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); _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); return; } } session().data().processUsers(d.vusers); session().data().processChats(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); _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); return; } } session().data().processUsers(d.vusers); session().data().processChats(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 (!session().data().userLoaded(d.vuser_id.v) || (d.has_via_bot_id() && !session().data().userLoaded(d.vvia_bot_id.v)) || (d.has_entities() && !MentionUsersLoaded(&session(), d.ventities)) || (d.has_fwd_from() && !ForwardedInfoDataLoaded(&session(), d.vfwd_from))) { MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); return getDifference(); } if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, updates)) { // Update date as well. updSetState(0, d.vdate.v, updQts, updSeq); } } break; case mtpc_updateShortChatMessage: { auto &d = updates.c_updateShortChatMessage(); const auto noFrom = !session().data().userLoaded(d.vfrom_id.v); const auto chat = session().data().chatLoaded(d.vchat_id.v); if (!chat || noFrom || (d.has_via_bot_id() && !session().data().userLoaded(d.vvia_bot_id.v)) || (d.has_entities() && !MentionUsersLoaded(&session(), d.ventities)) || (d.has_fwd_from() && !ForwardedInfoDataLoaded(&session(), d.vfwd_from))) { MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); if (chat && noFrom) { session().api().requestFullPeer(chat); } return getDifference(); } if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, updates)) { // Update date as well. updSetState(0, d.vdate.v, updQts, updSeq); } } break; case mtpc_updateShortSentMessage: { auto &d = updates.c_updateShortSentMessage(); if (!IsServerMsgId(d.vid.v)) { LOG(("API Error: Bad msgId got from server: %1").arg(d.vid.v)); } else if (randomId) { const auto sent = session().data().messageSentData(randomId); const auto lookupMessage = [&] { return sent.peerId ? session().data().message( peerToChannel(sent.peerId), d.vid.v) : nullptr; }; const auto wasAlready = (lookupMessage() != nullptr); feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date if (const auto item = lookupMessage()) { if (d.has_entities() && !MentionUsersLoaded(&session(), d.ventities)) { session().api().requestMessageData( item->history()->peer->asChannel(), item->id, ApiWrap::RequestMessageDataCallback()); } const auto entities = d.has_entities() ? TextUtilities::EntitiesFromMTP(d.ventities.v) : EntitiesInText(); const auto media = d.has_media() ? &d.vmedia : nullptr; item->setText({ sent.text, entities }); item->updateSentMedia(media); if (!wasAlready) { item->indexAsNewItem(); } } } if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, updates)) { // Update date as well. 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; } session().data().sendHistoryChangeNotifications(); } void MainWidget::feedUpdate(const MTPUpdate &update) { switch (update.type()) { // New messages. case mtpc_updateNewMessage: { auto &d = update.c_updateNewMessage(); const auto isDataLoaded = AllDataLoadedForMessage(&session(), 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(); } ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateNewChannelMessage: { auto &d = update.c_updateNewChannelMessage(); auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage))); const auto isDataLoaded = AllDataLoadedForMessage(&session(), 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()) { if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { session().api().requestLastParticipants(channel); } } if (!_byMinChannelTimer.isActive()) { // getDifference after timeout _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); } return; } if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } else { session().api().applyUpdateNoPtsCheck(update); } } break; case mtpc_updateMessageID: { const auto &d = update.c_updateMessageID(); const auto randomId = d.vrandom_id.v; if (const auto id = session().data().messageIdByRandomId(randomId)) { const auto channel = id.channel; const auto newId = d.vid.v; if (const auto local = session().data().message(id)) { const auto existing = session().data().message(channel, newId); if (existing && !local->mainView()) { const auto history = local->history(); local->destroy(); history->requestChatListMessage(); } else { if (existing) { existing->destroy(); } local->setRealId(d.vid.v); } } session().data().unregisterMessageRandomId(randomId); } session().data().unregisterMessageSentData(randomId); } break; // Message contents being read. case mtpc_updateReadMessagesContents: { auto &d = update.c_updateReadMessagesContents(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateChannelReadMessagesContents: { auto &d = update.c_updateChannelReadMessagesContents(); auto channel = session().data().channelLoaded(d.vchannel_id.v); if (!channel) { if (!_byMinChannelTimer.isActive()) { // getDifference after timeout. _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); } return; } auto possiblyReadMentions = base::flat_set(); for_const (auto &msgId, d.vmessages.v) { if (auto item = session().data().message(channel, msgId.v)) { if (item->isUnreadMedia() || item->isUnreadMention()) { item->markMediaRead(); session().data().requestItemRepaint(item); } } else { // Perhaps it was an unread mention! possiblyReadMentions.insert(msgId.v); } } session().api().checkForUnreadMentions(possiblyReadMentions, channel); } break; // Edited messages. case mtpc_updateEditMessage: { auto &d = update.c_updateEditMessage(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateEditChannelMessage: { auto &d = update.c_updateEditChannelMessage(); auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage))); if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } else { channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } } else { session().api().applyUpdateNoPtsCheck(update); } } break; // Messages being read. case mtpc_updateReadHistoryInbox: { auto &d = update.c_updateReadHistoryInbox(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateReadHistoryOutbox: { auto &d = update.c_updateReadHistoryOutbox(); if (ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update)) { // We could've updated the double checks. // Better would be for history to be subscribed to outbox read events. _history->update(); } } break; case mtpc_updateReadChannelInbox: { const auto &d = update.c_updateReadChannelInbox(); const auto peer = peerFromChannel(d.vchannel_id.v); if (const auto history = session().data().historyLoaded(peer)) { const auto folderId = d.has_folder_id() ? d.vfolder_id.v : 0; history->applyInboxReadUpdate( folderId, d.vmax_id.v, d.vstill_unread_count.v, d.vpts.v); } } break; case mtpc_updateReadChannelOutbox: { const auto &d = update.c_updateReadChannelOutbox(); const auto peer = peerFromChannel(d.vchannel_id.v); if (const auto history = session().data().historyLoaded(peer)) { history->outboxRead(d.vmax_id.v); if (!requestingDifference()) { if (const auto user = history->peer->asUser()) { user->madeAction(unixtime()); } } if (_history->peer() && _history->peer()->id == peer) { _history->update(); } } } break; //case mtpc_updateReadFeed: { // #feed // const auto &d = update.c_updateReadFeed(); // const auto feedId = d.vfeed_id.v; // if (const auto feed = session().data().feedLoaded(feedId)) { // feed->setUnreadPosition( // Data::FeedPositionFromMTP(d.vmax_position)); // if (d.has_unread_count() && d.has_unread_muted_count()) { // feed->setUnreadCounts( // d.vunread_count.v, // d.vunread_muted_count.v); // } else { // session().api().requestDialogEntry(feed); // } // } //} break; case mtpc_updateDialogUnreadMark: { const auto &data = update.c_updateDialogUnreadMark(); data.vpeer.match( [&](const MTPDdialogPeer &dialog) { const auto id = peerFromMTP(dialog.vpeer); if (const auto history = session().data().historyLoaded(id)) { history->setUnreadMark(data.is_unread()); } }, [&](const MTPDdialogPeerFolder &dialog) { const auto id = dialog.vfolder_id.v; // #TODO archive //if (const auto folder = session().data().folderLoaded(id)) { // folder->setUnreadMark(data.is_unread()); //} }); } break; case mtpc_updateFolderPeers: { const auto &data = update.c_updateFolderPeers(); ptsUpdateAndApply(data.vpts.v, data.vpts_count.v, update); } break; // Deleted messages. case mtpc_updateDeleteMessages: { auto &d = update.c_updateDeleteMessages(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateDeleteChannelMessages: { auto &d = update.c_updateDeleteChannelMessages(); auto channel = session().data().channelLoaded(d.vchannel_id.v); if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } else { session().api().applyUpdateNoPtsCheck(update); } } break; case mtpc_updateWebPage: { auto &d = update.c_updateWebPage(); // Update web page anyway. session().data().processWebpage(d.vwebpage); _history->updatePreview(); session().data().sendWebPageGamePollNotifications(); ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } break; case mtpc_updateChannelWebPage: { auto &d = update.c_updateChannelWebPage(); // Update web page anyway. session().data().processWebpage(d.vwebpage); _history->updatePreview(); session().data().sendWebPageGamePollNotifications(); auto channel = session().data().channelLoaded(d.vchannel_id.v); if (channel && !_handlingChannelDifference) { if (channel->ptsRequesting()) { // skip global updates while getting channel difference return; } else { channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update); } } else { session().api().applyUpdateNoPtsCheck(update); } } break; case mtpc_updateMessagePoll: { session().data().applyUpdate(update.c_updateMessagePoll()); } break; case mtpc_updateUserTyping: { auto &d = update.c_updateUserTyping(); const auto userId = peerFromUser(d.vuser_id); const auto history = session().data().historyLoaded(userId); const auto user = session().data().userLoaded(d.vuser_id.v); if (history && user) { const auto when = requestingDifference() ? 0 : unixtime(); session().data().registerSendAction(history, user, d.vaction, when); } } break; case mtpc_updateChatUserTyping: { auto &d = update.c_updateChatUserTyping(); const auto history = [&]() -> History* { if (const auto chat = session().data().chatLoaded(d.vchat_id.v)) { return session().data().historyLoaded(chat->id); } else if (const auto channel = session().data().channelLoaded(d.vchat_id.v)) { return session().data().historyLoaded(channel->id); } return nullptr; }(); const auto user = (d.vuser_id.v == session().userId()) ? nullptr : session().data().userLoaded(d.vuser_id.v); if (history && user) { const auto when = requestingDifference() ? 0 : unixtime(); session().data().registerSendAction(history, user, d.vaction, when); } } break; case mtpc_updateChatParticipants: { session().data().applyUpdate(update.c_updateChatParticipants()); } break; case mtpc_updateChatParticipantAdd: { session().data().applyUpdate(update.c_updateChatParticipantAdd()); } break; case mtpc_updateChatParticipantDelete: { session().data().applyUpdate(update.c_updateChatParticipantDelete()); } break; case mtpc_updateChatParticipantAdmin: { session().data().applyUpdate(update.c_updateChatParticipantAdmin()); } break; case mtpc_updateChatDefaultBannedRights: { session().data().applyUpdate(update.c_updateChatDefaultBannedRights()); } break; case mtpc_updateUserStatus: { auto &d = update.c_updateUserStatus(); if (auto user = session().data().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; } Notify::peerUpdatedDelayed( user, Notify::PeerUpdate::Flag::UserOnlineChanged); } if (d.vuser_id.v == session().userId()) { 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 = session().data().userLoaded(d.vuser_id.v)) { if (!user->isContact()) { user->setName( TextUtilities::SingleLine(qs(d.vfirst_name)), TextUtilities::SingleLine(qs(d.vlast_name)), user->nameOrPhone, TextUtilities::SingleLine(qs(d.vusername))); } else { user->setName( TextUtilities::SingleLine(user->firstName), TextUtilities::SingleLine(user->lastName), user->nameOrPhone, TextUtilities::SingleLine(qs(d.vusername))); } } } break; case mtpc_updateUserPhoto: { auto &d = update.c_updateUserPhoto(); if (auto user = session().data().userLoaded(d.vuser_id.v)) { user->setPhoto(d.vphoto); user->loadUserpic(); if (mtpIsTrue(d.vprevious) || !user->userpicPhotoId()) { session().storage().remove(Storage::UserPhotosRemoveAfter( user->bareId(), user->userpicPhotoId())); } else { session().storage().add(Storage::UserPhotosAddNew( user->bareId(), user->userpicPhotoId())); } } } break; case mtpc_updatePeerSettings: { const auto &d = update.c_updatePeerSettings(); const auto peerId = peerFromMTP(d.vpeer); if (const auto peer = session().data().peerLoaded(peerId)) { const auto settings = d.vsettings.match([]( const MTPDpeerSettings &data) { return data.vflags.v; }); peer->setSettings(settings); } } break; case mtpc_updateNotifySettings: { auto &d = update.c_updateNotifySettings(); session().data().applyNotifySetting(d.vpeer, d.vnotify_settings); } break; case mtpc_updateDcOptions: { auto &d = update.c_updateDcOptions(); Core::App().dcOptions()->addFromList(d.vdc_options); } break; case mtpc_updateConfig: { Core::App().mtp()->requestConfig(); } break; case mtpc_updateUserPhone: { const auto &d = update.c_updateUserPhone(); if (const auto user = session().data().userLoaded(d.vuser_id.v)) { const auto newPhone = qs(d.vphone); if (newPhone != user->phone()) { user->setPhone(newPhone); user->setName( user->firstName, user->lastName, ((user->isContact() || user->isServiceUser() || user->isSelf() || user->phone().isEmpty()) ? QString() : App::formatPhone(user->phone())), user->username); 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: { const auto &d = update.c_updateUserBlocked(); if (const auto user = session().data().userLoaded(d.vuser_id.v)) { user->setIsBlocked(mtpIsTrue(d.vblocked)); } } break; case mtpc_updateServiceNotification: { const auto &d = update.c_updateServiceNotification(); const auto text = TextWithEntities { qs(d.vmessage), TextUtilities::EntitiesFromMTP(d.ventities.v) }; if (IsForceLogoutNotification(d)) { Core::App().forceLogOut(text); } else if (d.is_popup()) { Ui::show(Box(text)); } else { session().data().serviceNotification(text, d.vmedia); session().data().checkNewAuthorization(); } } break; case mtpc_updatePrivacy: { auto &d = update.c_updatePrivacy(); const auto allChatsLoaded = [&](const MTPVector &ids) { for (const auto &chatId : ids.v) { if (!session().data().chatLoaded(chatId.v) && !session().data().channelLoaded(chatId.v)) { return false; } } return true; }; const auto allLoaded = [&] { for (const auto &rule : d.vrules.v) { const auto loaded = rule.match([&]( const MTPDprivacyValueAllowChatParticipants & data) { return allChatsLoaded(data.vchats); }, [&](const MTPDprivacyValueDisallowChatParticipants & data) { return allChatsLoaded(data.vchats); }, [](auto &&) { return true; }); if (!loaded) { return false; } } return true; }; if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey.type())) { if (allLoaded()) { session().api().handlePrivacyChange(*key, d.vrules); } else { session().api().reloadPrivacy(*key); } } } break; case mtpc_updatePinnedDialogs: { const auto &d = update.c_updatePinnedDialogs(); const auto folderId = d.has_folder_id() ? d.vfolder_id.v : 0; const auto loaded = !folderId || (session().data().folderLoaded(folderId) != nullptr); const auto folder = folderId ? session().data().folder(folderId).get() : nullptr; const auto done = [&] { if (!d.has_order()) { return false; } const auto &order = d.vorder.v; const auto notLoaded = [&](const MTPDialogPeer &peer) { return peer.match([&](const MTPDdialogPeer &data) { return !session().data().historyLoaded( peerFromMTP(data.vpeer)); }, [&](const MTPDdialogPeerFolder &data) { if (folderId) { LOG(("API Error: " "updatePinnedDialogs has nested folders.")); return true; } return !session().data().folderLoaded(data.vfolder_id.v); }); }; const auto allLoaded = ranges::find_if(order, notLoaded) == order.end(); if (!allLoaded) { return false; } session().data().applyPinnedChats(folder, order); return true; }(); if (!done) { session().api().requestPinnedDialogs(folder); } if (!loaded) { session().api().requestDialogEntry(folder); } } break; case mtpc_updateDialogPinned: { const auto &d = update.c_updateDialogPinned(); const auto folderId = d.has_folder_id() ? d.vfolder_id.v : 0; const auto folder = folderId ? session().data().folder(folderId).get() : nullptr; const auto done = d.vpeer.match([&](const MTPDdialogPeer &data) { const auto id = peerFromMTP(data.vpeer); if (const auto history = session().data().historyLoaded(id)) { history->applyPinnedUpdate(d); return true; } DEBUG_LOG(("API Error: " "pinned chat not loaded for peer %1, folder: %2" ).arg(id ).arg(folderId )); return false; }, [&](const MTPDdialogPeerFolder &data) { if (folderId != 0) { DEBUG_LOG(("API Error: Nested folders updateDialogPinned.")); return false; } const auto id = data.vfolder_id.v; if (const auto folder = session().data().folderLoaded(id)) { folder->applyPinnedUpdate(d); return true; } DEBUG_LOG(("API Error: " "pinned folder not loaded for folderId %1, folder: %2" ).arg(id ).arg(folderId )); return false; }); if (!done) { session().api().requestPinnedDialogs(folder); } } break; case mtpc_updateChannel: { auto &d = update.c_updateChannel(); if (const auto channel = session().data().channelLoaded(d.vchannel_id.v)) { channel->inviter = UserId(0); if (channel->amIn()) { const auto history = channel->owner().history(channel); //if (const auto feed = channel->feed()) { // #feed // feed->requestChatListMessage(); // if (!feed->unreadCountKnown()) { // feed->session().api().requestDialogEntry(feed); // } //} else { history->requestChatListMessage(); if (!history->unreadCountKnown()) { history->session().api().requestDialogEntry(history); } //} if (!channel->amCreator()) { session().api().requestSelfParticipant(channel); } } } } break; case mtpc_updateChannelTooLong: { auto &d = update.c_updateChannelTooLong(); if (auto channel = session().data().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 = session().data().message(d.vchannel_id.v, d.vid.v)) { item->setViewsCount(d.vviews.v); } } break; case mtpc_updateChannelAvailableMessages: { auto &d = update.c_updateChannelAvailableMessages(); if (const auto channel = session().data().channelLoaded(d.vchannel_id.v)) { channel->setAvailableMinId(d.vavailable_min_id.v); if (const auto history = session().data().historyLoaded(channel)) { history->clearUpTill(d.vavailable_min_id.v); } } } break; // Pinned message. case mtpc_updateUserPinnedMessage: { const auto &d = update.c_updateUserPinnedMessage(); if (const auto user = session().data().userLoaded(d.vuser_id.v)) { user->setPinnedMessageId(d.vid.v); } } break; case mtpc_updateChatPinnedMessage: { const auto &d = update.c_updateChatPinnedMessage(); if (const auto chat = session().data().chatLoaded(d.vchat_id.v)) { const auto status = chat->applyUpdateVersion(d.vversion.v); if (status == ChatData::UpdateStatus::Good) { chat->setPinnedMessageId(d.vid.v); } } } break; case mtpc_updateChannelPinnedMessage: { const auto &d = update.c_updateChannelPinnedMessage(); if (const auto channel = session().data().channelLoaded(d.vchannel_id.v)) { channel->setPinnedMessageId(d.vid.v); } } break; ////// Cloud sticker sets case mtpc_updateNewStickerSet: { const auto &d = update.c_updateNewStickerSet(); Stickers::NewSetReceived(d.vstickerset); } break; case mtpc_updateStickerSetsOrder: { auto &d = update.c_updateStickerSetsOrder(); if (!d.is_masks()) { auto &order = d.vorder.v; auto &sets = session().data().stickerSets(); Stickers::Order result; for (const auto &item : order) { if (sets.constFind(item.v) == sets.cend()) { break; } result.push_back(item.v); } if (result.size() != session().data().stickerSetsOrder().size() || result.size() != order.size()) { session().data().setLastStickersUpdate(0); session().api().updateStickers(); } else { session().data().stickerSetsOrderRef() = std::move(result); Local::writeInstalledStickers(); session().data().notifyStickersUpdated(); } } } break; case mtpc_updateStickerSets: { session().data().setLastStickersUpdate(0); session().api().updateStickers(); } break; case mtpc_updateRecentStickers: { session().data().setLastRecentStickersUpdate(0); session().api().updateStickers(); } break; case mtpc_updateFavedStickers: { session().data().setLastFavedStickersUpdate(0); session().api().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. session().data().setLastFeaturedStickersUpdate(0); session().api().updateStickers(); } break; ////// Cloud saved GIFs case mtpc_updateSavedGifs: { session().data().setLastSavedGifsUpdate(0); session().api().updateStickers(); } break; ////// Cloud drafts case mtpc_updateDraftMessage: { const auto &data = update.c_updateDraftMessage(); const auto peerId = peerFromMTP(data.vpeer); data.vdraft.match([&](const MTPDdraftMessage &data) { Data::applyPeerCloudDraft(peerId, data); }, [&](const MTPDdraftMessageEmpty &data) { Data::clearPeerCloudDraft( peerId, TimeId(data.has_date() ? data.vdate.v : 0)); }); } break; ////// Cloud langpacks case mtpc_updateLangPack: { const auto &data = update.c_updateLangPack(); Lang::CurrentCloudManager().applyLangPackDifference(data.vdifference); } break; case mtpc_updateLangPackTooLong: { const auto &data = update.c_updateLangPackTooLong(); const auto code = qs(data.vlang_code); if (!code.isEmpty()) { Lang::CurrentCloudManager().requestLangPackDifference(code); } } break; } }