/* 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 "history/view/history_view_contact_status.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/layers/generic_box.h" #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" #include "data/data_peer.h" #include "data/data_user.h" #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_session.h" #include "window/window_peer_menu.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "apiwrap.h" #include "main/main_session.h" #include "boxes/confirm_box.h" #include "boxes/peers/edit_contact_box.h" #include "app.h" #include "styles/style_history.h" #include "styles/style_layers.h" namespace HistoryView { namespace { bool BarCurrentlyHidden(not_null peer) { const auto settings = peer->settings(); if (!settings) { return false; } else if (!(*settings)) { return true; } using Setting = MTPDpeerSettings::Flag; if (const auto user = peer->asUser()) { if (user->isBlocked()) { return true; } else if (user->isContact() && !((*settings) & Setting::f_share_contact)) { return true; } } else if (!((*settings) & Setting::f_report_spam)) { return true; } return false; } } // namespace ContactStatus::Bar::Bar(QWidget *parent, const QString &name) : RpWidget(parent) , _name(name) , _add( this, QString(), st::historyContactStatusButton) , _unarchive( this, tr::lng_new_contact_unarchive(tr::now).toUpper(), st::historyContactStatusButton) , _block( this, tr::lng_new_contact_block(tr::now).toUpper(), st::historyContactStatusBlock) , _share( this, tr::lng_new_contact_share(tr::now).toUpper(), st::historyContactStatusButton) , _report( this, QString(), st::historyContactStatusBlock) , _close(this, st::historyReplyCancel) { resize(_close->size()); } void ContactStatus::Bar::showState(State state) { _add->setVisible(state == State::AddOrBlock || state == State::Add); _unarchive->setVisible(state == State::UnarchiveOrBlock || state == State::UnarchiveOrReport); _block->setVisible(state == State::AddOrBlock || state == State::UnarchiveOrBlock); _share->setVisible(state == State::SharePhoneNumber); _report->setVisible(state == State::ReportSpam || state == State::UnarchiveOrReport); _add->setText((state == State::Add) ? tr::lng_new_contact_add_name(tr::now, lt_user, _name).toUpper() : tr::lng_new_contact_add(tr::now).toUpper()); _report->setText((state == State::ReportSpam) ? tr::lng_report_spam_and_leave(tr::now).toUpper() : tr::lng_report_spam(tr::now).toUpper()); updateButtonsGeometry(); } rpl::producer<> ContactStatus::Bar::unarchiveClicks() const { return _unarchive->clicks() | rpl::to_empty; } rpl::producer<> ContactStatus::Bar::addClicks() const { return _add->clicks() | rpl::to_empty; } rpl::producer<> ContactStatus::Bar::blockClicks() const { return _block->clicks() | rpl::to_empty; } rpl::producer<> ContactStatus::Bar::shareClicks() const { return _share->clicks() | rpl::to_empty; } rpl::producer<> ContactStatus::Bar::reportClicks() const { return _report->clicks() | rpl::to_empty; } rpl::producer<> ContactStatus::Bar::closeClicks() const { return _close->clicks() | rpl::to_empty; } void ContactStatus::Bar::resizeEvent(QResizeEvent *e) { _close->moveToRight(0, 0); updateButtonsGeometry(); } void ContactStatus::Bar::updateButtonsGeometry() { const auto full = width(); const auto closeWidth = _close->width(); const auto available = full - closeWidth; const auto skip = st::historyContactStatusMinSkip; const auto buttonWidth = [&](const object_ptr &button) { return button->textWidth() + 2 * skip; }; auto accumulatedLeft = 0; const auto placeButton = [&]( const object_ptr &button, int buttonWidth, int rightTextMargin = 0) { button->setGeometry(accumulatedLeft, 0, buttonWidth, height()); button->setTextMargins({ 0, 0, rightTextMargin, 0 }); accumulatedLeft += buttonWidth; }; const auto placeOne = [&](const object_ptr &button) { if (button->isHidden()) { return; } const auto thatWidth = buttonWidth(button); const auto margin = std::clamp( thatWidth + closeWidth - available, 0, closeWidth); placeButton(button, full, margin); }; const auto &leftButton = _unarchive->isHidden() ? _add : _unarchive; const auto &rightButton = _block->isHidden() ? _report : _block; if (!leftButton->isHidden() && !rightButton->isHidden()) { const auto leftWidth = buttonWidth(leftButton); const auto rightWidth = buttonWidth(rightButton); const auto half = full / 2; if (leftWidth <= half && rightWidth + 2 * closeWidth <= full - half) { placeButton(leftButton, half); placeButton(rightButton, full - half); } else if (leftWidth + rightWidth <= available) { const auto margin = std::clamp( leftWidth + rightWidth + closeWidth - available, 0, closeWidth); const auto realBlockWidth = rightWidth + 2 * closeWidth - margin; if (leftWidth > realBlockWidth) { placeButton(leftButton, leftWidth); placeButton(rightButton, full - leftWidth, margin); } else { placeButton(leftButton, full - realBlockWidth); placeButton(rightButton, realBlockWidth, margin); } } else { const auto forLeft = (available * leftWidth) / (leftWidth + rightWidth); placeButton(leftButton, forLeft); placeButton(rightButton, full - forLeft, closeWidth); } } else { placeOne(_add); placeOne(_share); placeOne(_report); } } ContactStatus::ContactStatus( not_null window, not_null parent, not_null peer) : _controller(window) , _bar(parent, object_ptr(parent, peer->shortName())) , _shadow(parent) { setupWidgets(parent); setupState(peer); setupHandlers(peer); } void ContactStatus::setupWidgets(not_null parent) { parent->widthValue( ) | rpl::start_with_next([=](int width) { _bar.resizeToWidth(width); }, _bar.lifetime()); _bar.geometryValue( ) | rpl::start_with_next([=](QRect geometry) { _shadow.setGeometry( geometry.x(), geometry.y() + geometry.height(), geometry.width(), st::lineWidth); }, _shadow.lifetime()); _bar.shownValue( ) | rpl::start_with_next([=](bool shown) { _shadow.setVisible(shown); }, _shadow.lifetime()); } auto ContactStatus::PeerState(not_null peer) -> rpl::producer { using SettingsChange = PeerData::Settings::Change; using Setting = MTPDpeerSettings::Flag; if (const auto user = peer->asUser()) { using FlagsChange = UserData::Flags::Change; using FullFlagsChange = UserData::FullFlags::Change; using Flag = MTPDuser::Flag; using FullFlag = MTPDuserFull::Flag; auto isContactChanges = user->flagsValue( ) | rpl::filter([](FlagsChange flags) { return flags.diff & (Flag::f_contact | Flag::f_mutual_contact); }); auto isBlockedChanges = user->fullFlagsValue( ) | rpl::filter([](FullFlagsChange full) { return full.diff & FullFlag::f_blocked; }); return rpl::combine( std::move(isContactChanges), std::move(isBlockedChanges), user->settingsValue() ) | rpl::map([=]( FlagsChange flags, FullFlagsChange full, SettingsChange settings) { if (full.value & FullFlag::f_blocked) { return State::None; } else if (user->isContact()) { if (settings.value & Setting::f_share_contact) { return State::SharePhoneNumber; } else { return State::None; } } else if (settings.value & Setting::f_autoarchived) { return State::UnarchiveOrBlock; } else if (settings.value & Setting::f_block_contact) { return State::AddOrBlock; } else if (settings.value & Setting::f_add_contact) { return State::Add; } else { return State::None; } }); } return peer->settingsValue( ) | rpl::map([=](SettingsChange settings) { return (settings.value & Setting::f_autoarchived) ? State::UnarchiveOrReport : (settings.value & Setting::f_report_spam) ? State::ReportSpam : State::None; }); } void ContactStatus::setupState(not_null peer) { if (!BarCurrentlyHidden(peer)) { peer->session().api().requestPeerSettings(peer); } PeerState( peer ) | rpl::start_with_next([=](State state) { _state = state; if (state == State::None) { _bar.hide(anim::type::normal); } else { _bar.entity()->showState(state); _bar.show(anim::type::normal); } }, _bar.lifetime()); } void ContactStatus::setupHandlers(not_null peer) { if (const auto user = peer->asUser()) { setupAddHandler(user); setupBlockHandler(user); setupShareHandler(user); } setupUnarchiveHandler(peer); setupReportHandler(peer); setupCloseHandler(peer); } void ContactStatus::setupAddHandler(not_null user) { _bar.entity()->addClicks( ) | rpl::start_with_next([=] { _controller->window().show(Box(EditContactBox, _controller, user)); }, _bar.lifetime()); } void ContactStatus::setupBlockHandler(not_null user) { _bar.entity()->blockClicks( ) | rpl::start_with_next([=] { _controller->window().show(Box( Window::PeerMenuBlockUserBox, &_controller->window(), user, v::null, Window::ClearChat{})); }, _bar.lifetime()); } void ContactStatus::setupShareHandler(not_null user) { _bar.entity()->shareClicks( ) | rpl::start_with_next([=] { const auto share = [=](Fn &&close) { user->setSettings(0); user->session().api().request(MTPcontacts_AcceptContact( user->inputUser )).done([=](const MTPUpdates &result) { user->session().api().applyUpdates(result); Ui::Toast::Show(tr::lng_new_contact_share_done( tr::now, lt_user, user->shortName())); }).send(); close(); }; _controller->window().show(Box( tr::lng_new_contact_share_sure( tr::now, lt_phone, Ui::Text::WithEntities( App::formatPhone(user->session().user()->phone())), lt_user, Ui::Text::Bold(user->name), Ui::Text::WithEntities), tr::lng_box_ok(tr::now), share)); }, _bar.lifetime()); } void ContactStatus::setupUnarchiveHandler(not_null peer) { _bar.entity()->unarchiveClicks( ) | rpl::start_with_next([=] { Window::ToggleHistoryArchived(peer->owner().history(peer), false); peer->owner().resetNotifySettingsToDefault(peer); if (const auto settings = peer->settings()) { using Flag = MTPDpeerSettings::Flag; const auto flags = Flag::f_autoarchived | Flag::f_block_contact | Flag::f_report_spam; peer->setSettings(*settings & ~flags); } }, _bar.lifetime()); } void ContactStatus::setupReportHandler(not_null peer) { _bar.entity()->reportClicks( ) | rpl::start_with_next([=] { Expects(!peer->isUser()); const auto callback = crl::guard(&_bar, [=](Fn &&close) { close(); peer->session().api().request(MTPmessages_ReportSpam( peer->input )).send(); crl::on_main(&peer->session(), [=] { if (const auto from = peer->migrateFrom()) { peer->session().api().deleteConversation(from, false); } peer->session().api().deleteConversation(peer, false); }); Ui::Toast::Show(tr::lng_report_spam_done(tr::now)); // Destroys _bar. _controller->showBackFromStack(); }); if (const auto user = peer->asUser()) { peer->session().api().blockPeer(user); } const auto text = ((peer->isChat() || peer->isMegagroup()) ? tr::lng_report_spam_sure_group : tr::lng_report_spam_sure_channel)(tr::now); _controller->window().show(Box( text, tr::lng_report_spam_ok(tr::now), st::attentionBoxButton, callback)); }, _bar.lifetime()); } void ContactStatus::setupCloseHandler(not_null peer) { const auto request = _bar.lifetime().make_state(0); _bar.entity()->closeClicks( ) | rpl::filter([=] { return !(*request); }) | rpl::start_with_next([=] { peer->setSettings(0); *request = peer->session().api().request( MTPmessages_HidePeerSettingsBar(peer->input) ).send(); }, _bar.lifetime()); } void ContactStatus::show() { const auto visible = (_state != State::None); if (!_shown) { _shown = true; if (visible) { _bar.entity()->showState(_state); } } _bar.toggle(visible, anim::type::instant); } void ContactStatus::raise() { _bar.raise(); _shadow.raise(); } void ContactStatus::move(int x, int y) { _bar.move(x, y); _shadow.move(x, y + _bar.height()); } int ContactStatus::height() const { return _bar.height(); } rpl::producer ContactStatus::heightValue() const { return _bar.heightValue(); } } // namespace HistoryView