/* 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 "window/window_controller.h" #include "api/api_updates.h" #include "core/application.h" #include "core/click_handler_types.h" #include "export/export_manager.h" #include "ui/platform/ui_platform_window.h" #include "platform/platform_window_title.h" #include "main/main_account.h" #include "main/main_domain.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/main_app_config.h" #include "media/view/media_view_open_common.h" #include "lang/lang_keys.h" #include "intro/intro_widget.h" #include "mtproto/mtproto_config.h" #include "ui/toast/toast.h" #include "ui/emoji_config.h" #include "chat_helpers/emoji_sets_manager.h" #include "window/window_session_controller.h" #include "window/themes/window_theme_editor.h" #include "ui/boxes/confirm_box.h" #include "data/data_thread.h" #include "apiwrap.h" // ApiWrap::acceptTerms. #include "styles/style_layers.h" #include #include namespace Window { namespace { class Show final : public Ui::Show { public: explicit Show(not_null window); void showOrHideBoxOrLayer( std::variant< v::null_t, object_ptr, std::unique_ptr> &&layer, Ui::LayerOptions options, anim::type animated) const override; [[nodiscard]] not_null toastParent() const override; [[nodiscard]] bool valid() const override; operator bool() const override; private: const base::weak_ptr _window; }; Show::Show(not_null window) : _window(base::make_weak(window)) { } void Show::showOrHideBoxOrLayer( std::variant< v::null_t, object_ptr, std::unique_ptr> &&layer, Ui::LayerOptions options, anim::type animated) const { if (const auto window = _window.get()) { window->widget()->showOrHideBoxOrLayer( std::move(layer), options, animated); } } not_null Show::toastParent() const { const auto window = _window.get(); Assert(window != nullptr); return window->widget()->bodyWidget(); } bool Show::valid() const { return !_window.empty(); } Show::operator bool() const { return valid(); } } // namespace Controller::Controller() : Controller(CreateArgs{ nullptr }) { } Controller::Controller(SeparateId id, MsgId showAtMsgId) : Controller(CreateArgs{ id }) { if (id) { showAccount(id.account, showAtMsgId); } } Controller::Controller(CreateArgs &&args) : _id(args.id) , _isActiveTimer([=] { updateIsActive(); }) , _widget(this) , _adaptive(std::make_unique()) { _widget.init(); } Controller::~Controller() { // We want to delete all widgets before the _sessionController. _widget.ui_hideSettingsAndLayer(anim::type::instant); _widget.clearWidgets(); _accountLifetime.destroy(); _sessionControllerValue = nullptr; _sessionController = nullptr; } SeparateId Controller::id() const { return _id; } bool Controller::isPrimary() const { return _id.primary(); } Main::Account &Controller::account() const { Expects(_id.account != nullptr); return *_id.account; } void Controller::showAccount(not_null account) { showAccount(account, ShowAtUnreadMsgId); } void Controller::showAccount( not_null account, MsgId singlePeerShowAtMsgId) { Expects(isPrimary() || _id.account == account); const auto prevSession = maybeSession(); const auto prevSessionUniqueId = prevSession ? prevSession->uniqueId() : 0; _accountLifetime.destroy(); _id.account = account; Core::App().checkWindowId(this); const auto updateOnlineOfPrevSesssion = crl::guard(account, [=] { if (!prevSessionUniqueId) { return; } for (auto &[index, account] : _id.account->domain().accounts()) { if (const auto anotherSession = account->maybeSession()) { if (anotherSession->uniqueId() == prevSessionUniqueId) { anotherSession->updates().updateOnline(crl::now()); return; } } } }); if (!isPrimary()) { _id.account->sessionChanges( ) | rpl::start_with_next([=](Main::Session *session) { Core::App().closeWindow(this); }, _accountLifetime); } _id.account->sessionValue( ) | rpl::start_with_next([=](Main::Session *session) { const auto was = base::take(_sessionController); _sessionController = session ? std::make_unique(session, this) : nullptr; _sessionControllerValue = _sessionController.get(); auto oldContentCache = _widget.grabForSlideAnimation(); _widget.updateWindowIcon(); if (session) { setupSideBar(); setupMain(singlePeerShowAtMsgId, std::move(oldContentCache)); session->updates().isIdleValue( ) | rpl::filter([=](bool idle) { return !idle; }) | rpl::start_with_next([=] { widget()->checkActivation(); }, _sessionController->lifetime()); session->termsLockValue( ) | rpl::start_with_next([=] { checkLockByTerms(); _widget.updateGlobalMenu(); }, _sessionController->lifetime()); widget()->setInnerFocus(); _sessionController->activeChatChanges( ) | rpl::start_with_next([=] { _widget.updateTitle(); }, _sessionController->lifetime()); _widget.updateTitle(); session->updates().updateOnline(crl::now()); } else { sideBarChanged(); setupIntro(std::move(oldContentCache)); _widget.updateGlobalMenu(); } crl::on_main(updateOnlineOfPrevSesssion); }, _accountLifetime); } void Controller::setupSideBar() { Expects(_sessionController != nullptr); if (!isPrimary()) { return; } _sessionController->filtersMenuChanged( ) | rpl::start_with_next([=] { sideBarChanged(); }, _sessionController->lifetime()); if (_sessionController->session().settings().dialogsFiltersEnabled()) { _sessionController->toggleFiltersMenu(true); } else { sideBarChanged(); } } void Controller::checkLockByTerms() { const auto data = account().sessionExists() ? account().session().termsLocked() : std::nullopt; if (!data) { if (_termsBox) { _termsBox->closeBox(); } return; } hideSettingsAndLayer(anim::type::instant); const auto box = show(Box( *data, tr::lng_terms_agree(), tr::lng_terms_decline())); box->setCloseByEscape(false); box->setCloseByOutsideClick(false); const auto id = data->id; box->agreeClicks( ) | rpl::start_with_next([=] { const auto mention = box ? box->lastClickedMention() : QString(); box->closeBox(); if (const auto session = account().maybeSession()) { session->api().acceptTerms(id); session->unlockTerms(); if (!mention.isEmpty()) { MentionClickHandler(mention).onClick({}); } } }, box->lifetime()); box->cancelClicks( ) | rpl::start_with_next([=] { showTermsDecline(); }, box->lifetime()); QObject::connect(box, &QObject::destroyed, [=] { crl::on_main(widget(), [=] { checkLockByTerms(); }); }); _termsBox = box; } void Controller::showTermsDecline() { const auto box = show(Box( TextWithEntities{ tr::lng_terms_update_sorry(tr::now) }, tr::lng_terms_decline_and_delete(), tr::lng_terms_back(), true)); box->agreeClicks( ) | rpl::start_with_next([=] { if (box) { box->closeBox(); } showTermsDelete(); }, box->lifetime()); box->cancelClicks( ) | rpl::start_with_next([=] { if (box) { box->closeBox(); } }, box->lifetime()); } void Controller::showTermsDelete() { const auto deleteByTerms = [=] { if (const auto session = account().maybeSession()) { session->termsDeleteNow(); } else { hideLayer(); } }; show(Ui::MakeConfirmBox({ .text = tr::lng_terms_delete_warning(), .confirmed = deleteByTerms, .confirmText = tr::lng_terms_delete_now(), .confirmStyle = &st::attentionBoxButton, })); } void Controller::firstShow() { _widget.firstShow(); } void Controller::finishFirstShow() { _widget.finishFirstShow(); checkThemeEditor(); } Main::Session *Controller::maybeSession() const { return _id.account ? _id.account->maybeSession() : nullptr; } auto Controller::sessionControllerValue() const -> rpl::producer { return _sessionControllerValue.value(); } auto Controller::sessionControllerChanges() const -> rpl::producer { return _sessionControllerValue.changes(); } bool Controller::locked() const { if (Core::App().passcodeLocked()) { return true; } else if (const auto controller = sessionController()) { return controller->session().termsLocked().has_value(); } return false; } void Controller::checkThemeEditor() { using namespace Window::Theme; if (const auto editing = Background()->editingTheme()) { showRightColumn(Box(this, *editing)); } } void Controller::setupPasscodeLock() { _widget.setupPasscodeLock(); } void Controller::clearPasscodeLock() { if (!_id) { showAccount(&Core::App().activeAccount()); } else { _widget.clearPasscodeLock(); } } void Controller::setupIntro(QPixmap oldContentCache) { const auto point = Core::App().domain().maybeLastOrSomeAuthedAccount() ? Intro::EnterPoint::Qr : Intro::EnterPoint::Start; _widget.setupIntro(point, std::move(oldContentCache)); } void Controller::setupMain( MsgId singlePeerShowAtMsgId, QPixmap oldContentCache) { Expects(_sessionController != nullptr); _widget.setupMain(singlePeerShowAtMsgId, std::move(oldContentCache)); if (const auto id = Ui::Emoji::NeedToSwitchBackToId()) { Ui::Emoji::LoadAndSwitchTo(&_sessionController->session(), id); } } void Controller::showSettings() { _widget.showSettings(); } int Controller::verticalShadowTop() const { return (Platform::NativeTitleRequiresShadow() && Ui::Platform::NativeWindowFrameSupported() && Core::App().settings().nativeWindowFrame()) ? st::lineWidth : 0; } void Controller::showToast(Ui::Toast::Config &&config) { Show(this).showToast(std::move(config)); } void Controller::showToast(TextWithEntities &&text, crl::time duration) { Show(this).showToast(std::move(text), duration); } void Controller::showToast(const QString &text, crl::time duration) { Show(this).showToast(text, duration); } void Controller::showLayer( std::unique_ptr &&layer, Ui::LayerOptions options, anim::type animated) { _widget.showOrHideBoxOrLayer(std::move(layer), options, animated); } void Controller::showBox( object_ptr content, Ui::LayerOptions options, anim::type animated) { _widget.showOrHideBoxOrLayer(std::move(content), options, animated); } void Controller::showRightColumn(object_ptr widget) { _widget.showRightColumn(std::move(widget)); } void Controller::hideLayer(anim::type animated) { _widget.showOrHideBoxOrLayer(v::null, Ui::LayerOption::CloseOther, animated); } void Controller::hideSettingsAndLayer(anim::type animated) { _widget.ui_hideSettingsAndLayer(animated); } bool Controller::isLayerShown() const { return _widget.ui_isLayerShown(); } void Controller::sideBarChanged() { _widget.recountGeometryConstraints(); } void Controller::activate() { _widget.activate(); } void Controller::updateIsActiveFocus() { _isActiveTimer.callOnce(sessionController() ? sessionController()->session().serverConfig().onlineFocusTimeout : crl::time(1000)); } void Controller::updateIsActiveBlur() { _isActiveTimer.callOnce(sessionController() ? sessionController()->session().serverConfig().offlineBlurTimeout : crl::time(1000)); } void Controller::updateIsActive() { _widget.updateIsActive(); } void Controller::minimize() { if (Core::App().settings().workMode() == Core::Settings::WorkMode::TrayOnly) { _widget.minimizeToTray(); } else { _widget.setWindowState(_widget.windowState() | Qt::WindowMinimized); } } void Controller::close() { _widget.close(); } void Controller::preventOrInvoke(Fn &&callback) { _widget.preventOrInvoke(std::move(callback)); } void Controller::invokeForSessionController( not_null account, PeerData *singlePeer, Fn)> &&callback) { const auto separateWindow = singlePeer ? Core::App().separateWindowFor(not_null(singlePeer)) : nullptr; const auto separateSession = separateWindow ? separateWindow->sessionController() : nullptr; if (separateSession) { return callback(separateSession); } const auto accountWindow = account ? Core::App().separateWindowFor(not_null(account)) : nullptr; const auto accountSession = accountWindow ? accountWindow->sessionController() : nullptr; if (accountSession) { return callback(accountSession); } _id.account->domain().activate(std::move(account)); if (_sessionController) { callback(_sessionController.get()); } } QPoint Controller::getPointForCallPanelCenter() const { return _widget.isActive() ? _widget.geometry().center() : _widget.screen()->geometry().center(); } void Controller::showLogoutConfirmation() { const auto account = Core::App().passcodeLocked() ? nullptr : sessionController() ? &sessionController()->session().account() : nullptr; const auto weak = base::make_weak(account); const auto callback = [=](Fn close) { if (!account || weak) { Core::App().logoutWithChecks(account); } if (close) { close(); } }; show(Ui::MakeConfirmBox({ .text = tr::lng_sure_logout(), .confirmed = callback, .confirmText = tr::lng_settings_logout(), .confirmStyle = &st::attentionBoxButton, })); } Window::Adaptive &Controller::adaptive() const { return *_adaptive; } void Controller::openInMediaView(Media::View::OpenRequest &&request) { _openInMediaViewRequests.fire(std::move(request)); } auto Controller::openInMediaViewRequests() const -> rpl::producer { return _openInMediaViewRequests.events(); } void Controller::setDefaultFloatPlayerDelegate( not_null delegate) { _defaultFloatPlayerDelegate = delegate; _replacementFloatPlayerDelegate = nullptr; _floatPlayerDelegate = delegate; } void Controller::replaceFloatPlayerDelegate( not_null replacement) { Expects(_defaultFloatPlayerDelegate != nullptr); _replacementFloatPlayerDelegate = replacement; _floatPlayerDelegate = replacement; } void Controller::restoreFloatPlayerDelegate( not_null replacement) { Expects(_defaultFloatPlayerDelegate != nullptr); if (_replacementFloatPlayerDelegate == replacement) { _replacementFloatPlayerDelegate = nullptr; _floatPlayerDelegate = _defaultFloatPlayerDelegate; } } auto Controller::floatPlayerDelegate() const -> FloatDelegate* { return _floatPlayerDelegate.current(); } auto Controller::floatPlayerDelegateValue() const -> rpl::producer { return _floatPlayerDelegate.value(); } std::shared_ptr Controller::uiShow() { return std::make_shared(this); } rpl::lifetime &Controller::lifetime() { return _lifetime; } } // namespace Window