Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e75a41ee6 | ||
|
|
84266aef2c | ||
|
|
40a7f8ea50 | ||
|
|
7c4fcdd9cb | ||
|
|
92e87852c1 | ||
|
|
73c4da2b21 | ||
|
|
9f4da7e890 | ||
|
|
8b23457373 | ||
|
|
ab2fd7c749 | ||
|
|
040a6ddf3a | ||
|
|
55afe0912f | ||
|
|
ee48127094 | ||
|
|
445576d568 | ||
|
|
8f1d40892e | ||
|
|
766db9660c | ||
|
|
68665ec1f2 | ||
|
|
b400964aa1 | ||
|
|
317530cfa3 | ||
|
|
e8fba23b59 | ||
|
|
ef749e695e | ||
|
|
712ef33d6b | ||
|
|
0441b7dbc3 | ||
|
|
3ee0dcbacd | ||
|
|
57411b962f | ||
|
|
4eee00d95e | ||
|
|
957a08962f | ||
|
|
21c82f5fe1 | ||
|
|
27964993f6 | ||
|
|
14e296e1f9 | ||
|
|
23c0ff934f | ||
|
|
c64ef1e20e | ||
|
|
ed97619c6c | ||
|
|
924ec592b1 | ||
|
|
669c581701 | ||
|
|
e97ae3d537 | ||
|
|
08800b68f4 | ||
|
|
a59db6529c | ||
|
|
02a54ceea6 | ||
|
|
f0a7c547e8 | ||
|
|
ecfb343690 | ||
|
|
c65472c9b3 | ||
|
|
c42864d35e | ||
|
|
b02ce599e6 | ||
|
|
1ae5495b91 | ||
|
|
c4fbb8c199 | ||
|
|
313872dacc | ||
|
|
ef15136a3b | ||
|
|
4342c8d761 | ||
|
|
644744ac9e | ||
|
|
cbc03d1e45 | ||
|
|
7f56192b97 | ||
|
|
6590f3b741 | ||
|
|
c0bbb669e0 | ||
|
|
63014adfef | ||
|
|
1ad055c8c8 | ||
|
|
9b558564e9 |
2
.github/workflows/docker.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
2
.github/workflows/mac.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
|
||||
2
.github/workflows/mac_packaged.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
|
||||
2
.github/workflows/snap.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
2
.github/workflows/win.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 935 KiB After Width: | Height: | Size: 938 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 250 KiB |
@@ -652,4 +652,101 @@ div.toast_shown {
|
||||
.reactions .reaction .count {
|
||||
margin-right: 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html, body {
|
||||
background-color: #1a2026; /* groupCallBg */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.page_wrap {
|
||||
background-color: #1a2026; /* groupCallBg */
|
||||
color: #ffffff; /* groupCallMembersFg */
|
||||
min-height: 100vh;
|
||||
}
|
||||
.page_wrap a {
|
||||
color: #4db8ff; /* groupCallActiveFg */
|
||||
}
|
||||
.page_header {
|
||||
background-color: #1a2026; /* groupCallBg */
|
||||
border-bottom: 1px solid #2c333d; /* groupCallMembersBg */
|
||||
}
|
||||
.bold {
|
||||
color: #ffffff; /* groupCallMembersFg */
|
||||
}
|
||||
.details {
|
||||
color: #91979e; /* groupCallMemberNotJoinedStatus */
|
||||
}
|
||||
.page_body {
|
||||
background-color: #1a2026; /* groupCallBg */
|
||||
}
|
||||
code {
|
||||
color: #ff8aac; /* historyPeer6UserpicBg */
|
||||
background-color: #2c333d; /* groupCallMembersBg */
|
||||
}
|
||||
pre {
|
||||
color: #ffffff; /* groupCallMembersFg */
|
||||
background-color: #2c333d; /* groupCallMembersBg */
|
||||
border: 1px solid #323a45; /* groupCallMembersBgOver */
|
||||
}
|
||||
.with_divider {
|
||||
border-top: 1px solid #2c333d; /* groupCallMembersBg */
|
||||
}
|
||||
a.block_link:hover {
|
||||
background-color: #323a45; /* groupCallMembersBgOver */
|
||||
}
|
||||
.list_page .entry {
|
||||
color: #ffffff; /* groupCallMembersFg */
|
||||
}
|
||||
.message {
|
||||
color: #ffffff; /* groupCallMembersFg */
|
||||
}
|
||||
div.selected {
|
||||
background-color: #323a45; /* groupCallMembersBgOver */
|
||||
}
|
||||
.default .from_name {
|
||||
color: #4db8ff; /* groupCallActiveFg */
|
||||
}
|
||||
.default .media .description {
|
||||
color: #ffffff; /* groupCallMembersFg */
|
||||
}
|
||||
msgInBg,
|
||||
.historyComposeAreaBg {
|
||||
background-color: #2c333d; /* groupCallMembersBg */
|
||||
}
|
||||
msgOutBg {
|
||||
background-color: #323a45; /* groupCallMembersBgOver */
|
||||
}
|
||||
msgInBgSelected {
|
||||
background-color: #39424f; /* groupCallMembersBgRipple */
|
||||
}
|
||||
msgOutBgSelected {
|
||||
background-color: #39424f; /* groupCallMembersBgRipple */
|
||||
}
|
||||
.spoiler {
|
||||
background: #323a45; /* groupCallMembersBgOver */
|
||||
}
|
||||
.spoiler.hidden {
|
||||
background: #61c0ff; /* groupCallMemberInactiveStatus */
|
||||
}
|
||||
.bot_button {
|
||||
background-color: #4db8ff40; /* groupCallActiveFg with opacity */
|
||||
}
|
||||
.reactions .reaction {
|
||||
background-color: #2c333d; /* groupCallMembersBg */
|
||||
color: #4db8ff; /* groupCallActiveFg */
|
||||
}
|
||||
.reactions .reaction.active {
|
||||
background-color: #4db8ff; /* groupCallActiveFg */
|
||||
color: #1a2026; /* groupCallBg */
|
||||
}
|
||||
.reactions .reaction.paid {
|
||||
background-color: #323a45; /* groupCallMembersBgOver */
|
||||
color: #febb5b; /* historyPeer8UserpicBg */
|
||||
}
|
||||
.reactions .reaction.active.paid {
|
||||
background-color: #febb5b; /* historyPeer8UserpicBg */
|
||||
color: #1a2026; /* groupCallBg */
|
||||
}
|
||||
}
|
||||
@@ -1735,6 +1735,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_block_user" = "Block user";
|
||||
"lng_profile_unblock_user" = "Unblock user";
|
||||
"lng_profile_export_chat" = "Export chat history";
|
||||
"lng_profile_export_topic" = "Export topic history";
|
||||
"lng_profile_gift_premium" = "Gift Premium";
|
||||
"lng_media_selected_photo#one" = "{count} Photo";
|
||||
"lng_media_selected_photo#other" = "{count} Photos";
|
||||
@@ -2957,6 +2958,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_commission" = "{amount} commission";
|
||||
"lng_credits_paid_messages_fee_live_reaction" = "Fee for Live Story Reaction";
|
||||
"lng_credits_paid_messages_fee#one" = "Fee for {count} Message";
|
||||
"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages";
|
||||
"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}";
|
||||
@@ -4790,6 +4792,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_paid_react_send" = "Send {price}";
|
||||
"lng_paid_react_agree" = "By sending stars, you agree to the {link}.";
|
||||
"lng_paid_react_agree_link" = "Terms of Service";
|
||||
"lng_paid_react_admin_cant" = "You can't send Stars to your own Live Story.";
|
||||
"lng_paid_react_toast#one" = "Star Sent!";
|
||||
"lng_paid_react_toast#other" = "Stars Sent!";
|
||||
"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!";
|
||||
@@ -4811,6 +4814,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_paid_reaction_title" = "React with Stars";
|
||||
"lng_paid_reaction_about" = "Highlight and pin your message by sending Stars to {name}.";
|
||||
"lng_paid_reaction_button" = "Send {stars}";
|
||||
"lng_paid_admin_title" = "Receive Stars from Viewers";
|
||||
"lng_paid_admin_about" = "Viewers can send you Star Reactions.";
|
||||
|
||||
"lng_sensitive_tag" = "18+";
|
||||
"lng_sensitive_title" = "18+";
|
||||
@@ -6312,6 +6317,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_export_option_other" = "Miscellaneous data";
|
||||
"lng_export_option_other_about" = "Other types of data not mentioned above (beta).";
|
||||
"lng_export_header_chats" = "Chat export settings";
|
||||
"lng_export_header_topic" = "Topic export settings";
|
||||
"lng_export_option_personal_chats" = "Personal chats";
|
||||
"lng_export_option_bot_chats" = "Bot chats";
|
||||
"lng_export_option_private_groups" = "Private groups";
|
||||
@@ -6806,6 +6812,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_stories_my_name" = "My Story";
|
||||
"lng_stories_archive" = "Hide Stories";
|
||||
"lng_stories_unarchive" = "Unhide Stories";
|
||||
"lng_stories_view_anonymously" = "View Anonymously";
|
||||
"lng_stories_row_count#one" = "{count} Story";
|
||||
"lng_stories_row_count#other" = "{count} Stories";
|
||||
"lng_stories_views#one" = "{count} view";
|
||||
@@ -6900,6 +6907,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_stealth_mode_next_about" = "Hide my views for the next 25 minutes.";
|
||||
"lng_stealth_mode_unlock" = "Unlock Stealth Mode";
|
||||
"lng_stealth_mode_enable" = "Enable Stealth Mode";
|
||||
"lng_stealth_mode_enable_and_open" = "Enable and open the story";
|
||||
"lng_stealth_mode_cooldown_in" = "Available in {left}";
|
||||
"lng_stealth_mode_cooldown_tip" = "Please wait until **Stealth Mode** is ready to use again.";
|
||||
"lng_stealth_mode_enabled_tip_title" = "Stealth Mode On";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="6.3.3.0" />
|
||||
Version="6.3.4.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,3,3,0
|
||||
PRODUCTVERSION 6,3,3,0
|
||||
FILEVERSION 6,3,4,0
|
||||
PRODUCTVERSION 6,3,4,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "6.3.3.0"
|
||||
VALUE "FileVersion", "6.3.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.3.3.0"
|
||||
VALUE "ProductVersion", "6.3.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,3,3,0
|
||||
PRODUCTVERSION 6,3,3,0
|
||||
FILEVERSION 6,3,4,0
|
||||
PRODUCTVERSION 6,3,4,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", "6.3.3.0"
|
||||
VALUE "FileVersion", "6.3.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.3.3.0"
|
||||
VALUE "ProductVersion", "6.3.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -146,6 +146,8 @@ Data::CreditsHistoryEntry CreditsHistoryEntryFromTL(
|
||||
? starrefAmount
|
||||
: CreditsAmount()),
|
||||
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
|
||||
.limitedCount = parsedGift ? parsedGift->limitedCount : 0,
|
||||
.limitedLeft = parsedGift ? parsedGift->limitedLeft : 0,
|
||||
.starsConverted = int(nonUniqueGift
|
||||
? nonUniqueGift->vconvert_stars().v
|
||||
: 0),
|
||||
|
||||
@@ -650,18 +650,31 @@ void EditFilterBox(
|
||||
}),
|
||||
anim::type::instant);
|
||||
|
||||
const auto &padding = st::defaultSubsectionTitlePadding;
|
||||
const auto isPremium = session->premium();
|
||||
const auto title = Ui::AddSubsectionTitle(
|
||||
colors,
|
||||
tr::lng_filters_tag_color_subtitle());
|
||||
const auto preview = Ui::CreateChild<Ui::RpWidget>(colors);
|
||||
title->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
const auto titleWrap = colors->add(
|
||||
object_ptr<Ui::FixedHeightWidget>(
|
||||
colors,
|
||||
rect::m::sum::v(padding)
|
||||
+ st::defaultSubsectionTitle.style.font->height));
|
||||
const auto title = Ui::CreateChild<Ui::FlatLabel>(
|
||||
titleWrap,
|
||||
tr::lng_filters_tag_color_subtitle(),
|
||||
st::defaultSubsectionTitle);
|
||||
title->move(rect::m::pos::tl(padding));
|
||||
const auto preview = Ui::CreateChild<Ui::RpWidget>(titleWrap);
|
||||
rpl::combine(
|
||||
title->sizeValue(),
|
||||
titleWrap->widthValue()
|
||||
) | rpl::start_with_next([=](const QSize &s, int w) {
|
||||
const auto h = st::normalFont->height;
|
||||
const auto left = padding.left()
|
||||
+ s.width()
|
||||
+ st::settingsFilterTagPreviewSkip;
|
||||
preview->setGeometry(
|
||||
rect::right(colors) - st::settingsFilterTagPreviewSkip,
|
||||
r.y() + (r.height() - h) / 2 + st::lineWidth,
|
||||
colors->width(),
|
||||
left,
|
||||
padding.top() + (s.height() - h) / 2,
|
||||
w - left,
|
||||
h);
|
||||
}, preview->lifetime());
|
||||
|
||||
@@ -673,12 +686,16 @@ void EditFilterBox(
|
||||
};
|
||||
const auto tag = preview->lifetime().make_state<TagState>();
|
||||
tag->context.textContext = Core::TextContext({ .session = session });
|
||||
const auto shift = st::settingsFilterTagPreviewSkip / 2;
|
||||
preview->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(preview);
|
||||
p.setOpacity(tag->alpha);
|
||||
const auto size = tag->frame.size() / style::DevicePixelRatio();
|
||||
const auto rect = QRect(
|
||||
preview->width() - size.width() - st::boxRowPadding.right(),
|
||||
preview->width()
|
||||
- size.width()
|
||||
- st::boxRowPadding.right()
|
||||
- shift,
|
||||
(st::normalFont->height - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height());
|
||||
@@ -688,7 +705,7 @@ void EditFilterBox(
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::windowSubTextFg);
|
||||
p.drawText(
|
||||
preview->rect() - st::boxRowPadding,
|
||||
preview->rect().translated(-shift, 0) - st::boxRowPadding,
|
||||
tr::lng_filters_tag_color_no(tr::now),
|
||||
style::al_right);
|
||||
}
|
||||
|
||||
@@ -1616,11 +1616,30 @@ void AddCreditsHistoryEntryTable(
|
||||
: entry.giftUpgraded
|
||||
? tr::lng_credits_box_history_entry_gift_from()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
const auto targetId = actorId ? actorId : peerId;
|
||||
const auto isPeerDefault = !entry.starrefCommission
|
||||
&& !entry.in
|
||||
&& !entry.giftResale
|
||||
&& !entry.giftUpgraded;
|
||||
const auto user = isPeerDefault
|
||||
? session->data().peer(targetId)->asUser()
|
||||
: nullptr;
|
||||
const auto withSendButton = user
|
||||
&& !user->isInaccessible()
|
||||
&& !user->isBot();
|
||||
auto send = withSendButton ? tr::lng_gift_send_small() : nullptr;
|
||||
auto handler = send
|
||||
? Fn<void()>([=] {
|
||||
if (const auto window = show->resolveWindow()) {
|
||||
Ui::ShowStarGiftBox(window, user);
|
||||
}
|
||||
})
|
||||
: nullptr;
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(text),
|
||||
show,
|
||||
actorId ? actorId : peerId);
|
||||
MakePeerTableValue(table, show, targetId, send, handler),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
@@ -1780,6 +1799,19 @@ void AddCreditsHistoryEntryTable(
|
||||
rpl::single(
|
||||
Ui::Text::Link(entry.successLink, entry.successLink)));
|
||||
}
|
||||
if (entry.limitedCount > 0 && entry.limitedLeft >= 0) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_availability(),
|
||||
tr::lng_gift_availability_left(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.limitedLeft) | tr::to_count(),
|
||||
lt_amount,
|
||||
rpl::single(TextWithEntities{
|
||||
Lang::FormatCountDecimal(entry.limitedCount)
|
||||
}),
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubscriptionEntryTable(
|
||||
|
||||
@@ -2294,6 +2294,9 @@ void Controller::saveUsernamesOrder() {
|
||||
continueSave();
|
||||
}).send();
|
||||
} else {
|
||||
const auto weakContinue = crl::guard(this, [=] {
|
||||
continueSave();
|
||||
});
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
const auto newUsernames = (*_savingData.usernamesOrder);
|
||||
_peer->session().api().usernames().reorder(
|
||||
@@ -2311,7 +2314,7 @@ void Controller::saveUsernamesOrder() {
|
||||
.editable = editable,
|
||||
};
|
||||
}) | ranges::to_vector);
|
||||
continueSave();
|
||||
weakContinue();
|
||||
lifetime->destroy();
|
||||
}, *lifetime);
|
||||
}
|
||||
|
||||
@@ -202,6 +202,8 @@ struct BidSliderValues {
|
||||
auto result = object_ptr<RpWidget>(parent.get());
|
||||
const auto raw = result.data();
|
||||
|
||||
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
struct State {
|
||||
std::unique_ptr<FlatLabel> place;
|
||||
std::unique_ptr<UserpicButton> userpic;
|
||||
@@ -497,7 +499,7 @@ void AddBidPlaces(
|
||||
if (i->amount < chosen
|
||||
|| (!setting
|
||||
&& i->amount == chosen
|
||||
&& i->date > value.my.date)) {
|
||||
&& i->date >= value.my.date)) {
|
||||
top.push_back({ show->session().user(), chosen });
|
||||
for (auto j = i; j != e; ++j) {
|
||||
if (!pushTop(j)) {
|
||||
@@ -579,7 +581,7 @@ void EditCustomBid(
|
||||
starsField->setFocusFast();
|
||||
});
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto submit = [=] {
|
||||
const auto value = starsField->getLastText().toLongLong();
|
||||
if (value <= min->current() || value > 1'000'000'000) {
|
||||
starsField->showError();
|
||||
@@ -587,7 +589,10 @@ void EditCustomBid(
|
||||
}
|
||||
save(value);
|
||||
box->closeBox();
|
||||
});
|
||||
};
|
||||
QObject::connect(starsField, &Ui::NumberInput::submitted, submit);
|
||||
|
||||
box->addButton(tr::lng_settings_save(), submit);
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
@@ -702,14 +707,7 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto bubble = AddStarSelectBubble(
|
||||
sliderWrap,
|
||||
initial ? BoxShowFinishes(box) : nullptr,
|
||||
state->chosen.value(),
|
||||
values.max,
|
||||
activeFgOverride);
|
||||
bubble->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
bubble->setClickedCallback([=] {
|
||||
const auto setCustom = [=] {
|
||||
auto min = state->value.value(
|
||||
) | rpl::map([=](const Data::GiftAuctionState &state) {
|
||||
return std::max(1, int(state.my.minBidAmount
|
||||
@@ -719,7 +717,16 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
show->show(Box(EditCustomBid, show, crl::guard(box, [=](int v) {
|
||||
state->chosen = v;
|
||||
}), std::move(min), state->chosen.current()));
|
||||
});
|
||||
};
|
||||
|
||||
const auto bubble = AddStarSelectBubble(
|
||||
sliderWrap,
|
||||
initial ? BoxShowFinishes(box) : nullptr,
|
||||
state->chosen.value(),
|
||||
values.max,
|
||||
activeFgOverride);
|
||||
bubble->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
bubble->setClickedCallback(setCustom);
|
||||
state->subtext.value() | rpl::start_with_next([=](QString &&text) {
|
||||
bubble->setSubtext(std::move(text));
|
||||
}, bubble->lifetime());
|
||||
@@ -735,6 +742,29 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
|
||||
activeFgOverride);
|
||||
|
||||
sliderWrap->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
const auto custom = CreateChild<AbstractButton>(sliderWrap);
|
||||
state->chosen.changes() | rpl::start_with_next([=] {
|
||||
custom->update();
|
||||
}, custom->lifetime());
|
||||
custom->show();
|
||||
custom->setClickedCallback(setCustom);
|
||||
custom->resize(st::paidReactSlider.width, st::paidReactSlider.width);
|
||||
custom->paintOn([=](QPainter &p) {
|
||||
const auto rem = st::paidReactSlider.borderWidth * 2;
|
||||
const auto inner = custom->width() - 2 * rem;
|
||||
const auto sub = (inner - 1) / 2;
|
||||
const auto stroke = inner - (2 * sub);
|
||||
const auto color = activeFgOverride(state->chosen.current());
|
||||
p.fillRect(rem + sub, rem, stroke, sub, color);
|
||||
p.fillRect(rem, rem + sub, inner, stroke, color);
|
||||
p.fillRect(rem + sub, rem + inner - sub, stroke, sub, color);
|
||||
});
|
||||
sliderWrap->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
custom->move(
|
||||
size.width() - st::boxRowPadding.right() - custom->width(),
|
||||
size.height() - custom->height());
|
||||
}, custom->lifetime());
|
||||
}, sliderWrap->lifetime());
|
||||
|
||||
box->addTopButton(
|
||||
@@ -1507,14 +1537,14 @@ void AuctionAboutBox(
|
||||
box,
|
||||
tr::lng_auction_about_title(),
|
||||
st::boxTitle),
|
||||
st::boxRowPadding + st::confcallLinkTitlePadding,
|
||||
st::boxRowPadding,
|
||||
style::al_top);
|
||||
box->addRow(
|
||||
object_ptr<FlatLabel>(
|
||||
box,
|
||||
tr::lng_auction_about_subtitle(tr::rich),
|
||||
st::confcallLinkCenteredText),
|
||||
st::boxRowPadding,
|
||||
st::boxRowPadding + st::auctionAboutTextPadding,
|
||||
style::al_top
|
||||
)->setTryMakeSimilarLines(true);
|
||||
|
||||
|
||||
@@ -1732,6 +1732,49 @@ void CheckMaybeGiftLocked(
|
||||
.forceTon = star->forceTon,
|
||||
},
|
||||
Settings::CreditsEntryBoxStyleOverrides()));
|
||||
} else if (unique && star->mine && !peer->isSelf()) {
|
||||
if (ShowTransferGiftLater(window->uiShow(), unique)) {
|
||||
return;
|
||||
}
|
||||
const auto done = [=] {
|
||||
window->session().credits().load(true);
|
||||
window->showPeerHistory(peer);
|
||||
};
|
||||
if (state->transferRequested == unique) {
|
||||
return;
|
||||
}
|
||||
state->transferRequested = unique;
|
||||
const auto savedId = star->transferId;
|
||||
using Payments::CheckoutResult;
|
||||
const auto formReady = [=](
|
||||
uint64 formId,
|
||||
CreditsAmount price,
|
||||
std::optional<CheckoutResult> failure) {
|
||||
state->transferRequested = nullptr;
|
||||
if (!failure && !price.stars()) {
|
||||
LOG(("API Error: "
|
||||
"Bad transfer invoice currenct."));
|
||||
} else if (!failure
|
||||
|| *failure == CheckoutResult::Free) {
|
||||
unique->starsForTransfer = failure
|
||||
? 0
|
||||
: price.whole();
|
||||
ShowTransferToBox(
|
||||
window,
|
||||
peer,
|
||||
unique,
|
||||
savedId,
|
||||
done);
|
||||
} else if (*failure == CheckoutResult::Cancelled) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
RequestOurForm(
|
||||
window->uiShow(),
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
Api::InputSavedStarGiftId(savedId, unique),
|
||||
peer->input),
|
||||
formReady);
|
||||
} else if (star && star->resale) {
|
||||
const auto id = star->info.id;
|
||||
if (state->resaleRequestingId == id) {
|
||||
@@ -1781,49 +1824,6 @@ void CheckMaybeGiftLocked(
|
||||
}
|
||||
});
|
||||
CheckMaybeGiftLocked(window, star->info.id, ready);
|
||||
} else if (unique && star->mine && !peer->isSelf()) {
|
||||
if (ShowTransferGiftLater(window->uiShow(), unique)) {
|
||||
return;
|
||||
}
|
||||
const auto done = [=] {
|
||||
window->session().credits().load(true);
|
||||
window->showPeerHistory(peer);
|
||||
};
|
||||
if (state->transferRequested == unique) {
|
||||
return;
|
||||
}
|
||||
state->transferRequested = unique;
|
||||
const auto savedId = star->transferId;
|
||||
using Payments::CheckoutResult;
|
||||
const auto formReady = [=](
|
||||
uint64 formId,
|
||||
CreditsAmount price,
|
||||
std::optional<CheckoutResult> failure) {
|
||||
state->transferRequested = nullptr;
|
||||
if (!failure && !price.stars()) {
|
||||
LOG(("API Error: "
|
||||
"Bad transfer invoice currenct."));
|
||||
} else if (!failure
|
||||
|| *failure == CheckoutResult::Free) {
|
||||
unique->starsForTransfer = failure
|
||||
? 0
|
||||
: price.whole();
|
||||
ShowTransferToBox(
|
||||
window,
|
||||
peer,
|
||||
unique,
|
||||
savedId,
|
||||
done);
|
||||
} else if (*failure == CheckoutResult::Cancelled) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
RequestOurForm(
|
||||
window->uiShow(),
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
Api::InputSavedStarGiftId(savedId, unique),
|
||||
peer->input),
|
||||
formReady);
|
||||
} else if (star
|
||||
&& star->info.perUserTotal
|
||||
&& !star->info.perUserRemains) {
|
||||
|
||||
@@ -1698,6 +1698,7 @@ groupCallMessagesScroll: ScrollArea(defaultScrollArea) {
|
||||
barHidden: true;
|
||||
}
|
||||
groupCallMessagePadding: margins(8px, 3px, 8px, 2px);
|
||||
groupCallPricePadding: margins(5px, 0px, 5px, 0px);
|
||||
groupCallMessageSkip: 8px;
|
||||
groupCallMessagePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: radialFg;
|
||||
@@ -1721,6 +1722,8 @@ groupCallUserpicPadding: margins(2px, 2px, 4px, 2px);
|
||||
groupCallPinnedPadding: margins(10px, 4px, 10px, 2px);
|
||||
groupCallPinnedMaxWidth: 96px;
|
||||
groupCallPinnedUserpic: 22px;
|
||||
groupCallEffectPadding: margins(3px, 1px, 3px, 1px);
|
||||
groupCallEffectUserpicPadding: margins(1px, 1px, 3px, 1px);
|
||||
|
||||
groupCallTopReactorBadge: RoundCheckbox(defaultRoundCheckbox) {
|
||||
width: 1px;
|
||||
|
||||
@@ -617,9 +617,16 @@ void SetupFingerprintTooltip(not_null<Ui::RpWidget*> widget) {
|
||||
widget->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
if (type == QEvent::Enter) {
|
||||
state->toggleTooltip(true);
|
||||
// Enter events may come from widget destructors,
|
||||
// in that case sync-showing tooltip (calling Grab)
|
||||
// crashes the whole thing.
|
||||
crl::on_main(widget, [=] {
|
||||
state->toggleTooltip(true);
|
||||
});
|
||||
} else if (type == QEvent::Leave) {
|
||||
state->toggleTooltip(false);
|
||||
crl::on_main(widget, [=] {
|
||||
state->toggleTooltip(false);
|
||||
});
|
||||
}
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
@@ -69,12 +69,6 @@ constexpr auto kStarsStatsShortPollDelay = 30 * crl::time(1000);
|
||||
return PinFinishDate(message.peer, message.date, message.stars);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<PeerId> MaybeShownPeer(
|
||||
uint32 privacySet,
|
||||
PeerId shownPeer) {
|
||||
return privacySet ? shownPeer : std::optional<PeerId>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Messages::Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api)
|
||||
@@ -104,9 +98,7 @@ Messages::~Messages() {
|
||||
finishPaidSending({
|
||||
.count = int(_paid.sending),
|
||||
.valid = true,
|
||||
.shownPeer = MaybeShownPeer(
|
||||
_paid.sendingPrivacySet,
|
||||
_paid.sendingShownPeer),
|
||||
.shownPeer = _paid.sendingShownPeer,
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
@@ -517,26 +509,23 @@ PeerId Messages::reactionsLocalShownPeer() const {
|
||||
return entry.peer ? entry.peer->id : PeerId();
|
||||
}
|
||||
}
|
||||
return _session->userPeerId();
|
||||
return _call->messagesFrom()->id;
|
||||
//const auto api = &_session->api();
|
||||
//return api->globalPrivacy().paidReactionShownPeerCurrent();
|
||||
};
|
||||
return (_paid.scheduledFlag && _paid.scheduledPrivacySet)
|
||||
return _paid.scheduledFlag
|
||||
? _paid.scheduledShownPeer
|
||||
: (_paid.sendingFlag && _paid.sendingPrivacySet)
|
||||
: _paid.sendingFlag
|
||||
? _paid.sendingShownPeer
|
||||
: minePaidShownPeer();
|
||||
}
|
||||
|
||||
void Messages::reactionsPaidAdd(int count, std::optional<PeerId> shownPeer) {
|
||||
void Messages::reactionsPaidAdd(int count) {
|
||||
Expects(count >= 0);
|
||||
|
||||
_paid.scheduled += count;
|
||||
_paid.scheduledFlag = 1;
|
||||
if (shownPeer.has_value()) {
|
||||
_paid.scheduledShownPeer = *shownPeer;
|
||||
_paid.scheduledPrivacySet = true;
|
||||
}
|
||||
_paid.scheduledShownPeer = _call->messagesFrom()->id;
|
||||
if (count > 0) {
|
||||
_session->credits().lock(CreditsAmount(count));
|
||||
}
|
||||
@@ -555,7 +544,6 @@ void Messages::reactionsPaidScheduledCancel() {
|
||||
_paid.scheduled = 0;
|
||||
_paid.scheduledFlag = 0;
|
||||
_paid.scheduledShownPeer = 0;
|
||||
_paid.scheduledPrivacySet = 0;
|
||||
_paidChanges.fire({});
|
||||
}
|
||||
|
||||
@@ -570,17 +558,13 @@ Data::PaidReactionSend Messages::startPaidReactionSending() {
|
||||
_paid.sending = _paid.scheduled;
|
||||
_paid.sendingFlag = _paid.scheduledFlag;
|
||||
_paid.sendingShownPeer = _paid.scheduledShownPeer;
|
||||
_paid.sendingPrivacySet = _paid.scheduledPrivacySet;
|
||||
_paid.scheduled = 0;
|
||||
_paid.scheduledFlag = 0;
|
||||
_paid.scheduledShownPeer = 0;
|
||||
_paid.scheduledPrivacySet = 0;
|
||||
return {
|
||||
.count = int(_paid.sending),
|
||||
.valid = true,
|
||||
.shownPeer = MaybeShownPeer(
|
||||
_paid.sendingPrivacySet,
|
||||
_paid.sendingShownPeer),
|
||||
.shownPeer = _paid.sendingShownPeer,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -589,25 +573,24 @@ void Messages::finishPaidSending(
|
||||
bool success) {
|
||||
Expects(send.count == _paid.sending);
|
||||
Expects(send.valid == (_paid.sendingFlag == 1));
|
||||
Expects(send.shownPeer == MaybeShownPeer(
|
||||
_paid.sendingPrivacySet,
|
||||
_paid.sendingShownPeer));
|
||||
Expects(send.shownPeer == _paid.sendingShownPeer);
|
||||
|
||||
_paid.sending = 0;
|
||||
_paid.sendingFlag = 0;
|
||||
_paid.sendingShownPeer = 0;
|
||||
_paid.sendingPrivacySet = 0;
|
||||
if (const auto amount = send.count) {
|
||||
if (success) {
|
||||
const auto from = _session->data().peer(*send.shownPeer);
|
||||
_session->credits().withdrawLocked(CreditsAmount(amount));
|
||||
|
||||
auto &donors = _paid.top.topDonors;
|
||||
const auto i = ranges::find(donors, true, &StarsTopDonor::my);
|
||||
const auto i = ranges::find(donors, true, &StarsDonor::my);
|
||||
if (i != end(donors)) {
|
||||
i->peer = from;
|
||||
i->stars += amount;
|
||||
} else {
|
||||
donors.push_back({
|
||||
.peer = _session->user(),
|
||||
.peer = from,
|
||||
.stars = amount,
|
||||
.my = true,
|
||||
});
|
||||
@@ -632,7 +615,7 @@ void Messages::reactionsPaidSend() {
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
_sendingIdByRandomId.emplace(randomId, localId);
|
||||
|
||||
const auto from = _call->messagesFrom();
|
||||
const auto from = _session->data().peer(*send.shownPeer);
|
||||
const auto stars = int(send.count);
|
||||
const auto skip = skipMessage({}, stars);
|
||||
if (skip) {
|
||||
@@ -676,7 +659,7 @@ void Messages::undoScheduledPaidOnDestroy() {
|
||||
|
||||
Messages::PaidLocalState Messages::starsLocalState() const {
|
||||
const auto &donors = _paid.top.topDonors;
|
||||
const auto i = ranges::find(donors, true, &StarsTopDonor::my);
|
||||
const auto i = ranges::find(donors, true, &StarsDonor::my);
|
||||
const auto local = int(_paid.scheduled);
|
||||
const auto my = (i != end(donors) ? i->stars : 0) + local;
|
||||
const auto total = _paid.top.total + local;
|
||||
@@ -728,7 +711,7 @@ void Messages::addStars(not_null<PeerData*> from, int stars, bool mine) {
|
||||
const auto i = ranges::find(
|
||||
_paid.top.topDonors,
|
||||
from.get(),
|
||||
&StarsTopDonor::peer);
|
||||
&StarsDonor::peer);
|
||||
if (i != end(_paid.top.topDonors)) {
|
||||
i->stars += stars;
|
||||
} else {
|
||||
@@ -741,8 +724,8 @@ void Messages::addStars(not_null<PeerData*> from, int stars, bool mine) {
|
||||
ranges::stable_sort(
|
||||
_paid.top.topDonors,
|
||||
ranges::greater(),
|
||||
&StarsTopDonor::stars);
|
||||
_paidChanges.fire({});
|
||||
&StarsDonor::stars);
|
||||
_paidChanges.fire({ .peer = from, .stars = stars });
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -54,18 +54,18 @@ struct MessageDeleteRequest {
|
||||
bool reportSpam = false;
|
||||
};
|
||||
|
||||
struct StarsTopDonor {
|
||||
struct StarsDonor {
|
||||
PeerData *peer = nullptr;
|
||||
int stars = 0;
|
||||
bool my = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarsTopDonor &,
|
||||
const StarsTopDonor &) = default;
|
||||
const StarsDonor &,
|
||||
const StarsDonor &) = default;
|
||||
};
|
||||
|
||||
struct StarsTop {
|
||||
std::vector<StarsTopDonor> topDonors;
|
||||
std::vector<StarsDonor> topDonors;
|
||||
int total = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
|
||||
[[nodiscard]] int reactionsPaidScheduled() const;
|
||||
[[nodiscard]] PeerId reactionsLocalShownPeer() const;
|
||||
void reactionsPaidAdd(int count, std::optional<PeerId> shownPeer = {});
|
||||
void reactionsPaidAdd(int count);
|
||||
void reactionsPaidScheduledCancel();
|
||||
void reactionsPaidSend();
|
||||
void undoScheduledPaidOnDestroy();
|
||||
@@ -101,7 +101,7 @@ public:
|
||||
int my = 0;
|
||||
};
|
||||
[[nodiscard]] PaidLocalState starsLocalState() const;
|
||||
[[nodiscard]] rpl::producer<> starsValueChanges() const {
|
||||
[[nodiscard]] rpl::producer<StarsDonor> starsValueChanges() const {
|
||||
return _paidChanges.events();
|
||||
}
|
||||
[[nodiscard]] const StarsTop &starsTop() const {
|
||||
@@ -155,7 +155,9 @@ private:
|
||||
const TextWithEntities &text,
|
||||
int stars) const;
|
||||
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
|
||||
void finishPaidSending(Data::PaidReactionSend send, bool success);
|
||||
void finishPaidSending(
|
||||
Data::PaidReactionSend send,
|
||||
bool success);
|
||||
void addStars(not_null<PeerData*> from, int stars, bool mine);
|
||||
void requestStarsStats();
|
||||
|
||||
@@ -181,7 +183,7 @@ private:
|
||||
|
||||
mtpRequestId _starsTopRequestId = 0;
|
||||
Paid _paid;
|
||||
rpl::event_stream<> _paidChanges;
|
||||
rpl::event_stream<StarsDonor> _paidChanges;
|
||||
bool _paidSendingPending = false;
|
||||
|
||||
TimeId _ttl = 0;
|
||||
|
||||
@@ -73,6 +73,13 @@ constexpr auto kAdminBadgeTextOpacity = 0.6;
|
||||
return minHeight / 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] int CountPriceRadius() {
|
||||
const auto height = st::groupCallPricePadding.top()
|
||||
+ st::normalFont->height
|
||||
+ st::groupCallPricePadding.bottom();
|
||||
return height / 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] int CountPinnedRadius() {
|
||||
const auto height = st::groupCallUserpicPadding.top()
|
||||
+ st::groupCallPinnedUserpic
|
||||
@@ -304,6 +311,7 @@ struct MessagesUi::MessageView {
|
||||
bool removed = false;
|
||||
bool sending = false;
|
||||
bool failed = false;
|
||||
bool simple = false;
|
||||
bool admin = false;
|
||||
bool mine = false;
|
||||
};
|
||||
@@ -333,6 +341,7 @@ MessagesUi::PayedBg::PayedBg(const Ui::StarsColoring &coloring)
|
||||
, pinnedLight(CountPinnedRadius(), light.color())
|
||||
, pinnedDark(CountPinnedRadius(), dark.color())
|
||||
, messageLight(CountMessageRadius(), light.color())
|
||||
, priceDark(CountPriceRadius(), dark.color())
|
||||
, badgeDark(st::roundRadiusLarge, dark.color()) {
|
||||
}
|
||||
|
||||
@@ -518,6 +527,7 @@ void MessagesUi::animateMessageSent(MessageView &entry) {
|
||||
|
||||
void MessagesUi::updateMessageSize(MessageView &entry) {
|
||||
const auto &padding = st::groupCallMessagePadding;
|
||||
const auto &pricePadding = st::groupCallPricePadding;
|
||||
|
||||
const auto hasUserpic = !entry.failed;
|
||||
const auto userpicPadding = st::groupCallUserpicPadding;
|
||||
@@ -532,6 +542,9 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
|
||||
entry.text,
|
||||
std::min(st::groupCallWidth / 2, inner),
|
||||
inner);
|
||||
const auto price = entry.simple
|
||||
? (pricePadding.left() + pricePadding.right() + entry.price.maxWidth())
|
||||
: 0;
|
||||
const auto space = st::normalFont->spacew;
|
||||
const auto nameWidth = entry.name.isEmpty() ? 0 : entry.name.maxWidth();
|
||||
const auto nameLineWidth = nameWidth
|
||||
@@ -546,8 +559,8 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
|
||||
? 0
|
||||
: st::messageTextStyle.font->height;
|
||||
const auto textHeight = size.height();
|
||||
entry.width = std::max(size.width(), std::min(nameLineWidth, inner))
|
||||
+ widthSkip;
|
||||
entry.width = widthSkip
|
||||
+ std::max(size.width() + price, std::min(nameLineWidth, inner));
|
||||
entry.left = _streamMode ? 0 : (_width - entry.width) / 2;
|
||||
entry.textLeft = leftSkip;
|
||||
entry.textTop = padding.top() + nameHeight;
|
||||
@@ -637,6 +650,8 @@ void MessagesUi::setContentFailed(MessageView &entry) {
|
||||
}
|
||||
|
||||
void MessagesUi::setContent(MessageView &entry) {
|
||||
entry.simple = !entry.admin && entry.original.empty() && entry.stars > 0;
|
||||
|
||||
const auto name = nameText(entry.from, entry.place);
|
||||
entry.name = entry.admin
|
||||
? Ui::Text::String(
|
||||
@@ -648,7 +663,7 @@ void MessagesUi::setContent(MessageView &entry) {
|
||||
: Ui::Text::String();
|
||||
if (const auto stars = entry.stars) {
|
||||
entry.price = Ui::Text::String(
|
||||
st::whoReadDateStyle,
|
||||
entry.simple ? st::messageTextStyle : st::whoReadDateStyle,
|
||||
Ui::Text::IconEmoji(
|
||||
&st::starIconEmojiSmall
|
||||
).append(Lang::FormatCountDecimal(stars)),
|
||||
@@ -668,12 +683,14 @@ void MessagesUi::setContent(MessageView &entry) {
|
||||
kMarkupTextOptions,
|
||||
st::groupCallWidth / 8,
|
||||
_crownHelper.context([this, id = entry.id] { repaintMessage(id); }));
|
||||
if (!entry.price.isEmpty()) {
|
||||
if (!entry.simple && !entry.price.isEmpty()) {
|
||||
entry.text.updateSkipBlock(
|
||||
entry.price.maxWidth(),
|
||||
st::normalFont->height);
|
||||
}
|
||||
entry.text.setLink(1, entry.fromLink);
|
||||
if (!entry.simple && !entry.admin) {
|
||||
entry.text.setLink(1, entry.fromLink);
|
||||
}
|
||||
if (entry.text.hasSpoilers()) {
|
||||
const auto id = entry.id;
|
||||
const auto guard = base::make_weak(_messages);
|
||||
@@ -1207,18 +1224,19 @@ void MessagesUi::setupMessagesWidget() {
|
||||
p.setOpacity(scale);
|
||||
p.translate(-mx, -my);
|
||||
}
|
||||
auto bg = (std::unique_ptr<PayedBg>*)nullptr;
|
||||
if (!_streamMode) {
|
||||
_messageBgRect.paint(p, { x, y, width, use });
|
||||
} else if (entry.stars) {
|
||||
const auto coloring = Ui::StarsColoringForCount(
|
||||
colorings,
|
||||
entry.stars);
|
||||
auto &bg = _bgs[ColoringKey(coloring)];
|
||||
if (!bg) {
|
||||
bg = std::make_unique<PayedBg>(coloring);
|
||||
bg = &_bgs[ColoringKey(coloring)];
|
||||
if (!*bg) {
|
||||
*bg = std::make_unique<PayedBg>(coloring);
|
||||
}
|
||||
p.setOpacity(kColoredMessageBgOpacity);
|
||||
bg->messageLight.paint(p, { x, y, width, use });
|
||||
(*bg)->messageLight.paint(p, { x, y, width, use });
|
||||
p.setOpacity(1.);
|
||||
if (_highlightAnimation.animating()
|
||||
&& entry.id == _highlightId) {
|
||||
@@ -1236,7 +1254,6 @@ void MessagesUi::setupMessagesWidget() {
|
||||
}
|
||||
|
||||
const auto textLeft = entry.textLeft;
|
||||
const auto priceSkip = padding.right() / 2;
|
||||
const auto hasUserpic = !entry.failed;
|
||||
if (hasUserpic) {
|
||||
const auto userpicSize = st::groupCallUserpic;
|
||||
@@ -1299,23 +1316,49 @@ void MessagesUi::setupMessagesWidget() {
|
||||
});
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
const auto pricePadding = st::groupCallPricePadding;
|
||||
const auto textRight = padding.right()
|
||||
+ (entry.simple
|
||||
? (entry.price.maxWidth()
|
||||
+ pricePadding.left()
|
||||
+ pricePadding.right())
|
||||
: 0);
|
||||
entry.text.draw(p, {
|
||||
.position = {
|
||||
x + textLeft,
|
||||
y + entry.textTop,
|
||||
},
|
||||
.availableWidth = entry.width - textLeft - padding.right(),
|
||||
.availableWidth = entry.width - textLeft - textRight,
|
||||
.palette = &st::groupCallMessagePalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = now,
|
||||
.paused = !_messages->window()->isActiveWindow(),
|
||||
});
|
||||
if (!entry.price.isEmpty()) {
|
||||
const auto priceRight = x
|
||||
+ entry.width
|
||||
- entry.price.maxWidth();
|
||||
const auto priceLeft = entry.simple
|
||||
? (priceRight
|
||||
- (padding.top() - pricePadding.top())
|
||||
- pricePadding.right())
|
||||
: (priceRight - (padding.right() / 2));
|
||||
const auto priceTop = entry.simple
|
||||
? (y + entry.textTop)
|
||||
: (y + use - st::normalFont->height);
|
||||
if (entry.simple && bg) {
|
||||
p.setOpacity(kDarkOverOpacity);
|
||||
const auto r = QRect(
|
||||
priceLeft,
|
||||
priceTop,
|
||||
entry.price.maxWidth(),
|
||||
st::normalFont->height
|
||||
).marginsAdded(pricePadding);
|
||||
(*bg)->priceDark.paint(p, r);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
entry.price.draw(p, {
|
||||
.position = {
|
||||
x + entry.width - entry.price.maxWidth() - priceSkip,
|
||||
y + use - st::normalFont->height,
|
||||
},
|
||||
.position = { priceLeft, priceTop },
|
||||
.availableWidth = entry.price.maxWidth(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ private:
|
||||
Ui::RoundRect pinnedLight;
|
||||
Ui::RoundRect pinnedDark;
|
||||
Ui::RoundRect messageLight;
|
||||
Ui::RoundRect priceDark;
|
||||
Ui::RoundRect badgeDark;
|
||||
};
|
||||
|
||||
|
||||
@@ -2111,10 +2111,18 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
|
||||
}
|
||||
widget->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Enter) {
|
||||
trackControlOver(widget, true);
|
||||
} else if (e->type() == QEvent::Leave) {
|
||||
trackControlOver(widget, false);
|
||||
const auto type = e->type();
|
||||
if (type == QEvent::Enter) {
|
||||
// Enter events may come from widget destructors,
|
||||
// in that case sync-showing tooltip (calling Grab)
|
||||
// crashes the whole thing.
|
||||
crl::on_main(widget, [=] {
|
||||
trackControlOver(widget, true);
|
||||
});
|
||||
} else if (type == QEvent::Leave) {
|
||||
crl::on_main(widget, [=] {
|
||||
trackControlOver(widget, false);
|
||||
});
|
||||
}
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
@@ -43,18 +43,21 @@ void VideoStreamStarsBox(
|
||||
VideoStreamStarsBoxArgs &&args) {
|
||||
args.show->session().credits().load();
|
||||
|
||||
const auto admin = args.admin;
|
||||
const auto sending = args.sending;
|
||||
auto submitText = [=](rpl::producer<int> amount) {
|
||||
auto nice = std::move(amount) | rpl::map([=](int count) {
|
||||
return Ui::CreditsEmojiSmall().append(
|
||||
Lang::FormatCountDecimal(count));
|
||||
});
|
||||
return (sending
|
||||
? tr::lng_paid_reaction_button
|
||||
: tr::lng_paid_comment_button)(
|
||||
lt_stars,
|
||||
std::move(nice),
|
||||
Ui::Text::RichLangValue);
|
||||
return admin
|
||||
? tr::lng_box_ok(tr::marked)
|
||||
: (sending
|
||||
? tr::lng_paid_reaction_button
|
||||
: tr::lng_paid_comment_button)(
|
||||
lt_stars,
|
||||
std::move(nice),
|
||||
tr::rich);
|
||||
};
|
||||
const auto &show = args.show;
|
||||
const auto session = &show->session();
|
||||
@@ -121,14 +124,17 @@ void VideoStreamStarsBox(
|
||||
.submit = std::move(submitText),
|
||||
.colorings = show->session().appConfig().groupCallColorings(),
|
||||
.balanceValue = session->credits().balanceValue(),
|
||||
.send = [weak, save = args.save](int count, uint64 barePeerId) {
|
||||
save(count);
|
||||
.send = [=, save = args.save](int count, uint64 barePeerId) {
|
||||
if (!admin) {
|
||||
save(count);
|
||||
}
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
},
|
||||
.videoStreamChoosing = !sending,
|
||||
.videoStreamSending = sending,
|
||||
.videoStreamAdmin = admin,
|
||||
.dark = true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ struct VideoStreamStarsBoxArgs {
|
||||
int min = 0;
|
||||
int current = 0;
|
||||
bool sending = false;
|
||||
bool admin = false;
|
||||
Fn<void(int)> save;
|
||||
QString name;
|
||||
};
|
||||
|
||||
@@ -65,14 +65,18 @@ Viewport::Viewport(
|
||||
}
|
||||
|
||||
Viewport::~Viewport() {
|
||||
if (_borrowed && _opengl) {
|
||||
const auto w = static_cast<QOpenGLWidget*>(widget().get());
|
||||
w->makeCurrent();
|
||||
const auto context = w->context();
|
||||
const auto valid = w->isValid()
|
||||
&& context
|
||||
&& (QOpenGLContext::currentContext() == context);
|
||||
ensureBorrowedCleared(valid ? context->functions() : nullptr);
|
||||
if (_borrowed) {
|
||||
if (_opengl) {
|
||||
const auto w = static_cast<QOpenGLWidget*>(widget().get());
|
||||
w->makeCurrent();
|
||||
const auto context = w->context();
|
||||
const auto valid = w->isValid()
|
||||
&& context
|
||||
&& (QOpenGLContext::currentContext() == context);
|
||||
ensureBorrowedCleared(valid ? context->functions() : nullptr);
|
||||
} else {
|
||||
ensureBorrowedCleared();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,13 +791,13 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
|
||||
top += height + st::groupCallVideoSmallSkip;
|
||||
}
|
||||
};
|
||||
const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
|
||||
const auto topPeer = _large ? _large->peer().get() : nullptr;
|
||||
const auto reorderNeeded = [&] {
|
||||
if (!topPeer) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &tile : _tiles) {
|
||||
if (tile.get() != _large && tile->row()->peer() == topPeer) {
|
||||
if (tile.get() != _large && tile->peer() == topPeer) {
|
||||
return (tile.get() != _tiles.front().get())
|
||||
&& !tile->trackOrUserpicSize().isEmpty();
|
||||
}
|
||||
@@ -809,7 +813,7 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
|
||||
ranges::stable_partition(
|
||||
_tilesForOrder,
|
||||
[&](not_null<VideoTile*> tile) {
|
||||
return (tile->row()->peer() == topPeer);
|
||||
return (tile->peer() == topPeer);
|
||||
});
|
||||
for (const auto &tile : _tilesForOrder) {
|
||||
layoutNext(tile);
|
||||
@@ -925,6 +929,7 @@ rpl::producer<bool> Viewport::mouseInsideValue() const {
|
||||
|
||||
void Viewport::ensureBorrowedRenderer(QOpenGLFunctions &f) {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(_opengl);
|
||||
|
||||
if (_borrowedRenderer) {
|
||||
return;
|
||||
@@ -935,6 +940,7 @@ void Viewport::ensureBorrowedRenderer(QOpenGLFunctions &f) {
|
||||
|
||||
void Viewport::ensureBorrowedCleared(QOpenGLFunctions *f) {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(_opengl);
|
||||
|
||||
if (const auto renderer = base::take(_borrowedRenderer)) {
|
||||
renderer->deinit(f);
|
||||
@@ -948,6 +954,23 @@ void Viewport::borrowedPaint(QOpenGLFunctions &f) {
|
||||
_borrowedRenderer->paint(static_cast<QOpenGLWidget*>(widget().get()), f);
|
||||
}
|
||||
|
||||
void Viewport::ensureBorrowedRenderer() {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(!_opengl);
|
||||
|
||||
if (_borrowedRenderer) {
|
||||
return;
|
||||
}
|
||||
_borrowedRenderer = makeRenderer();
|
||||
}
|
||||
|
||||
void Viewport::ensureBorrowedCleared() {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(!_opengl);
|
||||
|
||||
base::take(_borrowedRenderer);
|
||||
}
|
||||
|
||||
void Viewport::borrowedPaint(Painter &p, const QRegion &clip) {
|
||||
Expects(_borrowedRenderer != nullptr);
|
||||
Expects(!_opengl);
|
||||
|
||||
@@ -103,7 +103,11 @@ public:
|
||||
void ensureBorrowedRenderer(QOpenGLFunctions &f);
|
||||
void ensureBorrowedCleared(QOpenGLFunctions *f);
|
||||
void borrowedPaint(QOpenGLFunctions &f);
|
||||
|
||||
void ensureBorrowedRenderer();
|
||||
void ensureBorrowedCleared();
|
||||
void borrowedPaint(Painter &p, const QRegion &clip);
|
||||
|
||||
[[nodiscard]] QPoint borrowedOrigin() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
@@ -460,7 +460,7 @@ void Viewport::RendererGL::validateUserpicFrame(
|
||||
}
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
tileData.userpicFrame = PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0);
|
||||
@@ -1239,7 +1239,7 @@ void Viewport::RendererGL::validateDatas() {
|
||||
j->stale = false;
|
||||
const auto index = (j - begin(_tileData));
|
||||
_tileDataIndices[i] = index;
|
||||
const auto peer = tiles[i]->row()->peer();
|
||||
const auto peer = tiles[i]->peer();
|
||||
if ((j->peer != peer)
|
||||
|| (j->nameVersion != peer->nameVersion())
|
||||
|| (j->nameRect.width() != width)) {
|
||||
@@ -1263,7 +1263,7 @@ void Viewport::RendererGL::validateDatas() {
|
||||
continue;
|
||||
}
|
||||
const auto id = quintptr(tiles[i]->track().get());
|
||||
const auto peer = tiles[i]->row()->peer();
|
||||
const auto peer = tiles[i]->peer();
|
||||
const auto paused = (tiles[i]->track()->state()
|
||||
== Webrtc::VideoState::Paused);
|
||||
auto index = int(_tileData.size());
|
||||
|
||||
@@ -52,12 +52,14 @@ void Viewport::RendererSW::paintFallback(
|
||||
}
|
||||
paintTile(p, tile.get(), bounding, bg);
|
||||
}
|
||||
const auto fullscreen = _owner->_fullscreen;
|
||||
const auto color = fullscreen
|
||||
? QColor(0, 0, 0)
|
||||
: st::groupCallBg->c;
|
||||
for (const auto &rect : bg) {
|
||||
p.fillRect(rect, color);
|
||||
if (_owner->borrowedOrigin().isNull()) {
|
||||
const auto fullscreen = _owner->_fullscreen;
|
||||
const auto color = fullscreen
|
||||
? QColor(0, 0, 0)
|
||||
: st::groupCallBg->c;
|
||||
for (const auto &rect : bg) {
|
||||
p.fillRect(rect, color);
|
||||
}
|
||||
}
|
||||
for (auto i = _tileData.begin(); i != _tileData.end();) {
|
||||
if (i->second.stale) {
|
||||
@@ -80,7 +82,7 @@ void Viewport::RendererSW::validateUserpicFrame(
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
data.userpicFrame = Images::BlurLargeImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0),
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/group/calls_group_viewport_tile.h"
|
||||
|
||||
#include "calls/group/calls_group_members_row.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/round_rect.h"
|
||||
@@ -33,11 +34,11 @@ Viewport::VideoTile::VideoTile(
|
||||
: _endpoint(endpoint)
|
||||
, _update(std::move(update))
|
||||
, _track(std::move(track))
|
||||
, _peer(_track.row->peer())
|
||||
, _trackSize(std::move(trackSize))
|
||||
, _rtmp(endpoint.rtmp())
|
||||
, _self(self) {
|
||||
Expects(_track.track != nullptr);
|
||||
Expects(_track.row != nullptr);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
_track.track->stateValue(
|
||||
|
||||
@@ -37,6 +37,9 @@ public:
|
||||
[[nodiscard]] not_null<MembersRow*> row() const {
|
||||
return _track.row;
|
||||
}
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
[[nodiscard]] bool rtmp() const {
|
||||
return _rtmp;
|
||||
}
|
||||
@@ -113,8 +116,9 @@ private:
|
||||
|
||||
const VideoEndpoint _endpoint;
|
||||
const Fn<void()> _update;
|
||||
const VideoTileTrack _track;
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
VideoTileTrack _track;
|
||||
QRect _geometry;
|
||||
TileAnimation _animation;
|
||||
rpl::variable<QSize> _trackSize;
|
||||
|
||||
@@ -41,9 +41,9 @@ inline auto PreviewPath(int i) {
|
||||
|
||||
const auto kSets = {
|
||||
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
|
||||
Set{ { 1, 2290, 8'306'943, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 2291, 5'694'303, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 2292, 7'261'223, "JoyPixels" }, PreviewPath(3) },
|
||||
Set{ { 1, 2774, 8'455'034, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 2775, 5'713'503, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 2776, 7'347'332, "JoyPixels" }, PreviewPath(3) },
|
||||
};
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 6003003;
|
||||
constexpr auto AppVersionStr = "6.3.3";
|
||||
constexpr auto AppVersion = 6003004;
|
||||
constexpr auto AppVersionStr = "6.3.4";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -407,7 +407,7 @@ int MyAuctionPosition(const GiftAuctionState &state) {
|
||||
const auto &levels = state.bidLevels;
|
||||
for (auto i = begin(levels), e = end(levels); i != e; ++i) {
|
||||
if (i->amount < state.my.bid
|
||||
|| (i->amount == state.my.bid && i->date > state.my.date)) {
|
||||
|| (i->amount == state.my.bid && i->date >= state.my.date)) {
|
||||
return i->position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ struct CreditsHistoryEntry final {
|
||||
return !id.isEmpty();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isLiveStoryReaction() const {
|
||||
return paidMessagesCount && reaction && !bareMsgId;
|
||||
}
|
||||
|
||||
using PhotoId = uint64;
|
||||
enum class PeerType {
|
||||
Peer,
|
||||
|
||||
@@ -449,7 +449,7 @@ QImage *PeerData::userpicCloudImage(Ui::PeerUserpicView &view) const {
|
||||
}
|
||||
|
||||
void PeerData::paintUserpic(
|
||||
Painter &p,
|
||||
QPainter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
PaintUserpicContext context) const {
|
||||
if (const auto broadcast = monoforumBroadcast()) {
|
||||
|
||||
@@ -387,11 +387,11 @@ public:
|
||||
void setUserpicPhoto(const MTPPhoto &data);
|
||||
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
QPainter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
PaintUserpicContext context) const;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
QPainter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
@@ -406,7 +406,7 @@ public:
|
||||
});
|
||||
}
|
||||
void paintUserpicLeft(
|
||||
Painter &p,
|
||||
QPainter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
|
||||
@@ -1062,14 +1062,15 @@ void Widget::setupTopBarSuggestions(not_null<Ui::VerticalLayout*> dialogs) {
|
||||
return;
|
||||
}
|
||||
using namespace rpl::mappers;
|
||||
crl::on_main(&session(), [=, session = &session()] {
|
||||
session->api().authorizations().unreviewedChanges(
|
||||
crl::on_main(dialogs, [=] {
|
||||
const auto owner = &session().data();
|
||||
session().api().authorizations().unreviewedChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateForceDisplayWide();
|
||||
}, lifetime());
|
||||
(session->data().chatsListLoaded(nullptr)
|
||||
(owner->chatsListLoaded(nullptr)
|
||||
? rpl::single<Data::Folder*>(nullptr)
|
||||
: session->data().chatsListLoadedEvents()
|
||||
: owner->chatsListLoadedEvents()
|
||||
) | rpl::filter(_1 == nullptr) | rpl::map([=] {
|
||||
auto on = rpl::combine(
|
||||
controller()->activeChatsFilter(),
|
||||
@@ -1090,9 +1091,9 @@ void Widget::setupTopBarSuggestions(not_null<Ui::VerticalLayout*> dialogs) {
|
||||
&& wide
|
||||
&& !search
|
||||
&& !searchInPeer
|
||||
&& (id == session->data().chatsFilters().defaultId());
|
||||
&& (id == owner->chatsFilters().defaultId());
|
||||
});
|
||||
return TopBarSuggestionValue(dialogs, session, std::move(on));
|
||||
return TopBarSuggestionValue(dialogs, &session(), std::move(on));
|
||||
}) | rpl::flatten_latest() | rpl::start_with_next([=](
|
||||
Ui::SlideWrap<Ui::RpWidget> *raw) {
|
||||
if (raw) {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
@@ -21,12 +22,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/stories/media_stories_stealth.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/painter.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_media_stories.h"
|
||||
|
||||
namespace Dialogs::Stories {
|
||||
namespace {
|
||||
@@ -243,6 +247,23 @@ void FillSourceMenu(
|
||||
add(viewProfileText, [=] {
|
||||
controller->showPeerInfo(peer);
|
||||
}, channel ? &st::menuIconInfo : &st::menuIconProfile);
|
||||
if (peer->session().premiumPossible()
|
||||
&& peer->isUser()
|
||||
&& !peer->hasActiveVideoStream()
|
||||
&& peer->hasUnreadStories()) {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto stealth = owner->stories().stealthMode();
|
||||
add(tr::lng_stories_view_anonymously(tr::now), [=] {
|
||||
Media::Stories::SetupStealthMode(
|
||||
controller->uiShow(),
|
||||
Media::Stories::StealthModeDescriptor{
|
||||
[=] { controller->openPeerStories(peer->id); },
|
||||
&st::storiesStealthStyleDefault,
|
||||
});
|
||||
}, ((peer->session().premium() || (stealth.enabledTill > now))
|
||||
? &st::menuIconStealth
|
||||
: &st::menuIconStealthLocked));
|
||||
}
|
||||
const auto in = [&](Data::StorySourcesList list) {
|
||||
return ranges::contains(
|
||||
owner->stories().sources(list),
|
||||
|
||||
@@ -225,25 +225,41 @@ struct ApiWrap::DialogsProcess : ChatsProcess {
|
||||
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
|
||||
};
|
||||
|
||||
struct ApiWrap::ChatProcess {
|
||||
Data::DialogInfo info;
|
||||
|
||||
FnMut<bool(const Data::DialogInfo &)> start;
|
||||
struct ApiWrap::AbstractMessagesProcess {
|
||||
Fn<bool(DownloadProgress)> fileProgress;
|
||||
Fn<bool(Data::MessagesSlice&&)> handleSlice;
|
||||
FnMut<void()> done;
|
||||
|
||||
FnMut<void(MTPmessages_Messages&&)> requestDone;
|
||||
|
||||
int localSplitIndex = 0;
|
||||
int32 largestIdPlusOne = 1;
|
||||
|
||||
Data::ParseMediaContext context;
|
||||
std::optional<Data::MessagesSlice> slice;
|
||||
bool lastSlice = false;
|
||||
int fileIndex = 0;
|
||||
};
|
||||
|
||||
struct ApiWrap::ChatProcess : AbstractMessagesProcess {
|
||||
Data::DialogInfo info;
|
||||
|
||||
FnMut<bool(const Data::DialogInfo &)> start;
|
||||
|
||||
int localSplitIndex = 0;
|
||||
int32 largestIdPlusOne = 1;
|
||||
};
|
||||
|
||||
struct ApiWrap::TopicProcess : AbstractMessagesProcess {
|
||||
PeerId peerId = 0;
|
||||
MTPInputPeer inputPeer;
|
||||
int32 topicRootId = 0;
|
||||
QString relativePath;
|
||||
|
||||
FnMut<bool(int count)> start;
|
||||
|
||||
int32 offsetId = 0;
|
||||
int totalCount = 0;
|
||||
int processedCount = 0;
|
||||
};
|
||||
|
||||
|
||||
template <typename Request>
|
||||
class ApiWrap::RequestBuilder {
|
||||
@@ -2080,13 +2096,18 @@ std::optional<QByteArray> ApiWrap::getCustomEmoji(QByteArray &data) {
|
||||
}
|
||||
auto &file = i->second.file;
|
||||
const auto fileProgress = [=](FileProgress value) {
|
||||
return loadMessageEmojiProgress(value);
|
||||
if (_chatProcess) {
|
||||
return loadMessageEmojiProgress(value);
|
||||
} else if (_topicProcess) {
|
||||
return loadTopicEmojiProgress(value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto ready = processFileLoad(
|
||||
file,
|
||||
{ .customEmojiId = id },
|
||||
fileProgress,
|
||||
[=](const QString &path) { loadMessageEmojiDone(id, path); });
|
||||
[=](const QString &path) { loadCustomEmojiDone(id, path); });
|
||||
if (!ready) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -2262,6 +2283,36 @@ void ApiWrap::loadMessageEmojiDone(uint64 id, const QString &relativePath) {
|
||||
loadNextMessageFile();
|
||||
}
|
||||
|
||||
bool ApiWrap::loadTopicEmojiProgress(FileProgress progress) {
|
||||
Expects(_fileProcess != nullptr);
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
Expects((_topicProcess->fileIndex >= 0)
|
||||
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
|
||||
|
||||
return _topicProcess->fileProgress(DownloadProgress{
|
||||
.randomId = _fileProcess->randomId,
|
||||
.path = _fileProcess->relativePath,
|
||||
.itemIndex = _topicProcess->fileIndex,
|
||||
.ready = progress.ready,
|
||||
.total = progress.total });
|
||||
}
|
||||
|
||||
void ApiWrap::loadCustomEmojiDone(uint64 id, const QString &relativePath) {
|
||||
const auto i = _resolvedCustomEmoji.find(id);
|
||||
if (i != end(_resolvedCustomEmoji)) {
|
||||
i->second.file.relativePath = relativePath;
|
||||
if (relativePath.isEmpty()) {
|
||||
i->second.file.skipReason = Data::File::SkipReason::Unavailable;
|
||||
}
|
||||
}
|
||||
if (_chatProcess) {
|
||||
loadNextMessageFile();
|
||||
} else if (_topicProcess) {
|
||||
loadNextTopicMessageFile();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::finishMessages() {
|
||||
Expects(_chatProcess != nullptr);
|
||||
Expects(!_chatProcess->slice.has_value());
|
||||
@@ -2270,6 +2321,316 @@ void ApiWrap::finishMessages() {
|
||||
process->done();
|
||||
}
|
||||
|
||||
void ApiWrap::requestTopicMessages(
|
||||
PeerId peerId,
|
||||
MTPInputPeer inputPeer,
|
||||
int32 topicRootId,
|
||||
FnMut<bool(int count)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::MessagesSlice&&)> slice,
|
||||
FnMut<void()> done) {
|
||||
Expects(_topicProcess == nullptr);
|
||||
Expects(_selfId.has_value());
|
||||
|
||||
_topicProcess = std::make_unique<TopicProcess>();
|
||||
_topicProcess->context.selfPeerId = peerFromUser(*_selfId);
|
||||
_topicProcess->peerId = peerId;
|
||||
_topicProcess->inputPeer = inputPeer;
|
||||
_topicProcess->topicRootId = topicRootId;
|
||||
_topicProcess->relativePath = "chats/chat_"
|
||||
+ QString::number(peerId.value)
|
||||
+ "/topic_"
|
||||
+ QString::number(topicRootId)
|
||||
+ "/";
|
||||
_topicProcess->start = std::move(start);
|
||||
_topicProcess->fileProgress = std::move(progress);
|
||||
_topicProcess->handleSlice = std::move(slice);
|
||||
_topicProcess->done = std::move(done);
|
||||
|
||||
mainRequest(MTPchannels_GetMessages(
|
||||
MTP_inputChannel(
|
||||
inputPeer.c_inputPeerChannel().vchannel_id(),
|
||||
inputPeer.c_inputPeerChannel().vaccess_hash()),
|
||||
MTP_vector<MTPInputMessage>(
|
||||
1,
|
||||
MTP_inputMessageID(MTP_int(topicRootId)))
|
||||
)).done([=](const MTPmessages_Messages &rootResult) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
auto rootSlice = rootResult.match([&](
|
||||
const MTPDmessages_messagesNotModified &) {
|
||||
return Data::MessagesSlice();
|
||||
}, [&](const auto &data) {
|
||||
return Data::ParseMessagesSlice(
|
||||
_topicProcess->context,
|
||||
data.vmessages(),
|
||||
data.vusers(),
|
||||
data.vchats(),
|
||||
_topicProcess->relativePath);
|
||||
});
|
||||
|
||||
auto rootSlicePtr = std::make_shared<Data::MessagesSlice>(
|
||||
std::move(rootSlice));
|
||||
|
||||
requestTopicReplies(
|
||||
0,
|
||||
0,
|
||||
kMessagesSliceLimit,
|
||||
[=](const MTPmessages_Messages &result) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
const auto count = result.match(
|
||||
[](const MTPDmessages_messages &data) {
|
||||
return int(data.vmessages().v.size());
|
||||
}, [](const MTPDmessages_messagesSlice &data) {
|
||||
return data.vcount().v;
|
||||
}, [](const MTPDmessages_channelMessages &data) {
|
||||
return data.vcount().v;
|
||||
}, [](const MTPDmessages_messagesNotModified &data) {
|
||||
return -1;
|
||||
});
|
||||
if (count < 0) {
|
||||
error("Unexpected messagesNotModified received.");
|
||||
return;
|
||||
}
|
||||
_topicProcess->totalCount = count;
|
||||
if (!_topicProcess->start(count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rootSlicePtr->list.empty()) {
|
||||
collectMessagesCustomEmoji(*rootSlicePtr);
|
||||
_topicProcess->slice = std::move(*rootSlicePtr);
|
||||
_topicProcess->fileIndex = 0;
|
||||
resolveTopicCustomEmoji();
|
||||
return;
|
||||
}
|
||||
|
||||
requestTopicMessagesSlice();
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestTopicMessagesSlice() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
const auto offsetId = (_topicProcess->offsetId == 0)
|
||||
? 1
|
||||
: (_topicProcess->offsetId + 1);
|
||||
requestTopicReplies(
|
||||
offsetId,
|
||||
-kMessagesSliceLimit,
|
||||
kMessagesSliceLimit,
|
||||
[=](const MTPmessages_Messages &result) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
result.match([&](const MTPDmessages_messagesNotModified &data) {
|
||||
error("Unexpected messagesNotModified received.");
|
||||
}, [&](const auto &data) {
|
||||
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
|
||||
_topicProcess->lastSlice = true;
|
||||
}
|
||||
auto slice = Data::ParseMessagesSlice(
|
||||
_topicProcess->context,
|
||||
data.vmessages(),
|
||||
data.vusers(),
|
||||
data.vchats(),
|
||||
_topicProcess->relativePath);
|
||||
if (slice.list.empty()) {
|
||||
_topicProcess->lastSlice = true;
|
||||
}
|
||||
loadTopicMessagesFiles(std::move(slice));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::requestTopicReplies(
|
||||
int offsetId,
|
||||
int addOffset,
|
||||
int limit,
|
||||
FnMut<void(MTPmessages_Messages&&)> done) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
_topicProcess->requestDone = std::move(done);
|
||||
const auto doneHandler = [=](MTPmessages_Messages &&result) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
base::take(_topicProcess->requestDone)(std::move(result));
|
||||
};
|
||||
|
||||
mainRequest(MTPmessages_GetReplies(
|
||||
_topicProcess->inputPeer,
|
||||
MTP_int(_topicProcess->topicRootId),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(0),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_long(0)
|
||||
)).done(doneHandler).send();
|
||||
}
|
||||
|
||||
void ApiWrap::loadTopicMessagesFiles(Data::MessagesSlice &&slice) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(!_topicProcess->slice.has_value());
|
||||
|
||||
collectMessagesCustomEmoji(slice);
|
||||
|
||||
if (slice.list.empty()) {
|
||||
_topicProcess->lastSlice = true;
|
||||
}
|
||||
_topicProcess->slice = std::move(slice);
|
||||
_topicProcess->fileIndex = 0;
|
||||
|
||||
resolveTopicCustomEmoji();
|
||||
}
|
||||
|
||||
void ApiWrap::resolveTopicCustomEmoji() {
|
||||
if (_unresolvedCustomEmoji.empty()) {
|
||||
loadNextTopicMessageFile();
|
||||
return;
|
||||
}
|
||||
const auto count = std::min(
|
||||
int(_unresolvedCustomEmoji.size()),
|
||||
kMaxEmojiPerRequest);
|
||||
auto v = QVector<MTPlong>();
|
||||
v.reserve(count);
|
||||
const auto till = end(_unresolvedCustomEmoji);
|
||||
const auto from = end(_unresolvedCustomEmoji) - count;
|
||||
for (auto i = from; i != till; ++i) {
|
||||
v.push_back(MTP_long(*i));
|
||||
}
|
||||
_unresolvedCustomEmoji.erase(from, till);
|
||||
const auto finalize = [=] {
|
||||
for (const auto &id : v) {
|
||||
if (_resolvedCustomEmoji.contains(id.v)) {
|
||||
continue;
|
||||
}
|
||||
_resolvedCustomEmoji.emplace(
|
||||
id.v,
|
||||
Data::Document{
|
||||
.file = {
|
||||
.skipReason = Data::File::SkipReason::Unavailable,
|
||||
},
|
||||
});
|
||||
}
|
||||
resolveTopicCustomEmoji();
|
||||
};
|
||||
mainRequest(MTPmessages_GetCustomEmojiDocuments(
|
||||
MTP_vector<MTPlong>(v)
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
LOG(("Export Error: Failed to get documents for emoji."));
|
||||
finalize();
|
||||
return true;
|
||||
}).done([=](const MTPVector<MTPDocument> &result) {
|
||||
for (const auto &entry : result.v) {
|
||||
auto document = Data::ParseDocument(
|
||||
_topicProcess->context,
|
||||
entry,
|
||||
_topicProcess->relativePath,
|
||||
TimeId());
|
||||
_resolvedCustomEmoji.emplace(document.id, std::move(document));
|
||||
}
|
||||
finalize();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::loadNextTopicMessageFile() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
|
||||
const auto makeProgress = [=](FileProgress progress) {
|
||||
return _topicProcess->fileProgress(DownloadProgress{
|
||||
.randomId = _fileProcess->randomId,
|
||||
.path = _fileProcess->relativePath,
|
||||
.itemIndex = _topicProcess->fileIndex,
|
||||
.ready = progress.ready,
|
||||
.total = progress.total,
|
||||
});
|
||||
};
|
||||
for (auto &list = _topicProcess->slice->list
|
||||
; _topicProcess->fileIndex < list.size()
|
||||
; ++_topicProcess->fileIndex) {
|
||||
auto &message = list[_topicProcess->fileIndex];
|
||||
if (!messageCustomEmojiReady(message)) {
|
||||
return;
|
||||
}
|
||||
const auto origin = Data::FileOrigin{
|
||||
.peer = _topicProcess->inputPeer,
|
||||
.messageId = message.id
|
||||
};
|
||||
const auto ready = processFileLoad(
|
||||
message.file(),
|
||||
origin,
|
||||
makeProgress,
|
||||
[=, &message](const QString &path) {
|
||||
loadTopicMessageFileOrThumbDone(message.file(), path);
|
||||
},
|
||||
&message);
|
||||
if (!ready) {
|
||||
return;
|
||||
}
|
||||
const auto thumbReady = processFileLoad(
|
||||
message.thumb().file,
|
||||
origin,
|
||||
makeProgress,
|
||||
[=, &message](const QString &path) {
|
||||
loadTopicMessageFileOrThumbDone(message.thumb().file, path);
|
||||
},
|
||||
&message);
|
||||
if (!thumbReady) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
finishTopicMessagesSlice();
|
||||
}
|
||||
|
||||
void ApiWrap::finishTopicMessagesSlice() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
|
||||
auto slice = *base::take(_topicProcess->slice);
|
||||
if (!slice.list.empty()) {
|
||||
_topicProcess->offsetId = slice.list.back().id;
|
||||
_topicProcess->processedCount += slice.list.size();
|
||||
if (!_topicProcess->handleSlice(std::move(slice))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto reachedTotal = _topicProcess->totalCount > 0
|
||||
&& _topicProcess->processedCount >= _topicProcess->totalCount;
|
||||
|
||||
if (!_topicProcess->lastSlice && !reachedTotal) {
|
||||
requestTopicMessagesSlice();
|
||||
} else {
|
||||
finishTopicMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::loadTopicMessageFileOrThumbDone(
|
||||
Data::File &file,
|
||||
const QString &relativePath) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
Expects((_topicProcess->fileIndex >= 0)
|
||||
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
|
||||
|
||||
file.relativePath = relativePath;
|
||||
if (relativePath.isEmpty()) {
|
||||
file.skipReason = Data::File::SkipReason::Unavailable;
|
||||
}
|
||||
loadNextTopicMessageFile();
|
||||
}
|
||||
|
||||
void ApiWrap::finishTopicMessages() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(!_topicProcess->slice.has_value());
|
||||
|
||||
const auto process = base::take(_topicProcess);
|
||||
process->done();
|
||||
}
|
||||
|
||||
bool ApiWrap::processFileLoad(
|
||||
Data::File &file,
|
||||
const Data::FileOrigin &origin,
|
||||
|
||||
@@ -106,6 +106,15 @@ public:
|
||||
Fn<bool(Data::MessagesSlice&&)> slice,
|
||||
FnMut<void()> done);
|
||||
|
||||
void requestTopicMessages(
|
||||
PeerId peerId,
|
||||
MTPInputPeer inputPeer,
|
||||
int32 topicRootId,
|
||||
FnMut<bool(int count)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::MessagesSlice&&)> slice,
|
||||
FnMut<void()> done);
|
||||
|
||||
void finishExport(FnMut<void()> done);
|
||||
void skipFile(uint64 randomId);
|
||||
void cancelExportFast();
|
||||
@@ -125,7 +134,9 @@ private:
|
||||
struct ChatsProcess;
|
||||
struct LeftChannelsProcess;
|
||||
struct DialogsProcess;
|
||||
struct AbstractMessagesProcess;
|
||||
struct ChatProcess;
|
||||
struct TopicProcess;
|
||||
|
||||
void startMainSession(FnMut<void()> done);
|
||||
void sendNextStartRequest();
|
||||
@@ -202,6 +213,12 @@ private:
|
||||
int addOffset,
|
||||
int limit,
|
||||
FnMut<void(MTPmessages_Messages&&)> done);
|
||||
void requestTopicMessagesSlice();
|
||||
void requestTopicReplies(
|
||||
int offsetId,
|
||||
int addOffset,
|
||||
int limit,
|
||||
FnMut<void(MTPmessages_Messages&&)> done);
|
||||
void collectMessagesCustomEmoji(const Data::MessagesSlice &slice);
|
||||
void resolveCustomEmoji();
|
||||
void loadMessagesFiles(Data::MessagesSlice &&slice);
|
||||
@@ -217,6 +234,17 @@ private:
|
||||
void finishMessagesSlice();
|
||||
void finishMessages();
|
||||
|
||||
void loadTopicMessagesFiles(Data::MessagesSlice &&slice);
|
||||
void resolveTopicCustomEmoji();
|
||||
void loadNextTopicMessageFile();
|
||||
bool loadTopicEmojiProgress(FileProgress progress);
|
||||
void loadCustomEmojiDone(uint64 id, const QString &relativePath);
|
||||
void loadTopicMessageFileOrThumbDone(
|
||||
Data::File &file,
|
||||
const QString &relativePath);
|
||||
void finishTopicMessagesSlice();
|
||||
void finishTopicMessages();
|
||||
|
||||
[[nodiscard]] Data::Message *currentFileMessage() const;
|
||||
[[nodiscard]] Data::FileOrigin currentFileMessageOrigin() const;
|
||||
|
||||
@@ -285,6 +313,7 @@ private:
|
||||
std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;
|
||||
std::unique_ptr<DialogsProcess> _dialogsProcess;
|
||||
std::unique_ptr<ChatProcess> _chatProcess;
|
||||
std::unique_ptr<TopicProcess> _topicProcess;
|
||||
base::flat_set<uint64> _unresolvedCustomEmoji;
|
||||
base::flat_map<uint64, Data::Document> _resolvedCustomEmoji;
|
||||
QVector<MTPMessageRange> _splits;
|
||||
|
||||
@@ -37,6 +37,13 @@ public:
|
||||
crl::weak_on_queue<ControllerObject> weak,
|
||||
QPointer<MTP::Instance> mtproto,
|
||||
const MTPInputPeer &peer);
|
||||
ControllerObject(
|
||||
crl::weak_on_queue<ControllerObject> weak,
|
||||
QPointer<MTP::Instance> mtproto,
|
||||
const MTPInputPeer &peer,
|
||||
int32 topicRootId,
|
||||
uint64 peerId,
|
||||
const QString &topicTitle);
|
||||
|
||||
rpl::producer<State> state() const;
|
||||
|
||||
@@ -82,6 +89,7 @@ private:
|
||||
void exportOtherData();
|
||||
void exportDialogs();
|
||||
void exportNextDialog();
|
||||
void exportTopic();
|
||||
|
||||
template <typename Callback = const decltype(kNullStateCallback) &>
|
||||
ProcessingState prepareState(
|
||||
@@ -97,6 +105,7 @@ private:
|
||||
ProcessingState stateSessions() const;
|
||||
ProcessingState stateOtherData() const;
|
||||
ProcessingState stateDialogs(const DownloadProgress &progress) const;
|
||||
ProcessingState stateTopic(const DownloadProgress &progress) const;
|
||||
void fillMessagesState(
|
||||
ProcessingState &result,
|
||||
const Data::DialogsInfo &info,
|
||||
@@ -139,6 +148,10 @@ private:
|
||||
std::vector<Step> _steps;
|
||||
int _stepIndex = -1;
|
||||
|
||||
int32 _topicRootId = 0;
|
||||
uint64 _topicPeerId = 0;
|
||||
QString _topicTitle;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
@@ -167,6 +180,36 @@ ControllerObject::ControllerObject(
|
||||
setState(std::move(state));
|
||||
}
|
||||
|
||||
ControllerObject::ControllerObject(
|
||||
crl::weak_on_queue<ControllerObject> weak,
|
||||
QPointer<MTP::Instance> mtproto,
|
||||
const MTPInputPeer &peer,
|
||||
int32 topicRootId,
|
||||
uint64 peerId,
|
||||
const QString &topicTitle)
|
||||
: _api(mtproto, weak.runner())
|
||||
, _state(PasswordCheckState{})
|
||||
, _topicRootId(topicRootId)
|
||||
, _topicPeerId(peerId)
|
||||
, _topicTitle(topicTitle) {
|
||||
_api.errors(
|
||||
) | rpl::start_with_next([=](const MTP::Error &error) {
|
||||
setState(ApiErrorState{ error });
|
||||
}, _lifetime);
|
||||
|
||||
_api.ioErrors(
|
||||
) | rpl::start_with_next([=](const Output::Result &result) {
|
||||
ioCatchError(result);
|
||||
}, _lifetime);
|
||||
|
||||
//requestPasswordState();
|
||||
auto state = PasswordCheckState();
|
||||
state.checked = false;
|
||||
state.requesting = false;
|
||||
state.singlePeer = peer;
|
||||
setState(std::move(state));
|
||||
}
|
||||
|
||||
rpl::producer<State> ControllerObject::state() const {
|
||||
return rpl::single(
|
||||
_state
|
||||
@@ -257,6 +300,8 @@ void ControllerObject::startExport(
|
||||
}
|
||||
_settings = NormalizeSettings(settings);
|
||||
_environment = environment;
|
||||
_settings.singleTopicRootId = _topicRootId;
|
||||
_settings.singleTopicPeerId = _topicPeerId;
|
||||
|
||||
_settings.path = Output::NormalizePath(_settings);
|
||||
_writer = Output::CreateWriter(_settings.format);
|
||||
@@ -274,6 +319,10 @@ void ControllerObject::skipFile(uint64 randomId) {
|
||||
void ControllerObject::fillExportSteps() {
|
||||
using Type = Settings::Type;
|
||||
_steps.push_back(Step::Initializing);
|
||||
if (_settings.onlySingleTopic()) {
|
||||
_steps.push_back(Step::Topic);
|
||||
return;
|
||||
}
|
||||
if (_settings.types & Type::AnyChatsMask) {
|
||||
_steps.push_back(Step::DialogsList);
|
||||
}
|
||||
@@ -340,6 +389,9 @@ void ControllerObject::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
|
||||
if (_settings.types & Settings::Type::AnyChatsMask) {
|
||||
push(Step::Dialogs, info.dialogsCount);
|
||||
}
|
||||
if (_settings.onlySingleTopic()) {
|
||||
push(Step::Topic, 1);
|
||||
}
|
||||
_substepsInStep = std::move(result);
|
||||
_substepsTotal = ranges::accumulate(_substepsInStep, 0);
|
||||
}
|
||||
@@ -372,6 +424,7 @@ void ControllerObject::exportNext() {
|
||||
case Step::Sessions: return exportSessions();
|
||||
case Step::OtherData: return exportOtherData();
|
||||
case Step::Dialogs: return exportDialogs();
|
||||
case Step::Topic: return exportTopic();
|
||||
}
|
||||
Unexpected("Step in ControllerObject::exportNext.");
|
||||
}
|
||||
@@ -708,6 +761,71 @@ int ControllerObject::substepsInStep(Step step) const {
|
||||
return _substepsInStep[static_cast<int>(step)];
|
||||
}
|
||||
|
||||
void ControllerObject::exportTopic() {
|
||||
auto topicInfo = Data::DialogInfo();
|
||||
topicInfo.type = Data::DialogInfo::Type::PublicSupergroup;
|
||||
topicInfo.name = _topicTitle.toUtf8();
|
||||
topicInfo.peerId = PeerId(_topicPeerId);
|
||||
topicInfo.relativePath = QString();
|
||||
|
||||
if (ioCatchError(_writer->writeDialogStart(topicInfo))) {
|
||||
return;
|
||||
}
|
||||
|
||||
_api.requestTopicMessages(
|
||||
PeerId(_topicPeerId),
|
||||
_settings.singlePeer,
|
||||
_topicRootId,
|
||||
[=](int count) {
|
||||
_messagesWritten = 0;
|
||||
_messagesCount = count;
|
||||
setState(stateTopic(DownloadProgress()));
|
||||
return true;
|
||||
},
|
||||
[=](DownloadProgress progress) {
|
||||
setState(stateTopic(progress));
|
||||
return true;
|
||||
},
|
||||
[=](Data::MessagesSlice &&slice) {
|
||||
if (ioCatchError(_writer->writeDialogSlice(slice))) {
|
||||
return false;
|
||||
}
|
||||
_messagesWritten += slice.list.size();
|
||||
setState(stateTopic(DownloadProgress()));
|
||||
return true;
|
||||
},
|
||||
[=] {
|
||||
if (ioCatchError(_writer->writeDialogEnd())) {
|
||||
return;
|
||||
}
|
||||
if (ioCatchError(_writer->finish())) {
|
||||
return;
|
||||
}
|
||||
_api.finishExport([=] {
|
||||
setFinishedState();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ProcessingState ControllerObject::stateTopic(
|
||||
const DownloadProgress &progress) const {
|
||||
return prepareState(Step::Topic, [&](ProcessingState &result) {
|
||||
result.entityType = ProcessingState::EntityType::Topic;
|
||||
result.entityName = _topicTitle;
|
||||
result.entityIndex = 0;
|
||||
result.entityCount = 1;
|
||||
result.itemIndex = _messagesWritten + progress.itemIndex;
|
||||
result.itemCount = std::max(_messagesCount, result.itemIndex);
|
||||
result.bytesRandomId = progress.randomId;
|
||||
if (!progress.path.isEmpty()) {
|
||||
const auto last = progress.path.lastIndexOf('/');
|
||||
result.bytesName = progress.path.mid(last + 1);
|
||||
}
|
||||
result.bytesLoaded = progress.ready;
|
||||
result.bytesCount = progress.total;
|
||||
});
|
||||
}
|
||||
|
||||
void ControllerObject::setFinishedState() {
|
||||
setState(FinishedState{
|
||||
_writer->mainFilePath(),
|
||||
@@ -721,6 +839,20 @@ Controller::Controller(
|
||||
: _wrapped(std::move(mtproto), peer) {
|
||||
}
|
||||
|
||||
Controller::Controller(
|
||||
QPointer<MTP::Instance> mtproto,
|
||||
const MTPInputPeer &peer,
|
||||
int32 topicRootId,
|
||||
uint64 peerId,
|
||||
const QString &topicTitle)
|
||||
: _wrapped(
|
||||
std::move(mtproto),
|
||||
peer,
|
||||
static_cast<int32>(topicRootId),
|
||||
static_cast<uint64>(peerId),
|
||||
topicTitle) {
|
||||
}
|
||||
|
||||
rpl::producer<State> Controller::state() const {
|
||||
return _wrapped.producer_on_main([=](const Implementation &unwrapped) {
|
||||
return unwrapped.state();
|
||||
|
||||
@@ -44,12 +44,14 @@ struct ProcessingState {
|
||||
Sessions,
|
||||
OtherData,
|
||||
Dialogs,
|
||||
Topic,
|
||||
};
|
||||
enum class EntityType {
|
||||
Chat,
|
||||
SavedMessages,
|
||||
RepliesMessages,
|
||||
VerifyCodes,
|
||||
Topic,
|
||||
Other,
|
||||
};
|
||||
|
||||
@@ -115,6 +117,12 @@ public:
|
||||
Controller(
|
||||
QPointer<MTP::Instance> mtproto,
|
||||
const MTPInputPeer &peer);
|
||||
Controller(
|
||||
QPointer<MTP::Instance> mtproto,
|
||||
const MTPInputPeer &peer,
|
||||
int32 topicRootId,
|
||||
uint64 peerId,
|
||||
const QString &topicTitle);
|
||||
|
||||
rpl::producer<State> state() const;
|
||||
|
||||
|
||||
@@ -25,6 +25,23 @@ void Manager::start(not_null<PeerData*> peer) {
|
||||
start(&peer->session(), peer->input);
|
||||
}
|
||||
|
||||
void Manager::startTopic(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
const QString &topicTitle) {
|
||||
if (_panel) {
|
||||
_panel->activatePanel();
|
||||
return;
|
||||
}
|
||||
_controller = std::make_unique<Controller>(
|
||||
&peer->session().mtp(),
|
||||
peer->input,
|
||||
int32(topicRootId.bare),
|
||||
uint64(peer->id.value),
|
||||
topicTitle);
|
||||
setupPanel(&peer->session());
|
||||
}
|
||||
|
||||
void Manager::start(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPInputPeer &singlePeer) {
|
||||
@@ -35,6 +52,10 @@ void Manager::start(
|
||||
_controller = std::make_unique<Controller>(
|
||||
&session->mtp(),
|
||||
singlePeer);
|
||||
setupPanel(session);
|
||||
}
|
||||
|
||||
void Manager::setupPanel(not_null<Main::Session*> session) {
|
||||
_panel = std::make_unique<View::PanelController>(
|
||||
session,
|
||||
_controller.get());
|
||||
|
||||
@@ -34,6 +34,10 @@ public:
|
||||
void start(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPInputPeer &singlePeer = MTP_inputPeerEmpty());
|
||||
void startTopic(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
const QString &topicTitle);
|
||||
|
||||
[[nodiscard]] rpl::producer<View::PanelController*> currentView() const;
|
||||
[[nodiscard]] bool inProgress() const;
|
||||
@@ -42,6 +46,8 @@ public:
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void setupPanel(not_null<Main::Session*> session);
|
||||
|
||||
std::unique_ptr<Controller> _controller;
|
||||
std::unique_ptr<View::PanelController> _panel;
|
||||
rpl::event_stream<View::PanelController*> _viewChanges;
|
||||
|
||||
@@ -88,12 +88,20 @@ struct Settings {
|
||||
TimeId singlePeerFrom = 0;
|
||||
TimeId singlePeerTill = 0;
|
||||
|
||||
int32 singleTopicRootId = 0;
|
||||
uint64 singleTopicPeerId = 0;
|
||||
QString singleTopicTitle;
|
||||
|
||||
TimeId availableAt = 0;
|
||||
|
||||
bool onlySinglePeer() const {
|
||||
return singlePeer.type() != mtpc_inputPeerEmpty;
|
||||
}
|
||||
|
||||
bool onlySingleTopic() const {
|
||||
return onlySinglePeer() && singleTopicRootId != 0;
|
||||
}
|
||||
|
||||
static inline Types DefaultTypes() {
|
||||
return Type::PersonalInfo
|
||||
| Type::Userpics
|
||||
|
||||
@@ -752,6 +752,7 @@ QByteArray SerializeMessage(
|
||||
pushBare(
|
||||
"forwarded_from",
|
||||
wrapPeerName(message.forwardedFromId));
|
||||
push("forwarded_from_id", message.forwardedFromId);
|
||||
} else if (!message.forwardedFromName.isEmpty()) {
|
||||
pushBare(
|
||||
"forwarded_from",
|
||||
|
||||
@@ -138,6 +138,26 @@ Content ContentFromState(
|
||||
state.bytesName,
|
||||
state.bytesRandomId);
|
||||
break;
|
||||
case Step::Topic:
|
||||
pushMain(tr::lng_export_state_chats(tr::now));
|
||||
push(
|
||||
"topic",
|
||||
state.entityName.isEmpty()
|
||||
? tr::lng_deleted(tr::now)
|
||||
: state.entityName,
|
||||
(state.itemCount > 0
|
||||
? (QString::number(state.itemIndex)
|
||||
+ " / "
|
||||
+ QString::number(state.itemCount))
|
||||
: QString()),
|
||||
(state.itemCount > 0
|
||||
? (state.itemIndex / float64(state.itemCount))
|
||||
: 0.));
|
||||
pushBytes(
|
||||
"file_topic_" + QString::number(state.itemIndex),
|
||||
state.bytesName,
|
||||
state.bytesRandomId);
|
||||
break;
|
||||
default: Unexpected("Step in ContentFromState.");
|
||||
}
|
||||
const auto requiredRows = settings->onlySinglePeer() ? 2 : 3;
|
||||
|
||||
@@ -168,10 +168,13 @@ void PanelController::activatePanel() {
|
||||
|
||||
void PanelController::createPanel() {
|
||||
const auto singlePeer = _settings->onlySinglePeer();
|
||||
const auto singleTopic = _settings->onlySingleTopic();
|
||||
_panel = base::make_unique_q<Ui::SeparatePanel>(Ui::SeparatePanelArgs{
|
||||
.onAllSpaces = true,
|
||||
});
|
||||
_panel->setTitle((singlePeer
|
||||
_panel->setTitle((singleTopic
|
||||
? tr::lng_export_header_topic
|
||||
: singlePeer
|
||||
? tr::lng_export_header_chats
|
||||
: tr::lng_export_title)());
|
||||
_panel->setInnerSize(st::exportPanelSize);
|
||||
|
||||
@@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/painter.h" // remove when History::paintUserpic accepts QPainter
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/application.h"
|
||||
|
||||
@@ -307,7 +307,7 @@ HistoryWidget::HistoryWidget(
|
||||
, _paidReactionToast(std::make_unique<HistoryView::PaidReactionToast>(
|
||||
this,
|
||||
&session().data(),
|
||||
rpl::single(st::topBarHeight),
|
||||
rpl::single(0),
|
||||
[=](not_null<const HistoryView::Element*> view) {
|
||||
return _list && _list->itemTop(view) >= 0;
|
||||
}))
|
||||
@@ -5072,7 +5072,9 @@ void HistoryWidget::doneShow() {
|
||||
|
||||
void HistoryWidget::cornerButtonsShowAtPosition(
|
||||
Data::MessagePosition position) {
|
||||
if (position == Data::UnreadMessagePosition) {
|
||||
if (!_peer) {
|
||||
return;
|
||||
} else if (position == Data::UnreadMessagePosition) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Show at unread requested."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
|
||||
@@ -99,4 +99,9 @@ enum class ToggleCommentsState {
|
||||
WithNew,
|
||||
};
|
||||
|
||||
struct SendStarButtonEffect {
|
||||
not_null<PeerData*> from;
|
||||
int stars = 0;
|
||||
};
|
||||
|
||||
} // namespace HistoryView::Controls
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/event_filter.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/random.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/edit_caption_box.h"
|
||||
@@ -50,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "history/history.h"
|
||||
@@ -89,13 +91,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/silent_toggle.h"
|
||||
#include "ui/chat/choose_send_as.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/effects/reaction_fly_animation.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "mainwindow.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace HistoryView {
|
||||
@@ -110,6 +115,12 @@ constexpr auto kMouseEvents = {
|
||||
QEvent::MouseButtonRelease
|
||||
};
|
||||
constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
|
||||
constexpr auto kMaxStarSendEffects = 4;
|
||||
constexpr auto kMaxStarEffects = 4;
|
||||
constexpr auto kStarEffectDuration = 2 * crl::time(1000);
|
||||
constexpr auto kStarEffectRotationMax = 12;
|
||||
constexpr auto kStarEffectScaleMin = 0.3;
|
||||
constexpr auto kStarEffectScaleMax = 0.7;
|
||||
|
||||
constexpr auto kCommonModifiers = 0
|
||||
| Qt::ShiftModifier
|
||||
@@ -880,6 +891,87 @@ SendMenu::Details FieldHeader::saveMenuDetails(bool hasSendText) const {
|
||||
: SendMenu::Details();
|
||||
}
|
||||
|
||||
struct ComposeControls::StarEffect {
|
||||
StarEffect(
|
||||
not_null<Ui::RpWidget*> canvas,
|
||||
SendStarButtonEffect effect);
|
||||
|
||||
Ui::ReactionFlyAnimation around;
|
||||
Ui::PeerUserpicView userpic;
|
||||
QImage badge;
|
||||
not_null<PeerData*> from;
|
||||
crl::time start = 0;
|
||||
float64 shift = 0.;
|
||||
float64 progress = 0.;
|
||||
int stars = 0;
|
||||
};
|
||||
|
||||
ComposeControls::StarEffect::StarEffect(
|
||||
not_null<Ui::RpWidget*> canvas,
|
||||
SendStarButtonEffect effect)
|
||||
: around(
|
||||
&effect.from->owner().reactions(),
|
||||
Ui::ReactionFlyAnimationArgs{
|
||||
.id = Data::ReactionId::Paid(),
|
||||
.effectOnly = true,
|
||||
},
|
||||
[canvas] { canvas->update(); },
|
||||
st::reactionInlineImage)
|
||||
, from(effect.from)
|
||||
, start(crl::now())
|
||||
, stars(effect.stars) {
|
||||
auto price = Ui::Text::String(
|
||||
st::whoReadDateStyle,
|
||||
Ui::Text::IconEmoji(
|
||||
&st::starIconEmojiSmall
|
||||
).append(Lang::FormatCountDecimal(stars)),
|
||||
kMarkupTextOptions);
|
||||
const auto padding = st::groupCallEffectPadding;
|
||||
const auto priceHeight = st::whoReadDateStyle.font->height;
|
||||
const auto priceTop = padding.top();
|
||||
const auto height = priceTop + priceHeight + padding.bottom();
|
||||
|
||||
const auto userpicPadding = st::groupCallEffectUserpicPadding;
|
||||
const auto userpicSize = height
|
||||
- userpicPadding.top()
|
||||
- userpicPadding.bottom();
|
||||
|
||||
const auto leftSkip = userpicPadding.left()
|
||||
+ userpicSize
|
||||
+ userpicPadding.right();
|
||||
const auto widthSkip = leftSkip + padding.right();
|
||||
|
||||
const auto width = widthSkip + price.maxWidth();
|
||||
const auto priceLeft = leftSkip;
|
||||
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
badge = QImage(
|
||||
QSize(width, height) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
badge.fill(Qt::transparent);
|
||||
badge.setDevicePixelRatio(ratio);
|
||||
|
||||
auto p = QPainter(&badge);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto bg = Ui::ColorFromSerialized(StarsColoringForCount(
|
||||
from->session().appConfig().groupCallColorings(),
|
||||
stars).bgLight);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(bg);
|
||||
p.drawRoundedRect(0, 0, width, height, height / 2., height / 2.);
|
||||
from->paintUserpic(p, userpic, PaintUserpicContext{
|
||||
.position = QPoint(userpicPadding.left(), userpicPadding.top()),
|
||||
.size = userpicSize,
|
||||
.shape = Ui::PeerUserpicShape::Circle,
|
||||
});
|
||||
p.setPen(st::white);
|
||||
price.draw(p, {
|
||||
.position = QPoint(priceLeft, priceTop),
|
||||
.availableWidth = price.maxWidth(),
|
||||
});
|
||||
shift = base::RandomIndex(360) / 360.;
|
||||
}
|
||||
|
||||
ComposeControls::ComposeControls(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
ComposeControlsDescriptor descriptor)
|
||||
@@ -911,7 +1003,7 @@ ComposeControls::ComposeControls(
|
||||
? _regularWindow->tabbedSelector()
|
||||
: not_null(_ownedSelector.get()))
|
||||
, _mode(descriptor.mode)
|
||||
, _wrap(std::make_unique<Ui::RpWidget>(parent))
|
||||
, _wrap(std::make_unique<Ui::RpWidget>(_parent))
|
||||
, _send(std::make_shared<Ui::SendButton>(_wrap.get(), _st.send))
|
||||
, _like(_features.likes
|
||||
? Ui::CreateChild<Ui::IconButton>(_wrap.get(), _st.like)
|
||||
@@ -944,7 +1036,7 @@ ComposeControls::ComposeControls(
|
||||
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
|
||||
_wrap.get(),
|
||||
Controls::VoiceRecordBarDescriptor{
|
||||
.outerContainer = parent,
|
||||
.outerContainer = _parent,
|
||||
.show = _show,
|
||||
.send = _send,
|
||||
.customCancelText = descriptor.voiceCustomCancelText,
|
||||
@@ -1334,7 +1426,8 @@ rpl::producer<> ComposeControls::commentsShownToggles() const {
|
||||
}
|
||||
|
||||
void ComposeControls::setStarsReactionCounter(
|
||||
rpl::producer<Ui::SendStarButtonState> count) {
|
||||
rpl::producer<Ui::SendStarButtonState> count,
|
||||
rpl::producer<SendStarButtonEffect> effects) {
|
||||
if (!count) {
|
||||
delete base::take(_starsReaction);
|
||||
updateControlsGeometry(_wrap->size());
|
||||
@@ -1355,14 +1448,16 @@ void ComposeControls::setStarsReactionCounter(
|
||||
_starsReaction->setAcceptBoth();
|
||||
_starsReaction->clicks(
|
||||
) | rpl::start_with_next([=](Qt::MouseButton button) {
|
||||
if (button == Qt::LeftButton) {
|
||||
if (_chosenStarsCount && button == Qt::LeftButton) {
|
||||
_starsReactionIncrements.fire({ .count = 1 });
|
||||
startStarsSendEffect();
|
||||
} else {
|
||||
_show->show(Calls::Group::MakeVideoStreamStarsBox({
|
||||
.show = _show,
|
||||
.top = _starsReactionTop.current(),
|
||||
.current = 0,
|
||||
.sending = true,
|
||||
.admin = !_chosenStarsCount,
|
||||
.save = crl::guard(_starsReaction, [=](int count) {
|
||||
_starsReactionIncrements.fire({
|
||||
.count = count,
|
||||
@@ -1371,12 +1466,203 @@ void ComposeControls::setStarsReactionCounter(
|
||||
}),
|
||||
.name = _history ? _history->peer->shortName() : QString(),
|
||||
}));
|
||||
|
||||
}
|
||||
}, _starsReaction->lifetime());
|
||||
|
||||
std::move(
|
||||
effects
|
||||
) | rpl::start_with_next([=](const SendStarButtonEffect &event) {
|
||||
startStarsEffect(event);
|
||||
}, _starsReaction->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
void ComposeControls::startStarsSendEffect() {
|
||||
if (!_starSendEffectsCanvas) {
|
||||
setupStarsSendEffectsCanvas();
|
||||
}
|
||||
while (_starSendEffects.size() >= kMaxStarSendEffects) {
|
||||
_starSendEffects.erase(begin(_starSendEffects));
|
||||
}
|
||||
_starSendEffects.push_back(std::make_unique<Ui::ReactionFlyAnimation>(
|
||||
&_show->session().data().reactions(),
|
||||
Ui::ReactionFlyAnimationArgs{
|
||||
.id = Data::ReactionId::Paid(),
|
||||
.effectOnly = true,
|
||||
},
|
||||
[raw = _starSendEffectsCanvas.get()] { raw->update(); },
|
||||
st::reactionInlineImage));
|
||||
}
|
||||
|
||||
void ComposeControls::setupStarsSendEffectsCanvas() {
|
||||
_starSendEffectsCanvas = std::make_unique<Ui::RpWidget>(_parent);
|
||||
|
||||
const auto raw = _starSendEffectsCanvas.get();
|
||||
raw->show();
|
||||
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto effectSize = st::reactionInlineImage * 2;
|
||||
|
||||
rpl::combine(
|
||||
_wrap->geometryValue(),
|
||||
_writeRestricted->geometryValue(),
|
||||
_starsReaction->geometryValue()
|
||||
) | rpl::start_with_next([=](QRect wrap, QRect restriction, QRect star) {
|
||||
const auto parent = (_starsReaction->parentWidget() == _wrap.get())
|
||||
? wrap
|
||||
: restriction;
|
||||
const auto adjusted = star.translated(parent.topLeft());
|
||||
|
||||
raw->setGeometry(
|
||||
adjusted.x() + (adjusted.width() - effectSize) / 2,
|
||||
adjusted.y() + (adjusted.height() - effectSize) / 2,
|
||||
effectSize,
|
||||
effectSize);
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
for (auto i = begin(_starSendEffects); i != end(_starSendEffects);) {
|
||||
if ((*i)->finished()) {
|
||||
i = _starSendEffects.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_starSendEffects.empty()) {
|
||||
crl::on_main(raw, [=] {
|
||||
if (_starSendEffectsCanvas.get() == raw
|
||||
&& _starSendEffects.empty()) {
|
||||
_starSendEffectsCanvas = nullptr;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(raw);
|
||||
const auto now = crl::now();
|
||||
const auto size = raw->width();
|
||||
const auto color = st::radialFg->c;
|
||||
const auto skip = (size - st::reactionInlineImage) / 2;
|
||||
const auto target = QRect(
|
||||
QPoint(skip, skip),
|
||||
QSize(st::reactionInlineImage, st::reactionInlineImage));
|
||||
for (const auto &animation : _starSendEffects) {
|
||||
animation->paintGetArea(p, {}, target, color, {}, now);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void ComposeControls::startStarsEffect(SendStarButtonEffect event) {
|
||||
if (!_starEffectsCanvas) {
|
||||
setupStarsEffectsCanvas();
|
||||
}
|
||||
while (_starEffects.size() >= kMaxStarEffects) {
|
||||
_starEffects.erase(begin(_starEffects));
|
||||
}
|
||||
_starEffects.push_back(std::make_unique<StarEffect>(
|
||||
_starEffectsCanvas.get(),
|
||||
event));
|
||||
}
|
||||
|
||||
void ComposeControls::setupStarsEffectsCanvas() {
|
||||
_starEffectsCanvas = std::make_unique<Ui::RpWidget>(_parent);
|
||||
|
||||
const auto raw = _starEffectsCanvas.get();
|
||||
raw->show();
|
||||
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
raw->lifetime().make_state<Ui::Animations::Basic>([=] {
|
||||
raw->update();
|
||||
})->start();
|
||||
|
||||
const auto effectSize = st::reactionInlineImage * 2;
|
||||
const auto width = effectSize * 2;
|
||||
const auto height = effectSize * 4;
|
||||
|
||||
rpl::combine(
|
||||
_wrap->geometryValue(),
|
||||
_writeRestricted->geometryValue(),
|
||||
_starsReaction->geometryValue()
|
||||
) | rpl::start_with_next([=](QRect wrap, QRect restriction, QRect star) {
|
||||
const auto parent = (_starsReaction->parentWidget() == _wrap.get())
|
||||
? wrap
|
||||
: restriction;
|
||||
const auto adjusted = star.translated(parent.topLeft());
|
||||
|
||||
raw->setGeometry(
|
||||
adjusted.x() + (adjusted.width() - width) / 2,
|
||||
adjusted.y() + adjusted.height() - height,
|
||||
width,
|
||||
height);
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
const auto now = crl::now();
|
||||
for (auto i = begin(_starEffects); i != end(_starEffects);) {
|
||||
const auto progress = float64(now - (*i)->start)
|
||||
/ kStarEffectDuration;
|
||||
if (progress >= 1.) {
|
||||
i = _starEffects.erase(i);
|
||||
} else {
|
||||
(*i)->progress = progress;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_starEffects.empty()) {
|
||||
crl::on_main(raw, [=] {
|
||||
if (_starEffectsCanvas.get() == raw
|
||||
&& _starEffects.empty()) {
|
||||
_starEffectsCanvas = nullptr;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(raw);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto color = st::radialFg->c;
|
||||
const auto skip = (effectSize - st::reactionInlineImage) / 2;
|
||||
for (const auto &animation : _starEffects) {
|
||||
const auto progress = animation->progress;
|
||||
const auto left = anim::interpolate(
|
||||
0,
|
||||
width - effectSize,
|
||||
animation->shift);
|
||||
const auto top = anim::interpolate(
|
||||
height - effectSize,
|
||||
0,
|
||||
progress);
|
||||
const auto opacity = (progress < 0.125) ?
|
||||
(progress / 0.125) :
|
||||
(progress > 0.875) ?
|
||||
(1. - progress) / 0.125
|
||||
: 1.;
|
||||
const auto scale = kStarEffectScaleMin
|
||||
+ (kStarEffectScaleMax - kStarEffectScaleMin) * opacity;
|
||||
|
||||
const auto rotation = qSin(-M_PI_2
|
||||
+ M_PI * (animation->shift + animation->progress)
|
||||
) * kStarEffectRotationMax;
|
||||
const auto target = QRect(
|
||||
QPoint(left + skip, top + skip),
|
||||
QSize(st::reactionInlineImage, st::reactionInlineImage));
|
||||
const auto size = animation->badge.size()
|
||||
/ animation->badge.devicePixelRatio();
|
||||
const auto dx = (target.width() - size.width()) / 2;
|
||||
const auto dy = (target.height() - size.height()) / 2;
|
||||
|
||||
p.save();
|
||||
p.translate(target.center());
|
||||
p.rotate(rotation);
|
||||
p.scale(scale, scale);
|
||||
p.translate(-target.center());
|
||||
p.setOpacity(opacity);
|
||||
p.drawImage(target.topLeft() + QPoint(dx, dy), animation->badge);
|
||||
p.restore();
|
||||
|
||||
animation->around.paintGetArea(p, {}, target, color, {}, now);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void ComposeControls::setStarsReactionTop(
|
||||
rpl::producer<std::vector<StarReactionTop>> top) {
|
||||
_starsReactionTop = std::move(top);
|
||||
@@ -3025,9 +3311,9 @@ void ComposeControls::updateSendButtonType() {
|
||||
_send->setState({
|
||||
.type = type,
|
||||
.fillBgOverride = (_chosenStarsCount.value_or(0)
|
||||
? StarsColoringForCount(
|
||||
? Ui::ColorFromSerialized(StarsColoringForCount(
|
||||
appConfig.groupCallColorings(),
|
||||
*_chosenStarsCount).bgLight
|
||||
*_chosenStarsCount).bgLight)
|
||||
: QColor()),
|
||||
.slowmodeDelay = delay,
|
||||
.starsToSend = shownStarsPerMessage(),
|
||||
@@ -3272,15 +3558,9 @@ bool ComposeControls::updateSendAsButton(
|
||||
_sendAs = std::make_unique<Ui::SendAsButton>(_wrap.get(), st.button);
|
||||
if (videoStream) {
|
||||
Ui::SetupSendAsButton(_sendAs.get(), st, videoStream, _show);
|
||||
_videoStreamAdmin = videoStream->sendAsValue(
|
||||
) | rpl::map([=](not_null<PeerData*> peer) {
|
||||
return (videoStream->peer() == peer)
|
||||
|| (videoStream->creator() && peer->isSelf());
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::after_next([=](bool admin) {
|
||||
initEditStarsButton();
|
||||
updateControlsGeometry(_wrap->size());
|
||||
});
|
||||
_videoStreamAdmin = videoStream->creator();
|
||||
initEditStarsButton();
|
||||
updateControlsGeometry(_wrap->size());
|
||||
} else {
|
||||
Ui::SetupSendAsButton(_sendAs.get(), st, rpl::single(peer), _show);
|
||||
_videoStreamAdmin = false;
|
||||
|
||||
@@ -70,6 +70,7 @@ class SilentToggle;
|
||||
class DropdownMenu;
|
||||
struct PreparedList;
|
||||
struct SendStarButtonState;
|
||||
class ReactionFlyAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Emoji {
|
||||
@@ -143,6 +144,7 @@ public:
|
||||
using FieldHistoryAction = Ui::InputField::HistoryAction;
|
||||
using Mode = ComposeControlsMode;
|
||||
using ToggleCommentsState = Controls::ToggleCommentsState;
|
||||
using SendStarButtonEffect = Controls::SendStarButtonEffect;
|
||||
|
||||
ComposeControls(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
@@ -169,7 +171,8 @@ public:
|
||||
void setToggleCommentsButton(rpl::producer<ToggleCommentsState> state);
|
||||
[[nodiscard]] rpl::producer<> commentsShownToggles() const;
|
||||
void setStarsReactionCounter(
|
||||
rpl::producer<Ui::SendStarButtonState> count);
|
||||
rpl::producer<Ui::SendStarButtonState> count,
|
||||
rpl::producer<SendStarButtonEffect> effects);
|
||||
using StarReactionTop = Data::MessageReactionsTopPaid;
|
||||
void setStarsReactionTop(
|
||||
rpl::producer<std::vector<StarReactionTop>> top);
|
||||
@@ -278,6 +281,7 @@ public:
|
||||
[[nodiscard]] Ui::InputField *fieldForMention() const;
|
||||
|
||||
private:
|
||||
struct StarEffect;
|
||||
enum class TextUpdateEvent {
|
||||
SaveDraft = (1 << 0),
|
||||
SendTyping = (1 << 1),
|
||||
@@ -354,6 +358,10 @@ private:
|
||||
|
||||
[[nodiscard]] bool hasSilentBroadcastToggle() const;
|
||||
[[nodiscard]] bool editStarsButtonShown() const;
|
||||
void startStarsSendEffect();
|
||||
void setupStarsSendEffectsCanvas();
|
||||
void startStarsEffect(SendStarButtonEffect event);
|
||||
void setupStarsEffectsCanvas();
|
||||
|
||||
// Look in the _field for the inline bot and query string.
|
||||
void updateInlineBotQuery();
|
||||
@@ -389,7 +397,7 @@ private:
|
||||
|
||||
const style::ComposeControls &_st;
|
||||
ChatHelpers::ComposeFeatures _features;
|
||||
const not_null<QWidget*> _parent;
|
||||
const not_null<Ui::RpWidget*> _parent;
|
||||
const not_null<QWidget*> _panelsParent;
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const not_null<Main::Session*> _session;
|
||||
@@ -428,6 +436,10 @@ private:
|
||||
Ui::RpWidget *_commentsShownNewDot = nullptr;
|
||||
Ui::IconButton *_attachToggle = nullptr;
|
||||
Ui::AbstractButton *_starsReaction = nullptr;
|
||||
std::vector<std::unique_ptr<Ui::ReactionFlyAnimation>> _starSendEffects;
|
||||
std::unique_ptr<Ui::RpWidget> _starSendEffectsCanvas;
|
||||
std::vector<std::unique_ptr<StarEffect>> _starEffects;
|
||||
std::unique_ptr<Ui::RpWidget> _starEffectsCanvas;
|
||||
std::unique_ptr<Ui::IconButton> _replaceMedia;
|
||||
const not_null<Ui::EmojiButton*> _tabbedSelectorToggle;
|
||||
rpl::producer<QString> _fieldCustomPlaceholder;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/message_bubble.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/effects/reaction_fly_animation.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
@@ -453,7 +454,7 @@ void BottomInfo::layoutDateText() {
|
||||
const auto author = _data.author;
|
||||
const auto prefix = !author.isEmpty() ? u", "_q : QString();
|
||||
const auto date = edited + ((_data.flags & Data::Flag::ForwardedDate)
|
||||
? langDateTime(_data.date)
|
||||
? Ui::FormatDateTimeSavedFrom(_data.date, true)
|
||||
: QLocale().toString(_data.date.time(), QLocale::ShortFormat));
|
||||
const auto afterAuthor = prefix + date;
|
||||
const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor);
|
||||
@@ -656,8 +657,9 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
|
||||
result.scheduleRepeatPeriod = item->scheduleRepeatPeriod();
|
||||
}
|
||||
if (forwarded
|
||||
&& forwarded->savedFromPeer
|
||||
&& forwarded->savedFromMsgId
|
||||
&& ((forwarded->savedFromPeer && forwarded->savedFromMsgId)
|
||||
|| forwarded->savedFromHiddenSenderInfo
|
||||
|| forwarded->originalHiddenSenderInfo)
|
||||
&& !item->externalReply()) {
|
||||
result.date = base::unixtime::parse(forwarded->originalDate);
|
||||
result.flags |= Flag::ForwardedDate;
|
||||
|
||||
@@ -258,6 +258,11 @@ struct StatusFields {
|
||||
[[nodiscard]] rpl::producer<Info::Profile::Badge::Content> ContentForPeer(
|
||||
not_null<PeerData*> peer) {
|
||||
using namespace Info::Profile;
|
||||
if (peer->isSelf()
|
||||
|| peer->isRepliesChat()
|
||||
|| peer->isSavedHiddenAuthor()) {
|
||||
return rpl::single(Badge::Content{});
|
||||
}
|
||||
return rpl::combine(
|
||||
BadgeContentForPeer(peer),
|
||||
VerifiedContentForPeer(peer)
|
||||
|
||||
@@ -326,6 +326,9 @@ ChatWidget::ChatWidget(
|
||||
) | rpl::start_with_next([=] {
|
||||
searchRequested();
|
||||
}, _topBar->lifetime());
|
||||
if (_sublist) {
|
||||
_topBar->setCustomTitle(tr::lng_contacts_loading(tr::now));
|
||||
}
|
||||
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -3014,12 +3017,14 @@ rpl::producer<Data::MessagesSlice> ChatWidget::sublistSource(
|
||||
limitAfter
|
||||
) | rpl::before_next([=](const Data::MessagesSlice &result) {
|
||||
// after_next makes a copy of value.
|
||||
_topBar->setCustomTitle(result.fullCount
|
||||
? tr::lng_forum_messages(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
*result.fullCount)
|
||||
: tr::lng_contacts_loading(tr::now));
|
||||
_topBar->setCustomTitle(!result.fullCount
|
||||
? tr::lng_contacts_loading(tr::now)
|
||||
: (_sublist->parentChat()
|
||||
? tr::lng_forum_messages
|
||||
: tr::lng_profile_saved_messages)(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
*result.fullCount));
|
||||
markLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1914,11 +1914,12 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange {
|
||||
}
|
||||
|
||||
bool Element::hasHeavyPart() const {
|
||||
return (_flags & Flag::HeavyCustomEmoji);
|
||||
return (_flags & Flag::HeavyCustomEmoji)
|
||||
|| (_media && _media->hasHeavyPart());
|
||||
}
|
||||
|
||||
void Element::checkHeavyPart() {
|
||||
if (!hasHeavyPart() && (!_media || !_media->hasHeavyPart())) {
|
||||
if (!hasHeavyPart()) {
|
||||
history()->owner().unregisterHeavyViewPart(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,6 @@ public:
|
||||
void itemTextUpdated();
|
||||
void blockquoteExpandChanged();
|
||||
|
||||
[[nodiscard]] virtual bool hasHeavyPart() const;
|
||||
virtual void unloadHeavyPart();
|
||||
void checkHeavyPart();
|
||||
|
||||
@@ -687,6 +686,7 @@ protected:
|
||||
|
||||
[[nodiscard]] ClickHandlerPtr fromLink() const;
|
||||
|
||||
[[nodiscard]] virtual bool hasHeavyPart() const;
|
||||
virtual void refreshDataIdHook();
|
||||
|
||||
[[nodiscard]] const Ui::Text::String &text() const;
|
||||
|
||||
@@ -113,7 +113,6 @@ public:
|
||||
const TextState &reactionState) const override;
|
||||
int reactionsOptimalWidth() const override;
|
||||
|
||||
bool hasHeavyPart() const override;
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
// hasFromPhoto() returns true even if we don't display the photo
|
||||
@@ -165,14 +164,14 @@ public:
|
||||
QRect innerGeometry() const override;
|
||||
[[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const;
|
||||
|
||||
protected:
|
||||
void refreshDataIdHook() override;
|
||||
|
||||
private:
|
||||
struct CommentsButton;
|
||||
struct FromNameStatus;
|
||||
struct RightAction;
|
||||
|
||||
void refreshDataIdHook() override;
|
||||
bool hasHeavyPart() const override;
|
||||
|
||||
bool updateBottomInfo();
|
||||
|
||||
void initPaidInformation();
|
||||
|
||||
@@ -298,6 +298,7 @@ void PaidReactionToast::showFor(
|
||||
.padding = rpl::single(QMargins(leftSkip, 0, rightSkip, 0)),
|
||||
.st = &st,
|
||||
.attach = RectPart::Top,
|
||||
.addToAttachSide = _topOffset.value(),
|
||||
.acceptinput = true,
|
||||
.infinite = true,
|
||||
});
|
||||
|
||||
@@ -456,13 +456,20 @@ void SubsectionTabs::startFillingSlider(
|
||||
} else if (item.thread->peer()->isBot()) {
|
||||
sections.push_back({
|
||||
.text = { tr::lng_bot_new_chat(tr::now) },
|
||||
.userpic = Ui::MakeNewChatSubsectionsThumbnail(textFg),
|
||||
});
|
||||
if (vertical) {
|
||||
auto &last = sections.back();
|
||||
last.userpic = Ui::MakeNewChatSubsectionsThumbnail(
|
||||
textFg);
|
||||
}
|
||||
} else {
|
||||
sections.push_back({
|
||||
.text = { tr::lng_filters_all_short(tr::now) },
|
||||
.userpic = Ui::MakeAllSubsectionsThumbnail(textFg),
|
||||
});
|
||||
if (vertical) {
|
||||
auto &last = sections.back();
|
||||
last.userpic = Ui::MakeAllSubsectionsThumbnail(textFg);
|
||||
}
|
||||
}
|
||||
auto §ion = sections.back();
|
||||
section.badges = item.badges;
|
||||
|
||||
@@ -528,7 +528,9 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
||||
p.drawTextLeft(nameleft, statustop, width(), _customTitleText);
|
||||
}
|
||||
} else if (folder
|
||||
|| (peer && (peer->sharedMediaInfo() || peer->isVerifyCodes()))
|
||||
|| (peer
|
||||
&& (peer->sharedMediaInfo() || peer->isVerifyCodes())
|
||||
&& _activeChat.section != Section::SavedSublist)
|
||||
|| (_activeChat.section == Section::Scheduled)
|
||||
|| (_activeChat.section == Section::Pinned)) {
|
||||
auto text = (_activeChat.section == Section::Scheduled)
|
||||
|
||||
@@ -227,13 +227,6 @@ Contact::Contact(
|
||||
st::webPageDescriptionStyle,
|
||||
Ui::FormatPhone(data.phoneNumber),
|
||||
Ui::WebpageTextTitleOptions());
|
||||
|
||||
#if 0 // No info.
|
||||
_infoLine.setText(
|
||||
st::webPageDescriptionStyle,
|
||||
phone,
|
||||
Ui::WebpageTextTitleOptions());
|
||||
#endif
|
||||
}
|
||||
|
||||
Contact::~Contact() {
|
||||
@@ -322,10 +315,6 @@ QSize Contact::countOptimalSize() {
|
||||
accumulate_max(maxWidth, lineLeft + _phoneLine.maxWidth());
|
||||
textMinHeight += 1 * lineHeight;
|
||||
}
|
||||
if (!_infoLine.isEmpty()) {
|
||||
accumulate_max(maxWidth, lineLeft + _infoLine.maxWidth());
|
||||
textMinHeight += std::min(_infoLine.minHeight(), 1 * lineHeight);
|
||||
}
|
||||
minHeight = std::max(textMinHeight, st::contactsPhotoSize);
|
||||
|
||||
if (!_buttons.empty()) {
|
||||
@@ -500,26 +489,6 @@ void Contact::draw(Painter &p, const PaintContext &context) const {
|
||||
toTitleSelection(context.selection));
|
||||
tshift += 1 * lineHeight;
|
||||
}
|
||||
if (!_infoLine.isEmpty()) {
|
||||
tshift += st::lineWidth * 3; // Additional skip.
|
||||
const auto endskip = _infoLine.hasSkipBlock()
|
||||
? _parent->skipBlockWidth()
|
||||
: 0;
|
||||
_parent->prepareCustomEmojiPaint(p, context, _infoLine);
|
||||
_infoLine.draw(p, {
|
||||
.position = { lineLeft, tshift },
|
||||
.outerWidth = width(),
|
||||
.availableWidth = lineWidth,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = context.now,
|
||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||
.selection = toDescriptionSelection(context.selection),
|
||||
.elisionHeight = (1 * lineHeight),
|
||||
.elisionRemoveFromEnd = endskip,
|
||||
});
|
||||
tshift += (1 * lineHeight);
|
||||
}
|
||||
|
||||
if (!_buttons.empty()) {
|
||||
p.setFont(st::semiboldFont);
|
||||
|
||||
@@ -78,7 +78,6 @@ private:
|
||||
|
||||
Ui::Text::String _nameLine;
|
||||
Ui::Text::String _phoneLine;
|
||||
Ui::Text::String _infoLine;
|
||||
|
||||
Fn<void(not_null<Ui::GenericBox*>)> _vcardBoxFactory;
|
||||
|
||||
|
||||
@@ -160,7 +160,11 @@ void Location::checkLiveFinish() {
|
||||
const auto item = _parent->data();
|
||||
const auto start = item->date();
|
||||
if (_live->period != kUntilOffPeriod && now - start >= _live->period) {
|
||||
const auto had = hasHeavyPart();
|
||||
_live = nullptr;
|
||||
if (had && !hasHeavyPart()) {
|
||||
_parent->checkHeavyPart();
|
||||
}
|
||||
item->history()->owner().requestViewResize(_parent);
|
||||
} else {
|
||||
_parent->repaint();
|
||||
|
||||
@@ -317,6 +317,7 @@ void TodoList::updateTasks(bool skipAnimations) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto has = hasHeavyPart();
|
||||
_tasks = ranges::views::all(
|
||||
_todolist->items
|
||||
) | ranges::views::transform([&](const TodoListItem &item) {
|
||||
@@ -331,6 +332,10 @@ void TodoList::updateTasks(bool skipAnimations) {
|
||||
}
|
||||
|
||||
updateCompletionStatus();
|
||||
|
||||
if (has && !hasHeavyPart()) {
|
||||
_parent->checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr TodoList::createTaskClickHandler(
|
||||
|
||||
@@ -454,7 +454,6 @@ void Strip::resolveMainReactionIcon() {
|
||||
_mainReactionMedia = main->createMediaView();
|
||||
_mainReactionMedia->checkStickerLarge();
|
||||
if (_mainReactionMedia->loaded()) {
|
||||
_mainReactionLifetime.destroy();
|
||||
setMainReactionIcon();
|
||||
} else if (!_mainReactionLifetime) {
|
||||
main->session().downloaderTaskFinished(
|
||||
@@ -467,9 +466,14 @@ void Strip::resolveMainReactionIcon() {
|
||||
}
|
||||
|
||||
void Strip::setMainReactionIcon() {
|
||||
Expects(_mainReactionMedia->loaded());
|
||||
|
||||
_mainReactionLifetime.destroy();
|
||||
ranges::fill(_validEmoji, false);
|
||||
loadIcons();
|
||||
|
||||
Assert(_mainReactionMedia->loaded());
|
||||
|
||||
const auto i = _loadCache.find(_mainReactionMedia->owner());
|
||||
if (i != end(_loadCache) && i->second.icon) {
|
||||
const auto &icon = i->second.icon;
|
||||
@@ -479,6 +483,8 @@ void Strip::setMainReactionIcon() {
|
||||
}
|
||||
}
|
||||
_mainReactionImage = QImage();
|
||||
|
||||
Assert(_mainReactionMedia->loaded());
|
||||
_mainReactionIcon = DefaultIconFactory(
|
||||
_mainReactionMedia.get(),
|
||||
MainReactionSize());
|
||||
|
||||
@@ -421,8 +421,7 @@ void WrapWidget::setupTopBarMenuToggle() {
|
||||
const auto button = _topBar->addButton(
|
||||
base::make_unique_q<Ui::IconButton>(_topBar, st));
|
||||
button->addClickHandler([show, self] {
|
||||
show->show(
|
||||
Box(Ui::FillPeerQrBox, self, std::nullopt, nullptr));
|
||||
Ui::DefaultShowFillPeerQrBoxCallback(show, self);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ Memento::Memento(not_null<Controller*> controller)
|
||||
? controller->peer()
|
||||
: controller->storiesPeer()
|
||||
? controller->storiesPeer()
|
||||
: controller->musicPeer()
|
||||
? controller->musicPeer()
|
||||
: controller->parentController()->session().user()),
|
||||
controller->topic(),
|
||||
controller->sublist(),
|
||||
@@ -78,6 +80,8 @@ Memento::Memento(not_null<Controller*> controller)
|
||||
? Type::File
|
||||
: controller->section().type() == Section::Type::Stories
|
||||
? Type::PhotoVideo
|
||||
: controller->section().type() == Section::Type::SavedMusic
|
||||
? Type::MusicFile
|
||||
: controller->section().mediaType())) {
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,16 @@ base::options::toggle ShowChannelJoinedBelowAbout({
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> TopicSubtext(
|
||||
not_null<PeerData*> peer) {
|
||||
return rpl::conditional(
|
||||
UsernamesValue(peer) | rpl::map([](std::vector<TextWithEntities> v) {
|
||||
return !v.empty();
|
||||
}),
|
||||
tr::lng_filters_link_subtitle(Ui::Text::WithEntities),
|
||||
tr::lng_info_link_topic_label(Ui::Text::WithEntities));
|
||||
}
|
||||
|
||||
[[nodiscard]] Fn<void(QString)> UsernamesLinkCallback(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -1506,13 +1516,20 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
rpl::combine(
|
||||
container->widthValue(),
|
||||
label->geometryValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](int width, QRect, QSize buttonSize) {
|
||||
button->sizeValue(),
|
||||
button->shownValue()
|
||||
) | rpl::start_with_next([=](
|
||||
int width,
|
||||
QRect,
|
||||
QSize buttonSize,
|
||||
bool buttonShown) {
|
||||
button->moveToRight(
|
||||
rightSkip,
|
||||
(parent->height() - buttonSize.height()) / 2);
|
||||
const auto x = Ui::MapFrom(container, label, QPoint(0, 0)).x();
|
||||
const auto s = Ui::MapFrom(container, button, QPoint(0, 0)).x();
|
||||
const auto s = buttonShown
|
||||
? Ui::MapFrom(container, button, QPoint(0, 0)).x()
|
||||
: width;
|
||||
label->resizeToWidth(s - x);
|
||||
}, button->lifetime());
|
||||
};
|
||||
@@ -1632,12 +1649,14 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
const auto qrButton = Ui::CreateChild<Ui::IconButton>(
|
||||
usernameLine.text->parentWidget(),
|
||||
st::infoProfileLabeledButtonQr);
|
||||
UsernamesValue(_peer) | rpl::start_with_next([=](const auto &u) {
|
||||
qrButton->setVisible(!u.empty());
|
||||
}, qrButton->lifetime());
|
||||
const auto rightSkip = st::infoProfileLabeledButtonQrRightSkip;
|
||||
fitLabelToButton(qrButton, usernameLine.text, rightSkip);
|
||||
fitLabelToButton(qrButton, usernameLine.subtext, rightSkip);
|
||||
qrButton->setClickedCallback([=] {
|
||||
controller->show(
|
||||
Box(Ui::FillPeerQrBox, user, std::nullopt, nullptr));
|
||||
qrButton->setClickedCallback([=, show = controller->uiShow()] {
|
||||
Ui::DefaultShowFillPeerQrBoxCallback(show, user);
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -1693,7 +1712,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
});
|
||||
const auto linkLine = addInfoOneLine(
|
||||
(topicRootId
|
||||
? tr::lng_info_link_topic_label(Ui::Text::WithEntities)
|
||||
? TopicSubtext(_peer)
|
||||
: UsernamesSubtext(_peer, tr::lng_info_link_label())),
|
||||
std::move(linkText),
|
||||
QString());
|
||||
@@ -1710,12 +1729,15 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
const auto qr = Ui::CreateChild<Ui::IconButton>(
|
||||
linkLine.text->parentWidget(),
|
||||
st::infoProfileLabeledButtonQr);
|
||||
UsernamesValue(_peer) | rpl::start_with_next([=](const auto &u) {
|
||||
qr->setVisible(!u.empty());
|
||||
}, qr->lifetime());
|
||||
const auto rightSkip = st::infoProfileLabeledButtonQrRightSkip;
|
||||
fitLabelToButton(qr, linkLine.text, rightSkip);
|
||||
fitLabelToButton(qr, linkLine.subtext, rightSkip);
|
||||
qr->setClickedCallback([=, peer = _peer] {
|
||||
controller->show(
|
||||
Box(Ui::FillPeerQrBox, peer, std::nullopt, nullptr));
|
||||
const auto peer = _peer;
|
||||
qr->setClickedCallback([=, show = controller->uiShow()] {
|
||||
Ui::DefaultShowFillPeerQrBoxCallback(show, peer);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,7 +356,8 @@ TopBar::TopBar(
|
||||
}
|
||||
});
|
||||
return owned;
|
||||
}()) {
|
||||
}())
|
||||
, _backToggles(std::move(descriptor.backToggles)) {
|
||||
_peer->updateFull();
|
||||
if (const auto broadcast = _peer->monoforumBroadcast()) {
|
||||
broadcast->updateFull();
|
||||
@@ -420,16 +421,13 @@ TopBar::TopBar(
|
||||
badgeUpdates = rpl::merge(
|
||||
std::move(badgeUpdates),
|
||||
nameValue() | rpl::to_empty,
|
||||
rpl::duplicate(descriptor.backToggles) | rpl::to_empty);
|
||||
_backToggles.value() | rpl::to_empty);
|
||||
std::move(badgeUpdates) | rpl::start_with_next([=] {
|
||||
updateLabelsPosition();
|
||||
}, _title->lifetime());
|
||||
|
||||
setupUniqueBadgeTooltip();
|
||||
setupButtons(
|
||||
controller,
|
||||
rpl::duplicate(descriptor.backToggles),
|
||||
descriptor.source);
|
||||
setupButtons(controller, descriptor.source);
|
||||
setupUserpicButton(controller);
|
||||
if (_hasActions) {
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
@@ -587,9 +585,6 @@ void TopBar::adjustColors(const std::optional<QColor> &edgeColor) {
|
||||
}
|
||||
|
||||
void TopBar::updateCollectibleStatus() {
|
||||
if (width() <= 0) {
|
||||
return;
|
||||
}
|
||||
const auto collectible = effectiveCollectible();
|
||||
const auto colorProfile = effectiveColorProfile();
|
||||
_hasGradientBg = (collectible != nullptr)
|
||||
@@ -608,10 +603,14 @@ void TopBar::updateCollectibleStatus() {
|
||||
: _peer->profileBackgroundEmojiId();
|
||||
if (patternEmojiId) {
|
||||
const auto document = _peer->owner().document(patternEmojiId);
|
||||
_patternEmoji = document->owner().customEmojiManager().create(
|
||||
document,
|
||||
[=] { update(); },
|
||||
Data::CustomEmojiSizeTag::Normal);
|
||||
if (!_patternEmoji
|
||||
|| _patternEmoji->entityData()
|
||||
!= Data::SerializeCustomEmojiId(document)) {
|
||||
_patternEmoji = document->owner().customEmojiManager().create(
|
||||
document,
|
||||
[=] { update(); },
|
||||
Data::CustomEmojiSizeTag::Normal);
|
||||
}
|
||||
} else {
|
||||
_patternEmoji = nullptr;
|
||||
}
|
||||
@@ -1822,7 +1821,6 @@ void TopBar::paintEvent(QPaintEvent *e) {
|
||||
|
||||
void TopBar::setupButtons(
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<bool> backToggles,
|
||||
Source source) {
|
||||
if (source == Source::Preview) {
|
||||
setRoundEdges(false);
|
||||
@@ -1831,7 +1829,7 @@ void TopBar::setupButtons(
|
||||
rpl::combine(
|
||||
_wrap.value(),
|
||||
_edgeColor.value()
|
||||
) | rpl::start_with_next([=, backToggles = std::move(backToggles)](
|
||||
) | rpl::start_with_next([=](
|
||||
Wrap wrap,
|
||||
std::optional<QColor> edgeColor) mutable {
|
||||
const auto isLayer = (wrap == Wrap::Layer);
|
||||
@@ -1858,7 +1856,7 @@ void TopBar::setupButtons(
|
||||
_back->QWidget::show();
|
||||
_back->setDuration(0);
|
||||
_back->toggleOn(isLayer || isSide
|
||||
? rpl::duplicate(backToggles)
|
||||
? (_backToggles.value() | rpl::type_erased())
|
||||
: rpl::single(wrap == Wrap::Narrow));
|
||||
_back->entity()->clicks() | rpl::to_empty | rpl::start_to_stream(
|
||||
_backClicks,
|
||||
|
||||
@@ -154,7 +154,6 @@ private:
|
||||
void setupActions(not_null<Window::SessionController*> controller);
|
||||
void setupButtons(
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<bool> backToggles,
|
||||
Source source);
|
||||
void setupShowLastSeen(not_null<Window::SessionController*> controller);
|
||||
void setupUniqueBadgeTooltip();
|
||||
@@ -257,6 +256,7 @@ private:
|
||||
|
||||
base::unique_qptr<Ui::IconButton> _close;
|
||||
base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
|
||||
rpl::variable<bool> _backToggles;
|
||||
|
||||
rpl::event_stream<> _backClicks;
|
||||
|
||||
|
||||
@@ -882,11 +882,8 @@ void CreditsRow::init() {
|
||||
const auto name = !isSpecial
|
||||
? PeerListRow::generateName()
|
||||
: Ui::GenerateEntryName(_entry).text;
|
||||
_name = _entry.paidMessagesCount
|
||||
? tr::lng_credits_paid_messages_fee(
|
||||
tr::now,
|
||||
lt_count,
|
||||
_entry.paidMessagesCount)
|
||||
_name = (_entry.isLiveStoryReaction() || _entry.paidMessagesCount)
|
||||
? name
|
||||
: _entry.postsSearch
|
||||
? tr::lng_credits_box_history_entry_posts_search(tr::now)
|
||||
: ((!_entry.subscriptionUntil.isNull() && !isSpecial)
|
||||
@@ -901,8 +898,13 @@ void CreditsRow::init() {
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
_entry.floodSkip)
|
||||
: _entry.isLiveStoryReaction()
|
||||
? tr::lng_credits_paid_messages_fee_live_reaction(tr::now)
|
||||
: _entry.paidMessagesCount
|
||||
? name
|
||||
? tr::lng_credits_paid_messages_fee(
|
||||
tr::now,
|
||||
lt_count,
|
||||
_entry.paidMessagesCount)
|
||||
: (!_entry.subscriptionUntil.isNull() && !_entry.title.isEmpty())
|
||||
? _entry.title
|
||||
: _entry.refunded
|
||||
|
||||
@@ -413,6 +413,9 @@ void FillBotUsepic(
|
||||
rpl::single(bot->name()),
|
||||
box->getDelegate()->style().title);
|
||||
const auto icon = bot->isVerified() ? &st::infoVerifiedStar : nullptr;
|
||||
const auto iconCheck = icon
|
||||
? &st::infoPeerBadge.verifiedCheck
|
||||
: nullptr;
|
||||
title->resize(
|
||||
titleLabel->width() + (icon ? icon->width() : 0),
|
||||
titleLabel->height());
|
||||
@@ -422,17 +425,16 @@ void FillBotUsepic(
|
||||
- (icon ? icon->width() + st::lineWidth : 0));
|
||||
}, title->lifetime());
|
||||
if (icon) {
|
||||
title->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
title->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = Painter(title);
|
||||
p.fillRect(title->rect(), Qt::transparent);
|
||||
icon->paint(
|
||||
p,
|
||||
std::min(
|
||||
titleLabel->textMaxWidth() + st::lineWidth,
|
||||
title->width() - st::lineWidth - icon->width()),
|
||||
(title->height() - icon->height()) / 2,
|
||||
title->width());
|
||||
const auto x = std::min(
|
||||
titleLabel->textMaxWidth() + st::lineWidth,
|
||||
title->width() - st::lineWidth - icon->width());
|
||||
const auto y = (title->height() - icon->height()) / 2;
|
||||
const auto w = title->width();
|
||||
icon->paint(p, x, y, w);
|
||||
iconCheck->paint(p, x, y, w);
|
||||
}, title->lifetime());
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,13 @@ void CodeWidget::updateDescText() {
|
||||
const auto byTelegram = getData()->codeByTelegram;
|
||||
const auto isFragment = !getData()->codeByFragmentUrl.isEmpty();
|
||||
_isFragment = isFragment;
|
||||
setDescriptionText(isEmailVerification()
|
||||
const auto emailPattern = !getData()->emailPatternSetup.isEmpty()
|
||||
? getData()->emailPatternSetup
|
||||
: getData()->emailPatternLogin;
|
||||
setDescriptionText(!emailPattern.isEmpty()
|
||||
? tr::lng_intro_email_confirm_subtitle(
|
||||
lt_email,
|
||||
rpl::single(Ui::Text::WrapEmailPattern(getData()->emailPattern)),
|
||||
rpl::single(Ui::Text::WrapEmailPattern(emailPattern)),
|
||||
Ui::Text::WithEntities)
|
||||
: isFragment
|
||||
? tr::lng_intro_fragment_about(
|
||||
@@ -229,6 +232,37 @@ void CodeWidget::codeSubmitDone(const MTPauth_Authorization &result) {
|
||||
finish(result);
|
||||
}
|
||||
|
||||
void CodeWidget::emailVerifyDone(const MTPaccount_EmailVerified &result) {
|
||||
stopCheck();
|
||||
_sentRequest = 0;
|
||||
|
||||
result.match([&](const MTPDaccount_emailVerified &data) {
|
||||
_code->setEnabled(true);
|
||||
showCodeError(rpl::single(
|
||||
u"Unexpected type of response: emailVerifiedLogin"_q));
|
||||
}, [&](const MTPDaccount_emailVerifiedLogin &data) {
|
||||
getData()->emailPatternSetup.clear();
|
||||
data.vsent_code().match([&](const MTPDauth_sentCode &sentData) {
|
||||
fillSentCodeData(sentData);
|
||||
getData()->phoneHash = qba(sentData.vphone_code_hash());
|
||||
const auto next = sentData.vnext_type();
|
||||
if (next && next->type() == mtpc_auth_codeTypeCall) {
|
||||
getData()->callStatus = CallStatus::Waiting;
|
||||
getData()->callTimeout = sentData.vtimeout().value_or(60);
|
||||
} else {
|
||||
getData()->callStatus = CallStatus::Disabled;
|
||||
getData()->callTimeout = 0;
|
||||
}
|
||||
goReplace<CodeWidget>(Animate::Forward);
|
||||
}, [&](const MTPDauth_sentCodeSuccess &sentData) {
|
||||
finish(sentData.vauthorization());
|
||||
}, [](const MTPDauth_sentCodePaymentRequired &) {
|
||||
LOG(("API Error: Unexpected auth.sentCodePaymentRequired "
|
||||
"(CodeWidget::emailVerifyDone)."));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void CodeWidget::codeSubmitFail(const MTP::Error &error) {
|
||||
if (MTP::IsFloodError(error)) {
|
||||
stopCheck();
|
||||
@@ -352,23 +386,39 @@ void CodeWidget::submitCode(const QString &text) {
|
||||
_sentCode = text;
|
||||
_code->setEnabled(false);
|
||||
getData()->pwdState = Core::CloudPasswordState();
|
||||
_sentRequest = api().request(MTPauth_SignIn(
|
||||
MTP_flags(isEmailVerification()
|
||||
? MTPauth_SignIn::Flag::f_email_verification
|
||||
: MTPauth_SignIn::Flag::f_phone_code),
|
||||
MTP_string(getData()->phone),
|
||||
MTP_bytes(getData()->phoneHash),
|
||||
MTP_string(_sentCode),
|
||||
MTP_emailVerificationCode(MTP_string(_sentCode))
|
||||
)).done([=](const MTPauth_Authorization &result) {
|
||||
codeSubmitDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
codeSubmitFail(error);
|
||||
}).handleFloodErrors().send();
|
||||
|
||||
if (isEmailVerification()) {
|
||||
_sentRequest = api().request(MTPaccount_VerifyEmail(
|
||||
MTP_emailVerifyPurposeLoginSetup(
|
||||
MTP_string(getData()->phone),
|
||||
MTP_bytes(getData()->phoneHash)),
|
||||
MTP_emailVerificationCode(MTP_string(_sentCode))
|
||||
)).done([=](const MTPaccount_EmailVerified &result) {
|
||||
emailVerifyDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
codeSubmitFail(error);
|
||||
}).handleFloodErrors().send();
|
||||
} else {
|
||||
const auto isEmailLogin = !getData()->emailPatternLogin.isEmpty();
|
||||
_sentRequest = api().request(MTPauth_SignIn(
|
||||
MTP_flags(isEmailLogin
|
||||
? MTPauth_SignIn::Flag::f_email_verification
|
||||
: MTPauth_SignIn::Flag::f_phone_code),
|
||||
MTP_string(getData()->phone),
|
||||
MTP_bytes(getData()->phoneHash),
|
||||
MTP_string(_sentCode),
|
||||
MTP_emailVerificationCode(
|
||||
MTP_string(isEmailLogin ? _sentCode : QString()))
|
||||
)).done([=](const MTPauth_Authorization &result) {
|
||||
codeSubmitDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
codeSubmitFail(error);
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeWidget::isEmailVerification() const {
|
||||
return !getData()->emailPattern.isEmpty();
|
||||
return !getData()->emailPatternSetup.isEmpty();
|
||||
}
|
||||
|
||||
rpl::producer<QString> CodeWidget::nextButtonText() const {
|
||||
|
||||
@@ -62,6 +62,7 @@ private:
|
||||
|
||||
void codeSubmitDone(const MTPauth_Authorization &result);
|
||||
void codeSubmitFail(const MTP::Error &error);
|
||||
void emailVerifyDone(const MTPaccount_EmailVerified &result);
|
||||
|
||||
void showCodeError(rpl::producer<QString> text);
|
||||
void callDone(const MTPauth_SentCode &result);
|
||||
|
||||
@@ -78,6 +78,9 @@ EmailWidget::EmailWidget(
|
||||
newInput->changes() | rpl::start_with_next([=] {
|
||||
error->hide();
|
||||
}, newInput->lifetime());
|
||||
newInput->submits() | rpl::start_with_next([=] {
|
||||
submit();
|
||||
}, newInput->lifetime());
|
||||
newInput->setText(getData()->email);
|
||||
if (newInput->hasText()) {
|
||||
newInput->selectAll();
|
||||
@@ -93,7 +96,7 @@ EmailWidget::EmailWidget(
|
||||
const auto done = [=](int length, const QString &pattern) {
|
||||
_sentRequest = 0;
|
||||
getData()->codeLength = length;
|
||||
getData()->emailPattern = pattern;
|
||||
getData()->emailPatternSetup = pattern;
|
||||
goNext<CodeWidget>();
|
||||
};
|
||||
const auto fail = [=](const QString &type) {
|
||||
|
||||
@@ -374,8 +374,9 @@ void Step::fillSentCodeData(const MTPDauth_sentCode &data) {
|
||||
bad("MissedCall");
|
||||
}, [&](const MTPDauth_sentCodeTypeFirebaseSms &) {
|
||||
bad("FirebaseSms");
|
||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||
bad("EmailCode");
|
||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &data) {
|
||||
getData()->emailPatternLogin = qs(data.vemail_pattern());
|
||||
getData()->codeLength = data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeSmsWord &) {
|
||||
bad("SmsWord");
|
||||
}, [&](const MTPDauth_sentCodeTypeSmsPhrase &) {
|
||||
|
||||
@@ -65,7 +65,8 @@ struct Data {
|
||||
|
||||
EmailStatus emailStatus = EmailStatus::None;
|
||||
QString email;
|
||||
QString emailPattern;
|
||||
QString emailPatternSetup;
|
||||
QString emailPatternLogin;
|
||||
|
||||
Core::CloudPasswordState pwdState;
|
||||
|
||||
|
||||
80
Telegram/SourceFiles/media/stories/media_stories.style
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
using "ui/basic.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "boxes/boxes.style";
|
||||
|
||||
StealthBoxStyle {
|
||||
box: Box;
|
||||
buttonLabel: FlatLabel;
|
||||
lockIcon: icon;
|
||||
boxClose: IconButton;
|
||||
about: FlatLabel;
|
||||
featureTitle: FlatLabel;
|
||||
featureAbout: FlatLabel;
|
||||
featurePastIcon: icon;
|
||||
featureNextIcon: icon;
|
||||
logoIcon: icon;
|
||||
logoBg: color;
|
||||
}
|
||||
|
||||
storiesStealthStyleDefault: StealthBoxStyle {
|
||||
box: Box(defaultBox) {
|
||||
buttonPadding: margins(10px, 10px, 10px, 10px);
|
||||
buttonHeight: 42px;
|
||||
button: RoundButton(defaultActiveButton) {
|
||||
height: 42px;
|
||||
textTop: 12px;
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
margin: margins(0px, 56px, 0px, 10px);
|
||||
bg: windowBg;
|
||||
title: FlatLabel(boxTitle) {
|
||||
align: align(top);
|
||||
}
|
||||
titleAdditionalFg: windowSubTextFg;
|
||||
}
|
||||
buttonLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: activeButtonFg;
|
||||
style: semiboldTextStyle;
|
||||
align: align(top);
|
||||
minWidth: 20px;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
lockIcon: icon {{ "dialogs/dialogs_lock_on", windowFgActive }};
|
||||
boxClose: IconButton(defaultIconButton) {
|
||||
width: boxTitleHeight;
|
||||
height: boxTitleHeight;
|
||||
icon: icon {{ "box_button_close", boxTitleCloseFg }};
|
||||
iconOver: icon {{ "box_button_close", boxTitleCloseFgOver }};
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
about: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
align: align(top);
|
||||
minWidth: 20px;
|
||||
}
|
||||
featureTitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowBoldFg;
|
||||
style: semiboldTextStyle;
|
||||
minWidth: 10px;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
featureAbout: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
minWidth: 20px;
|
||||
}
|
||||
featurePastIcon: icon {{ "stories/stealth_5m", windowActiveTextFg }};
|
||||
featureNextIcon: icon {{ "stories/stealth_25m", windowActiveTextFg }};
|
||||
logoIcon: icon {{ "stories/stealth_logo", windowFgActive }};
|
||||
logoBg: windowBgActive;
|
||||
}
|
||||
@@ -1779,13 +1779,18 @@ auto Controller::starsReactionsValue() const
|
||||
});
|
||||
}
|
||||
|
||||
auto Controller::starsReactionsEffects() const
|
||||
-> rpl::producer<SendStarButtonEffect> {
|
||||
return _starsReactionEffects.events();
|
||||
}
|
||||
|
||||
void Controller::setStarsReactionIncrements(rpl::producer<int> increments) {
|
||||
std::move(
|
||||
increments
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
if (const auto call = _videoStreamCall.get()) {
|
||||
const auto show = _delegate->storiesShow();
|
||||
Payments::TryAddingPaidReaction(call, count, std::nullopt, show);
|
||||
Payments::TryAddingPaidReaction(call, count, show);
|
||||
}
|
||||
}, _videoStreamLifetime);
|
||||
}
|
||||
@@ -1925,9 +1930,15 @@ void Controller::updateVideoStream(not_null<Calls::GroupCall*> videoStream) {
|
||||
_commentsStateShowFromPinned,
|
||||
_videoStreamLifetime);
|
||||
|
||||
_starsReactions = rpl::single(rpl::empty) | rpl::then(
|
||||
_starsReactions = rpl::single(Calls::Group::StarsDonor()) | rpl::then(
|
||||
videoStream->messages()->starsValueChanges()
|
||||
) | rpl::map([=] {
|
||||
) | rpl::map([=](const Calls::Group::StarsDonor &donor) {
|
||||
if (const auto peer = donor.peer) {
|
||||
_starsReactionEffects.fire({
|
||||
.from = peer,
|
||||
.stars = donor.stars,
|
||||
});
|
||||
}
|
||||
return videoStream->messages()->starsLocalState().total;
|
||||
});
|
||||
_paidReactionToast->shownForCall(
|
||||
|
||||
@@ -35,6 +35,7 @@ class DocumentMedia;
|
||||
|
||||
namespace HistoryView::Controls {
|
||||
enum class ToggleCommentsState;
|
||||
struct SendStarButtonEffect;
|
||||
} // namespace HistoryView::Controls
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
@@ -86,6 +87,7 @@ struct RepostClickHandler;
|
||||
|
||||
using CommentsState = HistoryView::Controls::ToggleCommentsState;
|
||||
using PaidReactionToast = HistoryView::PaidReactionToast;
|
||||
using SendStarButtonEffect = HistoryView::Controls::SendStarButtonEffect;
|
||||
|
||||
enum class HeaderLayout {
|
||||
Normal,
|
||||
@@ -186,6 +188,8 @@ public:
|
||||
void setCommentsShownToggles(rpl::producer<> toggles);
|
||||
[[nodiscard]] auto starsReactionsValue() const
|
||||
-> rpl::producer<Ui::SendStarButtonState>;
|
||||
[[nodiscard]] auto starsReactionsEffects() const
|
||||
-> rpl::producer<SendStarButtonEffect>;
|
||||
void setStarsReactionIncrements(rpl::producer<int> increments);
|
||||
|
||||
void unfocusReply();
|
||||
@@ -354,6 +358,7 @@ private:
|
||||
MsgId _commentsLastId = 0;
|
||||
rpl::variable<int> _starsReactions;
|
||||
rpl::variable<bool> _starsReactionHighlighted;
|
||||
rpl::event_stream<SendStarButtonEffect> _starsReactionEffects;
|
||||
|
||||
std::vector<CachedSource> _cachedSourcesList;
|
||||
int _cachedSourceIndex = -1;
|
||||
|
||||
@@ -896,9 +896,9 @@ void ReplyArea::show(
|
||||
_controls->commentsShownToggles());
|
||||
}
|
||||
using Controls = HistoryView::ComposeControls;
|
||||
_controls->setStarsReactionCounter(stream
|
||||
? _controller->starsReactionsValue()
|
||||
: nullptr);
|
||||
_controls->setStarsReactionCounter(
|
||||
stream ? _controller->starsReactionsValue() : nullptr,
|
||||
stream ? _controller->starsReactionsEffects() : nullptr);
|
||||
_controller->setStarsReactionIncrements(
|
||||
_controls->starsReactionIncrements(
|
||||
) | rpl::map([](Controls::StarReactionIncrement increment) {
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_media_view.h"
|
||||
#include "styles/style_media_stories.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Media::Stories {
|
||||
@@ -39,6 +40,7 @@ struct State {
|
||||
Data::StealthMode mode;
|
||||
TimeId now = 0;
|
||||
bool premium = false;
|
||||
bool hasCallback = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] Ui::Toast::Config ToastAlready(TimeId left) {
|
||||
@@ -79,11 +81,12 @@ struct State {
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<State> StateValue(
|
||||
not_null<Main::Session*> session) {
|
||||
not_null<Main::Session*> session,
|
||||
bool hasCallback = false) {
|
||||
return rpl::combine(
|
||||
session->data().stories().stealthModeValue(),
|
||||
Data::AmPremiumValue(session)
|
||||
) | rpl::map([](Data::StealthMode mode, bool premium) {
|
||||
) | rpl::map([=](Data::StealthMode mode, bool premium) {
|
||||
return rpl::make_producer<State>([=](auto consumer) {
|
||||
struct Info {
|
||||
base::Timer timer;
|
||||
@@ -116,7 +119,8 @@ struct State {
|
||||
info->timer.callOnce(left * crl::time(1000));
|
||||
}
|
||||
if (send) {
|
||||
consumer.put_next(State{ mode, now, premium });
|
||||
consumer.put_next(
|
||||
State{ mode, now, premium, hasCallback });
|
||||
}
|
||||
if (left <= 0) {
|
||||
consumer.put_done();
|
||||
@@ -129,25 +133,29 @@ struct State {
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
[[nodiscard]] Ui::FeatureListEntry FeaturePast() {
|
||||
[[nodiscard]] Ui::FeatureListEntry FeaturePast(
|
||||
const style::StealthBoxStyle &st) {
|
||||
return {
|
||||
.icon = st::storiesStealthFeaturePastIcon,
|
||||
.icon = st.featurePastIcon,
|
||||
.title = tr::lng_stealth_mode_past_title(tr::now),
|
||||
.about = { tr::lng_stealth_mode_past_about(tr::now) },
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Ui::FeatureListEntry FeatureNext() {
|
||||
[[nodiscard]] Ui::FeatureListEntry FeatureNext(
|
||||
const style::StealthBoxStyle &st) {
|
||||
return {
|
||||
.icon = st::storiesStealthFeatureNextIcon,
|
||||
.icon = st.featureNextIcon,
|
||||
.title = tr::lng_stealth_mode_next_title(tr::now),
|
||||
.about = { tr::lng_stealth_mode_next_about(tr::now) },
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLogo(QWidget *parent) {
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLogo(
|
||||
QWidget *parent,
|
||||
const style::StealthBoxStyle &st) {
|
||||
const auto add = st::storiesStealthLogoAdd;
|
||||
const auto icon = &st::storiesStealthLogoIcon;
|
||||
const auto icon = &st.logoIcon;
|
||||
const auto size = QSize(2 * add, 2 * add) + icon->size();
|
||||
auto result = object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
|
||||
parent,
|
||||
@@ -156,10 +164,10 @@ struct State {
|
||||
const auto inner = result->entity();
|
||||
inner->resize(size);
|
||||
inner->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=, &st] {
|
||||
auto p = QPainter(inner);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(st::storiesComposeBlue);
|
||||
p.setBrush(st.logoBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto left = (inner->width() - size.width()) / 2;
|
||||
const auto top = (inner->height() - size.height()) / 2;
|
||||
@@ -170,19 +178,22 @@ struct State {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeTitle(QWidget *parent) {
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeTitle(
|
||||
QWidget *parent,
|
||||
const style::StealthBoxStyle &st) {
|
||||
return object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
parent,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
parent,
|
||||
tr::lng_stealth_mode_title(tr::now),
|
||||
st::storiesStealthBox.title),
|
||||
st.box.title),
|
||||
st::storiesStealthTitleMargin);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeAbout(
|
||||
QWidget *parent,
|
||||
rpl::producer<State> state) {
|
||||
rpl::producer<State> state,
|
||||
const style::StealthBoxStyle &st) {
|
||||
auto text = std::move(state) | rpl::map([](const State &state) {
|
||||
return state.premium
|
||||
? tr::lng_stealth_mode_about(tr::now)
|
||||
@@ -193,18 +204,21 @@ struct State {
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
parent,
|
||||
std::move(text),
|
||||
st::storiesStealthAbout),
|
||||
st.about),
|
||||
st::storiesStealthAboutMargin);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RoundButton> MakeButton(
|
||||
QWidget *parent,
|
||||
rpl::producer<State> state) {
|
||||
rpl::producer<State> state,
|
||||
const style::StealthBoxStyle &st) {
|
||||
auto text = rpl::duplicate(state) | rpl::map([](const State &state) {
|
||||
if (!state.premium) {
|
||||
return tr::lng_stealth_mode_unlock();
|
||||
} else if (state.mode.cooldownTill <= state.now) {
|
||||
return tr::lng_stealth_mode_enable();
|
||||
return state.hasCallback
|
||||
? tr::lng_stealth_mode_enable_and_open()
|
||||
: tr::lng_stealth_mode_enable();
|
||||
}
|
||||
return rpl::single(
|
||||
rpl::empty
|
||||
@@ -223,32 +237,32 @@ struct State {
|
||||
auto result = object_ptr<Ui::RoundButton>(
|
||||
parent,
|
||||
rpl::single(QString()),
|
||||
st::storiesStealthBox.button);
|
||||
st.box.button);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st::storiesStealthButtonLabel);
|
||||
st.buttonLabel);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->show();
|
||||
|
||||
const auto lock = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
lock->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
lock->resize(st::storiesStealthLockIcon.size());
|
||||
lock->resize(st.lockIcon.size());
|
||||
lock->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=, &st] {
|
||||
auto p = QPainter(lock);
|
||||
st::storiesStealthLockIcon.paintInCenter(p, lock->rect());
|
||||
st.lockIcon.paintInCenter(p, lock->rect());
|
||||
}, lock->lifetime());
|
||||
|
||||
const auto lockLeft = -st::storiesStealthButtonLabel.style.font->height;
|
||||
const auto updateLabelLockGeometry = [=] {
|
||||
const auto lockLeft = -st.buttonLabel.style.font->height;
|
||||
const auto updateLabelLockGeometry = [=, &st] {
|
||||
const auto outer = raw->width();
|
||||
const auto added = -st::storiesStealthBox.button.width;
|
||||
const auto added = -st.box.button.width;
|
||||
const auto skip = lock->isHidden() ? 0 : (lockLeft + lock->width());
|
||||
const auto width = outer - added - skip;
|
||||
const auto top = st::storiesStealthBox.button.textTop;
|
||||
const auto top = st.box.button.textTop;
|
||||
label->resizeToWidth(width);
|
||||
label->move(added / 2, top);
|
||||
const auto inner = std::min(label->textMaxWidth(), width);
|
||||
@@ -272,41 +286,43 @@ struct State {
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> StealthModeBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show) {
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
Fn<void()> onActivated,
|
||||
const style::StealthBoxStyle &st) {
|
||||
return Box([=](not_null<Ui::GenericBox*> box) {
|
||||
struct Data {
|
||||
rpl::variable<State> state;
|
||||
bool requested = false;
|
||||
};
|
||||
const auto data = box->lifetime().make_state<Data>();
|
||||
data->state = StateValue(&show->session());
|
||||
data->state = StateValue(&show->session(), onActivated != nullptr);
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::storiesStealthBox);
|
||||
box->addRow(MakeLogo(box));
|
||||
box->addRow(MakeTitle(box), style::al_top);
|
||||
box->addRow(MakeAbout(box, data->state.value()), style::al_top);
|
||||
box->setStyle(st.box);
|
||||
box->addRow(MakeLogo(box, st));
|
||||
box->addRow(MakeTitle(box, st), style::al_top);
|
||||
box->addRow(MakeAbout(box, data->state.value(), st), style::al_top);
|
||||
const auto make = [&](const Ui::FeatureListEntry &entry) {
|
||||
return Ui::MakeFeatureListEntry(
|
||||
box,
|
||||
entry,
|
||||
{},
|
||||
st::storiesStealthFeatureTitle,
|
||||
st::storiesStealthFeatureAbout);
|
||||
st.featureTitle,
|
||||
st.featureAbout);
|
||||
};
|
||||
box->addRow(make(FeaturePast()));
|
||||
box->addRow(make(FeaturePast(st)));
|
||||
box->addRow(
|
||||
make(FeatureNext()),
|
||||
make(FeatureNext(st)),
|
||||
(st::boxRowPadding
|
||||
+ QMargins(0, 0, 0, st::storiesStealthBoxBottom)));
|
||||
box->setNoContentMargin(true);
|
||||
box->addTopButton(st::storiesStealthBoxClose, [=] {
|
||||
box->addTopButton(st.boxClose, [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
const auto button = box->addButton(
|
||||
MakeButton(box, data->state.value()));
|
||||
MakeButton(box, data->state.value(), st));
|
||||
button->resizeToWidth(st::boxWideWidth
|
||||
- st::storiesStealthBox.buttonPadding.left()
|
||||
- st::storiesStealthBox.buttonPadding.right());
|
||||
- st.box.buttonPadding.left()
|
||||
- st.box.buttonPadding.right());
|
||||
button->setClickedCallback([=] {
|
||||
const auto now = data->state.current();
|
||||
if (now.mode.enabledTill > now.now) {
|
||||
@@ -332,19 +348,30 @@ struct State {
|
||||
}) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
show->showToast(ToastActivated());
|
||||
if (onActivated) {
|
||||
onActivated();
|
||||
}
|
||||
}, box->lifetime());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetupStealthMode(std::shared_ptr<ChatHelpers::Show> show) {
|
||||
void SetupStealthMode(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
StealthModeDescriptor descriptor) {
|
||||
const auto onActivated = descriptor.onActivated;
|
||||
const auto st = descriptor.st;
|
||||
const auto now = base::unixtime::now();
|
||||
const auto mode = show->session().data().stories().stealthMode();
|
||||
if (const auto left = mode.enabledTill - now; left > 0) {
|
||||
show->showToast(ToastAlready(left));
|
||||
if (onActivated) {
|
||||
onActivated();
|
||||
}
|
||||
} else {
|
||||
show->show(StealthModeBox(show));
|
||||
const auto &style = st ? *st : st::storiesStealthStyle;
|
||||
show->show(StealthModeBox(show, onActivated, style));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace style {
|
||||
struct StealthBoxStyle;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
void SetupStealthMode(std::shared_ptr<ChatHelpers::Show> show);
|
||||
struct StealthModeDescriptor {
|
||||
Fn<void()> onActivated = nullptr;
|
||||
const style::StealthBoxStyle *st = nullptr;
|
||||
};
|
||||
|
||||
void SetupStealthMode(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
StealthModeDescriptor descriptor = {});
|
||||
|
||||
[[nodiscard]] QString TimeLeftText(int left);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using "ui/basic.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "ui/menu_icons.style";
|
||||
using "media/player/media_player.style";
|
||||
using "media/stories/media_stories.style";
|
||||
using "boxes/boxes.style";
|
||||
using "calls/calls.style";
|
||||
using "chat_helpers/chat_helpers.style";
|
||||
@@ -1032,7 +1033,6 @@ storiesCaptionPullThreshold: 50px;
|
||||
storiesShowMorePadding: margins(6px, 4px, 6px, 4px);
|
||||
storiesShowMoreFont: semiboldFont;
|
||||
|
||||
storiesStealthLogoIcon: icon{{ "stories/stealth_logo", storiesComposeWhiteText }};
|
||||
storiesStealthLogoAdd: 12px;
|
||||
storiesStealthLogoMargin: margins(0px, 28px, 0px, 7px);
|
||||
storiesStealthBox: Box(defaultBox) {
|
||||
@@ -1060,14 +1060,6 @@ storiesStealthBox: Box(defaultBox) {
|
||||
}
|
||||
titleAdditionalFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
storiesStealthButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
textFg: storiesComposeWhiteText;
|
||||
align: align(top);
|
||||
minWidth: 20px;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
storiesStealthLockIcon: icon {{ "dialogs/dialogs_lock_on", storiesComposeWhiteText }};
|
||||
storiesStealthTitleMargin: margins(0px, 10px, 0px, 0px);
|
||||
storiesStealthBoxClose: IconButton(defaultIconButton) {
|
||||
width: boxTitleHeight;
|
||||
@@ -1080,26 +1072,39 @@ storiesStealthBoxClose: IconButton(defaultIconButton) {
|
||||
rippleAreaSize: 40px;
|
||||
ripple: storiesComposeRippleLight;
|
||||
}
|
||||
storiesStealthAbout: FlatLabel(defaultFlatLabel) {
|
||||
textFg: storiesComposeGrayText;
|
||||
align: align(top);
|
||||
minWidth: 20px;
|
||||
}
|
||||
storiesStealthAboutMargin: margins(0px, 5px, 0px, 15px);
|
||||
storiesStealthFeatureTitle: storiesHeaderName;
|
||||
storiesStealthFeatureAbout: FlatLabel(defaultFlatLabel) {
|
||||
textFg: storiesComposeGrayText;
|
||||
minWidth: 20px;
|
||||
}
|
||||
storiesStealthFeaturePastIcon: icon{{ "stories/stealth_5m", storiesComposeBlue }};
|
||||
storiesStealthFeatureNextIcon: icon{{ "stories/stealth_25m", storiesComposeBlue }};
|
||||
storiesStealthFeatureLabelLeft: 46px;
|
||||
storiesStealthFeatureSkip: 2px;
|
||||
storiesStealthBoxBottom: 11px;
|
||||
storiesStealthToast: Toast(defaultMultilineToast) {
|
||||
maxWidth: 340px;
|
||||
}
|
||||
|
||||
storiesStealthStyle: StealthBoxStyle {
|
||||
box: storiesStealthBox;
|
||||
buttonLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
textFg: storiesComposeWhiteText;
|
||||
align: align(top);
|
||||
minWidth: 20px;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
lockIcon: icon {{ "dialogs/dialogs_lock_on", storiesComposeWhiteText }};
|
||||
boxClose: storiesStealthBoxClose;
|
||||
about: FlatLabel(defaultFlatLabel) {
|
||||
textFg: storiesComposeGrayText;
|
||||
align: align(top);
|
||||
minWidth: 20px;
|
||||
}
|
||||
featureTitle: storiesHeaderName;
|
||||
featureAbout: FlatLabel(defaultFlatLabel) {
|
||||
textFg: storiesComposeGrayText;
|
||||
minWidth: 20px;
|
||||
}
|
||||
featurePastIcon: icon{{ "stories/stealth_5m", storiesComposeBlue }};
|
||||
featureNextIcon: icon{{ "stories/stealth_25m", storiesComposeBlue }};
|
||||
logoIcon: icon{{ "stories/stealth_logo", storiesComposeWhiteText }};
|
||||
logoBg: storiesComposeBlue;
|
||||
}
|
||||
|
||||
storiesViewsPosition: point(4px, 29px);
|
||||
storiesViewsIcon: icon{{ "mediaview/views", storiesComposeGrayText }};
|
||||
storiesViewsText: FlatLabel(defaultFlatLabel) {
|
||||
|
||||
@@ -36,6 +36,9 @@ void OverlayWidget::RendererSW::paintFallback(
|
||||
p.fillRect(clip.boundingRect(), Qt::transparent);
|
||||
return;
|
||||
}
|
||||
if (const auto stream = _owner->_videoStream.get()) {
|
||||
stream->ensureBorrowedRenderer();
|
||||
}
|
||||
_p = &p;
|
||||
_clip = &clip;
|
||||
_clipOuter = clip.boundingRect();
|
||||
|
||||
@@ -65,7 +65,7 @@ auto TopVideoStreamDonors(not_null<Calls::GroupCall*> call)
|
||||
-> rpl::producer<std::vector<Data::MessageReactionsTopPaid>> {
|
||||
const auto messages = call->messages();
|
||||
return rpl::single(rpl::empty) | rpl::then(
|
||||
messages->starsValueChanges()
|
||||
messages->starsValueChanges() | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
const auto &list = messages->starsTop().topDonors;
|
||||
auto still = Ui::MaxTopPaidDonorsShown();
|
||||
@@ -272,14 +272,14 @@ void VideoStream::ensureBorrowedRenderer(QOpenGLFunctions &f) {
|
||||
_viewport->ensureBorrowedRenderer(f);
|
||||
}
|
||||
|
||||
void VideoStream::ensureBorrowedCleared(QOpenGLFunctions *f) {
|
||||
_viewport->ensureBorrowedCleared(f);
|
||||
}
|
||||
|
||||
void VideoStream::borrowedPaint(QOpenGLFunctions &f) {
|
||||
_viewport->borrowedPaint(f);
|
||||
}
|
||||
|
||||
void VideoStream::ensureBorrowedRenderer() {
|
||||
_viewport->ensureBorrowedRenderer();
|
||||
}
|
||||
|
||||
void VideoStream::borrowedPaint(Painter &p, const QRegion &clip) {
|
||||
_viewport->borrowedPaint(p, clip);
|
||||
}
|
||||
|
||||
@@ -63,8 +63,9 @@ public:
|
||||
void toggleCommentsOn(rpl::producer<bool> shown);
|
||||
|
||||
void ensureBorrowedRenderer(QOpenGLFunctions &f);
|
||||
void ensureBorrowedCleared(QOpenGLFunctions *f);
|
||||
void borrowedPaint(QOpenGLFunctions &f);
|
||||
|
||||
void ensureBorrowedRenderer();
|
||||
void borrowedPaint(Painter &p, const QRegion &clip);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
@@ -124,7 +124,6 @@ void TryAddingPaidReaction(
|
||||
void TryAddingPaidReaction(
|
||||
not_null<Calls::GroupCall*> call,
|
||||
int count,
|
||||
std::optional<PeerId> shownPeer,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
Fn<void(bool)> finished) {
|
||||
const auto checkCall = [weak = base::make_weak(call), finished] {
|
||||
@@ -141,7 +140,7 @@ void TryAddingPaidReaction(
|
||||
if (result == Settings::SmallBalanceResult::Success
|
||||
|| result == Settings::SmallBalanceResult::Already) {
|
||||
if (const auto call = checkCall()) {
|
||||
call->messages()->reactionsPaidAdd(count, shownPeer);
|
||||
call->messages()->reactionsPaidAdd(count);
|
||||
call->peer()->owner().notifyCallPaidReactionSent(call);
|
||||
if (const auto onstack = finished) {
|
||||
onstack(true);
|
||||
|
||||