Merge tag 'v5.5.3' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/history/history_widget.cpp
#	Telegram/SourceFiles/history/view/history_view_element.cpp
#	Telegram/lib_tl
#	Telegram/lib_ui
#	lib/xdg/org.telegram.desktop.metainfo.xml
#	snap/snapcraft.yaml
This commit is contained in:
AlexeyZavar 2024-09-11 20:23:00 +03:00
commit 409eedf58d
356 changed files with 8894 additions and 2907 deletions

View file

@ -37,6 +37,10 @@ include(cmake/td_scheme.cmake)
include(cmake/td_ui.cmake)
include(cmake/generate_appdata_changelog.cmake)
if (DESKTOP_APP_TEST_APPS)
include(cmake/tests.cmake)
endif()
if (WIN32)
include(cmake/generate_midl.cmake)
generate_midl(Telegram ${src_loc}
@ -390,6 +394,8 @@ PRIVATE
boxes/self_destruction_box.h
boxes/send_credits_box.cpp
boxes/send_credits_box.h
boxes/send_gif_with_caption_box.cpp
boxes/send_gif_with_caption_box.h
boxes/send_files_box.cpp
boxes/send_files_box.h
boxes/sessions_box.cpp
@ -1556,6 +1562,8 @@ PRIVATE
support/support_templates.h
ui/boxes/edit_invite_link_session.cpp
ui/boxes/edit_invite_link_session.h
ui/boxes/peer_qr_box.cpp
ui/boxes/peer_qr_box.h
ui/chat/attach/attach_item_single_file_preview.cpp
ui/chat/attach/attach_item_single_file_preview.h
ui/chat/attach/attach_item_single_media_preview.cpp
@ -1564,6 +1572,8 @@ PRIVATE
ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
ui/controls/emoji_button_factory.cpp
ui/controls/emoji_button_factory.h
ui/controls/location_picker.cpp
ui/controls/location_picker.h
ui/controls/silent_toggle.cpp
@ -1589,6 +1599,8 @@ PRIVATE
ui/image/image_location_factory.h
ui/text/format_song_document_name.cpp
ui/text/format_song_document_name.h
ui/widgets/expandable_peer_list.cpp
ui/widgets/expandable_peer_list.h
ui/widgets/label_with_custom_emoji.cpp
ui/widgets/label_with_custom_emoji.h
ui/countryinput.cpp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

View file

@ -0,0 +1 @@
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M226.328419,494.722069 C372.088573,431.216685 469.284839,389.350049 517.917216,369.122161 C656.772535,311.36743 685.625481,301.334815 704.431427,301.003532 C708.567621,300.93067 717.815839,301.955743 723.806446,306.816707 C728.864797,310.92121 730.256552,316.46581 730.922551,320.357329 C731.588551,324.248848 732.417879,333.113828 731.758626,340.040666 C724.234007,419.102486 691.675104,610.964674 675.110982,699.515267 C668.10208,736.984342 654.301336,749.547532 640.940618,750.777006 C611.904684,753.448938 589.856115,731.588035 561.733393,713.153237 C517.726886,684.306416 492.866009,666.349181 450.150074,638.200013 C400.78442,605.66878 432.786119,587.789048 460.919462,558.568563 C468.282091,550.921423 596.21508,434.556479 598.691227,424.000355 C599.00091,422.680135 599.288312,417.758981 596.36474,415.160431 C593.441168,412.561881 589.126229,413.450484 586.012448,414.157198 C581.598758,415.158943 511.297793,461.625274 375.109553,553.556189 C355.154858,567.258623 337.080515,573.934908 320.886524,573.585046 C303.033948,573.199351 268.692754,563.490928 243.163606,555.192408 C211.851067,545.013936 186.964484,539.632504 189.131547,522.346309 C190.260287,513.342589 202.659244,504.134509 226.328419,494.722069 Z" id="Path-3" fill="#FFFFFF"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load diff

View file

@ -31,6 +31,7 @@
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
<file alias="plane_white.svg">../../icons/plane_white.svg</file>
</qresource>
<qresource prefix="/icons">
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.4.1.0" />
Version="5.5.3.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,4,1,0
PRODUCTVERSION 5,4,1,0
FILEVERSION 5,5,3,0
PRODUCTVERSION 5,5,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.4.1.0"
VALUE "FileVersion", "5.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.4.1.0"
VALUE "ProductVersion", "5.5.3.0"
END
END
BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,4,1,0
PRODUCTVERSION 5,4,1,0
FILEVERSION 5,5,3,0
PRODUCTVERSION 5,5,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.4.1.0"
VALUE "FileVersion", "5.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.4.1.0"
VALUE "ProductVersion", "5.5.3.0"
END
END
BLOCK "VarFileInfo"

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/url_auth_box.h"
#include "boxes/peers/choose_peer_box.h"
#include "lang/lang_keys.h"
#include "chat_helpers/bot_command.h"
#include "core/core_cloud_password.h"
#include "core/click_handler_types.h"
#include "data/data_changes.h"

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/filter_icons.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_filter_icons.h"
#include "styles/style_layers.h"
@ -231,12 +232,12 @@ void ImportInvite(
api->request(MTPchatlists_JoinChatlistInvite(
MTP_string(slug),
MTP_vector<MTPInputPeer>(std::move(inputs))
)).done(callback).fail(error).send();
)).done(callback).fail(error).handleFloodErrors().send();
} else {
api->request(MTPchatlists_JoinChatlistUpdates(
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
MTP_vector<MTPInputPeer>(std::move(inputs))
)).done(callback).fail(error).send();
)).done(callback).fail(error).handleFloodErrors().send();
}
}
@ -516,6 +517,8 @@ void ShowImportError(
} else {
window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
? tr::lng_group_invite_bad_link(tr::now)
: error.startsWith(u"FLOOD_WAIT_"_q)
? tr::lng_flood_error(tr::now)
: error);
}
}

View file

@ -367,12 +367,15 @@ void CheckChatInvite(
result.match([=](const MTPDchatInvite &data) {
const auto isGroup = !data.is_broadcast();
const auto hasPricing = !!data.vsubscription_pricing();
if (hasPricing && !data.vsubscription_form_id()) {
const auto canRefulfill = data.is_can_refulfill_subscription();
if (hasPricing
&& !canRefulfill
&& !data.vsubscription_form_id()) {
strong->uiShow()->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
return;
}
const auto box = hasPricing
const auto box = (hasPricing && !canRefulfill)
? strong->show(Box(
ConfirmSubscriptionBox,
session,

View file

@ -264,14 +264,24 @@ ChatParticipant::ChatParticipant(
_rank = qs(data.vrank().value_or_empty());
_rights = ChatAdminRightsInfo(data.vadmin_rights());
_by = peerToUser(peerFromUser(data.vpromoted_by()));
_date = data.vdate().v;
}, [&](const MTPDchannelParticipantSelf &data) {
_type = Type::Member;
_date = data.vdate().v;
_by = peerToUser(peerFromUser(data.vinviter_id()));
if (data.vsubscription_until_date()) {
_subscriptionDate = data.vsubscription_until_date()->v;
}
}, [&](const MTPDchannelParticipant &data) {
_type = Type::Member;
_date = data.vdate().v;
if (data.vsubscription_until_date()) {
_subscriptionDate = data.vsubscription_until_date()->v;
}
}, [&](const MTPDchannelParticipantBanned &data) {
_restrictions = ChatRestrictionsInfo(data.vbanned_rights());
_by = peerToUser(peerFromUser(data.vkicked_by()));
_date = data.vdate().v;
_type = (_restrictions.flags & ChatRestriction::ViewMessages)
? Type::Banned
@ -348,6 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
return _rights;
}
TimeId ChatParticipant::subscriptionDate() const {
return _subscriptionDate;
}
TimeId ChatParticipant::promotedSince() const {
return (_type == Type::Admin) ? _date : TimeId(0);
}
TimeId ChatParticipant::restrictedSince() const {
return (_type == Type::Restricted || _type == Type::Banned)
? _date
: TimeId(0);
}
TimeId ChatParticipant::memberSince() const {
return (_type == Type::Member) ? _date : TimeId(0);
}
ChatParticipant::Type ChatParticipant::type() const {
return _type;
}

View file

@ -60,6 +60,11 @@ public:
ChatRestrictionsInfo restrictions() const;
ChatAdminRightsInfo rights() const;
TimeId subscriptionDate() const;
TimeId promotedSince() const;
TimeId restrictedSince() const;
TimeId memberSince() const;
Type type() const;
QString rank() const;
@ -73,6 +78,8 @@ private:
bool _canBeEdited = false;
QString _rank;
TimeId _subscriptionDate = 0;
TimeId _date = 0;
ChatRestrictionsInfo _restrictions;
ChatAdminRightsInfo _rights;

View file

@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/core_cloud_password.h"
#include "passport/passport_encryption.h"
#include "base/unixtime.h"
#include "base/call_delayed.h"
namespace Api {
namespace {

View file

@ -79,6 +79,8 @@ constexpr auto kTransactionsLimit = 100;
.credits = tl.data().vstars().v,
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = barePeerId,
.bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()),
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@ -215,6 +217,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
};
}
Data::CreditTopupOptions CreditsTopupOptions::options() const {
return _options;
}
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
: _peer(peer)
, _api(&peer->session().api().instance()) {
@ -294,10 +300,6 @@ void CreditsHistory::requestSubscriptions(
}).send();
}
Data::CreditTopupOptions CreditsTopupOptions::options() const {
return _options;
}
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
not_null<Main::Session*> session) {
const auto username = session->appConfig().get<QString>(
@ -385,4 +387,58 @@ Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
return _data;
}
CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
: _peer(peer)
, _api(&peer->session().api().instance()) {
}
rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
using TLOption = MTPStarsGiveawayOption;
const auto optionsFromTL = [=](const auto &options) {
return ranges::views::all(
options
) | ranges::views::transform([=](const auto &option) {
return Data::CreditsGiveawayOption{
.winners = ranges::views::all(
option.data().vwinners().v
) | ranges::views::transform([](const auto &winner) {
return Data::CreditsGiveawayOption::Winner{
.users = winner.data().vusers().v,
.perUserStars = winner.data().vper_user_stars().v,
.isDefault = winner.data().is_default(),
};
}) | ranges::to_vector,
.storeProduct = qs(
option.data().vstore_product().value_or_empty()),
.currency = qs(option.data().vcurrency()),
.amount = option.data().vamount().v,
.credits = option.data().vstars().v,
.yearlyBoosts = option.data().vyearly_boosts().v,
.isExtended = option.data().is_extended(),
.isDefault = option.data().is_default(),
};
}) | ranges::to_vector;
};
const auto fail = [=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
};
_api.request(MTPpayments_GetStarsGiveawayOptions(
)).done([=](const MTPVector<TLOption> &result) {
_options = optionsFromTL(result.v);
consumer.put_done();
}).fail(fail).send();
return lifetime;
};
}
Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
return _options;
}
} // namespace Api

View file

@ -36,6 +36,22 @@ private:
};
class CreditsGiveawayOptions final {
public:
CreditsGiveawayOptions(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
[[nodiscard]] Data::CreditsGiveawayOptions options() const;
private:
const not_null<PeerData*> _peer;
Data::CreditsGiveawayOptions _options;
MTP::Sender _api;
};
class CreditsStatus final {
public:
CreditsStatus(not_null<PeerData*> peer);

View file

@ -115,6 +115,28 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
return _newRequirePremium.value();
}
void GlobalPrivacy::loadPaidReactionAnonymous() {
if (_paidReactionAnonymousLoaded) {
return;
}
_paidReactionAnonymousLoaded = true;
_api.request(MTPmessages_GetPaidReactionPrivacy(
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
}).send();
}
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
_paidReactionAnonymous = value;
}
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
return _paidReactionAnonymous.current();
}
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
return _paidReactionAnonymous.value();
}
void GlobalPrivacy::updateArchiveAndMute(bool value) {
update(

View file

@ -49,6 +49,11 @@ public:
[[nodiscard]] bool newRequirePremiumCurrent() const;
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
void loadPaidReactionAnonymous();
void updatePaidReactionAnonymous(bool value);
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
private:
void apply(const MTPGlobalPrivacySettings &data);
@ -67,7 +72,9 @@ private:
rpl::variable<bool> _showArchiveAndMute = false;
rpl::variable<bool> _hideReadTime = false;
rpl::variable<bool> _newRequirePremium = false;
rpl::variable<bool> _paidReactionAnonymous = false;
std::vector<Fn<void()>> _callbacks;
bool _paidReactionAnonymousLoaded = false;
};

View file

@ -361,9 +361,10 @@ void Premium::resolveGiveawayInfo(
? GiveawayState::Refunded
: GiveawayState::Finished;
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
info.activatedCount = data.vactivated_count().v;
info.activatedCount = data.vactivated_count().value_or_empty();
info.finishDate = data.vfinish_date().v;
info.startDate = data.vstart_date().v;
info.credits = data.vstars_prize().value_or_empty();
});
_giveawayInfoDone(std::move(info));
}).fail([=] {
@ -508,7 +509,9 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
_api.request(MTPpayments_LaunchPrepaidGiveaway(
_peer->input,
MTP_long(prepaidId),
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
invoice.creditsAmount
? Payments::InvoiceCreditsGiveawayToTL(invoice)
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
consumer.put_done();
@ -537,10 +540,10 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
const auto token = Token{ users, months };
const auto &store = _stores[token];
return Payments::InvoicePremiumGiftCode{
.randomId = randomId,
.currency = _optionsForOnePerson.currency,
.amount = store.amount,
.storeProduct = store.product,
.randomId = randomId,
.amount = store.amount,
.storeQuantity = store.quantity,
.users = token.users,
.months = token.months,

View file

@ -57,6 +57,7 @@ struct GiveawayInfo {
TimeId tooEarlyDate = 0;
TimeId finishDate = 0;
TimeId startDate = 0;
uint64 credits = 0;
int winnersCount = 0;
int activatedCount = 0;
bool participating = false;

View file

@ -44,7 +44,10 @@ void InnerFillMessagePostFlags(
if (ShouldSendSilent(peer, options)) {
flags |= MessageFlag::Silent;
}
if (!peer->amAnonymous()) {
if (!peer->amAnonymous()
|| (!peer->isBroadcast()
&& options.sendAs
&& options.sendAs != peer)) {
flags |= MessageFlag::HasFromId;
}
const auto channel = peer->asBroadcast();

View file

@ -571,13 +571,22 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
_boostStatus.prepaidGiveaway = ranges::views::all(
data.vprepaid_giveaways()->v
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
return Data::BoostPrepaidGiveaway{
.months = r.data().vmonths().v,
.id = r.data().vid().v,
.quantity = r.data().vquantity().v,
.date = QDateTime::fromSecsSinceEpoch(
r.data().vdate().v),
};
return r.match([&](const MTPDprepaidGiveaway &data) {
return Data::BoostPrepaidGiveaway{
.date = base::unixtime::parse(data.vdate().v),
.id = data.vid().v,
.months = data.vmonths().v,
.quantity = data.vquantity().v,
};
}, [&](const MTPDprepaidStarsGiveaway &data) {
return Data::BoostPrepaidGiveaway{
.date = base::unixtime::parse(data.vdate().v),
.id = data.vid().v,
.credits = data.vstars().v,
.quantity = data.vquantity().v,
.boosts = data.vboosts().v,
};
});
}) | ranges::to_vector;
}
@ -635,19 +644,21 @@ void Boosts::requestBoosts(
}
: Data::GiftCodeLink();
list.push_back({
data.is_gift(),
data.is_giveaway(),
data.is_unclaimed(),
qs(data.vid()),
data.vuser_id().value_or_empty(),
data.vgiveaway_msg_id()
.id = qs(data.vid()),
.userId = UserId(data.vuser_id().value_or_empty()),
.giveawayMessage = data.vgiveaway_msg_id()
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
: FullMsgId(),
QDateTime::fromSecsSinceEpoch(data.vdate().v),
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
std::move(giftCodeLink),
data.vmultiplier().value_or_empty(),
.date = base::unixtime::parse(data.vdate().v),
.expiresAt = base::unixtime::parse(data.vexpires().v),
.expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
/ kMonthsDivider),
.giftCodeLink = std::move(giftCodeLink),
.multiplier = data.vmultiplier().value_or_empty(),
.credits = data.vstars().value_or_empty(),
.isGift = data.is_gift(),
.isGiveaway = data.is_giveaway(),
.isUnclaimed = data.is_unclaimed(),
});
}
done(Data::BoostsListSlice{

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_authorizations.h"
#include "api/api_user_names.h"
#include "api/api_chat_participants.h"
#include "api/api_global_privacy.h"
#include "api/api_ringtones.h"
#include "api/api_text_entities.h"
#include "api/api_user_privacy.h"
@ -2630,6 +2631,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
_session->credits().apply(data);
} break;
case mtpc_updatePaidReactionPrivacy: {
const auto &data = update.c_updatePaidReactionPrivacy();
_session->api().globalPrivacy().updatePaidReactionAnonymous(
mtpIsTrue(data.vprivate()));
} break;
}
}

View file

@ -214,7 +214,10 @@ struct State {
[[nodiscard]] QImage GenerateUserpic(Userpic &userpic, int size) {
size *= style::DevicePixelRatio();
auto result = userpic.peer->generateUserpicImage(userpic.view, size);
auto result = PeerData::GenerateUserpicImage(
userpic.peer,
userpic.view,
size);
result.setDevicePixelRatio(style::DevicePixelRatio());
return result;
}

View file

@ -4237,8 +4237,10 @@ void ApiWrap::sendMediaWithRandomId(
Data::Histories::ReplyToPlaceholder(),
(options.price
? MTPInputMedia(MTP_inputMediaPaidMedia(
MTP_flags(0),
MTP_long(options.price),
MTP_vector<MTPInputMedia>(1, media)))
MTP_vector<MTPInputMedia>(1, media),
MTPstring()))
: media),
MTP_string(caption.text),
MTP_long(randomId),
@ -4311,8 +4313,10 @@ void ApiWrap::sendMultiPaidMedia(
peer->input,
Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaPaidMedia(
MTP_flags(0),
MTP_long(options.price),
MTP_vector<MTPInputMedia>(std::move(medias))),
MTP_vector<MTPInputMedia>(std::move(medias)),
MTPstring()),
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),

View file

@ -217,7 +217,9 @@ void ShowAddParticipantsError(
channel,
user,
ChatAdminRightsInfo(),
QString());
QString(),
0,
nullptr);
box->setSaveCallback(saveCallback);
*weak = box.data();
show->showBox(std::move(box));

View file

@ -597,8 +597,6 @@ rightsHeaderLabel: FlatLabel(boxLabel) {
}
textFg: windowActiveTextFg;
}
rightsUntilMargin: margins(0px, 8px, 0px, 20px);
rightsRankMargin: margins(0px, 7px, 0px, 20px);
groupStickersRemove: defaultMultiSelectSearchCancel;
groupStickersRemovePosition: point(6px, 6px);
@ -756,6 +754,8 @@ createPollWarningPosition: point(16px, 6px);
createPollCheckboxMargin: margins(22px, 10px, 22px, 10px);
createPollFieldTitlePadding: margins(22px, 7px, 10px, 6px);
sendGifWithCaptionEmojiPosition: point(-30px, 23px);
backgroundCheckbox: Checkbox(defaultCheckbox) {
textFg: msgServiceFg;
textFgActive: msgServiceFg;
@ -785,7 +785,7 @@ backgroundConfirmPadding: margins(24px, 16px, 24px, 16px);
backgroundConfirm: RoundButton(defaultActiveButton) {
height: 44px;
textTop: 12px;
font: font(13px semibold);
style: semiboldTextStyle;
}
backgroundConfirmCancel: RoundButton(backgroundConfirm) {
textFg: mediaviewSaveMsgFg;
@ -797,7 +797,7 @@ backgroundConfirmCancel: RoundButton(backgroundConfirm) {
height: 44px;
textTop: 12px;
font: font(13px semibold);
style: semiboldTextStyle;
ripple: RippleAnimation(defaultRippleAnimation) {
color: shadowFg;
@ -949,7 +949,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
textFg: historyLinkInFg;
textFgOver: historyLinkInFg;
textTop: 7px;
font: normalFont;
style: defaultTextStyle;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
@ -1120,3 +1120,10 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
selectLinkFg: windowActiveTextFg;
}
}
profileQrFont: font(fsize bold);
profileQrCenterSize: 34px;
profileQrBackgroundRadius: 12px;
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);

View file

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "boxes/abstract_box.h" // Ui::show().
#include "window/window_session_controller.h"
#include "styles/style_layers.h"

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "menu/menu_send.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/emoji_button_factory.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
@ -37,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" // defaultComposeFiles.
@ -54,100 +56,6 @@ constexpr auto kSolutionLimit = 200;
constexpr auto kWarnSolutionLimit = 60;
constexpr auto kErrorLimit = 99;
[[nodiscard]] not_null<Ui::EmojiButton*> AddEmojiToggleToField(
not_null<Ui::InputField*> field,
not_null<Ui::BoxContent*> box,
not_null<Window::SessionController*> controller,
not_null<ChatHelpers::TabbedPanel*> emojiPanel,
QPoint shift) {
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
field->parentWidget(),
st::defaultComposeFiles.emoji);
const auto fade = Ui::CreateChild<Ui::FadeAnimation>(
emojiToggle,
emojiToggle,
0.5);
{
const auto fadeTarget = Ui::CreateChild<Ui::RpWidget>(emojiToggle);
fadeTarget->resize(emojiToggle->size());
fadeTarget->paintRequest(
) | rpl::start_with_next([=](const QRect &rect) {
auto p = QPainter(fadeTarget);
if (fade->animating()) {
p.fillRect(fadeTarget->rect(), st::boxBg);
}
fade->paint(p);
}, fadeTarget->lifetime());
rpl::single(false) | rpl::then(
field->focusedChanges()
) | rpl::start_with_next([=](bool shown) {
if (shown) {
fade->fadeIn(st::universalDuration);
} else {
fade->fadeOut(st::universalDuration);
}
}, emojiToggle->lifetime());
fade->fadeOut(1);
fade->finish();
}
const auto outer = box->getDelegate()->outerContainer();
const auto allow = [](not_null<DocumentData*>) { return true; };
InitMessageFieldHandlers(
controller,
field,
Window::GifPauseReason::Layer,
allow);
Ui::Emoji::SuggestionsController::Init(
outer,
field,
&controller->session(),
Ui::Emoji::SuggestionsController::Options{
.suggestCustomEmoji = true,
.allowCustomWithoutPremium = allow,
});
const auto updateEmojiPanelGeometry = [=] {
const auto parent = emojiPanel->parentWidget();
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
const auto right = local.x() + emojiToggle->width() * 3;
const auto isDropDown = local.y() < parent->height() / 2;
emojiPanel->setDropDown(isDropDown);
if (isDropDown) {
emojiPanel->moveTopRight(
local.y() + emojiToggle->height(),
right);
} else {
emojiPanel->moveBottomRight(local.y(), right);
}
};
rpl::combine(
box->sizeValue(),
field->geometryValue()
) | rpl::start_with_next([=](QSize outer, QRect inner) {
emojiToggle->moveToLeft(
rect::right(inner) + shift.x(),
inner.y() + shift.y());
emojiToggle->update();
}, emojiToggle->lifetime());
emojiToggle->installEventFilter(emojiPanel);
emojiToggle->addClickHandler([=] {
updateEmojiPanelGeometry();
emojiPanel->toggleAnimated();
});
const auto filterCallback = [=](not_null<QEvent*> event) {
if (event->type() == QEvent::Enter) {
updateEmojiPanelGeometry();
}
return base::EventFilterResult::Continue;
};
base::install_event_filter(emojiToggle, filterCallback);
return emojiToggle;
}
class Options {
public:
Options(
@ -770,7 +678,7 @@ void Options::addEmptyOption() {
_chooseCorrectGroup));
const auto field = _list.back()->field();
if (const auto emojiPanel = _emojiPanel) {
const auto emojiToggle = AddEmojiToggleToField(
const auto emojiToggle = Ui::AddEmojiToggleToField(
field,
_box,
_controller,
@ -972,7 +880,7 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(session->user());
const auto emojiToggle = AddEmojiToggleToField(
const auto emojiToggle = Ui::AddEmojiToggleToField(
question,
this,
_controller,

View file

@ -195,7 +195,7 @@ PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(x, y, size, size, radius, radius);
}
st::settingsPrivacyPremium.paintInCenter(p, { x, y, size, size });
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
};
}

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_info.h"
@ -581,6 +582,7 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
});
const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_filters_link_qr_about()));
@ -889,6 +891,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
};
const auto getLinkQr = [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_filters_link_qr_about()));
@ -965,9 +968,9 @@ void LinksController::rowPaintIcon(
p.setBrush(*bg);
{
auto hq = PainterHighQualityEnabler(p);
p.drawEllipse(QRect(0, 0, inner, inner));
p.drawEllipse(Rect(Size(inner)));
}
st::inviteLinkIcon.paintInCenter(p, { 0, 0, inner, inner });
st::inviteLinkIcon.paintInCenter(p, Rect(Size(inner)));
}
p.drawImage(x + skip, y + skip, icon);
}
@ -1113,7 +1116,7 @@ QString FilterChatStatusText(not_null<PeerData*> peer) {
? tr::lng_chat_status_subscribers
: tr::lng_chat_status_members)(
tr::now,
lt_count,
lt_count_decimal,
channel->membersCount());
}
}

View file

@ -98,7 +98,7 @@ void GiftCreditsBox(
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(
std::move(text),
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
u"internal:stars_examples"_q);
});
content->add(
object_ptr<Ui::CenterWrap<>>(

View file

@ -69,6 +69,24 @@ constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::PremiumSubscriptionOption;
using GiftOptions = Data::PremiumSubscriptionOptions;
[[nodiscard]] QString CreateMessageLink(
not_null<Main::Session*> session,
PeerId peerId,
uint64 messageId) {
if (const auto msgId = MsgId(peerId ? messageId : 0)) {
const auto peer = session->data().peer(peerId);
if (const auto channel = peer->asBroadcast()) {
const auto username = channel->username();
const auto base = username.isEmpty()
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
: username;
const auto query = base + '/' + QString::number(msgId.bare);
return session->createInternalLink(query);
}
}
return QString();
};
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
auto result = GiftOptions();
const auto gifts = data.vpremium_gifts();
@ -1419,21 +1437,56 @@ void GiveawayInfoBox(
: !start->channels.empty()
? start->channels.front()->name()
: u"channel"_q;
auto text = TextWithEntities();
if (!info.giftCode.isEmpty()) {
text.append("\n\n");
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
tr::now,
auto resultText = (!info.giftCode.isEmpty())
? tr::lng_prizes_you_won(
lt_cup,
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
text.append("\n\n");
} else if (info.state == State::Finished) {
text.append("\n\n");
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
text.append("\n\n");
rpl::single(
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
Ui::Text::WithEntities)
: (info.credits)
? tr::lng_prizes_you_won_credits(
lt_amount,
tr::lng_prizes_you_won_credits_amount(
lt_count,
rpl::single(float64(info.credits)),
Ui::Text::Bold),
lt_cup,
rpl::single(
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
Ui::Text::WithEntities)
: (info.state == State::Finished)
? tr::lng_prizes_you_didnt(Ui::Text::WithEntities)
: (rpl::producer<TextWithEntities>)(nullptr);
if (resultText) {
const auto &st = st::changePhoneDescription;
const auto skip = st.style.font->height * 0.5;
auto label = object_ptr<Ui::FlatLabel>(
box.get(),
std::move(resultText),
st);
if ((!info.giftCode.isEmpty()) || info.credits) {
label->setTextColorOverride(st::windowActiveTextFg->c);
}
const auto result = box->addRow(
object_ptr<Ui::PaddingWrap<Ui::CenterWrap<Ui::FlatLabel>>>(
box.get(),
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box.get(),
std::move(label)),
QMargins(0, skip, 0, skip)));
result->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(result);
p.setPen(Qt::NoPen);
p.setBrush(st::boxDividerBg);
p.drawRoundedRect(result->rect(), st::boxRadius, st::boxRadius);
}, result->lifetime());
Ui::AddSkip(box->verticalLayout());
}
auto text = TextWithEntities();
const auto quantity = start
? start->quantity
: (results->winnersCount + results->unclaimedCount);
@ -1442,22 +1495,39 @@ void GiveawayInfoBox(
? results->channel->isMegagroup()
: (!start->channels.empty()
&& start->channels.front()->isMegagroup());
const auto credits = start
? start->credits
: (results ? results->credits : 0);
text.append((finished
? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)(
tr::now,
lt_admins,
(group
? tr::lng_prizes_admins_group
: tr::lng_prizes_admins)(
tr::now,
lt_count,
quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
credits
? (group
? tr::lng_prizes_credits_admins_group
: tr::lng_prizes_credits_admins)(
tr::now,
lt_channel,
Ui::Text::Bold(first),
lt_amount,
tr::lng_prizes_credits_admins_amount(
tr::now,
lt_count_decimal,
float64(credits),
Ui::Text::Bold),
Ui::Text::RichLangValue)
: (group
? tr::lng_prizes_admins_group
: tr::lng_prizes_admins)(
tr::now,
lt_count,
quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
Ui::Text::RichLangValue));
const auto many = start
? (start->channels.size() > 1)
@ -1651,6 +1721,7 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
const auto session = &controller->session();
if (peerId) {
auto text = entry.in
? tr::lng_credits_box_history_entry_peer_in()
@ -1658,15 +1729,12 @@ void AddCreditsHistoryEntryTable(
AddTableRow(table, std::move(text), controller, peerId);
}
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
const auto session = &controller->session();
const auto peer = session->data().peer(peerId);
if (const auto channel = peer->asBroadcast()) {
const auto username = channel->username();
const auto base = username.isEmpty()
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
: username;
const auto query = base + '/' + QString::number(msgId.bare);
const auto link = session->createInternalLink(query);
const auto link = CreateMessageLink(
session,
peerId,
entry.bareMsgId);
auto label = object_ptr<Ui::FlatLabel>(
table,
rpl::single(Ui::Text::Link(link)),
@ -1717,6 +1785,37 @@ void AddCreditsHistoryEntryTable(
tr::lng_credits_box_history_entry_via_premium_bot(
Ui::Text::RichLangValue));
}
if (entry.bareGiveawayMsgId) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
controller,
controller->session().userId());
}
if (entry.bareGiveawayMsgId && entry.credits) {
AddTableRow(
table,
tr::lng_gift_link_label_gift(),
tr::lng_gift_stars_title(
lt_count,
rpl::single(float64(entry.credits)),
Ui::Text::RichLangValue));
}
{
const auto link = CreateMessageLink(
session,
peerId,
entry.bareGiveawayMsgId);
if (!link.isEmpty()) {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_gift_link_reason_giveaway(
) | rpl::map([link](const QString &text) {
return Ui::Text::Link(text, link);
}));
}
}
if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18;
const auto oneLine = entry.id.length() <= kOneLineCount;
@ -1813,3 +1912,60 @@ void AddSubscriberEntryTable(
rpl::single(Ui::Text::WithEntities(langDateTime(d))));
}
}
void AddCreditsBoostTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::Boost &b) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = b.giveawayMessage.peer;
if (!peerId) {
return;
}
const auto from = controller->session().data().peer(peerId);
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
controller,
from->id);
if (b.credits) {
AddTableRow(
table,
tr::lng_gift_link_label_gift(),
tr::lng_gift_stars_title(
lt_count,
rpl::single(float64(b.credits)),
Ui::Text::RichLangValue));
}
{
const auto link = CreateMessageLink(
&controller->session(),
peerId,
b.giveawayMessage.msg.bare);
if (!link.isEmpty()) {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_gift_link_reason_giveaway(
) | rpl::map([link](const QString &text) {
return Ui::Text::Link(text, link);
}));
}
}
if (!b.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(b.date))));
}
if (!b.expiresAt.isNull()) {
AddTableRow(
table,
tr::lng_gift_until(),
rpl::single(Ui::Text::WithEntities(langDateTime(b.expiresAt))));
}
}

View file

@ -16,6 +16,7 @@ struct GiftCode;
} // namespace Api
namespace Data {
struct Boost;
struct CreditsHistoryEntry;
struct GiveawayStart;
struct GiveawayResults;
@ -89,3 +90,8 @@ void AddSubscriberEntryTable(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
TimeId date);
void AddCreditsBoostTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::Boost &boost);

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "storage/localstorage.h"
#include "boxes/abstract_box.h"
#include "boxes/premium_preview_box.h"

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/moderate_messages_box.h"
#include "api/api_blocked_peers.h"
#include "api/api_chat_participants.h"
#include "api/api_messages_search.h"
#include "apiwrap.h"
@ -31,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/toggle_arrow.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
@ -39,15 +39,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/expandable_peer_list.h"
#include "ui/widgets/participants_check_view.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
namespace {
using Participants = std::vector<not_null<PeerData*>>;
struct ModerateOptions final {
bool allCanBan = false;
bool allCanDelete = false;
@ -103,117 +104,14 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
};
}
class Button final : public Ui::RippleButton {
public:
Button(not_null<QWidget*> parent, int count);
void setChecked(bool checked);
[[nodiscard]] bool checked() const;
[[nodiscard]] static QSize ComputeSize(int);
private:
void paintEvent(QPaintEvent *event) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
const int _count;
const QString _text;
bool _checked = false;
Ui::Animations::Simple _animation;
};
Button::Button(not_null<QWidget*> parent, int count)
: RippleButton(parent, st::defaultRippleAnimation)
, _count(count)
, _text(QString::number(std::abs(_count))) {
}
QSize Button::ComputeSize(int count) {
return QSize(
st::moderateBoxExpandHeight
+ st::moderateBoxExpand.width()
+ st::moderateBoxExpandInnerSkip * 4
+ st::moderateBoxExpandFont->width(
QString::number(std::abs(count)))
+ st::moderateBoxExpandToggleSize,
st::moderateBoxExpandHeight);
}
void Button::setChecked(bool checked) {
if (_checked == checked) {
return;
}
_checked = checked;
_animation.stop();
_animation.start(
[=] { update(); },
checked ? 0 : 1,
checked ? 1 : 0,
st::slideWrapDuration);
}
bool Button::checked() const {
return _checked;
}
void Button::paintEvent(QPaintEvent *event) {
auto p = Painter(this);
auto hq = PainterHighQualityEnabler(p);
Ui::RippleButton::paintRipple(p, QPoint());
const auto radius = height() / 2;
p.setPen(Qt::NoPen);
st::moderateBoxExpand.paint(
p,
radius,
(height() - st::moderateBoxExpand.height()) / 2,
width());
const auto innerSkip = st::moderateBoxExpandInnerSkip;
p.setBrush(Qt::NoBrush);
p.setPen(st::boxTextFg);
p.setFont(st::moderateBoxExpandFont);
p.drawText(
QRect(
innerSkip + radius + st::moderateBoxExpand.width(),
0,
width(),
height()),
_text,
style::al_left);
const auto path = Ui::ToggleUpDownArrowPath(
width() - st::moderateBoxExpandToggleSize - radius,
height() / 2,
st::moderateBoxExpandToggleSize,
st::moderateBoxExpandToggleFourStrokes,
_animation.value(_checked ? 1. : 0.));
p.fillPath(path, st::boxTextFg);
}
QImage Button::prepareRippleMask() const {
return Ui::RippleAnimation::RoundRectMask(size(), size().height() / 2);
}
QPoint Button::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos());
}
} // namespace
void CreateModerateMessagesBox(
not_null<Ui::GenericBox*> box,
const HistoryItemsList &items,
Fn<void()> confirmed) {
struct Controller final {
rpl::event_stream<bool> toggleRequestsFromTop;
rpl::event_stream<bool> toggleRequestsFromInner;
rpl::event_stream<bool> checkAllRequests;
Fn<Participants()> collectRequests;
};
using Controller = Ui::ExpandablePeerListController;
const auto [allCanBan, allCanDelete, participants]
= CalculateModerateOptions(items);
const auto inner = box->verticalLayout();
@ -225,7 +123,12 @@ void CreateModerateMessagesBox(
const auto isSingle = participants.size() == 1;
const auto buttonPadding = isSingle
? QMargins()
: QMargins(0, 0, Button::ComputeSize(participants.size()).width(), 0);
: QMargins(
0,
0,
Ui::ParticipantsCheckView::ComputeSize(
participants.size()).width(),
0);
const auto session = &items.front()->history()->session();
const auto historyPeerId = items.front()->history()->peer->id;
@ -307,135 +210,6 @@ void CreateModerateMessagesBox(
});
};
const auto createParticipantsList = [&](
not_null<Controller*> controller) {
const auto wrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
wrap->toggle(false, anim::type::instant);
controller->toggleRequestsFromTop.events(
) | rpl::start_with_next([=](bool toggled) {
wrap->toggle(toggled, anim::type::normal);
}, wrap->lifetime());
const auto container = wrap->entity();
Ui::AddSkip(container);
auto &lifetime = wrap->lifetime();
const auto clicks = lifetime.make_state<rpl::event_stream<>>();
const auto checkboxes = ranges::views::all(
participants
) | ranges::views::transform([&](not_null<PeerData*> peer) {
const auto line = container->add(
object_ptr<Ui::AbstractButton>(container));
const auto &st = st::moderateBoxUserpic;
line->resize(line->width(), st.size.height());
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
line,
peer,
st);
const auto checkbox = Ui::CreateChild<Ui::Checkbox>(
line,
peer->name(),
false,
st::defaultBoxCheckbox);
line->widthValue(
) | rpl::start_with_next([=](int width) {
userpic->moveToLeft(
st::boxRowPadding.left()
+ checkbox->checkRect().width()
+ st::defaultBoxCheckbox.textPosition.x(),
0);
const auto skip = st::defaultBoxCheckbox.textPosition.x();
checkbox->resizeToWidth(width
- rect::right(userpic)
- skip
- st::boxRowPadding.right());
checkbox->moveToLeft(
rect::right(userpic) + skip,
((userpic->height() - checkbox->height()) / 2)
+ st::defaultBoxCheckbox.margin.top());
}, checkbox->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
line->setClickedCallback([=] {
checkbox->setChecked(!checkbox->checked());
clicks->fire({});
});
return checkbox;
}) | ranges::to_vector;
clicks->events(
) | rpl::start_with_next([=] {
controller->toggleRequestsFromInner.fire_copy(
ranges::any_of(checkboxes, &Ui::Checkbox::checked));
}, container->lifetime());
controller->checkAllRequests.events(
) | rpl::start_with_next([=](bool checked) {
for (const auto &c : checkboxes) {
c->setChecked(checked);
}
}, container->lifetime());
controller->collectRequests = [=] {
auto result = Participants();
for (auto i = 0; i < checkboxes.size(); i++) {
if (checkboxes[i]->checked()) {
result.push_back(participants[i]);
}
}
return result;
};
};
const auto appendList = [&](
not_null<Ui::Checkbox*> checkbox,
not_null<Controller*> controller) {
if (isSingle) {
const auto p = participants.front();
controller->collectRequests = [=] { return Participants{ p }; };
return;
}
const auto count = int(participants.size());
const auto button = Ui::CreateChild<Button>(inner, count);
button->resize(Button::ComputeSize(count));
const auto overlay = Ui::CreateChild<Ui::AbstractButton>(inner);
checkbox->geometryValue(
) | rpl::start_with_next([=](const QRect &rect) {
overlay->setGeometry(rect);
overlay->raise();
button->moveToRight(
st::moderateBoxExpandRight,
rect.top() + (rect.height() - button->height()) / 2,
box->width());
button->raise();
}, button->lifetime());
controller->toggleRequestsFromInner.events(
) | rpl::start_with_next([=](bool toggled) {
checkbox->setChecked(toggled);
}, checkbox->lifetime());
button->setClickedCallback([=] {
button->setChecked(!button->checked());
controller->toggleRequestsFromTop.fire_copy(button->checked());
});
overlay->setClickedCallback([=] {
checkbox->setChecked(!checkbox->checked());
controller->checkAllRequests.fire_copy(checkbox->checked());
});
createParticipantsList(controller);
};
Ui::AddSkip(inner);
const auto title = box->addRow(
object_ptr<Ui::FlatLabel>(
@ -457,8 +231,9 @@ void CreateModerateMessagesBox(
false,
st::defaultBoxCheckbox),
st::boxRowPadding + buttonPadding);
const auto controller = box->lifetime().make_state<Controller>();
appendList(report, controller);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(report, controller, inner);
handleSubmition(report);
const auto ids = items.front()->from()->owner().itemsToIds(items);
@ -515,8 +290,9 @@ void CreateModerateMessagesBox(
}, title->lifetime());
}
const auto controller = box->lifetime().make_state<Controller>();
appendList(deleteAll, controller);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(deleteAll, controller, inner);
handleSubmition(deleteAll);
handleConfirmation(deleteAll, controller, [=](
@ -545,8 +321,9 @@ void CreateModerateMessagesBox(
false,
st::defaultBoxCheckbox),
st::boxRowPadding + buttonPadding);
const auto controller = box->lifetime().make_state<Controller>();
appendList(ban, controller);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(ban, controller, inner);
handleSubmition(ban);
Ui::AddSkip(inner);
@ -659,14 +436,16 @@ void CreateModerateMessagesBox(
return result;
}();
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
box,
Ui::AddSubsectionTitle(
inner,
rpl::conditional(
rpl::single(isSingle),
tr::lng_restrict_users_part_single_header(),
tr::lng_restrict_users_part_header(
lt_count,
rpl::single(participants.size()) | tr::to_count())),
rpl::single(participants.size()) | tr::to_count())));
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
box,
prepareFlags,
disabledMessages,
{ .isForum = peer->isForum() });
@ -757,38 +536,24 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
return base::EventFilterResult::Continue;
});
const auto line = container->add(object_ptr<Ui::RpWidget>(container));
const auto &st = st::mainMenuUserpic;
line->resize(line->width(), st.size.height());
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
line,
container,
peer,
st);
st::mainMenuUserpic);
userpic->showSavedMessagesOnSelf(true);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
line,
peer->isSelf()
? tr::lng_saved_messages() | Ui::Text::ToBold()
: maybeUser
? tr::lng_profile_delete_conversation() | Ui::Text::ToBold()
: rpl::single(Ui::Text::Bold(peer->name())) | rpl::type_erased(),
box->getDelegate()->style().title);
line->widthValue(
) | rpl::start_with_next([=](int width) {
userpic->moveToLeft(st::boxRowPadding.left(), 0);
const auto skip = st::defaultBoxCheckbox.textPosition.x();
label->resizeToWidth(width
- rect::right(userpic)
- skip
- st::boxRowPadding.right());
label->moveToLeft(
rect::right(userpic) + skip,
((userpic->height() - label->height()) / 2));
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::IconWithTitle(
container,
userpic,
Ui::CreateChild<Ui::FlatLabel>(
container,
peer->isSelf()
? tr::lng_saved_messages() | Ui::Text::ToBold()
: maybeUser
? tr::lng_profile_delete_conversation() | Ui::Text::ToBold()
: rpl::single(
peer->name()
) | Ui::Text::ToBold() | rpl::type_erased(),
box->getDelegate()->style().title));
Ui::AddSkip(container);
Ui::AddSkip(container);
@ -829,6 +594,20 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
st::defaultBoxCheckbox));
}();
const auto maybeBotCheckbox = [&]() -> Ui::Checkbox* {
if (!maybeUser || !maybeUser->isBot()) {
return nullptr;
}
Ui::AddSkip(container);
Ui::AddSkip(container);
return box->addRow(
object_ptr<Ui::Checkbox>(
container,
tr::lng_profile_block_bot(tr::now, Ui::Text::WithEntities),
false,
st::defaultBoxCheckbox));
}();
Ui::AddSkip(container);
auto buttonText = maybeUser
@ -842,7 +621,11 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto close = crl::guard(box, [=] { box->closeBox(); });
box->addButton(std::move(buttonText), [=] {
const auto revoke = maybeCheckbox && maybeCheckbox->checked();
const auto stopBot = maybeBotCheckbox && maybeBotCheckbox->checked();
Core::App().closeChatFromWindows(peer);
if (stopBot) {
peer->session().api().blockedPeers().block(peer);
}
// Don't delete old history by default,
// because Android app doesn't.
//

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "lang/lang_keys.h"
#include "storage/file_download.h"
#include "data/data_peer_values.h"

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/ui_utility.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "data/data_session.h"

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "base/random.h"
#include "base/weak_ptr.h"
#include "api/api_chat_participants.h"
@ -169,11 +170,15 @@ void AddBotToGroupBoxController::requestExistingRights(
channel);
_existingRights = participant.rights().flags;
_existingRank = participant.rank();
_promotedSince = participant.promotedSince();
_promotedBy = participant.by();
addBotToGroup(_existingRightsChannel);
});
}).fail([=] {
_existingRights = ChatAdminRights();
_existingRank = QString();
_promotedSince = 0;
_promotedBy = 0;
addBotToGroup(_existingRightsChannel);
}).send();
}
@ -190,6 +195,8 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
_existingRights = {};
_existingRank = QString();
_existingRightsChannel = nullptr;
_promotedSince = 0;
_promotedBy = 0;
_bot->session().api().request(_existingRightsRequestId).cancel();
}
const auto requestedAddAdmin = (_scope == Scope::GroupAdmin)
@ -240,9 +247,12 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
bot,
ChatAdminRightsInfo(rights),
_existingRank,
_promotedSince,
_promotedBy ? chat->owner().user(_promotedBy).get() : nullptr,
EditAdminBotFields{
_token,
_existingRights.value_or(ChatAdminRights()) });
_existingRights.value_or(ChatAdminRights()),
});
box->setSaveCallback(saveCallback);
controller->show(std::move(box));
} else {

View file

@ -65,6 +65,8 @@ private:
mtpRequestId _existingRightsRequestId = 0;
std::optional<ChatAdminRights> _existingRights;
QString _existingRank;
TimeId _promotedSince = 0;
UserId _promotedBy = 0;
rpl::event_stream<not_null<PeerData*>> _groups;
rpl::event_stream<not_null<PeerData*>> _channels;

View file

@ -1276,7 +1276,9 @@ void AddSpecialBoxController::showAdmin(
_peer,
user,
currentRights,
_additional.adminRank(user));
_additional.adminRank(user),
_additional.adminPromotedSince(user),
_additional.adminPromotedBy(user));
const auto show = delegate()->peerListUiShow();
if (_additional.canAddOrEditAdmin(user)) {
const auto done = crl::guard(this, [=](
@ -1354,7 +1356,9 @@ void AddSpecialBoxController::showRestricted(
_peer,
user,
_additional.adminRights(user).has_value(),
currentRights);
currentRights,
_additional.restrictedBy(user),
_additional.restrictedSince(user));
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
ChatRestrictionsInfo newRights) {

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "base/event_filter.h"
#include "base/random.h"
#include "base/qt_signal_producer.h"
#include "chat_helpers/emoji_list_widget.h"
@ -482,6 +483,9 @@ void EditForumTopicBox(
state->defaultIcon.current().colorId,
};
}, title->lifetime());
title->submits() | rpl::start_with_next([box] {
box->triggerButton(0);
}, title->lifetime());
if (!topic || !topic->isGeneral()) {
Ui::AddDividerText(top, tr::lng_forum_choose_title_and_icon());

View file

@ -16,6 +16,10 @@ struct TopicIconDescriptor;
enum class CustomEmojiSizeTag : uchar;
} // namespace Data
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Window {
class SessionController;
} // namespace Window

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/controls/userpic_button.h"
#include "ui/vertical_list.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
@ -63,6 +64,10 @@ public:
template <typename Widget>
Widget *addControl(object_ptr<Widget> widget, QMargins margin);
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const {
return _rows;
}
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
@ -164,6 +169,10 @@ EditParticipantBox::EditParticipantBox(
, _hasAdminRights(hasAdminRights) {
}
not_null<Ui::VerticalLayout*> EditParticipantBox::verticalLayout() const {
return _inner->verticalLayout();
}
void EditParticipantBox::prepare() {
_inner = setInnerWidget(object_ptr<Inner>(
this,
@ -197,6 +206,8 @@ EditAdminBox::EditAdminBox(
not_null<UserData*> user,
ChatAdminRightsInfo rights,
const QString &rank,
TimeId promotedSince,
UserData *by,
std::optional<EditAdminBotFields> addingBot)
: EditParticipantBox(
nullptr,
@ -205,6 +216,8 @@ EditAdminBox::EditAdminBox(
(rights.flags != 0))
, _oldRights(rights)
, _oldRank(rank)
, _promotedSince(promotedSince)
, _by(by)
, _addingBot(std::move(addingBot)) {
}
@ -279,9 +292,26 @@ void EditAdminBox::prepare() {
object_ptr<Ui::VerticalLayout>(this)));
const auto inner = _adminControlsWrap->entity();
inner->add(
object_ptr<Ui::BoxContentDivider>(inner),
st::rightsDividerMargin);
if (_promotedSince) {
const auto parsed = base::unixtime::parse(_promotedSince);
const auto label = Ui::AddDividerText(
inner,
tr::lng_rights_about_by(
lt_user,
rpl::single(_by
? Ui::Text::Link(_by->name(), 1)
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
lt_date,
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
Ui::Text::WithEntities));
if (_by) {
label->setLink(1, _by->createOpenLink());
}
Ui::AddSkip(inner);
} else {
Ui::AddDivider(inner);
Ui::AddSkip(inner);
}
const auto chat = peer()->asChat();
const auto channel = peer()->asChannel();
@ -335,9 +365,9 @@ void EditAdminBox::prepare() {
.isForum = peer()->isForum(),
.anyoneCanAddMembers = anyoneCanAddMembers,
};
Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
auto [checkboxes, getChecked, changes] = CreateEditAdminRights(
inner,
tr::lng_rights_edit_admin_header(),
prepareFlags,
disabledMessages,
options);
@ -348,17 +378,47 @@ void EditAdminBox::prepare() {
) | rpl::then(std::move(
changes
));
_aboutAddAdmins = inner->add(
object_ptr<Ui::FlatLabel>(inner, st::boxDividerLabel),
st::rightsAboutMargin);
rpl::duplicate(
selectedFlags
) | rpl::map(
(_1 & Flag::AddAdmins) != 0
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool checked) {
refreshAboutAddAdminsText(checked);
}, lifetime());
const auto hasRank = canSave() && (chat || channel->isMegagroup());
{
const auto aboutAddAdminsInner = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
const auto emptyAboutAddAdminsInner = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
aboutAddAdminsInner->toggle(false, anim::type::instant);
emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
if (hasRank) {
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
}
Ui::AddSkip(aboutAddAdminsInner->entity());
Ui::AddDividerText(
aboutAddAdminsInner->entity(),
rpl::duplicate(
selectedFlags
) | rpl::map(
(_1 & Flag::AddAdmins) != 0
) | rpl::distinct_until_changed(
) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
const auto empty = (amCreator() && user()->isSelf());
aboutAddAdminsInner->toggle(!empty, anim::type::instant);
emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
if (empty) {
return rpl::single(QString());
} else if (!canSave()) {
return tr::lng_rights_about_admin_cant_edit();
} else if (canAddAdmins) {
return tr::lng_rights_about_add_admins_yes();
}
return tr::lng_rights_about_add_admins_no();
}) | rpl::flatten_latest());
}
if (canTransferOwnership()) {
const auto allFlags = AdminRightsForOwnershipTransfer(options);
@ -373,9 +433,7 @@ void EditAdminBox::prepare() {
}
if (canSave()) {
_rank = (chat || channel->isMegagroup())
? addRankInput(inner).get()
: nullptr;
_rank = hasRank ? addRankInput(inner).get() : nullptr;
_finishSave = [=, value = getChecked] {
const auto newFlags = (value() | ChatAdminRight::Other)
& ((!channel || channel->amCreator())
@ -441,9 +499,7 @@ void EditAdminBox::refreshButtons() {
not_null<Ui::InputField*> EditAdminBox::addRankInput(
not_null<Ui::VerticalLayout*> container) {
container->add(
object_ptr<Ui::BoxContentDivider>(container),
st::rightsRankMargin);
// Ui::AddDivider(container);
container->add(
object_ptr<Ui::FlatLabel>(
@ -480,14 +536,13 @@ not_null<Ui::InputField*> EditAdminBox::addRankInput(
}
}, result->lifetime());
container->add(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_rights_edit_admin_rank_about(
lt_title,
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()),
st::boxDividerLabel),
st::rightsAboutMargin);
Ui::AddSkip(container);
Ui::AddDividerText(
container,
tr::lng_rights_edit_admin_rank_about(
lt_title,
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()));
Ui::AddSkip(container);
return result;
}
@ -681,27 +736,18 @@ void EditAdminBox::sendTransferRequestFrom(
})).handleFloodErrors().send();
}
void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) {
_aboutAddAdmins->setText([&] {
if (amCreator() && user()->isSelf()) {
return QString();
} else if (!canSave()) {
return tr::lng_rights_about_admin_cant_edit(tr::now);
} else if (canAddAdmins) {
return tr::lng_rights_about_add_admins_yes(tr::now);
}
return tr::lng_rights_about_add_admins_no(tr::now);
}());
}
EditRestrictedBox::EditRestrictedBox(
QWidget*,
not_null<PeerData*> peer,
not_null<UserData*> user,
bool hasAdminRights,
ChatRestrictionsInfo rights)
ChatRestrictionsInfo rights,
UserData *by,
TimeId since)
: EditParticipantBox(nullptr, peer, user, hasAdminRights)
, _oldRights(rights) {
, _oldRights(rights)
, _by(by)
, _since(since) {
}
void EditRestrictedBox::prepare() {
@ -712,9 +758,8 @@ void EditRestrictedBox::prepare() {
setTitle(tr::lng_rights_user_restrictions());
addControl(
object_ptr<Ui::BoxContentDivider>(this),
st::rightsDividerMargin);
Ui::AddDivider(verticalLayout());
Ui::AddSkip(verticalLayout());
const auto chat = peer()->asChat();
const auto channel = peer()->asChannel();
@ -749,16 +794,20 @@ void EditRestrictedBox::prepare() {
return result;
}();
Ui::AddSubsectionTitle(
verticalLayout(),
tr::lng_rights_user_restrictions_header());
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
this,
tr::lng_rights_user_restrictions_header(),
prepareFlags,
disabledMessages,
{ .isForum = peer()->isForum() });
addControl(std::move(checkboxes), QMargins());
_until = prepareRights.until;
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
addControl(
object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
Ui::AddDivider(verticalLayout());
addControl(
object_ptr<Ui::FlatLabel>(
this,
@ -773,6 +822,29 @@ void EditRestrictedBox::prepare() {
// tr::lng_rights_chat_banned_block(tr::now),
// st::boxLinkButton));
if (_since) {
const auto parsed = base::unixtime::parse(_since);
const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
const auto isBanned = (_oldRights.flags
& ChatRestriction::ViewMessages);
Ui::AddSkip(inner);
const auto label = Ui::AddDividerText(
inner,
(isBanned
? tr::lng_rights_chat_banned_by
: tr::lng_rights_chat_restricted_by)(
lt_user,
rpl::single(_by
? Ui::Text::Link(_by->name(), 1)
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
lt_date,
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
Ui::Text::WithEntities));
if (_by) {
label->setLink(1, _by->createOpenLink());
}
}
if (canSave()) {
const auto save = [=, value = getRestrictions] {
if (!_saveCallback) {

View file

@ -36,6 +36,8 @@ public:
not_null<UserData*> user,
bool hasAdminRights);
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const;
protected:
void prepare() override;
@ -77,6 +79,8 @@ public:
not_null<UserData*> user,
ChatAdminRightsInfo rights,
const QString &rank,
TimeId promotedSince,
UserData *by,
std::optional<EditAdminBotFields> addingBot = {});
void setSaveCallback(
@ -108,7 +112,6 @@ private:
}
void finishAddAdmin();
void refreshButtons();
void refreshAboutAddAdminsText(bool canAddAdmins);
bool canTransferOwnership() const;
not_null<Ui::SlideWrap<Ui::RpWidget>*> setupTransferButton(
not_null<Ui::VerticalLayout*> container,
@ -125,11 +128,12 @@ private:
Ui::Checkbox *_addAsAdmin = nullptr;
Ui::SlideWrap<Ui::VerticalLayout> *_adminControlsWrap = nullptr;
Ui::InputField *_rank = nullptr;
QPointer<Ui::FlatLabel> _aboutAddAdmins;
mtpRequestId _checkTransferRequestId = 0;
mtpRequestId _transferRequestId = 0;
Fn<void()> _save, _finishSave;
TimeId _promotedSince = 0;
UserData *_by = nullptr;
std::optional<EditAdminBotFields> _addingBot;
};
@ -144,7 +148,9 @@ public:
not_null<PeerData*> peer,
not_null<UserData*> user,
bool hasAdminRights,
ChatRestrictionsInfo rights);
ChatRestrictionsInfo rights,
UserData *by,
TimeId since);
void setSaveCallback(
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> callback) {
@ -168,6 +174,8 @@ private:
TimeId getRealUntilValue() const;
const ChatRestrictionsInfo _oldRights;
UserData *_by = nullptr;
TimeId _since = 0;
TimeId _until = 0;
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> _saveCallback;

View file

@ -387,6 +387,24 @@ QString ParticipantsAdditionalData::adminRank(
return (i != end(_adminRanks)) ? i->second : QString();
}
TimeId ParticipantsAdditionalData::adminPromotedSince(
not_null<UserData*> user) const {
const auto i = _adminPromotedSince.find(user);
return (i != end(_adminPromotedSince)) ? i->second : TimeId(0);
}
TimeId ParticipantsAdditionalData::restrictedSince(
not_null<PeerData*> peer) const {
const auto i = _restrictedSince.find(peer);
return (i != end(_restrictedSince)) ? i->second : TimeId(0);
}
TimeId ParticipantsAdditionalData::memberSince(
not_null<UserData*> user) const {
const auto i = _memberSince.find(user);
return (i != end(_memberSince)) ? i->second : TimeId(0);
}
auto ParticipantsAdditionalData::restrictedRights(
not_null<PeerData*> participant) const
-> std::optional<ChatRestrictionsInfo> {
@ -689,6 +707,11 @@ UserData *ParticipantsAdditionalData::applyAdmin(
} else {
_adminRanks.remove(user);
}
if (data.promotedSince()) {
_adminPromotedSince[user] = data.promotedSince();
} else {
_adminPromotedSince.remove(user);
}
if (const auto by = _peer->owner().userLoaded(data.by())) {
const auto i = _adminPromotedBy.find(user);
if (i == _adminPromotedBy.end()) {
@ -741,6 +764,11 @@ PeerData *ParticipantsAdditionalData::applyBanned(
} else {
_kicked.erase(participant);
}
if (data.restrictedSince()) {
_restrictedSince[participant] = data.restrictedSince();
} else {
_restrictedSince.remove(participant);
}
_restrictedRights[participant] = data.restrictions();
if (const auto by = _peer->owner().userLoaded(data.by())) {
const auto i = _restrictedBy.find(participant);
@ -1720,7 +1748,9 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
_peer,
user,
currentRights,
_additional.adminRank(user));
_additional.adminRank(user),
_additional.adminPromotedSince(user),
_additional.adminPromotedBy(user));
if (_additional.canAddOrEditAdmin(user)) {
const auto done = crl::guard(this, [=](
ChatAdminRightsInfo newRights,
@ -1776,7 +1806,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
_peer,
user,
hasAdminRights,
currentRights);
currentRights,
_additional.restrictedBy(user),
_additional.restrictedSince(user));
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
ChatRestrictionsInfo newRights) {

View file

@ -106,14 +106,19 @@ public:
not_null<PeerData*> participant) const;
[[nodiscard]] std::optional<ChatAdminRightsInfo> adminRights(
not_null<UserData*> user) const;
QString adminRank(not_null<UserData*> user) const;
[[nodiscard]] QString adminRank(not_null<UserData*> user) const;
[[nodiscard]] std::optional<ChatRestrictionsInfo> restrictedRights(
not_null<PeerData*> participant) const;
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
[[nodiscard]] UserData *restrictedBy(
not_null<PeerData*> participant) const;
[[nodiscard]] TimeId adminPromotedSince(not_null<UserData*>) const;
[[nodiscard]] TimeId restrictedSince(not_null<PeerData*>) const;
[[nodiscard]] TimeId memberSince(not_null<UserData*>) const;
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
@ -144,6 +149,9 @@ private:
// Data for channels.
base::flat_map<not_null<UserData*>, ChatAdminRightsInfo> _adminRights;
base::flat_map<not_null<UserData*>, QString> _adminRanks;
base::flat_map<not_null<UserData*>, TimeId> _adminPromotedSince;
base::flat_map<not_null<PeerData*>, TimeId> _restrictedSince;
base::flat_map<not_null<UserData*>, TimeId> _memberSince;
base::flat_set<not_null<UserData*>> _adminCanEdit;
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
std::map<not_null<PeerData*>, ChatRestrictionsInfo> _restrictedRights;

View file

@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "api/api_invite_links.h"
#include "styles/style_chat_helpers.h"

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/edit_invite_link.h"
#include "ui/boxes/edit_invite_link_session.h"
#include "ui/boxes/peer_qr_box.h"
#include "ui/controls/invite_link_buttons.h"
#include "ui/controls/invite_link_label.h"
#include "ui/controls/userpic_button.h"
@ -64,8 +65,8 @@ namespace {
constexpr auto kFirstPage = 20;
constexpr auto kPerPage = 100;
constexpr auto kShareQrSize = 768;
constexpr auto kShareQrPadding = 16;
// constexpr auto kShareQrSize = 768;
// constexpr auto kShareQrPadding = 16;
using LinkData = Api::InviteLink;
@ -282,6 +283,8 @@ private:
return updated.link.isEmpty() || (!revoked && updated.revoked);
}
#if 0
QImage QrExact(const Qr::Data &data, int pixel, QColor color) {
const auto image = [](int size) {
auto result = QImage(
@ -383,6 +386,8 @@ void QrBox(
box->addLeftButton(tr::lng_group_invite_context_copy(), copyCallback);
}
#endif
Controller::Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
@ -421,6 +426,7 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
});
const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
_peer,
link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@ -1253,6 +1259,7 @@ void AddPermanentLinkBlock(
const auto getLinkQr = crl::guard(weak, [=] {
if (const auto current = value->current(); !current.link.isEmpty()) {
show->showBox(InviteLinkQrBox(
peer,
current.link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@ -1510,16 +1517,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
}
object_ptr<Ui::BoxContent> InviteLinkQrBox(
PeerData *peer,
const QString &link,
rpl::producer<QString> title,
rpl::producer<QString> about) {
return Box(QrBox, link, std::move(title), std::move(about), [=](
const QImage &image,
std::shared_ptr<Ui::Show> show) {
auto mime = std::make_unique<QMimeData>();
mime->setImageData(image);
QGuiApplication::clipboard()->setMimeData(mime.release());
show->showToast(tr::lng_group_invite_qr_copied(tr::now));
return Box([=, t = std::move(title), a = std::move(about)](
not_null<Ui::GenericBox*> box) {
Ui::FillPeerQrBox(box, peer, link, std::move(a));
box->setTitle(std::move(t));
});
}

View file

@ -50,6 +50,7 @@ void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link);
const QString &link,
const QString &copied = {});
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(
PeerData *peer,
const QString &link,
rpl::producer<QString> title,
rpl::producer<QString> about);

View file

@ -587,6 +587,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
}, &st::menuIconShare);
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@ -734,7 +735,7 @@ void LinksController::rowPaintIcon(
} else {
(color == Color::Revoked
? st::inviteLinkRevokedIcon
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
: st::inviteLinkIcon).paintInCenter(p, Rect(Size(inner)));
}
}
p.drawImage(x + skip, y + skip, icon);

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_permissions_box.h"
#include "lang/lang_keys.h"
#include "history/admin_log/history_admin_log_filter.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
@ -18,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
@ -55,6 +57,11 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
return {};
}
[[nodiscard]] auto Dependencies(AdminLog::FilterValue::Flags) {
using Flag = AdminLog::FilterValue::Flag;
return std::vector<std::pair<Flag, Flag>>{};
}
[[nodiscard]] auto NestedRestrictionLabelsList(
Data::RestrictionsSetOptions options)
-> std::vector<NestedEditFlagsLabels<ChatRestrictions>> {
@ -579,14 +586,6 @@ template <typename Flags>
ApplyDependencies(state->checkViews, dependencies, view);
};
if (descriptor.header) {
container->add(
object_ptr<Ui::FlatLabel>(
container,
std::move(descriptor.header),
st::rightsHeaderLabel),
st::rightsHeaderMargin);
}
const auto addCheckbox = [&](
not_null<Ui::VerticalLayout*> verticalLayout,
bool isInner,
@ -1142,9 +1141,11 @@ void ShowEditPeerPermissionsBox(
return result;
}();
Ui::AddSubsectionTitle(
inner,
tr::lng_rights_default_restrictions_header());
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
inner,
tr::lng_rights_default_restrictions_header(),
restrictions,
disabledMessages,
{ .isForum = peer->isForum() });
@ -1308,7 +1309,6 @@ std::vector<AdminRightLabel> AdminRightLabels(
EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
QWidget *parent,
rpl::producer<QString> header,
ChatRestrictions restrictions,
base::flat_map<ChatRestrictions, QString> disabledMessages,
Data::RestrictionsSetOptions options) {
@ -1317,7 +1317,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
widget.data(),
NegateRestrictions(restrictions),
{
.header = std::move(header),
.labels = NestedRestrictionLabelsList(options),
.disabledMessages = std::move(disabledMessages),
});
@ -1334,7 +1333,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
QWidget *parent,
rpl::producer<QString> header,
ChatAdminRights rights,
base::flat_map<ChatAdminRights, QString> disabledMessages,
Data::AdminRightsSetOptions options) {
@ -1343,7 +1341,6 @@ EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
widget.data(),
rights,
{
.header = std::move(header),
.labels = NestedAdminRightLabels(options),
.disabledMessages = std::move(disabledMessages),
});
@ -1434,3 +1431,18 @@ EditFlagsControl<PowerSaving::Flags> CreateEditPowerSaving(
return result;
}
EditFlagsControl<AdminLog::FilterValue::Flags> CreateEditAdminLogFilter(
QWidget *parent,
AdminLog::FilterValue::Flags flags,
bool isChannel) {
auto widget = object_ptr<Ui::VerticalLayout>(parent);
auto descriptor = AdminLog::FilterValueLabels(isChannel);
auto result = CreateEditFlags(
widget.data(),
flags,
std::move(descriptor));
result.widget = std::move(widget);
return result;
}

View file

@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_chat_participant_status.h"
#include "base/object_ptr.h"
#include "data/data_chat_participant_status.h"
#include "history/admin_log/history_admin_log_filter_value.h"
namespace style {
struct SettingsButton;
@ -72,7 +73,6 @@ struct NestedEditFlagsLabels {
template <typename Flags>
struct EditFlagsDescriptor {
rpl::producer<QString> header;
std::vector<NestedEditFlagsLabels<Flags>> labels;
base::flat_map<Flags, QString> disabledMessages;
const style::SettingsButton *st = nullptr;
@ -89,7 +89,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
[[nodiscard]] auto CreateEditRestrictions(
QWidget *parent,
rpl::producer<QString> header,
ChatRestrictions restrictions,
base::flat_map<ChatRestrictions, QString> disabledMessages,
Data::RestrictionsSetOptions options)
@ -97,7 +96,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
[[nodiscard]] auto CreateEditAdminRights(
QWidget *parent,
rpl::producer<QString> header,
ChatAdminRights rights,
base::flat_map<ChatAdminRights, QString> disabledMessages,
Data::AdminRightsSetOptions options)
@ -115,3 +113,9 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
PowerSaving::Flags flags,
rpl::producer<QString> forceDisabledMessage
) -> EditFlagsControl<PowerSaving::Flags>;
[[nodiscard]] auto CreateEditAdminLogFilter(
QWidget *parent,
AdminLog::FilterValue::Flags flags,
bool isChannel
) -> EditFlagsControl<AdminLog::FilterValue::Flags>;

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller_link_info.h"
#include "styles/style_chat_helpers.h"

View file

@ -241,8 +241,8 @@ RequestsBoxController::RowHelper::RowHelper(bool isGroup)
? tr::lng_group_requests_add(tr::now)
: tr::lng_group_requests_add_channel(tr::now))
, _rejectText(tr::lng_group_requests_dismiss(tr::now))
, _acceptTextWidth(st::requestsAcceptButton.font->width(_acceptText))
, _rejectTextWidth(st::requestsRejectButton.font->width(_rejectText)) {
, _acceptTextWidth(st::requestsAcceptButton.style.font->width(_acceptText))
, _rejectTextWidth(st::requestsRejectButton.style.font->width(_rejectText)) {
}
RequestsBoxController::RequestsBoxController(
@ -491,7 +491,7 @@ void RequestsBoxController::RowHelper::paintButton(
const auto textLeft = geometry.x()
+ ((geometry.width() - textWidth) / 2);
const auto textTop = geometry.y() + st.textTop;
p.setFont(st.font);
p.setFont(st.style.font);
p.setPen(over ? st.textFgOver : st.textFg);
p.drawTextLeft(textLeft, textTop, outerWidth, text);
}

View file

@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/fields/special_fields.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "styles/style_layers.h"

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/vertical_layout_reorder.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h" // contactsStatusFont.
#include "styles/style_info.h"
#include "styles/style_layers.h"

View file

@ -721,6 +721,7 @@ void PeerShortInfoBox::prepare() {
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
refreshRoundedTopImage(getDelegate()->style().bg->c);
setCustomCornersFilling(RectPart::FullTop);
setDimensionsToContent(st::shortInfoWidth, _rows);
}
@ -795,10 +796,6 @@ void PeerShortInfoBox::prepareRows() {
tr::lng_mediaview_copy(tr::now));
}
RectParts PeerShortInfoBox::customCornersFilling() {
return RectPart::FullTop;
}
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);

View file

@ -162,7 +162,6 @@ public:
private:
void prepare() override;
void prepareRows();
RectParts customCornersFilling() override;
void resizeEvent(QResizeEvent *e) override;

View file

@ -79,7 +79,8 @@ void ProcessUserpic(
if (!state->userpicView.cloud) {
GenerateImage(
state,
peer->generateUserpicImage(
PeerData::GenerateUserpicImage(
peer,
state->userpicView,
st::shortInfoWidth * style::DevicePixelRatio(),
0),

View file

@ -23,7 +23,7 @@ using Type = SelfDestructionBox::Type;
[[nodiscard]] std::vector<int> Values(Type type) {
switch (type) {
case Type::Account: return { 30, 90, 180, 365 };
case Type::Account: return { 30, 90, 180, 365, 548, 720 };
case Type::Sessions: return { 7, 30, 90, 180, 365 };
}
Unexpected("SelfDestructionBox::Type in Values.");
@ -113,8 +113,8 @@ void SelfDestructionBox::showContent() {
QString SelfDestructionBox::DaysLabel(int days) {
return !days
? QString()
: (days > 364)
? tr::lng_years(tr::now, lt_count, days / 365)
//: (days > 364)
//? tr::lng_years(tr::now, lt_count, days / 365)
: (days > 25)
? tr::lng_months(tr::now, lt_count, std::max(days / 30, 1))
: tr::lng_weeks(tr::now, lt_count, std::max(days / 7, 1));

View file

@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "lottie/lottie_single_player.h"
#include "data/data_channel.h"
#include "data/data_document.h"

View file

@ -0,0 +1,276 @@
/*
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 "boxes/send_gif_with_caption_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_peer_values.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "history/view/controls/history_view_characters_limit.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "media/clip/media_clip_reader.h"
#include "menu/menu_send.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/emoji_button_factory.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
namespace Ui {
namespace {
[[nodiscard]] not_null<Ui::RpWidget*> AddGifWidget(
not_null<Ui::VerticalLayout*> container,
not_null<DocumentData*> document,
int width) {
struct State final {
std::shared_ptr<Data::DocumentMedia> mediaView;
::Media::Clip::ReaderPointer gif;
rpl::lifetime loadingLifetime;
};
const auto state = container->lifetime().make_state<State>();
state->mediaView = document->createMediaView();
state->mediaView->automaticLoad(Data::FileOriginSavedGifs(), nullptr);
state->mediaView->thumbnailWanted(Data::FileOriginSavedGifs());
state->mediaView->videoThumbnailWanted(Data::FileOriginSavedGifs());
const auto widget = container->add(
Ui::CreateSkipWidget(
container,
document->dimensions.scaled(
width - rect::m::sum::h(st::boxRowPadding),
std::numeric_limits<int>::max(),
Qt::KeepAspectRatio).height()),
st::boxRowPadding);
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(widget);
if (state->gif && state->gif->started()) {
p.drawImage(
0,
0,
state->gif->current({ .frame = widget->size() }, crl::now()));
} else if (const auto thumb = state->mediaView->thumbnail()) {
p.drawImage(
widget->rect(),
thumb->pixNoCache(
widget->size() * style::DevicePixelRatio(),
{ .outer = widget->size() }).toImage());
} else if (const auto thumb = state->mediaView->thumbnailInline()) {
p.drawImage(
widget->rect(),
thumb->pixNoCache(
widget->size() * style::DevicePixelRatio(),
{
.options = Images::Option::Blur,
.outer = widget->size(),
}).toImage());
}
}, widget->lifetime());
const auto updateThumbnail = [=] {
if (document->dimensions.isEmpty()) {
return false;
}
if (!state->mediaView->loaded()) {
return false;
}
const auto callback = [=](::Media::Clip::Notification) {
if (state->gif && state->gif->ready() && !state->gif->started()) {
state->gif->start({ .frame = widget->size() });
}
widget->update();
};
state->gif = ::Media::Clip::MakeReader(
state->mediaView->owner()->location(),
state->mediaView->bytes(),
callback);
return true;
};
if (!updateThumbnail()) {
document->owner().session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
if (updateThumbnail()) {
state->loadingLifetime.destroy();
widget->update();
}
}, state->loadingLifetime);
}
return widget;
}
[[nodiscard]] not_null<Ui::InputField*> AddInputField(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller) {
using Limit = HistoryView::Controls::CharactersLimitLabel;
const auto bottomContainer = box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box));
const auto wrap = bottomContainer->add(
object_ptr<Ui::RpWidget>(box),
st::boxRowPadding);
const auto input = Ui::CreateChild<Ui::InputField>(
wrap,
st::defaultComposeFiles.caption,
Ui::InputField::Mode::MultiLine,
tr::lng_photo_caption());
Ui::ResizeFitChild(wrap, input);
struct State final {
base::unique_qptr<ChatHelpers::TabbedPanel> emojiPanel;
base::unique_qptr<Limit> charsLimitation;
};
const auto state = box->lifetime().make_state<State>();
{
const auto container = box->getDelegate()->outerContainer();
using Selector = ChatHelpers::TabbedSelector;
state->emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
container,
controller,
object_ptr<Selector>(
nullptr,
controller->uiShow(),
Window::GifPauseReason::Layer,
Selector::Mode::EmojiOnly));
const auto emojiPanel = state->emojiPanel.get();
emojiPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(controller->session().user());
emojiPanel->selector()->emojiChosen(
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
Ui::InsertEmojiAtCursor(input->textCursor(), data.emoji);
}, input->lifetime());
emojiPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
const auto info = data.document->sticker();
if (info
&& info->setType == Data::StickersType::Emoji
&& !controller->session().premium()) {
ShowPremiumPreviewBox(
controller,
PremiumFeature::AnimatedEmoji);
} else {
Data::InsertCustomEmoji(input, data.document);
}
}, input->lifetime());
}
const auto emojiButton = Ui::AddEmojiToggleToField(
input,
box,
controller,
state->emojiPanel.get(),
st::sendGifWithCaptionEmojiPosition);
emojiButton->show();
const auto session = &controller->session();
const auto checkCharsLimitation = [=](auto repeat) -> void {
const auto remove = Ui::ComputeFieldCharacterCount(input)
- Data::PremiumLimits(session).captionLengthCurrent();
if (remove > 0) {
if (!state->charsLimitation) {
state->charsLimitation = base::make_unique_q<Limit>(
input,
emojiButton,
style::al_top);
state->charsLimitation->show();
Data::AmPremiumValue(session) | rpl::start_with_next([=] {
repeat(repeat);
}, state->charsLimitation->lifetime());
}
state->charsLimitation->setLeft(remove);
state->charsLimitation->show();
} else {
state->charsLimitation = nullptr;
}
};
input->changes() | rpl::start_with_next([=] {
checkCharsLimitation(checkCharsLimitation);
}, input->lifetime());
return input;
}
} // namespace
void SendGifWithCaptionBox(
not_null<Ui::GenericBox*> box,
not_null<DocumentData*> document,
const SendMenu::Details &details,
Fn<void(Api::SendOptions, TextWithTags)> done) {
const auto window = Core::App().findWindow(box);
const auto controller = window ? window->sessionController() : nullptr;
if (!controller) {
return;
}
box->setTitle(tr::lng_send_gif_with_caption());
box->setWidth(st::boxWidth);
box->getDelegate()->setStyle(st::sendGifBox);
const auto container = box->verticalLayout();
[[maybe_unused]] const auto gifWidget = AddGifWidget(
container,
document,
st::boxWidth);
Ui::AddSkip(container);
const auto input = AddInputField(box, controller);
box->setFocusCallback([=] {
input->setFocus();
});
input->setSubmitSettings(Core::App().settings().sendSubmitWay());
InitMessageField(controller, input, [=](not_null<DocumentData*>) {
return true;
});
const auto send = [=](Api::SendOptions options) {
done(std::move(options), input->getTextWithTags());
};
const auto confirm = box->addButton(
tr::lng_send_button(),
[=] { send({}); });
SendMenu::SetupMenuAndShortcuts(
confirm,
controller->uiShow(),
[=] { return details; },
SendMenu::DefaultCallback(controller->uiShow(), send));
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
input->submits(
) | rpl::start_with_next([=] { send({}); }, input->lifetime());
}
} // namespace Ui

View file

@ -0,0 +1,30 @@
/*
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
class DocumentData;
namespace Api {
struct SendOptions;
} // namespace Api
namespace SendMenu {
struct Details;
} // namespace SendMenu
namespace Ui {
class GenericBox;
void SendGifWithCaptionBox(
not_null<Ui::GenericBox*> box,
not_null<DocumentData*> document,
const SendMenu::Details &details,
Fn<void(Api::SendOptions, TextWithTags)> done);
} // namespace Ui

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "chat_helpers/message_field.h"
#include "menu/menu_check_item.h"
#include "menu/menu_send.h"

View file

@ -1216,11 +1216,12 @@ StickersBox::Inner::Inner(
})
, _itemsTop(st::lineWidth)
, _addText(tr::lng_stickers_featured_add(tr::now))
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
, _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
, _undoText(tr::lng_stickers_return(tr::now))
, _undoWidth(st::stickersUndoRemove.font->width(_undoText))
, _undoWidth(st::stickersUndoRemove.style.font->width(_undoText))
, _installedText(tr::lng_stickers_featured_installed(tr::now))
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText)) {
, _installedWidth(st::stickersTrendingInstalled.style.font->width(
_installedText)) {
setup();
}
@ -1666,7 +1667,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
row->ripple.reset();
}
}
p.setFont(st.font);
p.setFont(st.style.font);
p.setPen(st.textFg);
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
} else {
@ -1700,7 +1701,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
row->ripple.reset();
}
}
p.setFont(st.font);
p.setFont(st.style.font);
p.setPen(selected ? st.textFgOver : st.textFg);
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
}

View file

@ -155,6 +155,8 @@ callMicrophoneMute: CallButton(callAnswer) {
bg: callIconBg;
outerBg: callMuteRipple;
label: callButtonLabel;
cornerButtonPosition: point(40px, 4px);
cornerButtonBorder: 2px;
}
callMicrophoneUnmute: CallButton(callMicrophoneMute) {
button: IconButton(callButton) {
@ -181,6 +183,34 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) {
}
}
}
callCornerButtonInner: IconButton {
width: 20px;
height: 20px;
iconPosition: point(-1px, -1px);
rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 20px;
ripple: defaultRippleAnimation;
}
callCornerButton: CallButton(callMicrophoneMute) {
button: IconButton(callCornerButtonInner) {
icon: icon {{ "calls/mini_calls_arrow", callIconFg }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: callMuteRipple;
}
}
bgSize: 20px;
bgPosition: point(0px, 0px);
}
callCornerButtonInactive: CallButton(callMicrophoneUnmute, callCornerButton) {
button: IconButton(callCornerButtonInner) {
icon: icon {{ "calls/mini_calls_arrow", callIconFgActive }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: callIconActiveRipple;
}
}
}
callScreencastOn: CallButton(callMicrophoneMute) {
button: IconButton(callButton) {
icon: icon {{ "calls/calls_present", callIconFg }};
@ -576,6 +606,18 @@ groupCallMenuAbout: FlatLabel(defaultFlatLabel) {
minWidth: 200px;
maxHeight: 92px;
}
callDeviceSelectionLabel: FlatLabel(defaultSubsectionTitle) {
textFg: groupCallActiveFg;
minWidth: 200px;
maxHeight: 20px;
}
callDeviceSelectionMenu: PopupMenu(groupCallPopupMenu) {
scrollPadding: margins(0px, 3px, 0px, 8px);
menu: Menu(groupCallMenu) {
widthMin: 240px;
itemPadding: margins(17px, 8px, 17px, 7px);
}
}
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
groupCallRecordingTimerFont: font(12px);

View file

@ -1310,6 +1310,19 @@ void Call::toggleScreenSharing(std::optional<QString> uniqueId) {
_videoOutgoing->setState(Webrtc::VideoState::Active);
}
auto Call::playbackDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId> {
return _playbackDeviceId.value();
}
rpl::producer<Webrtc::DeviceResolvedId> Call::captureDeviceIdValue() const {
return _captureDeviceId.value();
}
rpl::producer<Webrtc::DeviceResolvedId> Call::cameraDeviceIdValue() const {
return _cameraDeviceId.value();
}
void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
Expects(type != FinishType::None);

View file

@ -31,6 +31,7 @@ enum class AudioState;
namespace Webrtc {
enum class VideoState;
class VideoTrack;
struct DeviceResolvedId;
} // namespace Webrtc
namespace Calls {
@ -220,6 +221,13 @@ public:
void toggleCameraSharing(bool enabled);
void toggleScreenSharing(std::optional<QString> uniqueId);
[[nodiscard]] auto playbackDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId>;
[[nodiscard]] auto captureDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId>;
[[nodiscard]] auto cameraDeviceIdValue() const
-> rpl::producer<Webrtc::DeviceResolvedId>;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
#include "calls/group/calls_group_common.h"
#include "calls/ui/calls_device_menu.h"
#include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_signal_bars.h"
#include "calls/calls_userpic.h"
@ -24,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/call_button.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/rp_window.h"
#include "ui/layers/layer_manager.h"
@ -130,6 +132,7 @@ Panel::Panel(not_null<Call*> call)
initWidget();
initControls();
initLayout();
initMediaDeviceToggles();
showAndActivate();
}
@ -736,6 +739,58 @@ void Panel::initGeometry() {
updateControlsGeometry();
}
void Panel::initMediaDeviceToggles() {
_cameraDeviceToggle = _camera->addCornerButton(
st::callCornerButton,
&st::callCornerButtonInactive);
_audioDeviceToggle = _mute->entity()->addCornerButton(
st::callCornerButton,
&st::callCornerButtonInactive);
_cameraDeviceToggle->setClickedCallback([=] {
showDevicesMenu(_cameraDeviceToggle, {
{ Webrtc::DeviceType::Camera, _call->cameraDeviceIdValue() },
});
});
_audioDeviceToggle->setClickedCallback([=] {
showDevicesMenu(_audioDeviceToggle, {
{ Webrtc::DeviceType::Playback, _call->playbackDeviceIdValue() },
{ Webrtc::DeviceType::Capture, _call->captureDeviceIdValue() },
});
});
}
void Panel::showDevicesMenu(
not_null<QWidget*> button,
std::vector<DeviceSelection> types) {
if (!_call || _devicesMenu) {
return;
}
const auto chosen = [=](Webrtc::DeviceType type, QString id) {
switch (type) {
case Webrtc::DeviceType::Playback:
Core::App().settings().setCallPlaybackDeviceId(id);
break;
case Webrtc::DeviceType::Capture:
Core::App().settings().setCallCaptureDeviceId(id);
break;
case Webrtc::DeviceType::Camera:
Core::App().settings().setCameraDeviceId(id);
break;
}
Core::App().saveSettingsDelayed();
};
_devicesMenu = MakeDeviceSelectionMenu(
widget(),
&Core::App().mediaDevices(),
std::move(types),
chosen);
_devicesMenu->setForcedVerticalOrigin(
Ui::PopupMenu::VerticalOrigin::Bottom);
_devicesMenu->popup(button->mapToGlobal(QPoint())
- QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0));
}
void Panel::refreshOutgoingPreviewInBody(State state) {
const auto inBody = (state != State::Established)
&& (_call->videoOutgoing()->state() != Webrtc::VideoState::Inactive)

View file

@ -37,6 +37,7 @@ class FadeWrap;
template <typename Widget>
class PaddingWrap;
class RpWindow;
class PopupMenu;
namespace GL {
enum class Backend;
} // namespace GL
@ -55,6 +56,7 @@ namespace Calls {
class Userpic;
class SignalBars;
class VideoBubble;
struct DeviceSelection;
class Panel final : private Group::Ui::DesktopCapture::ChooseSourceDelegate {
public:
@ -104,6 +106,7 @@ private:
void initControls();
void reinitWithCall(Call *call);
void initLayout();
void initMediaDeviceToggles();
void initGeometry();
[[nodiscard]] bool handleClose() const;
@ -126,6 +129,10 @@ private:
void showRemoteLowBattery();
void refreshAnswerHangupRedialLabel();
void showDevicesMenu(
not_null<QWidget*> button,
std::vector<DeviceSelection> types);
[[nodiscard]] QRect incomingFrameGeometry() const;
[[nodiscard]] QRect outgoingFrameGeometry() const;
@ -156,8 +163,10 @@ private:
Ui::Animations::Simple _hangupShownProgress;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _screencast;
object_ptr<Ui::CallButton> _camera;
Ui::CallButton *_cameraDeviceToggle = nullptr;
base::unique_qptr<Ui::CallButton> _startVideo;
object_ptr<Ui::FadeWrap<Ui::CallButton>> _mute;
Ui::CallButton *_audioDeviceToggle = nullptr;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<Ui::RpWidget> _fingerprint = { nullptr };
@ -170,6 +179,8 @@ private:
int _bodyTop = 0;
int _buttonsTop = 0;
base::unique_qptr<Ui::PopupMenu> _devicesMenu;
base::Timer _updateDurationTimer;
base::Timer _updateOuterRippleTimer;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/rect_part.h"
#include "ui/rp_widget.h"
namespace Webrtc {

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_hardcoded.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
#include "ui/toast/toast.h"
#include "ui/ui_utility.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/core_settings.h"

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/image/image_prepare.h"
#include "ui/integration.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "info/profile/info_profile_values.h" // Info::Profile::Value.

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/view/media_view_pip.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/integration.h"
#include "ui/painter.h"
#include "ui/abstract_button.h"
#include "ui/gl/gl_surface.h"

View file

@ -460,7 +460,8 @@ void Viewport::RendererGL::validateUserpicFrame(
return;
}
const auto size = tile->trackOrUserpicSize();
tileData.userpicFrame = tile->row()->peer()->generateUserpicImage(
tileData.userpicFrame = PeerData::GenerateUserpicImage(
tile->row()->peer(),
tile->row()->ensureUserpicView(),
size.width(),
0);

View file

@ -77,7 +77,8 @@ void Viewport::RendererSW::validateUserpicFrame(
}
const auto size = tile->trackOrUserpicSize();
data.userpicFrame = Images::BlurLargeImage(
tile->row()->peer()->generateUserpicImage(
PeerData::GenerateUserpicImage(
tile->row()->peer(),
tile->row()->ensureUserpicView(),
size.width(),
0),

View file

@ -0,0 +1,250 @@
/*
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 "calls/ui/calls_device_menu.h"
#include "lang/lang_keys.h"
#include "ui/widgets/menu/menu_item_base.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "webrtc/webrtc_device_common.h"
#include "webrtc/webrtc_environment.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
namespace Calls {
namespace {
class Subsection final : public Ui::Menu::ItemBase {
public:
Subsection(
not_null<RpWidget*> parent,
const style::Menu &st,
const QString &text);
not_null<QAction*> action() const override;
bool isEnabled() const override;
private:
int contentHeight() const override;
const style::Menu &_st;
const base::unique_qptr<Ui::FlatLabel> _text;
const not_null<QAction*> _dummyAction;
};
class Selector final : public Ui::Menu::ItemBase {
public:
Selector(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<std::vector<Webrtc::DeviceInfo>> devices,
rpl::producer<Webrtc::DeviceResolvedId> chosen,
Fn<void(QString)> selected);
not_null<QAction*> action() const override;
bool isEnabled() const override;
private:
int contentHeight() const override;
[[nodiscard]] int registerId(const QString &id);
const base::unique_qptr<Ui::ScrollArea> _scroll;
const not_null<Ui::VerticalLayout*> _list;
const not_null<QAction*> _dummyAction;
base::flat_map<QString, int> _ids;
};
Subsection::Subsection(
not_null<RpWidget*> parent,
const style::Menu &st,
const QString &text)
: Ui::Menu::ItemBase(parent, st)
, _st(st)
, _text(base::make_unique_q<Ui::FlatLabel>(
this,
text,
st::callDeviceSelectionLabel))
, _dummyAction(new QAction(parent)) {
setPointerCursor(false);
initResizeHook(parent->sizeValue());
_text->resizeToWidth(st::callDeviceSelectionLabel.minWidth);
_text->moveToLeft(st.itemPadding.left(), st.itemPadding.top());
}
not_null<QAction*> Subsection::action() const {
return _dummyAction;
}
bool Subsection::isEnabled() const {
return false;
}
int Subsection::contentHeight() const {
return _st.itemPadding.top()
+ _text->height()
+ _st.itemPadding.bottom();
}
Selector::Selector(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<std::vector<Webrtc::DeviceInfo>> devices,
rpl::producer<Webrtc::DeviceResolvedId> chosen,
Fn<void(QString)> selected)
: Ui::Menu::ItemBase(parent, st)
, _scroll(base::make_unique_q<Ui::ScrollArea>(this))
, _list(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
, _dummyAction(new QAction(parent)) {
setPointerCursor(false);
initResizeHook(parent->sizeValue());
const auto padding = st.itemPadding;
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
std::move(
chosen
) | rpl::start_with_next([=](Webrtc::DeviceResolvedId id) {
const auto value = id.isDefault() ? 0 : registerId(id.value);
if (!group->hasValue() || group->current() != value) {
group->setValue(value);
}
}, lifetime());
group->setChangedCallback([=](int value) {
if (value == 0) {
selected({});
} else {
for (const auto &[id, index] : _ids) {
if (index == value) {
selected(id);
break;
}
}
}
});
std::move(
devices
) | rpl::start_with_next([=](const std::vector<Webrtc::DeviceInfo> &v) {
while (_list->count()) {
delete _list->widgetAt(0);
}
_list->add(
object_ptr<Ui::Radiobutton>(
_list.get(),
group,
0,
tr::lng_settings_call_device_default(tr::now),
st::groupCallCheckbox,
st::groupCallRadio),
padding);
for (const auto &device : v) {
if (device.inactive) {
continue;
}
_list->add(
object_ptr<Ui::Radiobutton>(
_list.get(),
group,
registerId(device.id),
device.name,
st::groupCallCheckbox,
st::groupCallRadio),
padding);
}
resize(width(), contentHeight());
}, lifetime());
}
not_null<QAction*> Selector::action() const {
return _dummyAction;
}
bool Selector::isEnabled() const {
return false;
}
int Selector::contentHeight() const {
_list->resizeToWidth(width());
if (_list->count() <= 3) {
_scroll->resize(width(), _list->height());
} else {
_scroll->resize(
width(),
3.5 * st::defaultRadio.diameter);
}
return _scroll->height();
}
int Selector::registerId(const QString &id) {
auto &result = _ids[id];
if (!result) {
result = int(_ids.size());
}
return result;
}
void AddDeviceSelection(
not_null<Ui::PopupMenu*> menu,
not_null<Webrtc::Environment*> environment,
DeviceSelection type,
Fn<void(QString)> selected) {
const auto title = [&] {
switch (type.type) {
case Webrtc::DeviceType::Camera:
return tr::lng_settings_call_camera(tr::now);
case Webrtc::DeviceType::Playback:
return tr::lng_settings_call_section_output(tr::now);
case Webrtc::DeviceType::Capture:
return tr::lng_settings_call_section_input(tr::now);
}
Unexpected("Type in AddDeviceSelection.");
}();
menu->addAction(
base::make_unique_q<Subsection>(menu, menu->st().menu, title));
menu->addAction(
base::make_unique_q<Selector>(
menu,
menu->st().menu,
environment->devicesValue(type.type),
std::move(type.chosen),
selected));
}
} // namespace
base::unique_qptr<Ui::PopupMenu> MakeDeviceSelectionMenu(
not_null<Ui::RpWidget*> parent,
not_null<Webrtc::Environment*> environment,
std::vector<DeviceSelection> types,
Fn<void(Webrtc::DeviceType, QString)> choose) {
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
st::callDeviceSelectionMenu);
const auto raw = result.get();
for (auto type : types) {
if (!raw->empty()) {
raw->addSeparator();
}
const auto selected = [=, type = type.type](QString id) {
choose(type, id);
};
AddDeviceSelection(raw, environment, std::move(type), selected);
}
return result;
}
} // namespace Calls

View file

@ -0,0 +1,36 @@
/*
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 "base/unique_qptr.h"
namespace Webrtc {
class Environment;
struct DeviceResolvedId;
enum class DeviceType : uchar;
} // namespace Webrtc
namespace Ui {
class RpWidget;
class PopupMenu;
} // namespace Ui
namespace Calls {
struct DeviceSelection {
Webrtc::DeviceType type;
rpl::producer<Webrtc::DeviceResolvedId> chosen;
};
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> MakeDeviceSelectionMenu(
not_null<Ui::RpWidget*> parent,
not_null<Webrtc::Environment*> environment,
std::vector<DeviceSelection> types,
Fn<void(Webrtc::DeviceType, QString)> choose);
} // namespace Calls

View file

@ -7,18 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/bot_keyboard.h"
#include "api/api_bot.h"
#include "core/click_handler_types.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "ui/cached_round_corners.h"
#include "ui/painter.h"
#include "api/api_bot.h"
#include "styles/style_widgets.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_widgets.h"
namespace {

View file

@ -296,7 +296,9 @@ emojiPanButton: RoundButton(defaultActiveButton) {
textTop: 2px;
}
emojiPanExpand: RoundButton(defaultActiveButton) {
font: font(12px bold);
style: TextStyle(semiboldTextStyle) {
font: font(12px bold);
}
width: -8px;
height: 19px;
textTop: 1px;
@ -1499,5 +1501,11 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
height: 44px;
textTop: 11px;
width: -96px;
font: font(15px semibold);
style: TextStyle(semiboldTextStyle) {
font: font(15px semibold);
}
}
sendGifBox: Box(defaultBox) {
shadowIgnoreBottomSkip: true;
}

View file

@ -1450,17 +1450,17 @@ void EmojiListWidget::drawCollapsedBadge(
int count) {
const auto &st = st::emojiPanExpand;
const auto text = u"+%1"_q.arg(count - _columnCount * kCollapsedRows + 1);
const auto textWidth = st.font->width(text);
const auto textWidth = st.style.font->width(text);
const auto buttonw = std::max(textWidth - st.width, st.height);
const auto buttonh = st.height;
const auto buttonx = position.x() + (_singleSize.width() - buttonw) / 2;
const auto buttony = position.y() + (_singleSize.height() - buttonh) / 2;
_collapsedBg.paint(p, QRect(buttonx, buttony, buttonw, buttonh));
p.setPen(this->st().bg);
p.setFont(st.font);
p.setFont(st.style.font);
p.drawText(
buttonx + (buttonw - textWidth) / 2,
(buttony + st.textTop + st.font->ascent),
(buttony + st.textTop + st.style.font->ascent),
text);
}
@ -2546,12 +2546,12 @@ int EmojiListWidget::paintButtonGetWidth(
: selected
? st::emojiPanButton.textFgOver
: st::emojiPanButton.textFg);
p.setFont(st::emojiPanButton.font);
p.setFont(st::emojiPanButton.style.font);
p.drawText(
rect.x() - (st::emojiPanButton.width / 2),
(rect.y()
+ st::emojiPanButton.textTop
+ st::emojiPanButton.font->ascent),
+ st::emojiPanButton.style.font->ascent),
button.text);
return emojiRight() - rect.x();
}
@ -2678,7 +2678,7 @@ void EmojiListWidget::initButton(
const QString &text,
bool gradient) {
button.text = text;
button.textWidth = st::emojiPanButton.font->width(text);
button.textWidth = st::emojiPanButton.style.font->width(text);
const auto width = button.textWidth - st::emojiPanButton.width;
const auto height = st::emojiPanButton.height;
const auto factor = style::DevicePixelRatio();

View file

@ -46,6 +46,7 @@ enum class Section;
} // namespace Ui::Emoji
namespace Ui::Text {
class CustomEmoji;
struct CustomEmojiPaintContext;
} // namespace Ui::Text

View file

@ -24,12 +24,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_config.h"
#include "core/click_handler_types.h"
#include "ui/controls/tabbed_search.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/popup_menu.h"
#include "ui/effects/ripple_animation.h"
#include "ui/image/image.h"
#include "ui/painter.h"
#include "boxes/send_gif_with_caption_box.h"
#include "boxes/stickers_box.h"
#include "inline_bots/inline_bot_result.h"
#include "storage/localstorage.h"
@ -414,6 +416,22 @@ base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
SendMenu::DefaultCallback(_show, send),
icons);
if (!isInlineResult) {
auto done = crl::guard(this, [=](
Api::SendOptions options,
TextWithTags text) {
selectInlineResult(selected, options, true, std::move(text));
});
const auto show = _show;
menu->addAction(tr::lng_send_gif_with_caption(tr::now), [=] {
show->show(Box(
Ui::SendGifWithCaptionBox,
item->getDocument(),
copyDetails,
std::move(done)));
}, &st::menuIconEdit);
}
if (const auto item = _mosaic.maybeItemAt(_selected)) {
const auto document = item->getDocument()
? item->getDocument() // Saved GIF.
@ -464,7 +482,8 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
void GifsListWidget::selectInlineResult(
int index,
Api::SendOptions options,
bool forceSend) {
bool forceSend,
TextWithTags caption) {
const auto item = _mosaic.maybeItemAt(index);
if (!item) {
return;
@ -527,6 +546,7 @@ void GifsListWidget::selectInlineResult(
}));
} else {
sendGIFCallback();
.caption = std::move(caption),
}
} else if (!preview.usingThumbnail()) {
if (preview.loading()) {

View file

@ -172,7 +172,8 @@ private:
void selectInlineResult(
int index,
Api::SendOptions options,
bool forceSend = false);
bool forceSend = false,
TextWithTags caption = {});
const std::shared_ptr<Show> _show;
std::unique_ptr<Ui::TabbedSearch> _search;

View file

@ -1063,10 +1063,26 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
st::historySendDisabled);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->setPointerCursor(false);
const auto &st = st::historyComposeField;
const auto metrics = QFontMetricsF(st.style.font->f);
const auto realAscent = int(base::SafeRound(metrics.ascent()));
const auto ascentAdd = st.style.font->ascent - realAscent;
const auto customFontMarginTop = ascentAdd;
const auto leading = qMax(metrics.leading(), qreal(0.0));
const auto adjustment = (metrics.ascent() + leading)
- ((st.style.font->height * 4) / 5);
const auto placeholderCustomFontSkip = int(base::SafeRound(-adjustment));
const auto margins = st.textMargins
+ st.placeholderMargins
+ QMargins(0, style::ConvertScale(4)
+ placeholderCustomFontSkip
+ customFontMarginTop, 0, 0);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto &st = st::historyComposeField;
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
const auto skip = st::historySendDisabledIconSkip;
label->resizeToWidth(available - skip);
@ -1075,8 +1091,6 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
raw->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(raw);
const auto &st = st::historyComposeField;
const auto margins = (st.textMargins + st.placeholderMargins);
const auto &icon = st::historySendDisabledIcon;
icon.paint(
p,

View file

@ -20,6 +20,10 @@ class InputField;
class CrossButton;
} // namespace Ui
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Data {
class StickersSet;
class StickersSetThumbnailView;

View file

@ -220,11 +220,14 @@ StickersListWidget::StickersListWidget(
st().pathBg,
st().pathFg,
[=] { update(); }))
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st().headerLeft)
, _megagroupSetAbout(st::columnMinimalWidthThird
- st::emojiScroll.width
- st().headerLeft)
, _addText(tr::lng_stickers_featured_add(tr::now))
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
, _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
, _installedText(tr::lng_stickers_featured_installed(tr::now))
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
, _installedWidth(
st::stickersTrendingInstalled.style.font->width(_installedText))
, _settings(this, tr::lng_stickers_you_have(tr::now))
, _previewTimer([=] { showPreview(); })
, _premiumMark(std::make_unique<StickerPremiumMark>(
@ -981,7 +984,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
const auto &st = installedSet
? st::stickersTrendingInstalled
: st::stickersTrendingAdd;
p.setFont(st.font);
p.setFont(st.style.font);
p.setPen(selected ? st.textFgOver : st.textFg);
p.drawTextLeft(
add.x() - (st.width / 2),
@ -1245,7 +1248,7 @@ void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSe
_megagroupSetButtonRipple.reset();
}
}
p.setFont(st::stickerGroupCategoryAdd.font);
p.setFont(st::stickerGroupCategoryAdd.style.font);
p.setPen(buttonSelected ? st::stickerGroupCategoryAdd.textFgOver : st::stickerGroupCategoryAdd.textFg);
p.drawTextLeft(button.x() - (st::stickerGroupCategoryAdd.width / 2), button.y() + st::stickerGroupCategoryAdd.textTop, width(), _megagroupSetButtonText, _megagroupSetButtonTextWidth);
}
@ -2779,7 +2782,7 @@ void StickersListWidget::refreshMegagroupSetGeometry() {
auto left = megagroupSetInfoLeft();
auto availableWidth = (width() - left);
auto top = _megagroupSetAbout.countHeight(availableWidth) + st::stickerGroupCategoryAddMargin.top();
_megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.font->width(_megagroupSetButtonText);
_megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.style.font->width(_megagroupSetButtonText);
auto buttonWidth = _megagroupSetButtonTextWidth - st::stickerGroupCategoryAdd.width;
_megagroupSetButtonRect = QRect(left, top, buttonWidth, st::stickerGroupCategoryAdd.height);
}

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_section.h"
#include "chat_helpers/tabbed_selector.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_chat_helpers.h"

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image_prepare.h"
#include "ui/cached_round_corners.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"

View file

@ -62,6 +62,7 @@ struct FileChosen {
not_null<DocumentData*> document;
Api::SendOptions options;
Ui::MessageSendingAnimationFrom messageSendingFrom;
TextWithTags caption;
};
struct PhotoChosen {

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/tooltip.h"
#include "ui/ui_utility.h"
#include "window/section_widget.h" // Window::ChatThemeValueFromPeer.
#include "window/themes/window_theme.h"
#include "window/window_controller.h"

Some files were not shown because too many files have changed in this diff Show more