Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f05191e668 | ||
|
|
1ba189e59d | ||
|
|
c40ca70aa6 | ||
|
|
521f991167 | ||
|
|
edf1417bbb | ||
|
|
686e9643ad | ||
|
|
975460d268 | ||
|
|
6b0c606d25 | ||
|
|
93ff0bdcff | ||
|
|
bf9d90ca4e | ||
|
|
d6e5e1e8f7 | ||
|
|
d3ae2ef9ea | ||
|
|
40b0854704 | ||
|
|
9aec6b6496 | ||
|
|
d01f977960 | ||
|
|
1444009ee2 | ||
|
|
14ee9bee26 | ||
|
|
93587ddc3c | ||
|
|
0c4d03477e | ||
|
|
a35092f012 | ||
|
|
49ee7ee52b | ||
|
|
52c77a1970 | ||
|
|
e4269ae7fb | ||
|
|
bd6011c524 | ||
|
|
46cb3ec103 | ||
|
|
0241129948 | ||
|
|
e44aca06cb | ||
|
|
19d386f977 | ||
|
|
a67fdda913 | ||
|
|
5286c7b1c3 | ||
|
|
d9d96d0a6f | ||
|
|
13353bb615 | ||
|
|
d78348fd16 | ||
|
|
1e8e660133 | ||
|
|
5196982c98 | ||
|
|
99f857fbf6 | ||
|
|
0982aa166a | ||
|
|
3ae9f86097 | ||
|
|
9efd9b0d68 | ||
|
|
267a73f355 | ||
|
|
437d8ea890 | ||
|
|
7ed92ec402 | ||
|
|
678d9ffbf9 | ||
|
|
bc4b427ed1 | ||
|
|
36141a9df9 | ||
|
|
d68ba75457 | ||
|
|
00ad32c5f4 | ||
|
|
064bab60ff | ||
|
|
5b146217c0 | ||
|
|
202c81b2e5 | ||
|
|
4520480604 | ||
|
|
aea87bb5cb | ||
|
|
19492f7e7b | ||
|
|
51030e3c45 | ||
|
|
e6b8b4be18 | ||
|
|
6ac13b7f80 | ||
|
|
7e5e6003a9 | ||
|
|
f445440995 | ||
|
|
d81547f091 | ||
|
|
2b185d491b | ||
|
|
ca47440950 | ||
|
|
5e32602f4a | ||
|
|
1f4516028c | ||
|
|
7f29d269a3 | ||
|
|
62c3374911 | ||
|
|
2116e04af5 | ||
|
|
a97d5e80c7 | ||
|
|
b5098038d0 | ||
|
|
6fce718252 | ||
|
|
40cf96202d | ||
|
|
c18e59218e | ||
|
|
216865a20d | ||
|
|
6c5036ee8d | ||
|
|
dc2f59ca24 | ||
|
|
6f6457137e | ||
|
|
bc5cb6e2a2 | ||
|
|
16825fff41 | ||
|
|
5024f1db8c | ||
|
|
a60385fc3d | ||
|
|
b20e2c37c1 | ||
|
|
acd40cbeb6 | ||
|
|
6a45a862dd | ||
|
|
5bd45e9a20 | ||
|
|
52e42f23ab | ||
|
|
96b7755cde | ||
|
|
c589ee1ca5 | ||
|
|
18c9ee093b | ||
|
|
b82fa3112c | ||
|
|
8d0f66d562 | ||
|
|
20a5e0ba73 | ||
|
|
5a2667c71e | ||
|
|
1f4a8d7eb6 | ||
|
|
4430bd0328 | ||
|
|
ab2e7f4c03 | ||
|
|
54e5c06b4d | ||
|
|
add5a6a0be | ||
|
|
89c2ba4293 | ||
|
|
dd100fb709 | ||
|
|
e8dd2b9e7b | ||
|
|
71e3cd227c | ||
|
|
1648c31a22 | ||
|
|
f8c820f319 | ||
|
|
300f35e78f | ||
|
|
2aa5849997 | ||
|
|
fb1b845211 | ||
|
|
3d2af9db8e | ||
|
|
6129e5a1cf | ||
|
|
7f70ee1227 | ||
|
|
c9f7da6e82 | ||
|
|
f956c0f227 | ||
|
|
df935e0477 | ||
|
|
841f1afe1e | ||
|
|
fb8b88557e | ||
|
|
2b502b22b9 | ||
|
|
5ac80d2655 | ||
|
|
8aa7499e63 | ||
|
|
8cd5e51982 | ||
|
|
e5c2133446 | ||
|
|
3dccdf2f05 | ||
|
|
8a708c6655 | ||
|
|
9e1d9eee4b | ||
|
|
f30aabc365 | ||
|
|
a5546d016f | ||
|
|
f79d70d112 | ||
|
|
ec28f258fb | ||
|
|
08f3a6fb40 | ||
|
|
8823d5256f | ||
|
|
60e7aa90d2 | ||
|
|
71357a9546 | ||
|
|
a5b06e9c56 | ||
|
|
0f94419f6d | ||
|
|
94e7aabea5 | ||
|
|
f6c816cafe | ||
|
|
4cf160e8dc | ||
|
|
9252be5e8c | ||
|
|
ed342eea64 | ||
|
|
e6405bc455 | ||
|
|
fed09461ce | ||
|
|
6e1ddef4fe | ||
|
|
76be4a3eb9 | ||
|
|
997a9e2fe3 | ||
|
|
03790f3da0 | ||
|
|
e46a703c7d | ||
|
|
ae17acdfd4 | ||
|
|
2422c9ce9e | ||
|
|
7f75d7082a | ||
|
|
4749ab175e | ||
|
|
e651699e1d | ||
|
|
3f4157bab2 | ||
|
|
a98f559066 | ||
|
|
ffd54c452c | ||
|
|
61424eeab9 | ||
|
|
c131d6637d | ||
|
|
24b0b33f1d | ||
|
|
675198d361 | ||
|
|
ed667a42ad | ||
|
|
920484d540 | ||
|
|
07c5e6542b | ||
|
|
6a43f2e508 | ||
|
|
ad3e447f08 | ||
|
|
1840da1d68 | ||
|
|
c82b86cea3 | ||
|
|
0fb383c466 | ||
|
|
69fc2f48bf | ||
|
|
7c604fc86a | ||
|
|
ec7fbb8952 | ||
|
|
679350e23d | ||
|
|
f0209c9d6e | ||
|
|
5020aec6ec | ||
|
|
13737577e7 | ||
|
|
4ee7d46d78 | ||
|
|
d143e32022 | ||
|
|
0a0dab74a1 | ||
|
|
c6ea91e671 | ||
|
|
8011adb219 | ||
|
|
6ba5d5f16b | ||
|
|
d5774830d8 | ||
|
|
eb268102fc | ||
|
|
b364c4f23a | ||
|
|
91b9266a91 | ||
|
|
1e6236a987 | ||
|
|
10ebd7e6ef | ||
|
|
6b83c52c7c | ||
|
|
46304c7a2d | ||
|
|
c3fda41224 | ||
|
|
a4017e930e | ||
|
|
95bdb925d5 | ||
|
|
a26bae70c7 | ||
|
|
a38dcb6ee5 | ||
|
|
eb27c12117 | ||
|
|
c03fcf9a23 | ||
|
|
71f83b5993 | ||
|
|
fd2d12d6b1 | ||
|
|
50f9f36c3d | ||
|
|
f650c679e0 | ||
|
|
8349cb0dd4 | ||
|
|
2c1788a63a | ||
|
|
eec59611ef | ||
|
|
bfbdf1b935 | ||
|
|
f2ea0edc95 | ||
|
|
754b3a5ae8 | ||
|
|
62a20ba975 | ||
|
|
f3dca6efb7 | ||
|
|
dc1df14a71 | ||
|
|
73c018667d | ||
|
|
3905fc7c38 | ||
|
|
0c3cabf4ac | ||
|
|
838d5669ed | ||
|
|
3a7a485dd0 | ||
|
|
a4ac00acbd | ||
|
|
7d52c13625 | ||
|
|
520de600a0 | ||
|
|
4f8e914d53 |
@@ -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}
|
||||
@@ -310,6 +314,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
|
||||
@@ -1484,6 +1490,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
|
||||
@@ -1509,6 +1517,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
|
||||
|
||||
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 426 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/calls/mini_calls_arrow.png
Normal file
|
After Width: | Height: | Size: 240 B |
BIN
Telegram/Resources/icons/calls/mini_calls_arrow@2x.png
Normal file
|
After Width: | Height: | Size: 324 B |
BIN
Telegram/Resources/icons/calls/mini_calls_arrow@3x.png
Normal file
|
After Width: | Height: | Size: 422 B |
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.4.0.0" />
|
||||
Version="5.5.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
@@ -37,8 +37,8 @@
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
<uap3:Protocol Name="tg" Parameters="-- "%1"" />
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
</uap3:Extension>
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
<uap3:Protocol Name="tonsite" Parameters="-- "%1"" />
|
||||
</uap3:Extension>
|
||||
<desktop:Extension
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,0,0
|
||||
PRODUCTVERSION 5,4,0,0
|
||||
FILEVERSION 5,5,2,0
|
||||
PRODUCTVERSION 5,5,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.4.0.0"
|
||||
VALUE "FileVersion", "5.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.0.0"
|
||||
VALUE "ProductVersion", "5.5.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,0,0
|
||||
PRODUCTVERSION 5,4,0,0
|
||||
FILEVERSION 5,5,2,0
|
||||
PRODUCTVERSION 5,5,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.4.0.0"
|
||||
VALUE "FileVersion", "5.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.0.0"
|
||||
VALUE "ProductVersion", "5.5.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
@@ -165,25 +168,15 @@ void SendExistingMedia(
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
InnerFillMessagePostFlags(action.options, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto messageFromId = sendAs
|
||||
? sendAs->id
|
||||
: anonymousPost
|
||||
? 0
|
||||
: session->userPeerId();
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
auto caption = TextWithEntities{
|
||||
message.textWithTags.text,
|
||||
TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags)
|
||||
@@ -219,11 +212,11 @@ void SendExistingMedia(
|
||||
history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, media, caption);
|
||||
|
||||
@@ -361,25 +354,15 @@ bool SendDice(MessageToSend &message) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
InnerFillMessagePostFlags(action.options, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto messageFromId = sendAs
|
||||
? sendAs->id
|
||||
: anonymousPost
|
||||
? 0
|
||||
: session->userPeerId();
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
@@ -401,11 +384,11 @@ bool SendDice(MessageToSend &message) {
|
||||
history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaDice(
|
||||
MTP_int(0),
|
||||
@@ -529,7 +512,6 @@ void SendConfirmedFile(
|
||||
if (file->to.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (file->to.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
@@ -551,16 +533,6 @@ void SendConfirmedFile(
|
||||
if (file->to.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
}
|
||||
|
||||
const auto messageFromId = file->to.options.sendAs
|
||||
? file->to.options.sendAs->id
|
||||
: anonymousPost
|
||||
? PeerId()
|
||||
: session->userPeerId();
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
const auto media = MTPMessageMedia([&] {
|
||||
if (file->type == SendMediaType::Photo) {
|
||||
using Flag = MTPDmessageMediaPhoto::Flag;
|
||||
@@ -626,11 +598,11 @@ void SendConfirmedFile(
|
||||
history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = file->to.replyTo,
|
||||
.date = HistoryItem::NewMessageDate(file->to.options),
|
||||
.date = NewMessageDate(file->to.options),
|
||||
.shortcutId = file->to.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.groupedId = groupId,
|
||||
.effectId = file->to.options.effectId,
|
||||
}, caption, media);
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"
|
||||
@@ -2622,6 +2623,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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3283,7 +3283,6 @@ void ApiWrap::forwardMessages(
|
||||
histories.readInbox(history);
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
|
||||
using SendFlag = MTPmessages_ForwardMessages::Flag;
|
||||
@@ -3375,23 +3374,14 @@ void ApiWrap::forwardMessages(
|
||||
const auto newId = FullMsgId(
|
||||
peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto self = _session->user();
|
||||
const auto messageFromId = sendAs
|
||||
? sendAs->id
|
||||
: anonymousPost
|
||||
? PeerId(0)
|
||||
: self->id;
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? self->name()
|
||||
: QString();
|
||||
history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = { .topicRootId = topMsgId },
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
|
||||
// forwarded messages don't have effects
|
||||
//.effectId = action.options.effectId,
|
||||
@@ -3466,8 +3456,6 @@ void ApiWrap::sendSharedContact(
|
||||
const auto newId = FullMsgId(
|
||||
peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
if (action.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
@@ -3479,22 +3467,14 @@ void ApiWrap::sendSharedContact(
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
}
|
||||
const auto messageFromId = action.options.sendAs
|
||||
? action.options.sendAs->id
|
||||
: anonymousPost
|
||||
? PeerId()
|
||||
: _session->userPeerId();
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? _session->user()->name()
|
||||
: QString();
|
||||
const auto item = history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaContact(
|
||||
MTP_string(phone),
|
||||
@@ -3780,7 +3760,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
MTP_string(fields.url),
|
||||
MTP_int(page->pendingTill)));
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if ((exactWebPage && !ignoreWebPage && message.webPage.invert)
|
||||
@@ -3808,18 +3787,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
history->startSavingCloudDraft(draftTopicRootId);
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto messageFromId = sendAs
|
||||
? sendAs->id
|
||||
: anonymousPost
|
||||
? PeerId()
|
||||
: _session->userPeerId();
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? _session->user()->name()
|
||||
: QString();
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
|
||||
@@ -3837,11 +3808,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
lastMessage = history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, sending, media);
|
||||
const auto done = [=](
|
||||
@@ -3987,7 +3958,6 @@ void ApiWrap::sendInlineResult(
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= SendFlag::f_reply_to;
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (silentPost) {
|
||||
@@ -4006,30 +3976,22 @@ void ApiWrap::sendInlineResult(
|
||||
}
|
||||
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto messageFromId = sendAs
|
||||
? sendAs->id
|
||||
: anonymousPost ? PeerId()
|
||||
: _session->userPeerId();
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? _session->user()->name()
|
||||
: QString();
|
||||
|
||||
_session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
data->addToHistory(history, {
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = messageFromId,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.viaBotId = ((bot && !action.options.hideViaBot)
|
||||
? peerToUser(bot->id)
|
||||
: UserId()),
|
||||
.postAuthor = messagePostAuthor,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
});
|
||||
|
||||
history->clearCloudDraft(topicRootId);
|
||||
@@ -4198,8 +4160,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),
|
||||
@@ -4270,8 +4234,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(),
|
||||
|
||||
@@ -217,7 +217,9 @@ void ShowAddParticipantsError(
|
||||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
QString());
|
||||
QString(),
|
||||
0,
|
||||
nullptr);
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -965,9 +966,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 +1114,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<>>(
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1577,9 +1577,11 @@ object_ptr<Ui::BoxContent> EditLinkBox(
|
||||
const auto isGroup = !peer->isBroadcast();
|
||||
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
|
||||
auto object = Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto fill = [=] {
|
||||
return Ui::FillCreateInviteLinkSubscriptionToggle(box, peer);
|
||||
};
|
||||
const auto fill = isGroup
|
||||
? Fn<Ui::InviteLinkSubscriptionToggle()>(nullptr)
|
||||
: [=] {
|
||||
return Ui::FillCreateInviteLinkSubscriptionToggle(box, peer);
|
||||
};
|
||||
if (creating) {
|
||||
Ui::CreateInviteLinkBox(box, fill, isGroup, isPublic, done);
|
||||
} else {
|
||||
|
||||
@@ -734,7 +734,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);
|
||||
|
||||
@@ -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>> {
|
||||
@@ -577,14 +584,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,
|
||||
@@ -1140,9 +1139,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() });
|
||||
@@ -1306,7 +1307,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) {
|
||||
@@ -1315,7 +1315,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
widget.data(),
|
||||
NegateRestrictions(restrictions),
|
||||
{
|
||||
.header = std::move(header),
|
||||
.labels = NestedRestrictionLabelsList(options),
|
||||
.disabledMessages = std::move(disabledMessages),
|
||||
});
|
||||
@@ -1332,7 +1331,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
|
||||
EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatAdminRights rights,
|
||||
base::flat_map<ChatAdminRights, QString> disabledMessages,
|
||||
Data::AdminRightsSetOptions options) {
|
||||
@@ -1341,7 +1339,6 @@ EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
||||
widget.data(),
|
||||
rights,
|
||||
{
|
||||
.header = std::move(header),
|
||||
.labels = NestedAdminRightLabels(options),
|
||||
.disabledMessages = std::move(disabledMessages),
|
||||
});
|
||||
@@ -1432,3 +1429,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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ public:
|
||||
private:
|
||||
void prepare() override;
|
||||
void prepareRows();
|
||||
RectParts customCornersFilling() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ void ProcessUserpic(
|
||||
if (!state->userpicView.cloud) {
|
||||
GenerateImage(
|
||||
state,
|
||||
peer->generateUserpicImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
peer,
|
||||
state->userpicView,
|
||||
st::shortInfoWidth * style::DevicePixelRatio(),
|
||||
0),
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "ui/effects/scroll_content_shadow.h"
|
||||
#include "ui/widgets/fields/number_input.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
@@ -46,6 +47,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"
|
||||
@@ -70,7 +72,7 @@ constexpr auto kMaxMessageLength = 4096;
|
||||
using Ui::SendFilesWay;
|
||||
|
||||
[[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
|
||||
return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
|
||||
return !urls.isEmpty() && ranges::all_of(urls, Core::UrlIsLocal);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {
|
||||
|
||||
276
Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp
Normal 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
|
||||
30
Telegram/SourceFiles/boxes/send_gif_with_caption_box.h
Normal 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
250
Telegram/SourceFiles/calls/ui/calls_device_menu.cpp
Normal 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
|
||||
36
Telegram/SourceFiles/calls/ui/calls_device_menu.h
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -46,6 +46,7 @@ enum class Section;
|
||||
} // namespace Ui::Emoji
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
struct CustomEmojiPaintContext;
|
||||
} // namespace Ui::Text
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -407,6 +409,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.
|
||||
@@ -457,7 +475,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;
|
||||
@@ -498,6 +517,7 @@ void GifsListWidget::selectInlineResult(
|
||||
.document = document,
|
||||
.options = options,
|
||||
.messageSendingFrom = messageSendingFrom(),
|
||||
.caption = std::move(caption),
|
||||
});
|
||||
} else if (!preview.usingThumbnail()) {
|
||||
if (preview.loading()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -20,6 +20,10 @@ class InputField;
|
||||
class CrossButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Data {
|
||||
class StickersSet;
|
||||
class StickersSetThumbnailView;
|
||||
|
||||
@@ -213,11 +213,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>(
|
||||
@@ -974,7 +977,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),
|
||||
@@ -1238,7 +1241,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);
|
||||
}
|
||||
@@ -2734,7 +2737,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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -62,6 +62,7 @@ struct FileChosen {
|
||||
not_null<DocumentData*> document;
|
||||
Api::SendOptions options;
|
||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||
TextWithTags caption;
|
||||
};
|
||||
|
||||
struct PhotoChosen {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/click_handler_types.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "chat_helpers/bot_command.h"
|
||||
#include "core/application.h"
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "mainwidget.h"
|
||||
|
||||
@@ -258,7 +258,7 @@ QByteArray Settings::serialize() const {
|
||||
}
|
||||
stream
|
||||
<< qint32(_sendFilesWay.serialize())
|
||||
<< qint32(_sendSubmitWay)
|
||||
<< qint32(_sendSubmitWay.current())
|
||||
<< qint32(_includeMutedCounter ? 1 : 0)
|
||||
<< qint32(_countUnreadMessages ? 1 : 0)
|
||||
<< qint32(1) // legacy exe launch warning
|
||||
@@ -421,7 +421,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 soundOverridesCount = 0;
|
||||
base::flat_map<QString, QString> soundOverrides;
|
||||
qint32 sendFilesWay = _sendFilesWay.serialize();
|
||||
qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay);
|
||||
qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay.current());
|
||||
qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0;
|
||||
qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0;
|
||||
std::optional<QString> noWarningExtensions;
|
||||
|
||||
@@ -388,7 +388,11 @@ public:
|
||||
_sendSubmitWay = value;
|
||||
}
|
||||
[[nodiscard]] Ui::InputSubmitSettings sendSubmitWay() const {
|
||||
return _sendSubmitWay;
|
||||
return _sendSubmitWay.current();
|
||||
}
|
||||
[[nodiscard]] auto sendSubmitWayValue() const
|
||||
-> rpl::producer<Ui::InputSubmitSettings> {
|
||||
return _sendSubmitWay.value();
|
||||
}
|
||||
void setSoundOverride(const QString &key, const QString &path) {
|
||||
_soundOverrides.emplace(key, path);
|
||||
@@ -966,7 +970,8 @@ private:
|
||||
Window::Theme::AccentColors _themesAccentColors;
|
||||
bool _lastSeenWarningSeen = false;
|
||||
Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay();
|
||||
Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings();
|
||||
rpl::variable<Ui::InputSubmitSettings> _sendSubmitWay
|
||||
= Ui::InputSubmitSettings();
|
||||
base::flat_map<QString, QString> _soundOverrides;
|
||||
base::flat_set<QString> _noWarningExtensions;
|
||||
bool _ipRevealWarning = true;
|
||||
|
||||
@@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
bool UrlIsLocal(const QUrl &url);
|
||||
} // namespace Core
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -45,7 +49,7 @@ void ShowInFolder(const QString &filepath);
|
||||
namespace internal {
|
||||
|
||||
inline QString UrlToLocalDefault(const QUrl &url) {
|
||||
return url.toLocalFile();
|
||||
return Core::UrlIsLocal(url) ? url.toLocalFile() : QString();
|
||||
}
|
||||
|
||||
void UnsafeOpenUrlDefault(const QString &url);
|
||||
|
||||
@@ -19,9 +19,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/update_checker.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "dialogs/ui/dialogs_suggestions.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/edit_birthday_box.h"
|
||||
#include "ui/integration.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/connection_box.h"
|
||||
@@ -922,6 +924,17 @@ bool ShowCollectibleUsername(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowStarsExamples(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Dialogs::StarsExamplesBox(controller));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
@@ -1379,6 +1392,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
ShowCollectibleUsername,
|
||||
},
|
||||
{
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -226,13 +226,24 @@ bool CanSendFiles(not_null<const QMimeData*> data) {
|
||||
if (data->hasImage()) {
|
||||
return true;
|
||||
} else if (const auto urls = ReadMimeUrls(data); !urls.empty()) {
|
||||
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
||||
if (ranges::all_of(urls, UrlIsLocal)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UrlIsLocal(const QUrl &url) {
|
||||
if (!url.isLocalFile()) {
|
||||
return false;
|
||||
}
|
||||
const auto result = url.toLocalFile();
|
||||
if (result.startsWith("//")) {
|
||||
return false;
|
||||
}
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
QString FileExtension(const QString &filepath) {
|
||||
const auto reversed = ranges::views::reverse(filepath);
|
||||
const auto last = ranges::find_first_of(reversed, ".\\/");
|
||||
|
||||