431 lines
11 KiB
C++
431 lines
11 KiB
C++
/*
|
|
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 "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<PeerData*> 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;
|
|
}
|
|
|
|
auto MapToEmpty() {
|
|
return rpl::map([] { return rpl::empty_value(); });
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ContactStatus::Bar::Bar(QWidget *parent, const QString &name)
|
|
: RpWidget(parent)
|
|
, _name(name)
|
|
, _add(
|
|
this,
|
|
QString(),
|
|
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,
|
|
tr::lng_report_spam_and_leave(tr::now).toUpper(),
|
|
st::historyContactStatusBlock)
|
|
, _close(this, st::historyReplyCancel) {
|
|
resize(_close->size());
|
|
}
|
|
|
|
void ContactStatus::Bar::showState(State state) {
|
|
_add->setVisible(state == State::AddOrBlock || state == State::Add);
|
|
_block->setVisible(state == State::AddOrBlock);
|
|
_share->setVisible(state == State::SharePhoneNumber);
|
|
_report->setVisible(state == State::ReportSpam);
|
|
_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());
|
|
updateButtonsGeometry();
|
|
}
|
|
|
|
rpl::producer<> ContactStatus::Bar::addClicks() const {
|
|
return _add->clicks() | MapToEmpty();
|
|
}
|
|
|
|
rpl::producer<> ContactStatus::Bar::blockClicks() const {
|
|
return _block->clicks() | MapToEmpty();
|
|
}
|
|
|
|
rpl::producer<> ContactStatus::Bar::shareClicks() const {
|
|
return _share->clicks() | MapToEmpty();
|
|
}
|
|
|
|
rpl::producer<> ContactStatus::Bar::reportClicks() const {
|
|
return _report->clicks() | MapToEmpty();
|
|
}
|
|
|
|
rpl::producer<> ContactStatus::Bar::closeClicks() const {
|
|
return _close->clicks() | MapToEmpty();
|
|
}
|
|
|
|
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<Ui::FlatButton> &button) {
|
|
return button->textWidth() + 2 * skip;
|
|
};
|
|
|
|
auto accumulatedLeft = 0;
|
|
const auto placeButton = [&](
|
|
const object_ptr<Ui::FlatButton> &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<Ui::FlatButton> &button) {
|
|
if (button->isHidden()) {
|
|
return;
|
|
}
|
|
const auto thatWidth = buttonWidth(button);
|
|
const auto margin = std::clamp(
|
|
thatWidth + closeWidth - available,
|
|
0,
|
|
closeWidth);
|
|
placeButton(button, full, margin);
|
|
};
|
|
if (!_add->isHidden() && !_block->isHidden()) {
|
|
const auto addWidth = buttonWidth(_add);
|
|
const auto blockWidth = buttonWidth(_block);
|
|
const auto half = full / 2;
|
|
if (addWidth <= half
|
|
&& blockWidth + 2 * closeWidth <= full - half) {
|
|
placeButton(_add, half);
|
|
placeButton(_block, full - half);
|
|
} else if (addWidth + blockWidth <= available) {
|
|
const auto margin = std::clamp(
|
|
addWidth + blockWidth + closeWidth - available,
|
|
0,
|
|
closeWidth);
|
|
const auto realBlockWidth = blockWidth + 2 * closeWidth - margin;
|
|
if (addWidth > realBlockWidth) {
|
|
placeButton(_add, addWidth);
|
|
placeButton(_block, full - addWidth, margin);
|
|
} else {
|
|
placeButton(_add, full - realBlockWidth);
|
|
placeButton(_block, realBlockWidth, margin);
|
|
}
|
|
} else {
|
|
const auto forAdd = (available * addWidth)
|
|
/ (addWidth + blockWidth);
|
|
placeButton(_add, forAdd);
|
|
placeButton(_block, full - forAdd, closeWidth);
|
|
}
|
|
} else {
|
|
placeOne(_add);
|
|
placeOne(_share);
|
|
placeOne(_report);
|
|
}
|
|
}
|
|
|
|
ContactStatus::ContactStatus(
|
|
not_null<Window::Controller*> window,
|
|
not_null<Ui::RpWidget*> parent,
|
|
not_null<PeerData*> peer)
|
|
: _window(window)
|
|
, _bar(parent, object_ptr<Bar>(parent, peer->shortName()))
|
|
, _shadow(parent) {
|
|
setupWidgets(parent);
|
|
setupState(peer);
|
|
setupHandlers(peer);
|
|
}
|
|
|
|
void ContactStatus::setupWidgets(not_null<Ui::RpWidget*> 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<PeerData*> peer)
|
|
-> rpl::producer<State> {
|
|
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_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_report_spam)
|
|
? State::ReportSpam
|
|
: State::None;
|
|
});
|
|
}
|
|
|
|
void ContactStatus::setupState(not_null<PeerData*> 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<PeerData*> peer) {
|
|
if (const auto user = peer->asUser()) {
|
|
setupAddHandler(user);
|
|
setupBlockHandler(user);
|
|
setupShareHandler(user);
|
|
}
|
|
setupReportHandler(peer);
|
|
setupCloseHandler(peer);
|
|
}
|
|
|
|
void ContactStatus::setupAddHandler(not_null<UserData*> user) {
|
|
_bar.entity()->addClicks(
|
|
) | rpl::start_with_next([=] {
|
|
_window->show(Box(EditContactBox, _window, user));
|
|
}, _bar.lifetime());
|
|
}
|
|
|
|
void ContactStatus::setupBlockHandler(not_null<UserData*> user) {
|
|
_bar.entity()->blockClicks(
|
|
) | rpl::start_with_next([=] {
|
|
_window->show(
|
|
Box(Window::PeerMenuBlockUserBox, _window, user, true));
|
|
}, _bar.lifetime());
|
|
}
|
|
|
|
void ContactStatus::setupShareHandler(not_null<UserData*> user) {
|
|
_bar.entity()->shareClicks(
|
|
) | rpl::start_with_next([=] {
|
|
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
|
const auto share = [=] {
|
|
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();
|
|
if (*box) {
|
|
(*box)->closeBox();
|
|
}
|
|
};
|
|
*box = _window->show(Box<ConfirmBox>(
|
|
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::setupReportHandler(not_null<PeerData*> peer) {
|
|
_bar.entity()->reportClicks(
|
|
) | rpl::start_with_next([=] {
|
|
Expects(!peer->isUser());
|
|
|
|
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
|
const auto callback = crl::guard(&_bar, [=] {
|
|
if (*box) {
|
|
(*box)->closeBox();
|
|
}
|
|
|
|
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.
|
|
_window->sessionController()->showBackFromStack();
|
|
});
|
|
if (const auto user = peer->asUser()) {
|
|
peer->session().api().blockUser(user);
|
|
}
|
|
const auto text = ((peer->isChat() || peer->isMegagroup())
|
|
? tr::lng_report_spam_sure_group
|
|
: tr::lng_report_spam_sure_channel)(tr::now);
|
|
_window->show(Box<ConfirmBox>(
|
|
text,
|
|
tr::lng_report_spam_ok(tr::now),
|
|
st::attentionBoxButton,
|
|
callback));
|
|
}, _bar.lifetime());
|
|
}
|
|
|
|
void ContactStatus::setupCloseHandler(not_null<PeerData*> peer) {
|
|
const auto request = _bar.lifetime().make_state<mtpRequestId>(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<int> ContactStatus::heightValue() const {
|
|
return _bar.heightValue();
|
|
}
|
|
|
|
} // namespace HistoryView
|