mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Implement connected websites section.
This commit is contained in:
parent
7d4c3766d5
commit
92fec8304e
25 changed files with 1261 additions and 126 deletions
|
@ -167,6 +167,8 @@ PRIVATE
|
||||||
api/api_user_privacy.h
|
api/api_user_privacy.h
|
||||||
api/api_views.cpp
|
api/api_views.cpp
|
||||||
api/api_views.h
|
api/api_views.h
|
||||||
|
api/api_websites.cpp
|
||||||
|
api/api_websites.h
|
||||||
api/api_who_reacted.cpp
|
api/api_who_reacted.cpp
|
||||||
api/api_who_reacted.h
|
api/api_who_reacted.h
|
||||||
boxes/filters/edit_filter_box.cpp
|
boxes/filters/edit_filter_box.cpp
|
||||||
|
@ -1270,6 +1272,8 @@ PRIVATE
|
||||||
settings/settings_scale_preview.cpp
|
settings/settings_scale_preview.cpp
|
||||||
settings/settings_scale_preview.h
|
settings/settings_scale_preview.h
|
||||||
settings/settings_type.h
|
settings/settings_type.h
|
||||||
|
settings/settings_websites.cpp
|
||||||
|
settings/settings_websites.h
|
||||||
storage/details/storage_file_utilities.cpp
|
storage/details/storage_file_utilities.cpp
|
||||||
storage/details/storage_file_utilities.h
|
storage/details/storage_file_utilities.h
|
||||||
storage/details/storage_settings_scheme.cpp
|
storage/details/storage_settings_scheme.cpp
|
||||||
|
|
BIN
Telegram/Resources/icons/menu/ip_address.png
Normal file
BIN
Telegram/Resources/icons/menu/ip_address.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 701 B |
BIN
Telegram/Resources/icons/menu/ip_address@2x.png
Normal file
BIN
Telegram/Resources/icons/menu/ip_address@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/ip_address@3x.png
Normal file
BIN
Telegram/Resources/icons/menu/ip_address@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
Telegram/Resources/icons/menu/payment_address.png
Normal file
BIN
Telegram/Resources/icons/menu/payment_address.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 584 B |
BIN
Telegram/Resources/icons/menu/payment_address@2x.png
Normal file
BIN
Telegram/Resources/icons/menu/payment_address@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/payment_address@3x.png
Normal file
BIN
Telegram/Resources/icons/menu/payment_address@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -646,13 +646,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||||
"lng_settings_security_bots" = "Bots and websites";
|
"lng_settings_security_bots" = "Bots and websites";
|
||||||
"lng_settings_clear_payment_info" = "Clear Payment and Shipping Info";
|
"lng_settings_clear_payment_info" = "Clear Payment and Shipping Info";
|
||||||
"lng_settings_logged_in" = "Logged In with Telegram";
|
"lng_settings_logged_in" = "Connected websites";
|
||||||
"lng_settings_logged_in_about" = "Websites where you've used Telegram to log in.";
|
"lng_settings_logged_in_title" = "Logged in with Telegram";
|
||||||
"lng_settings_logged_in_title" = "Logged In with Telegram";
|
|
||||||
"lng_settings_logged_in_description" = "You can log in on websites that support signing in with Telegram.";
|
"lng_settings_logged_in_description" = "You can log in on websites that support signing in with Telegram.";
|
||||||
"lng_settings_disconnect_all" = "Disconnect All Websites";
|
"lng_settings_disconnect_all" = "Disconnect all websites";
|
||||||
|
"lng_settings_disconnect_title" = "Disconnect website";
|
||||||
|
"lng_settings_disconnect_sure" = "Are you sure you want to disconnect {domain}?";
|
||||||
|
"lng_settings_disconnect_block" = "Block {name}";
|
||||||
|
"lng_settings_disconnect_all_title" = "Disconnect websites";
|
||||||
|
"lng_settings_disconnect_all_sure" = "Are you sure you want to disconnect all websites where you logged in with Telegram?";
|
||||||
|
"lng_settings_disconnect" = "Disconnect";
|
||||||
"lng_settings_connected_title" = "Connected websites";
|
"lng_settings_connected_title" = "Connected websites";
|
||||||
"lng_settings_connected_about" = "Click to disconnect from your Telegram account.";
|
|
||||||
|
|
||||||
"lng_settings_power_menu" = "Battery and Animations";
|
"lng_settings_power_menu" = "Battery and Animations";
|
||||||
"lng_settings_power_title" = "Power Usage";
|
"lng_settings_power_title" = "Power Usage";
|
||||||
|
@ -962,6 +966,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_sessions_terminate" = "Terminate Session";
|
"lng_sessions_terminate" = "Terminate Session";
|
||||||
"lng_sessions_application" = "Application";
|
"lng_sessions_application" = "Application";
|
||||||
"lng_sessions_system" = "System version";
|
"lng_sessions_system" = "System version";
|
||||||
|
"lng_sessions_browser" = "Browser";
|
||||||
"lng_sessions_ip" = "IP address";
|
"lng_sessions_ip" = "IP address";
|
||||||
"lng_sessions_location" = "Location";
|
"lng_sessions_location" = "Location";
|
||||||
"lng_sessions_location_about" = "This location is based only on the IP address and may not always be accurate.";
|
"lng_sessions_location_about" = "This location is based only on the IP address and may not always be accurate.";
|
||||||
|
|
|
@ -72,26 +72,9 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) {
|
||||||
appName,
|
appName,
|
||||||
appVer.isEmpty() ? QString() : (' ' + appVer));
|
appVer.isEmpty() ? QString() : (' ' + appVer));
|
||||||
result.ip = qs(data.vip());
|
result.ip = qs(data.vip());
|
||||||
if (!result.hash) {
|
result.active = result.hash
|
||||||
result.active = tr::lng_status_online(tr::now);
|
? Authorizations::ActiveDateString(result.activeTime)
|
||||||
} else {
|
: tr::lng_status_online(tr::now);
|
||||||
const auto now = QDateTime::currentDateTime();
|
|
||||||
const auto lastTime = base::unixtime::parse(result.activeTime);
|
|
||||||
const auto nowDate = now.date();
|
|
||||||
const auto lastDate = lastTime.date();
|
|
||||||
if (lastDate == nowDate) {
|
|
||||||
result.active = QLocale().toString(
|
|
||||||
lastTime.time(),
|
|
||||||
QLocale::ShortFormat);
|
|
||||||
} else if (lastDate.year() == nowDate.year()
|
|
||||||
&& lastDate.weekNumber() == nowDate.weekNumber()) {
|
|
||||||
result.active = langDayOfWeek(lastDate);
|
|
||||||
} else {
|
|
||||||
result.active = QLocale().toString(
|
|
||||||
lastDate,
|
|
||||||
QLocale::ShortFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.location = country;
|
result.location = country;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -129,16 +112,15 @@ void Authorizations::reload() {
|
||||||
)).done([=](const MTPaccount_Authorizations &result) {
|
)).done([=](const MTPaccount_Authorizations &result) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
_lastReceived = crl::now();
|
_lastReceived = crl::now();
|
||||||
result.match([&](const MTPDaccount_authorizations &auths) {
|
const auto &data = result.data();
|
||||||
_ttlDays = auths.vauthorization_ttl_days().v;
|
_ttlDays = data.vauthorization_ttl_days().v;
|
||||||
_list = (
|
_list = ranges::views::all(
|
||||||
auths.vauthorizations().v
|
data.vauthorizations().v
|
||||||
) | ranges::views::transform([](const MTPAuthorization &d) {
|
) | ranges::views::transform([](const MTPAuthorization &auth) {
|
||||||
return ParseEntry(d.c_authorization());
|
return ParseEntry(auth.data());
|
||||||
}) | ranges::to<List>;
|
}) | ranges::to<List>;
|
||||||
refreshCallsDisabledHereFromCloud();
|
refreshCallsDisabledHereFromCloud();
|
||||||
_listChanges.fire({});
|
_listChanges.fire({});
|
||||||
});
|
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -190,19 +172,21 @@ Authorizations::List Authorizations::list() const {
|
||||||
return _list;
|
return _list;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Authorizations::listChanges() const
|
auto Authorizations::listValue() const
|
||||||
-> rpl::producer<Authorizations::List> {
|
-> rpl::producer<Authorizations::List> {
|
||||||
return rpl::single(
|
return rpl::single(
|
||||||
list()
|
list()
|
||||||
) | rpl::then(
|
) | rpl::then(
|
||||||
_listChanges.events() | rpl::map([=] { return list(); }));
|
_listChanges.events() | rpl::map([=] { return list(); })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<int> Authorizations::totalChanges() const {
|
rpl::producer<int> Authorizations::totalValue() const {
|
||||||
return rpl::single(
|
return rpl::single(
|
||||||
total()
|
total()
|
||||||
) | rpl::then(
|
) | rpl::then(
|
||||||
_listChanges.events() | rpl::map([=] { return total(); }));
|
_listChanges.events() | rpl::map([=] { return total(); })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Authorizations::updateTTL(int days) {
|
void Authorizations::updateTTL(int days) {
|
||||||
|
@ -254,6 +238,19 @@ rpl::producer<bool> Authorizations::callsDisabledHereChanges() const {
|
||||||
return _callsDisabledHere.changes();
|
return _callsDisabledHere.changes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Authorizations::ActiveDateString(TimeId active) {
|
||||||
|
const auto now = QDateTime::currentDateTime();
|
||||||
|
const auto lastTime = base::unixtime::parse(active);
|
||||||
|
const auto nowDate = now.date();
|
||||||
|
const auto lastDate = lastTime.date();
|
||||||
|
return (lastDate == nowDate)
|
||||||
|
? QLocale().toString(lastTime.time(), QLocale::ShortFormat)
|
||||||
|
: (lastDate.year() == nowDate.year()
|
||||||
|
&& lastDate.weekNumber() == nowDate.weekNumber())
|
||||||
|
? langDayOfWeek(lastDate)
|
||||||
|
: QLocale().toString(lastDate, QLocale::ShortFormat);
|
||||||
|
}
|
||||||
|
|
||||||
int Authorizations::total() const {
|
int Authorizations::total() const {
|
||||||
return ranges::count_if(
|
return ranges::count_if(
|
||||||
_list,
|
_list,
|
||||||
|
|
|
@ -38,9 +38,9 @@ public:
|
||||||
[[nodiscard]] crl::time lastReceivedTime();
|
[[nodiscard]] crl::time lastReceivedTime();
|
||||||
|
|
||||||
[[nodiscard]] List list() const;
|
[[nodiscard]] List list() const;
|
||||||
[[nodiscard]] rpl::producer<List> listChanges() const;
|
[[nodiscard]] rpl::producer<List> listValue() const;
|
||||||
[[nodiscard]] int total() const;
|
[[nodiscard]] int total() const;
|
||||||
[[nodiscard]] rpl::producer<int> totalChanges() const;
|
[[nodiscard]] rpl::producer<int> totalValue() const;
|
||||||
|
|
||||||
void updateTTL(int days);
|
void updateTTL(int days);
|
||||||
[[nodiscard]] rpl::producer<int> ttlDays() const;
|
[[nodiscard]] rpl::producer<int> ttlDays() const;
|
||||||
|
@ -53,6 +53,8 @@ public:
|
||||||
[[nodiscard]] rpl::producer<bool> callsDisabledHereValue() const;
|
[[nodiscard]] rpl::producer<bool> callsDisabledHereValue() const;
|
||||||
[[nodiscard]] rpl::producer<bool> callsDisabledHereChanges() const;
|
[[nodiscard]] rpl::producer<bool> callsDisabledHereChanges() const;
|
||||||
|
|
||||||
|
[[nodiscard]] static QString ActiveDateString(TimeId active);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshCallsDisabledHereFromCloud();
|
void refreshCallsDisabledHereFromCloud();
|
||||||
|
|
||||||
|
|
138
Telegram/SourceFiles/api/api_websites.cpp
Normal file
138
Telegram/SourceFiles/api/api_websites.cpp
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
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 "api/api_websites.h"
|
||||||
|
|
||||||
|
#include "api/api_authorizations.h"
|
||||||
|
#include "api/api_blocked_peers.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto TestApiId = 17349;
|
||||||
|
constexpr auto SnapApiId = 611335;
|
||||||
|
constexpr auto DesktopApiId = 2040;
|
||||||
|
|
||||||
|
Websites::Entry ParseEntry(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
const MTPDwebAuthorization &data) {
|
||||||
|
auto result = Websites::Entry{
|
||||||
|
.hash = data.vhash().v,
|
||||||
|
.bot = owner->user(data.vbot_id()),
|
||||||
|
.platform = qs(data.vplatform()),
|
||||||
|
.domain = qs(data.vdomain()),
|
||||||
|
.browser = qs(data.vbrowser()),
|
||||||
|
.ip = qs(data.vip()),
|
||||||
|
.location = qs(data.vregion()),
|
||||||
|
};
|
||||||
|
result.activeTime = data.vdate_active().v
|
||||||
|
? data.vdate_active().v
|
||||||
|
: data.vdate_created().v;
|
||||||
|
result.active = Authorizations::ActiveDateString(result.activeTime);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Websites::Websites(not_null<ApiWrap*> api)
|
||||||
|
: _session(&api->session())
|
||||||
|
, _api(&api->instance()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Websites::reload() {
|
||||||
|
if (_requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestId = _api.request(MTPaccount_GetWebAuthorizations(
|
||||||
|
)).done([=](const MTPaccount_WebAuthorizations &result) {
|
||||||
|
_requestId = 0;
|
||||||
|
_lastReceived = crl::now();
|
||||||
|
const auto owner = &_session->data();
|
||||||
|
const auto &data = result.data();
|
||||||
|
owner->processUsers(data.vusers());
|
||||||
|
_list = ranges::views::all(
|
||||||
|
data.vauthorizations().v
|
||||||
|
) | ranges::views::transform([&](const MTPwebAuthorization &auth) {
|
||||||
|
return ParseEntry(owner, auth.data());
|
||||||
|
}) | ranges::to<List>;
|
||||||
|
_listChanges.fire({});
|
||||||
|
}).fail([=] {
|
||||||
|
_requestId = 0;
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Websites::cancelCurrentRequest() {
|
||||||
|
_api.request(base::take(_requestId)).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Websites::requestTerminate(
|
||||||
|
Fn<void(const MTPBool &result)> &&done,
|
||||||
|
Fn<void(const MTP::Error &error)> &&fail,
|
||||||
|
std::optional<uint64> hash,
|
||||||
|
UserData *botToBlock) {
|
||||||
|
const auto send = [&](auto request) {
|
||||||
|
_api.request(
|
||||||
|
std::move(request)
|
||||||
|
).done([=, done = std::move(done)](const MTPBool &result) {
|
||||||
|
done(result);
|
||||||
|
if (hash) {
|
||||||
|
_list.erase(
|
||||||
|
ranges::remove(_list, *hash, &Entry::hash),
|
||||||
|
end(_list));
|
||||||
|
} else {
|
||||||
|
_list.clear();
|
||||||
|
}
|
||||||
|
_listChanges.fire({});
|
||||||
|
}).fail(
|
||||||
|
std::move(fail)
|
||||||
|
).send();
|
||||||
|
};
|
||||||
|
if (hash) {
|
||||||
|
send(MTPaccount_ResetWebAuthorization(MTP_long(*hash)));
|
||||||
|
if (botToBlock) {
|
||||||
|
botToBlock->session().api().blockedPeers().block(botToBlock);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send(MTPaccount_ResetWebAuthorizations());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Websites::List Websites::list() const {
|
||||||
|
return _list;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Websites::listValue() const
|
||||||
|
-> rpl::producer<Websites::List> {
|
||||||
|
return rpl::single(
|
||||||
|
list()
|
||||||
|
) | rpl::then(
|
||||||
|
_listChanges.events() | rpl::map([=] { return list(); })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> Websites::totalValue() const {
|
||||||
|
return rpl::single(
|
||||||
|
total()
|
||||||
|
) | rpl::then(
|
||||||
|
_listChanges.events() | rpl::map([=] { return total(); })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Websites::total() const {
|
||||||
|
return _list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
crl::time Websites::lastReceivedTime() {
|
||||||
|
return _lastReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Api
|
62
Telegram/SourceFiles/api/api_websites.h
Normal file
62
Telegram/SourceFiles/api/api_websites.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mtproto/sender.h"
|
||||||
|
|
||||||
|
class ApiWrap;
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
|
||||||
|
class Websites final {
|
||||||
|
public:
|
||||||
|
explicit Websites(not_null<ApiWrap*> api);
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
uint64 hash = 0;
|
||||||
|
|
||||||
|
not_null<UserData*> bot;
|
||||||
|
TimeId activeTime = 0;
|
||||||
|
QString active, platform, domain, browser, ip, location;
|
||||||
|
};
|
||||||
|
using List = std::vector<Entry>;
|
||||||
|
|
||||||
|
void reload();
|
||||||
|
void cancelCurrentRequest();
|
||||||
|
void requestTerminate(
|
||||||
|
Fn<void(const MTPBool &result)> &&done,
|
||||||
|
Fn<void(const MTP::Error &error)> &&fail,
|
||||||
|
std::optional<uint64> hash = std::nullopt,
|
||||||
|
UserData *botToBlock = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] crl::time lastReceivedTime();
|
||||||
|
|
||||||
|
[[nodiscard]] List list() const;
|
||||||
|
[[nodiscard]] rpl::producer<List> listValue() const;
|
||||||
|
[[nodiscard]] int total() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> totalValue() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
MTP::Sender _api;
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
|
||||||
|
List _list;
|
||||||
|
rpl::event_stream<> _listChanges;
|
||||||
|
|
||||||
|
crl::time _lastReceived = 0;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_transcribes.h"
|
#include "api/api_transcribes.h"
|
||||||
#include "api/api_premium.h"
|
#include "api/api_premium.h"
|
||||||
#include "api/api_user_names.h"
|
#include "api/api_user_names.h"
|
||||||
|
#include "api/api_websites.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
|
@ -176,7 +177,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||||
, _ringtones(std::make_unique<Api::Ringtones>(this))
|
, _ringtones(std::make_unique<Api::Ringtones>(this))
|
||||||
, _transcribes(std::make_unique<Api::Transcribes>(this))
|
, _transcribes(std::make_unique<Api::Transcribes>(this))
|
||||||
, _premium(std::make_unique<Api::Premium>(this))
|
, _premium(std::make_unique<Api::Premium>(this))
|
||||||
, _usernames(std::make_unique<Api::Usernames>(this)) {
|
, _usernames(std::make_unique<Api::Usernames>(this))
|
||||||
|
, _websites(std::make_unique<Api::Websites>(this)) {
|
||||||
crl::on_main(session, [=] {
|
crl::on_main(session, [=] {
|
||||||
// You can't use _session->lifetime() in the constructor,
|
// You can't use _session->lifetime() in the constructor,
|
||||||
// only queued, because it is not constructed yet.
|
// only queued, because it is not constructed yet.
|
||||||
|
@ -4290,3 +4292,7 @@ Api::Premium &ApiWrap::premium() {
|
||||||
Api::Usernames &ApiWrap::usernames() {
|
Api::Usernames &ApiWrap::usernames() {
|
||||||
return *_usernames;
|
return *_usernames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Api::Websites &ApiWrap::websites() {
|
||||||
|
return *_websites;
|
||||||
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ class Ringtones;
|
||||||
class Transcribes;
|
class Transcribes;
|
||||||
class Premium;
|
class Premium;
|
||||||
class Usernames;
|
class Usernames;
|
||||||
|
class Websites;
|
||||||
|
|
||||||
namespace details {
|
namespace details {
|
||||||
|
|
||||||
|
@ -383,6 +384,7 @@ public:
|
||||||
[[nodiscard]] Api::Transcribes &transcribes();
|
[[nodiscard]] Api::Transcribes &transcribes();
|
||||||
[[nodiscard]] Api::Premium &premium();
|
[[nodiscard]] Api::Premium &premium();
|
||||||
[[nodiscard]] Api::Usernames &usernames();
|
[[nodiscard]] Api::Usernames &usernames();
|
||||||
|
[[nodiscard]] Api::Websites &websites();
|
||||||
|
|
||||||
void updatePrivacyLastSeens();
|
void updatePrivacyLastSeens();
|
||||||
|
|
||||||
|
@ -693,6 +695,7 @@ private:
|
||||||
const std::unique_ptr<Api::Transcribes> _transcribes;
|
const std::unique_ptr<Api::Transcribes> _transcribes;
|
||||||
const std::unique_ptr<Api::Premium> _premium;
|
const std::unique_ptr<Api::Premium> _premium;
|
||||||
const std::unique_ptr<Api::Usernames> _usernames;
|
const std::unique_ptr<Api::Usernames> _usernames;
|
||||||
|
const std::unique_ptr<Api::Websites> _websites;
|
||||||
|
|
||||||
mtpRequestId _wallPaperRequestId = 0;
|
mtpRequestId _wallPaperRequestId = 0;
|
||||||
QString _wallPaperSlug;
|
QString _wallPaperSlug;
|
||||||
|
|
|
@ -35,10 +35,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kSessionsShortPollTimeout = 60 * crl::time(1000);
|
constexpr auto kShortPollTimeout = 60 * crl::time(1000);
|
||||||
constexpr auto kMaxDeviceModelLength = 32;
|
constexpr auto kMaxDeviceModelLength = 32;
|
||||||
|
|
||||||
using EntryData = Api::Authorizations::Entry;
|
using EntryData = Api::Authorizations::Entry;
|
||||||
|
@ -80,6 +81,14 @@ public:
|
||||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||||
bool forceRound) override;
|
bool forceRound) override;
|
||||||
|
|
||||||
|
QSize rightActionSize() const override {
|
||||||
|
return elementGeometry(2, 0).size();
|
||||||
|
}
|
||||||
|
QMargins rightActionMargins() const override {
|
||||||
|
const auto rect = elementGeometry(2, 0);
|
||||||
|
return QMargins(0, rect.y(), -(rect.x() + rect.width()), 0);
|
||||||
|
}
|
||||||
|
|
||||||
int elementsCount() const override;
|
int elementsCount() const override;
|
||||||
QRect elementGeometry(int element, int outerWidth) const override;
|
QRect elementGeometry(int element, int outerWidth) const override;
|
||||||
bool elementDisabled(int element) const override;
|
bool elementDisabled(int element) const override;
|
||||||
|
@ -458,28 +467,27 @@ void SessionInfoBox(
|
||||||
AddSkip(container, st::sessionSubtitleSkip);
|
AddSkip(container, st::sessionSubtitleSkip);
|
||||||
AddSubsectionTitle(container, tr::lng_sessions_info());
|
AddSubsectionTitle(container, tr::lng_sessions_info());
|
||||||
|
|
||||||
const auto add = [&](rpl::producer<QString> label, QString value) {
|
AddSessionInfoRow(
|
||||||
if (value.isEmpty()) {
|
container,
|
||||||
return;
|
tr::lng_sessions_application(),
|
||||||
}
|
data.info,
|
||||||
container->add(
|
st::menuIconDevices);
|
||||||
object_ptr<Ui::FlatLabel>(
|
AddSessionInfoRow(
|
||||||
container,
|
container,
|
||||||
rpl::single(value),
|
tr::lng_sessions_system(),
|
||||||
st::boxLabel),
|
data.system,
|
||||||
st::boxRowPadding + st::sessionValuePadding);
|
st::menuIconInfo);
|
||||||
container->add(
|
AddSessionInfoRow(
|
||||||
object_ptr<Ui::FlatLabel>(
|
container,
|
||||||
container,
|
tr::lng_sessions_ip(),
|
||||||
std::move(label),
|
data.ip,
|
||||||
st::sessionValueLabel),
|
st::menuIconIpAddress);
|
||||||
(st::boxRowPadding
|
AddSessionInfoRow(
|
||||||
+ style::margins{ 0, 0, 0, st::sessionValueSkip }));
|
container,
|
||||||
};
|
tr::lng_sessions_location(),
|
||||||
add(tr::lng_sessions_application(), data.info);
|
data.location,
|
||||||
add(tr::lng_sessions_system(), data.system);
|
st::menuIconAddress);
|
||||||
add(tr::lng_sessions_ip(), data.ip);
|
|
||||||
add(tr::lng_sessions_location(), data.location);
|
|
||||||
AddSkip(container, st::sessionValueSkip);
|
AddSkip(container, st::sessionValueSkip);
|
||||||
if (!data.location.isEmpty()) {
|
if (!data.location.isEmpty()) {
|
||||||
AddDividerText(container, tr::lng_sessions_location_about());
|
AddDividerText(container, tr::lng_sessions_location_about());
|
||||||
|
@ -615,8 +623,6 @@ void Row::elementsPaint(
|
||||||
outerWidth);
|
outerWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
class SessionsContent : public Ui::RpWidget {
|
class SessionsContent : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
SessionsContent(
|
SessionsContent(
|
||||||
|
@ -760,7 +766,7 @@ void SessionsContent::setupContent() {
|
||||||
_inner->setVisible(!value);
|
_inner->setVisible(!value);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_authorizations->listChanges(
|
_authorizations->listValue(
|
||||||
) | rpl::start_with_next([=](const Api::Authorizations::List &list) {
|
) | rpl::start_with_next([=](const Api::Authorizations::List &list) {
|
||||||
parse(list);
|
parse(list);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
@ -791,7 +797,7 @@ void SessionsContent::parse(const Api::Authorizations::List &list) {
|
||||||
|
|
||||||
_inner->showData(_data);
|
_inner->showData(_data);
|
||||||
|
|
||||||
_shortPollTimer.callOnce(kSessionsShortPollTimeout);
|
_shortPollTimer.callOnce(kShortPollTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionsContent::resizeEvent(QResizeEvent *e) {
|
void SessionsContent::resizeEvent(QResizeEvent *e) {
|
||||||
|
@ -816,7 +822,7 @@ void SessionsContent::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionsContent::shortPollSessions() {
|
void SessionsContent::shortPollSessions() {
|
||||||
const auto left = kSessionsShortPollTimeout
|
const auto left = kShortPollTimeout
|
||||||
- (crl::now() - _authorizations->lastReceivedTime());
|
- (crl::now() - _authorizations->lastReceivedTime());
|
||||||
if (left > 0) {
|
if (left > 0) {
|
||||||
parse(_authorizations->list());
|
parse(_authorizations->list());
|
||||||
|
@ -1148,27 +1154,7 @@ auto SessionsContent::ListController::Add(
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionsBox::SessionsBox(
|
} // namespace
|
||||||
QWidget*,
|
|
||||||
not_null<Window::SessionController*> controller)
|
|
||||||
: _controller(controller) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void SessionsBox::prepare() {
|
|
||||||
setTitle(tr::lng_sessions_other_header());
|
|
||||||
|
|
||||||
addButton(tr::lng_close(), [=] { closeBox(); });
|
|
||||||
|
|
||||||
const auto w = st::boxWideWidth;
|
|
||||||
|
|
||||||
const auto content = setInnerWidget(
|
|
||||||
object_ptr<SessionsContent>(this, _controller),
|
|
||||||
st::sessionsScroll);
|
|
||||||
content->resize(w, st::noContactsHeight);
|
|
||||||
content->setupContent();
|
|
||||||
|
|
||||||
setDimensions(w, st::sessionsHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
|
@ -1193,4 +1179,41 @@ void Sessions::setupContent(not_null<Window::SessionController*> controller) {
|
||||||
Ui::ResizeFitChild(this, container);
|
Ui::ResizeFitChild(this, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddSessionInfoRow(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
rpl::producer<QString> label,
|
||||||
|
const QString &value,
|
||||||
|
const style::icon &icon) {
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto text = container->add(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
rpl::single(value),
|
||||||
|
st::boxLabel),
|
||||||
|
st::boxRowPadding + st::sessionValuePadding);
|
||||||
|
const auto left = st::sessionValuePadding.left();
|
||||||
|
container->add(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
std::move(label),
|
||||||
|
st::sessionValueLabel),
|
||||||
|
(st::boxRowPadding
|
||||||
|
+ style::margins{ left, 0, 0, st::sessionValueSkip }));
|
||||||
|
|
||||||
|
const auto widget = Ui::CreateChild<Ui::RpWidget>(container.get());
|
||||||
|
widget->resize(icon.size());
|
||||||
|
|
||||||
|
text->topValue() | rpl::start_with_next([=](int top) {
|
||||||
|
widget->move(st::sessionValueIconPosition + QPoint(0, top));
|
||||||
|
}, widget->lifetime());
|
||||||
|
|
||||||
|
widget->paintRequest() | rpl::start_with_next([=, &icon] {
|
||||||
|
auto p = QPainter(widget);
|
||||||
|
icon.paintInCenter(p, widget->rect());
|
||||||
|
}, widget->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
|
@ -7,12 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "boxes/abstract_box.h"
|
|
||||||
#include "settings/settings_common.h"
|
#include "settings/settings_common.h"
|
||||||
|
|
||||||
namespace Main {
|
namespace Ui {
|
||||||
class Session;
|
class VerticalLayout;
|
||||||
} // namespace Main
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
|
||||||
|
@ -29,16 +28,10 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void AddSessionInfoRow(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
rpl::producer<QString> label,
|
||||||
|
const QString &value,
|
||||||
|
const style::icon &icon);
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
||||||
class SessionsBox : public Ui::BoxContent {
|
|
||||||
public:
|
|
||||||
SessionsBox(QWidget*, not_null<Window::SessionController*> controller);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void prepare() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const not_null<Window::SessionController*> _controller;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
|
@ -316,21 +316,15 @@ sessionLocationTop: 54px;
|
||||||
sessionCurrentSkip: 8px;
|
sessionCurrentSkip: 8px;
|
||||||
sessionSubtitleSkip: 14px;
|
sessionSubtitleSkip: 14px;
|
||||||
sessionInfoFg: windowSubTextFg;
|
sessionInfoFg: windowSubTextFg;
|
||||||
sessionTerminateTop: 9px;
|
sessionTerminateTop: 8px;
|
||||||
sessionTerminateSkip: 12px;
|
sessionTerminateSkip: 11px;
|
||||||
sessionTerminate: IconButton {
|
sessionTerminate: IconButton {
|
||||||
width: 20px;
|
width: 34px;
|
||||||
height: 20px;
|
height: 34px;
|
||||||
|
|
||||||
icon: smallCloseIcon;
|
icon: smallCloseIcon;
|
||||||
iconOver: smallCloseIconOver;
|
iconOver: smallCloseIconOver;
|
||||||
iconPosition: point(5px, 5px);
|
iconPosition: point(12px, 12px);
|
||||||
|
|
||||||
rippleAreaPosition: point(0px, 0px);
|
|
||||||
rippleAreaSize: 20px;
|
|
||||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
|
||||||
color: windowBgOver;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sessionIconWindows: icon{{ "settings/devices/device_desktop_win", historyPeerUserpicFg }};
|
sessionIconWindows: icon{{ "settings/devices/device_desktop_win", historyPeerUserpicFg }};
|
||||||
sessionIconMac: icon{{ "settings/devices/device_desktop_mac", historyPeerUserpicFg }};
|
sessionIconMac: icon{{ "settings/devices/device_desktop_mac", historyPeerUserpicFg }};
|
||||||
|
@ -365,11 +359,12 @@ sessionDateLabel: FlatLabel(defaultFlatLabel) {
|
||||||
align: align(top);
|
align: align(top);
|
||||||
}
|
}
|
||||||
sessionDateSkip: 19px;
|
sessionDateSkip: 19px;
|
||||||
sessionValuePadding: margins(0px, 5px, 0px, 2px);
|
sessionValuePadding: margins(37px, 5px, 0px, 0px);
|
||||||
sessionValueLabel: FlatLabel(defaultFlatLabel) {
|
sessionValueLabel: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: windowSubTextFg;
|
textFg: windowSubTextFg;
|
||||||
}
|
}
|
||||||
sessionValueSkip: 8px;
|
sessionValueSkip: 8px;
|
||||||
|
sessionValueIconPosition: point(20px, 9px);
|
||||||
|
|
||||||
sessionListItem: PeerListItem(defaultPeerListItem) {
|
sessionListItem: PeerListItem(defaultPeerListItem) {
|
||||||
button: OutlineButton(defaultPeerListButton) {
|
button: OutlineButton(defaultPeerListButton) {
|
||||||
|
@ -391,6 +386,21 @@ sessionList: PeerList(defaultPeerList) {
|
||||||
item: sessionListItem;
|
item: sessionListItem;
|
||||||
padding: margins(0px, 4px, 0px, 0px);
|
padding: margins(0px, 4px, 0px, 0px);
|
||||||
}
|
}
|
||||||
|
websiteListItem: PeerListItem(sessionListItem) {
|
||||||
|
height: 72px;
|
||||||
|
photoPosition: point(18px, 10px);
|
||||||
|
namePosition: point(64px, 6px);
|
||||||
|
statusPosition: point(64px, 26px);
|
||||||
|
photoSize: 32px;
|
||||||
|
}
|
||||||
|
websiteList: PeerList(sessionList) {
|
||||||
|
item: websiteListItem;
|
||||||
|
}
|
||||||
|
websiteLocationTop: 46px;
|
||||||
|
websiteBigUserpic: UserpicButton(defaultUserpicButton) {
|
||||||
|
size: size(70px, 70px);
|
||||||
|
photoSize: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
settingsPhotoLeft: 22px;
|
settingsPhotoLeft: 22px;
|
||||||
settingsPhotoTop: 8px;
|
settingsPhotoTop: 8px;
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_self_destruct.h"
|
#include "api/api_self_destruct.h"
|
||||||
#include "api/api_sensitive_content.h"
|
#include "api/api_sensitive_content.h"
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
|
#include "api/api_websites.h"
|
||||||
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
|
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
|
||||||
#include "settings/cloud_password/settings_cloud_password_input.h"
|
#include "settings/cloud_password/settings_cloud_password_input.h"
|
||||||
#include "settings/cloud_password/settings_cloud_password_start.h"
|
#include "settings/cloud_password/settings_cloud_password_start.h"
|
||||||
|
@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "settings/settings_local_passcode.h"
|
#include "settings/settings_local_passcode.h"
|
||||||
#include "settings/settings_premium.h" // Settings::ShowPremium.
|
#include "settings/settings_premium.h" // Settings::ShowPremium.
|
||||||
#include "settings/settings_privacy_controllers.h"
|
#include "settings/settings_privacy_controllers.h"
|
||||||
|
#include "settings/settings_websites.h"
|
||||||
#include "base/timer_rpl.h"
|
#include "base/timer_rpl.h"
|
||||||
#include "boxes/edit_privacy_box.h"
|
#include "boxes/edit_privacy_box.h"
|
||||||
#include "boxes/passcode_box.h"
|
#include "boxes/passcode_box.h"
|
||||||
|
@ -592,6 +594,44 @@ void SetupBlockedList(
|
||||||
}, blockedPeers->lifetime());
|
}, blockedPeers->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetupWebsitesList(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
rpl::producer<> updateTrigger,
|
||||||
|
Fn<void(Type)> showOther) {
|
||||||
|
std::move(
|
||||||
|
updateTrigger
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
controller->session().api().websites().reload();
|
||||||
|
}, container->lifetime());
|
||||||
|
|
||||||
|
auto count = controller->session().api().websites().totalValue();
|
||||||
|
auto countText = rpl::duplicate(
|
||||||
|
count
|
||||||
|
) | rpl::filter(rpl::mappers::_1 > 0) | rpl::map([](int count) {
|
||||||
|
return QString::number(count);
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto wrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
|
const auto inner = wrap->entity();
|
||||||
|
|
||||||
|
AddButtonWithLabel(
|
||||||
|
inner,
|
||||||
|
tr::lng_settings_logged_in(),
|
||||||
|
std::move(countText),
|
||||||
|
st::settingsButton,
|
||||||
|
{ &st::menuIconIpAddress }
|
||||||
|
)->addClickHandler([=] {
|
||||||
|
showOther(Websites::Id());
|
||||||
|
});
|
||||||
|
|
||||||
|
wrap->toggleOn(std::move(count) | rpl::map(rpl::mappers::_1 > 0));
|
||||||
|
wrap->finishAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
void SetupSessionsList(
|
void SetupSessionsList(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
@ -603,7 +643,7 @@ void SetupSessionsList(
|
||||||
controller->session().api().authorizations().reload();
|
controller->session().api().authorizations().reload();
|
||||||
}, container->lifetime());
|
}, container->lifetime());
|
||||||
|
|
||||||
auto count = controller->session().api().authorizations().totalChanges(
|
auto count = controller->session().api().authorizations().totalValue(
|
||||||
) | rpl::map([](int count) {
|
) | rpl::map([](int count) {
|
||||||
return count ? QString::number(count) : QString();
|
return count ? QString::number(count) : QString();
|
||||||
});
|
});
|
||||||
|
@ -664,12 +704,17 @@ void SetupSecurity(
|
||||||
container,
|
container,
|
||||||
rpl::duplicate(updateTrigger),
|
rpl::duplicate(updateTrigger),
|
||||||
showOther);
|
showOther);
|
||||||
|
SetupLocalPasscode(controller, container, showOther);
|
||||||
SetupBlockedList(
|
SetupBlockedList(
|
||||||
controller,
|
controller,
|
||||||
container,
|
container,
|
||||||
rpl::duplicate(updateTrigger),
|
rpl::duplicate(updateTrigger),
|
||||||
showOther);
|
showOther);
|
||||||
SetupLocalPasscode(controller, container, showOther);
|
SetupWebsitesList(
|
||||||
|
controller,
|
||||||
|
container,
|
||||||
|
rpl::duplicate(updateTrigger),
|
||||||
|
showOther);
|
||||||
SetupSessionsList(
|
SetupSessionsList(
|
||||||
controller,
|
controller,
|
||||||
container,
|
container,
|
||||||
|
|
783
Telegram/SourceFiles/settings/settings_websites.cpp
Normal file
783
Telegram/SourceFiles/settings/settings_websites.cpp
Normal file
|
@ -0,0 +1,783 @@
|
||||||
|
/*
|
||||||
|
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 "settings/settings_websites.h"
|
||||||
|
|
||||||
|
#include "api/api_websites.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "boxes/peer_list_box.h"
|
||||||
|
#include "boxes/sessions_box.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "ui/boxes/confirm_box.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "ui/controls/userpic_button.h"
|
||||||
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_settings.h"
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kShortPollTimeout = 60 * crl::time(1000);
|
||||||
|
constexpr auto kMaxDeviceModelLength = 32;
|
||||||
|
|
||||||
|
using EntryData = Api::Websites::Entry;
|
||||||
|
|
||||||
|
class Row;
|
||||||
|
|
||||||
|
class RowDelegate {
|
||||||
|
public:
|
||||||
|
virtual void rowUpdateRow(not_null<Row*> row) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Row final : public PeerListRow {
|
||||||
|
public:
|
||||||
|
Row(not_null<RowDelegate*> delegate, const EntryData &data);
|
||||||
|
|
||||||
|
void update(const EntryData &data);
|
||||||
|
void updateName(const QString &name);
|
||||||
|
|
||||||
|
[[nodiscard]] EntryData data() const;
|
||||||
|
|
||||||
|
QString generateName() override;
|
||||||
|
QString generateShortName() override;
|
||||||
|
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||||
|
bool forceRound) override;
|
||||||
|
|
||||||
|
QSize rightActionSize() const override {
|
||||||
|
return elementGeometry(2, 0).size();
|
||||||
|
}
|
||||||
|
QMargins rightActionMargins() const override {
|
||||||
|
const auto rect = elementGeometry(2, 0);
|
||||||
|
return QMargins(0, rect.y(), -(rect.x() + rect.width()), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int elementsCount() const override;
|
||||||
|
QRect elementGeometry(int element, int outerWidth) const override;
|
||||||
|
bool elementDisabled(int element) const override;
|
||||||
|
bool elementOnlySelect(int element) const override;
|
||||||
|
void elementAddRipple(
|
||||||
|
int element,
|
||||||
|
QPoint point,
|
||||||
|
Fn<void()> updateCallback) override;
|
||||||
|
void elementsStopLastRipple() override;
|
||||||
|
void elementsPaint(
|
||||||
|
Painter &p,
|
||||||
|
int outerWidth,
|
||||||
|
bool selected,
|
||||||
|
int selectedElement) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<RowDelegate*> _delegate;
|
||||||
|
QImage _emptyUserpic;
|
||||||
|
Ui::PeerUserpicView _userpic;
|
||||||
|
Ui::Text::String _location;
|
||||||
|
EntryData _data;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QString JoinNonEmpty(QStringList list) {
|
||||||
|
list.erase(ranges::remove(list, QString()), list.end());
|
||||||
|
return list.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString LocationAndDate(const EntryData &entry) {
|
||||||
|
return (entry.location.isEmpty() ? entry.ip : entry.location)
|
||||||
|
+ (entry.hash
|
||||||
|
? (QString::fromUtf8(" \xE2\x80\xA2 ") + entry.active)
|
||||||
|
: QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InfoBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
const EntryData &data,
|
||||||
|
Fn<void(uint64)> terminate) {
|
||||||
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
|
const auto shown = box->lifetime().make_state<rpl::event_stream<>>();
|
||||||
|
box->setShowFinishedCallback([=] {
|
||||||
|
shown->fire({});
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto userpic = box->addRow(
|
||||||
|
object_ptr<Ui::CenterWrap<Ui::UserpicButton>>(
|
||||||
|
box,
|
||||||
|
object_ptr<Ui::UserpicButton>(
|
||||||
|
box,
|
||||||
|
data.bot,
|
||||||
|
st::websiteBigUserpic)),
|
||||||
|
st::sessionBigCoverPadding)->entity();
|
||||||
|
userpic->forceForumShape(true);
|
||||||
|
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
const auto nameWrap = box->addRow(
|
||||||
|
object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
box,
|
||||||
|
st::sessionBigName.maxHeight));
|
||||||
|
const auto name = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
nameWrap,
|
||||||
|
rpl::single(data.bot->name()),
|
||||||
|
st::sessionBigName);
|
||||||
|
nameWrap->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
name->resizeToWidth(width);
|
||||||
|
name->move((width - name->width()) / 2, 0);
|
||||||
|
}, name->lifetime());
|
||||||
|
|
||||||
|
const auto domainWrap = box->addRow(
|
||||||
|
object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
box,
|
||||||
|
st::sessionDateLabel.style.font->height),
|
||||||
|
style::margins(0, 0, 0, st::sessionDateSkip));
|
||||||
|
const auto domain = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
domainWrap,
|
||||||
|
rpl::single(data.domain),
|
||||||
|
st::sessionDateLabel);
|
||||||
|
rpl::combine(
|
||||||
|
domainWrap->widthValue(),
|
||||||
|
domain->widthValue()
|
||||||
|
) | rpl::start_with_next([=](int outer, int inner) {
|
||||||
|
domain->move((outer - inner) / 2, 0);
|
||||||
|
}, domain->lifetime());
|
||||||
|
|
||||||
|
using namespace Settings;
|
||||||
|
const auto container = box->verticalLayout();
|
||||||
|
AddDivider(container);
|
||||||
|
AddSkip(container, st::sessionSubtitleSkip);
|
||||||
|
AddSubsectionTitle(container, tr::lng_sessions_info());
|
||||||
|
|
||||||
|
AddSessionInfoRow(
|
||||||
|
container,
|
||||||
|
tr::lng_sessions_browser(),
|
||||||
|
JoinNonEmpty({ data.browser, data.platform }),
|
||||||
|
st::menuIconDevices);
|
||||||
|
AddSessionInfoRow(
|
||||||
|
container,
|
||||||
|
tr::lng_sessions_ip(),
|
||||||
|
data.ip,
|
||||||
|
st::menuIconIpAddress);
|
||||||
|
AddSessionInfoRow(
|
||||||
|
container,
|
||||||
|
tr::lng_sessions_location(),
|
||||||
|
data.location,
|
||||||
|
st::menuIconAddress);
|
||||||
|
|
||||||
|
AddSkip(container, st::sessionValueSkip);
|
||||||
|
if (!data.location.isEmpty()) {
|
||||||
|
AddDividerText(container, tr::lng_sessions_location_about());
|
||||||
|
}
|
||||||
|
|
||||||
|
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
||||||
|
if (const auto hash = data.hash) {
|
||||||
|
box->addLeftButton(tr::lng_settings_disconnect(), [=] {
|
||||||
|
const auto weak = Ui::MakeWeak(box.get());
|
||||||
|
terminate(hash);
|
||||||
|
if (weak) {
|
||||||
|
box->closeBox();
|
||||||
|
}
|
||||||
|
}, st::attentionBoxButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row::Row(not_null<RowDelegate*> delegate, const EntryData &data)
|
||||||
|
: PeerListRow(data.hash)
|
||||||
|
, _delegate(delegate)
|
||||||
|
, _location(st::defaultTextStyle, LocationAndDate(data))
|
||||||
|
, _data(data) {
|
||||||
|
setCustomStatus(_data.ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Row::update(const EntryData &data) {
|
||||||
|
_data = data;
|
||||||
|
setCustomStatus(
|
||||||
|
JoinNonEmpty({ _data.domain, _data.browser, _data.platform }));
|
||||||
|
refreshName(st::websiteListItem);
|
||||||
|
_location.setText(st::defaultTextStyle, LocationAndDate(_data));
|
||||||
|
_delegate->rowUpdateRow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryData Row::data() const {
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Row::generateName() {
|
||||||
|
return _data.bot->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Row::generateShortName() {
|
||||||
|
return _data.bot->shortName();
|
||||||
|
}
|
||||||
|
|
||||||
|
PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) {
|
||||||
|
const auto peer = _data.bot;
|
||||||
|
auto userpic = _userpic = peer->createUserpicView();
|
||||||
|
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
if (const auto cloud = peer->userpicCloudImage(userpic)) {
|
||||||
|
Ui::ValidateUserpicCache(
|
||||||
|
userpic,
|
||||||
|
cloud,
|
||||||
|
nullptr,
|
||||||
|
size * ratio,
|
||||||
|
true);
|
||||||
|
p.drawImage(QRect(x, y, size, size), userpic.cached);
|
||||||
|
} else {
|
||||||
|
if (_emptyUserpic.isNull()) {
|
||||||
|
_emptyUserpic = peer->generateUserpicImage(
|
||||||
|
_userpic,
|
||||||
|
size * ratio,
|
||||||
|
size * ratio * Ui::ForumUserpicRadiusMultiplier());
|
||||||
|
}
|
||||||
|
p.drawImage(QRect(x, y, size, size), _emptyUserpic);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int Row::elementsCount() const {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect Row::elementGeometry(int element, int outerWidth) const {
|
||||||
|
switch (element) {
|
||||||
|
case 1: {
|
||||||
|
return QRect(
|
||||||
|
st::websiteListItem.namePosition.x(),
|
||||||
|
st::websiteLocationTop,
|
||||||
|
outerWidth,
|
||||||
|
st::normalFont->height);
|
||||||
|
} break;
|
||||||
|
case 2: {
|
||||||
|
const auto size = QSize(
|
||||||
|
st::sessionTerminate.width,
|
||||||
|
st::sessionTerminate.height);
|
||||||
|
const auto right = st::sessionTerminateSkip;
|
||||||
|
const auto top = st::sessionTerminateTop;
|
||||||
|
const auto left = outerWidth - right - size.width();
|
||||||
|
return QRect(QPoint(left, top), size);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
return QRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Row::elementDisabled(int element) const {
|
||||||
|
return !id() || (element == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Row::elementOnlySelect(int element) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Row::elementAddRipple(
|
||||||
|
int element,
|
||||||
|
QPoint point,
|
||||||
|
Fn<void()> updateCallback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Row::elementsStopLastRipple() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Row::elementsPaint(
|
||||||
|
Painter &p,
|
||||||
|
int outerWidth,
|
||||||
|
bool selected,
|
||||||
|
int selectedElement) {
|
||||||
|
const auto geometry = elementGeometry(2, outerWidth);
|
||||||
|
const auto position = geometry.topLeft()
|
||||||
|
+ st::sessionTerminate.iconPosition;
|
||||||
|
const auto &icon = (selectedElement == 2)
|
||||||
|
? st::sessionTerminate.iconOver
|
||||||
|
: st::sessionTerminate.icon;
|
||||||
|
icon.paint(p, position.x(), position.y(), outerWidth);
|
||||||
|
|
||||||
|
p.setFont(st::normalFont);
|
||||||
|
p.setPen(st::sessionInfoFg);
|
||||||
|
const auto locationLeft = st::websiteListItem.namePosition.x();
|
||||||
|
const auto available = outerWidth - locationLeft;
|
||||||
|
_location.drawLeftElided(
|
||||||
|
p,
|
||||||
|
locationLeft,
|
||||||
|
st::websiteLocationTop,
|
||||||
|
available,
|
||||||
|
outerWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Content : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
Content(
|
||||||
|
QWidget*,
|
||||||
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
void setupContent();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Inner;
|
||||||
|
class ListController;
|
||||||
|
|
||||||
|
void shortPoll();
|
||||||
|
void parse(const Api::Websites::List &list);
|
||||||
|
|
||||||
|
void terminate(
|
||||||
|
Fn<void(bool block)> sendRequest,
|
||||||
|
rpl::producer<QString> title,
|
||||||
|
rpl::producer<QString> text,
|
||||||
|
QString blockText = QString());
|
||||||
|
void terminateOne(uint64 hash);
|
||||||
|
void terminateAll();
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
const not_null<Api::Websites*> _websites;
|
||||||
|
|
||||||
|
rpl::variable<bool> _loading = false;
|
||||||
|
Api::Websites::List _data;
|
||||||
|
|
||||||
|
object_ptr<Inner> _inner;
|
||||||
|
QPointer<Ui::BoxContent> _terminateBox;
|
||||||
|
|
||||||
|
base::Timer _shortPollTimer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Content::ListController final
|
||||||
|
: public PeerListController
|
||||||
|
, public RowDelegate
|
||||||
|
, public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
explicit ListController(not_null<Main::Session*> session);
|
||||||
|
|
||||||
|
Main::Session &session() const override;
|
||||||
|
void prepare() override;
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
|
||||||
|
|
||||||
|
void rowUpdateRow(not_null<Row*> row) override;
|
||||||
|
|
||||||
|
void showData(gsl::span<const EntryData> items);
|
||||||
|
rpl::producer<int> itemsCount() const;
|
||||||
|
rpl::producer<uint64> terminateRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<EntryData> showRequests() const;
|
||||||
|
|
||||||
|
[[nodiscard]] static std::unique_ptr<ListController> Add(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
style::margins margins = {});
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
rpl::event_stream<uint64> _terminateRequests;
|
||||||
|
rpl::event_stream<int> _itemsCount;
|
||||||
|
rpl::event_stream<EntryData> _showRequests;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Content::Inner : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
Inner(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
void showData(const Api::Websites::List &data);
|
||||||
|
[[nodiscard]] rpl::producer<EntryData> showRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<uint64> terminateOne() const;
|
||||||
|
[[nodiscard]] rpl::producer<> terminateAll() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupContent();
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
QPointer<Ui::SettingsButton> _terminateAll;
|
||||||
|
std::unique_ptr<ListController> _list;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Content::Content(
|
||||||
|
QWidget*,
|
||||||
|
not_null<Window::SessionController*> controller)
|
||||||
|
: _controller(controller)
|
||||||
|
, _websites(&controller->session().api().websites())
|
||||||
|
, _inner(this, controller)
|
||||||
|
, _shortPollTimer([=] { shortPoll(); }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::setupContent() {
|
||||||
|
_inner->heightValue(
|
||||||
|
) | rpl::distinct_until_changed(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
resize(width(), height);
|
||||||
|
}, _inner->lifetime());
|
||||||
|
|
||||||
|
_inner->showRequests(
|
||||||
|
) | rpl::start_with_next([=](const EntryData &data) {
|
||||||
|
_controller->show(Box(
|
||||||
|
InfoBox,
|
||||||
|
data,
|
||||||
|
[=](uint64 hash) { terminateOne(hash); }));
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_inner->terminateOne(
|
||||||
|
) | rpl::start_with_next([=](uint64 hash) {
|
||||||
|
terminateOne(hash);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_inner->terminateAll(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
terminateAll();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_loading.changes(
|
||||||
|
) | rpl::start_with_next([=](bool value) {
|
||||||
|
_inner->setVisible(!value);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_websites->listValue(
|
||||||
|
) | rpl::start_with_next([=](const Api::Websites::List &list) {
|
||||||
|
parse(list);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_loading = true;
|
||||||
|
shortPoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::parse(const Api::Websites::List &list) {
|
||||||
|
_loading = false;
|
||||||
|
|
||||||
|
_data = list;
|
||||||
|
|
||||||
|
ranges::sort(_data, std::greater<>(), &EntryData::activeTime);
|
||||||
|
|
||||||
|
_inner->showData(_data);
|
||||||
|
|
||||||
|
_shortPollTimer.callOnce(kShortPollTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::resizeEvent(QResizeEvent *e) {
|
||||||
|
RpWidget::resizeEvent(e);
|
||||||
|
|
||||||
|
_inner->resize(width(), _inner->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::paintEvent(QPaintEvent *e) {
|
||||||
|
RpWidget::paintEvent(e);
|
||||||
|
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
if (_loading.current()) {
|
||||||
|
p.setFont(st::noContactsFont);
|
||||||
|
p.setPen(st::noContactsColor);
|
||||||
|
p.drawText(
|
||||||
|
QRect(0, 0, width(), st::noContactsHeight),
|
||||||
|
tr::lng_contacts_loading(tr::now),
|
||||||
|
style::al_center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::shortPoll() {
|
||||||
|
const auto left = kShortPollTimeout
|
||||||
|
- (crl::now() - _websites->lastReceivedTime());
|
||||||
|
if (left > 0) {
|
||||||
|
parse(_websites->list());
|
||||||
|
_shortPollTimer.cancel();
|
||||||
|
_shortPollTimer.callOnce(left);
|
||||||
|
} else {
|
||||||
|
_websites->reload();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::terminate(
|
||||||
|
Fn<void(bool block)> sendRequest,
|
||||||
|
rpl::producer<QString> title,
|
||||||
|
rpl::producer<QString> text,
|
||||||
|
QString blockText) {
|
||||||
|
if (const auto strong = _terminateBox.data()) {
|
||||||
|
strong->deleteLater();
|
||||||
|
}
|
||||||
|
auto box = Box([=](not_null<Ui::GenericBox*> box) {
|
||||||
|
auto &lifetime = box->lifetime();
|
||||||
|
const auto block = lifetime.make_state<Ui::Checkbox*>(nullptr);
|
||||||
|
const auto callback = crl::guard(this, [=] {
|
||||||
|
const auto blocked = (*block) && (*block)->checked();
|
||||||
|
if (_terminateBox) {
|
||||||
|
_terminateBox->closeBox();
|
||||||
|
_terminateBox = nullptr;
|
||||||
|
}
|
||||||
|
sendRequest(blocked);
|
||||||
|
});
|
||||||
|
Ui::ConfirmBox(box, {
|
||||||
|
.text = rpl::duplicate(text),
|
||||||
|
.confirmed = callback,
|
||||||
|
.confirmText = tr::lng_settings_disconnect(),
|
||||||
|
.confirmStyle = &st::attentionBoxButton,
|
||||||
|
.title = rpl::duplicate(title),
|
||||||
|
});
|
||||||
|
if (!blockText.isEmpty()) {
|
||||||
|
*block = box->addRow(object_ptr<Ui::Checkbox>(box, blockText));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_terminateBox = Ui::MakeWeak(box.data());
|
||||||
|
_controller->show(std::move(box));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::terminateOne(uint64 hash) {
|
||||||
|
const auto weak = Ui::MakeWeak(this);
|
||||||
|
const auto i = ranges::find(_data, hash, &EntryData::hash);
|
||||||
|
if (i == end(_data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto bot = i->bot;
|
||||||
|
auto callback = [=](bool block) {
|
||||||
|
auto done = crl::guard(weak, [=](const MTPBool &result) {
|
||||||
|
_data.erase(
|
||||||
|
ranges::remove(_data, hash, &EntryData::hash),
|
||||||
|
end(_data));
|
||||||
|
_inner->showData(_data);
|
||||||
|
});
|
||||||
|
auto fail = crl::guard(weak, [=](const MTP::Error &error) {
|
||||||
|
});
|
||||||
|
_websites->requestTerminate(
|
||||||
|
std::move(done),
|
||||||
|
std::move(fail),
|
||||||
|
hash,
|
||||||
|
block ? bot.get() : nullptr);
|
||||||
|
};
|
||||||
|
terminate(
|
||||||
|
std::move(callback),
|
||||||
|
tr::lng_settings_disconnect_title(),
|
||||||
|
tr::lng_settings_disconnect_sure(lt_domain, rpl::single(i->domain)),
|
||||||
|
tr::lng_settings_disconnect_block(tr::now, lt_name, bot->name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::terminateAll() {
|
||||||
|
const auto weak = Ui::MakeWeak(this);
|
||||||
|
auto callback = [=](bool block) {
|
||||||
|
const auto reset = crl::guard(weak, [=] {
|
||||||
|
_websites->cancelCurrentRequest();
|
||||||
|
_websites->reload();
|
||||||
|
});
|
||||||
|
_websites->requestTerminate(
|
||||||
|
[=](const MTPBool &result) { reset(); },
|
||||||
|
[=](const MTP::Error &result) { reset(); });
|
||||||
|
_loading = true;
|
||||||
|
};
|
||||||
|
terminate(
|
||||||
|
std::move(callback),
|
||||||
|
tr::lng_settings_disconnect_all_title(),
|
||||||
|
tr::lng_settings_disconnect_all_sure());
|
||||||
|
}
|
||||||
|
|
||||||
|
Content::Inner::Inner(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _controller(controller) {
|
||||||
|
resize(width(), st::noContactsHeight);
|
||||||
|
setupContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::Inner::setupContent() {
|
||||||
|
using namespace Settings;
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
|
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||||
|
|
||||||
|
const auto session = &_controller->session();
|
||||||
|
const auto terminateWrap = content->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
content,
|
||||||
|
object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
|
||||||
|
const auto terminateInner = terminateWrap->entity();
|
||||||
|
_terminateAll = terminateInner->add(
|
||||||
|
CreateButton(
|
||||||
|
terminateInner,
|
||||||
|
tr::lng_settings_disconnect_all(),
|
||||||
|
st::infoBlockButton,
|
||||||
|
{ .icon = &st::infoIconBlock }));
|
||||||
|
AddSkip(terminateInner);
|
||||||
|
AddDividerText(
|
||||||
|
terminateInner,
|
||||||
|
tr::lng_settings_logged_in_description());
|
||||||
|
|
||||||
|
const auto listWrap = content->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
content,
|
||||||
|
object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
|
||||||
|
const auto listInner = listWrap->entity();
|
||||||
|
AddSkip(listInner, st::sessionSubtitleSkip);
|
||||||
|
AddSubsectionTitle(listInner, tr::lng_settings_logged_in_title());
|
||||||
|
_list = ListController::Add(listInner, session);
|
||||||
|
AddSkip(listInner);
|
||||||
|
|
||||||
|
const auto skip = st::noContactsHeight / 2;
|
||||||
|
const auto placeholder = content->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
|
||||||
|
content,
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
content,
|
||||||
|
tr::lng_settings_logged_in_description(),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
st::settingsDividerLabelPadding + QMargins(0, skip, 0, skip))
|
||||||
|
)->setDuration(0);
|
||||||
|
|
||||||
|
terminateWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0));
|
||||||
|
listWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0));
|
||||||
|
placeholder->toggleOn(_list->itemsCount() | rpl::map(_1 == 0));
|
||||||
|
|
||||||
|
Ui::ResizeFitChild(this, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::Inner::showData(const Api::Websites::List &data) {
|
||||||
|
_list->showData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> Content::Inner::terminateAll() const {
|
||||||
|
return _terminateAll->clicks() | rpl::to_empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<uint64> Content::Inner::terminateOne() const {
|
||||||
|
return _list->terminateRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<EntryData> Content::Inner::showRequests() const {
|
||||||
|
return _list->showRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
Content::ListController::ListController(
|
||||||
|
not_null<Main::Session*> session)
|
||||||
|
: _session(session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Main::Session &Content::ListController::session() const {
|
||||||
|
return *_session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::ListController::prepare() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::ListController::rowClicked(
|
||||||
|
not_null<PeerListRow*> row) {
|
||||||
|
_showRequests.fire_copy(static_cast<Row*>(row.get())->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::ListController::rowElementClicked(
|
||||||
|
not_null<PeerListRow*> row,
|
||||||
|
int element) {
|
||||||
|
if (element == 2) {
|
||||||
|
if (const auto hash = static_cast<Row*>(row.get())->data().hash) {
|
||||||
|
_terminateRequests.fire_copy(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::ListController::rowUpdateRow(not_null<Row*> row) {
|
||||||
|
delegate()->peerListUpdateRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Content::ListController::showData(
|
||||||
|
gsl::span<const EntryData> items) {
|
||||||
|
auto index = 0;
|
||||||
|
auto positions = base::flat_map<uint64, int>();
|
||||||
|
positions.reserve(items.size());
|
||||||
|
for (const auto &entry : items) {
|
||||||
|
const auto id = entry.hash;
|
||||||
|
positions.emplace(id, index++);
|
||||||
|
if (const auto row = delegate()->peerListFindRow(id)) {
|
||||||
|
static_cast<Row*>(row)->update(entry);
|
||||||
|
} else {
|
||||||
|
delegate()->peerListAppendRow(
|
||||||
|
std::make_unique<Row>(this, entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != delegate()->peerListFullRowsCount();) {
|
||||||
|
const auto row = delegate()->peerListRowAt(i);
|
||||||
|
if (positions.contains(row->id())) {
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
delegate()->peerListRemoveRow(row);
|
||||||
|
}
|
||||||
|
delegate()->peerListSortRows([&](
|
||||||
|
const PeerListRow &a,
|
||||||
|
const PeerListRow &b) {
|
||||||
|
return positions[a.id()] < positions[b.id()];
|
||||||
|
});
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
_itemsCount.fire(delegate()->peerListFullRowsCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> Content::ListController::itemsCount() const {
|
||||||
|
return _itemsCount.events_starting_with(
|
||||||
|
delegate()->peerListFullRowsCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<uint64> Content::ListController::terminateRequests() const {
|
||||||
|
return _terminateRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<EntryData> Content::ListController::showRequests() const {
|
||||||
|
return _showRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Content::ListController::Add(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
style::margins margins)
|
||||||
|
-> std::unique_ptr<ListController> {
|
||||||
|
auto &lifetime = container->lifetime();
|
||||||
|
const auto delegate = lifetime.make_state<
|
||||||
|
PeerListContentDelegateSimple
|
||||||
|
>();
|
||||||
|
auto controller = std::make_unique<ListController>(session);
|
||||||
|
controller->setStyleOverrides(&st::websiteList);
|
||||||
|
const auto content = container->add(
|
||||||
|
object_ptr<PeerListContent>(
|
||||||
|
container,
|
||||||
|
controller.get()),
|
||||||
|
margins);
|
||||||
|
delegate->setContent(content);
|
||||||
|
controller->setDelegate(delegate);
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
|
||||||
|
Websites::Websites(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller)
|
||||||
|
: Section(parent) {
|
||||||
|
setupContent(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> Websites::title() {
|
||||||
|
return tr::lng_settings_connected_title();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Websites::setupContent(not_null<Window::SessionController*> controller) {
|
||||||
|
const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||||
|
AddSkip(container);
|
||||||
|
const auto content = container->add(
|
||||||
|
object_ptr<Content>(container, controller));
|
||||||
|
content->setupContent();
|
||||||
|
|
||||||
|
Ui::ResizeFitChild(this, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Settings
|
27
Telegram/SourceFiles/settings/settings_websites.h
Normal file
27
Telegram/SourceFiles/settings/settings_websites.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "settings/settings_common.h"
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
|
||||||
|
class Websites : public Section<Websites> {
|
||||||
|
public:
|
||||||
|
Websites(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QString> title() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupContent(not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Settings
|
|
@ -17,18 +17,26 @@ void ConfirmBox(not_null<Ui::GenericBox*> box, ConfirmBoxArgs &&args) {
|
||||||
const auto weak = Ui::MakeWeak(box);
|
const auto weak = Ui::MakeWeak(box);
|
||||||
const auto lifetime = box->lifetime().make_state<rpl::lifetime>();
|
const auto lifetime = box->lifetime().make_state<rpl::lifetime>();
|
||||||
|
|
||||||
v::match(args.text, [](v::null_t) {
|
const auto withTitle = !v::is_null(args.title);
|
||||||
}, [&](auto &&) {
|
if (withTitle) {
|
||||||
|
box->setTitle(v::text::take_marked(std::move(args.title)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!v::is_null(args.text)) {
|
||||||
|
const auto padding = st::boxPadding;
|
||||||
|
const auto use = withTitle
|
||||||
|
? QMargins(padding.left(), 0, padding.right(), padding.bottom())
|
||||||
|
: padding;
|
||||||
const auto label = box->addRow(
|
const auto label = box->addRow(
|
||||||
object_ptr<Ui::FlatLabel>(
|
object_ptr<Ui::FlatLabel>(
|
||||||
box.get(),
|
box.get(),
|
||||||
v::text::take_marked(std::move(args.text)),
|
v::text::take_marked(std::move(args.text)),
|
||||||
args.labelStyle ? *args.labelStyle : st::boxLabel),
|
args.labelStyle ? *args.labelStyle : st::boxLabel),
|
||||||
st::boxPadding);
|
use);
|
||||||
if (args.labelFilter) {
|
if (args.labelFilter) {
|
||||||
label->setClickHandlerFilter(std::move(args.labelFilter));
|
label->setClickHandlerFilter(std::move(args.labelFilter));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const auto prepareCallback = [&](ConfirmBoxArgs::Callback &callback) {
|
const auto prepareCallback = [&](ConfirmBoxArgs::Callback &callback) {
|
||||||
return [=, confirmed = std::move(callback)]() {
|
return [=, confirmed = std::move(callback)]() {
|
||||||
|
|
|
@ -31,6 +31,8 @@ struct ConfirmBoxArgs {
|
||||||
const style::FlatLabel *labelStyle = nullptr;
|
const style::FlatLabel *labelStyle = nullptr;
|
||||||
Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)> labelFilter;
|
Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)> labelFilter;
|
||||||
|
|
||||||
|
v::text::data title = v::null;
|
||||||
|
|
||||||
bool inform = false;
|
bool inform = false;
|
||||||
// If strict cancel is set the cancel.callback() is only called
|
// If strict cancel is set the cancel.callback() is only called
|
||||||
// if the cancel button was pressed.
|
// if the cancel button was pressed.
|
||||||
|
|
|
@ -871,6 +871,11 @@ void UserpicButton::switchChangePhotoOverlay(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UserpicButton::forceForumShape(bool force) {
|
||||||
|
_forceForumShape = force;
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
|
||||||
void UserpicButton::showSavedMessagesOnSelf(bool enabled) {
|
void UserpicButton::showSavedMessagesOnSelf(bool enabled) {
|
||||||
if (_showSavedMessagesOnSelf != enabled) {
|
if (_showSavedMessagesOnSelf != enabled) {
|
||||||
_showSavedMessagesOnSelf = enabled;
|
_showSavedMessagesOnSelf = enabled;
|
||||||
|
@ -1003,7 +1008,26 @@ void UserpicButton::prepareUserpicPixmap() {
|
||||||
_userpic = CreateSquarePixmap(size, [&](Painter &p) {
|
_userpic = CreateSquarePixmap(size, [&](Painter &p) {
|
||||||
if (_userpicHasImage) {
|
if (_userpicHasImage) {
|
||||||
if (_showPeerUserpic) {
|
if (_showPeerUserpic) {
|
||||||
_peer->paintUserpic(p, _userpicView, 0, 0, size);
|
if (useForumShape()) {
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
if (const auto cloud = _peer->userpicCloudImage(_userpicView)) {
|
||||||
|
Ui::ValidateUserpicCache(
|
||||||
|
_userpicView,
|
||||||
|
cloud,
|
||||||
|
nullptr,
|
||||||
|
size * ratio,
|
||||||
|
true);
|
||||||
|
p.drawImage(QRect(0, 0, size, size), _userpicView.cached);
|
||||||
|
} else {
|
||||||
|
const auto empty = _peer->generateUserpicImage(
|
||||||
|
_userpicView,
|
||||||
|
size * ratio,
|
||||||
|
size * ratio * Ui::ForumUserpicRadiusMultiplier());
|
||||||
|
p.drawImage(QRect(0, 0, size, size), empty);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_peer->paintUserpic(p, _userpicView, 0, 0, size);
|
||||||
|
}
|
||||||
} else if (_nonPersonalView) {
|
} else if (_nonPersonalView) {
|
||||||
using Size = Data::PhotoSize;
|
using Size = Data::PhotoSize;
|
||||||
if (const auto full = _nonPersonalView->image(Size::Large)) {
|
if (const auto full = _nonPersonalView->image(Size::Large)) {
|
||||||
|
|
|
@ -94,6 +94,7 @@ public:
|
||||||
bool enabled,
|
bool enabled,
|
||||||
Fn<void(ChosenImage)> chosen);
|
Fn<void(ChosenImage)> chosen);
|
||||||
void showSavedMessagesOnSelf(bool enabled);
|
void showSavedMessagesOnSelf(bool enabled);
|
||||||
|
void forceForumShape(bool force);
|
||||||
|
|
||||||
// Role::ChoosePhoto or Role::ChangePhoto
|
// Role::ChoosePhoto or Role::ChangePhoto
|
||||||
[[nodiscard]] rpl::producer<ChosenImage> chosenImages() const {
|
[[nodiscard]] rpl::producer<ChosenImage> chosenImages() const {
|
||||||
|
|
|
@ -135,6 +135,8 @@ menuIconAntispam: icon {{ "menu/antispam", menuIconColor }};
|
||||||
menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }};
|
menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }};
|
||||||
menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }};
|
menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }};
|
||||||
menuIconPremium: icon {{ "menu/premium", menuIconColor }};
|
menuIconPremium: icon {{ "menu/premium", menuIconColor }};
|
||||||
|
menuIconIpAddress: icon {{ "menu/ip_address", menuIconColor }};
|
||||||
|
menuIconAddress: icon {{ "menu/payment_address", menuIconColor }};
|
||||||
|
|
||||||
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
|
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
|
||||||
menuIconTTLAnyTextPosition: point(11px, 22px);
|
menuIconTTLAnyTextPosition: point(11px, 22px);
|
||||||
|
|
Loading…
Add table
Reference in a new issue