Compare commits

...

56 Commits

Author SHA1 Message Date
John Preston
6e75a41ee6 Version 6.3.4: Fix build with Xcode. 2025-11-26 16:44:09 +04:00
John Preston
84266aef2c Version 6.3.4.
- Show active auctions above chats list.
- Add star sending effects in live stories.
- Fix back button in group member profile view.
- Export separate topics message history.
- Export audio files saved to profile.
- Many different crash fixes.
2025-11-26 14:58:32 +04:00
John Preston
40a7f8ea50 Try allowing only tab-focus for screen readers. 2025-11-26 14:57:35 +04:00
John Preston
7c4fcdd9cb Update emoji to Unicode 16. 2025-11-26 14:48:16 +04:00
John Preston
92e87852c1 Fix top bar title/subtitle in saved messages.
Fixes #30018.
2025-11-26 13:27:32 +04:00
John Preston
73c4da2b21 Show '+' to the right of all bids. 2025-11-26 13:01:15 +04:00
23rd
9f4da7e890 Added button for profile color to settings information section. 2025-11-26 05:37:09 +03:00
23rd
8b23457373 Fixed display of verified icon in box from web bot preview.
Regression was introduced in e7fa330215.
2025-11-26 05:30:38 +03:00
23rd
ab2fd7c749 Once again fixed position of tag preview in chats filter settings.
Previous related commit 512e6de39b.
2025-11-26 05:20:23 +03:00
John Preston
040a6ddf3a Fix build with MSVC. 2025-11-25 21:34:26 +04:00
John Preston
55afe0912f Add screen reader state logging. 2025-11-25 21:28:57 +04:00
23rd
ee48127094 Added send small button to box of entry from credits earn history. 2025-11-25 20:01:09 +03:00
23rd
445576d568 Added ToS link to any box of entry from credits earn history. 2025-11-25 18:59:47 +03:00
23rd
8f1d40892e Added info of limited gifts to box of entry from credits earn history. 2025-11-25 18:59:47 +03:00
23rd
766db9660c Swap name of sender and subtext for paid messages in earn history list. 2025-11-25 18:59:47 +03:00
23rd
68665ec1f2 Added phrase for paid reactions of live stories to credits earn history. 2025-11-25 18:59:47 +03:00
John Preston
b400964aa1 Don't allow admin to send stars in streams. 2025-11-25 19:37:26 +04:00
John Preston
317530cfa3 Attempt to fix audio cracks in streams. 2025-11-25 19:37:10 +04:00
John Preston
e8fba23b59 Don't allow sending stars from stream admin. 2025-11-25 19:05:55 +04:00
John Preston
ef749e695e Show nice layout of stars-only messages. 2025-11-25 18:30:09 +04:00
John Preston
712ef33d6b Submit custom bid with Enter. 2025-11-25 17:50:25 +04:00
John Preston
0441b7dbc3 Show star senders in live stories. 2025-11-25 17:50:25 +04:00
23rd
3ee0dcbacd Provided topic name to export controller for topic history. 2025-11-25 06:19:41 +03:00
23rd
57411b962f Added initial ability to export topic history. 2025-11-25 06:19:34 +03:00
23rd
4eee00d95e Added forwarded_from_id field to json export for forwarded messages. 2025-11-25 06:14:06 +03:00
23rd
957a08962f Added dark theme to exported html. 2025-11-25 06:14:06 +03:00
23rd
21c82f5fe1 Added ability to view stories anonymously from context menu. 2025-11-25 06:14:06 +03:00
23rd
27964993f6 Added safe guard to userpic in peer qr box. 2025-11-25 06:14:06 +03:00
John Preston
14e296e1f9 Improve layout of about auction box. 2025-11-24 22:06:55 +04:00
John Preston
23c0ff934f Skip premium badge for Saved Messages. 2025-11-24 22:01:56 +04:00
John Preston
c64ef1e20e Allow transfering gifts without premium. 2025-11-24 21:04:51 +04:00
John Preston
ed97619c6c Guard by widget instead of session. 2025-11-24 19:22:20 +04:00
23rd
924ec592b1 Improved paint with ready full peer before animation in profile top bar. 2025-11-24 18:18:13 +03:00
23rd
669c581701 Added dummy filler to PeerQrBox when result link is empty. 2025-11-24 17:46:49 +03:00
23rd
e97ae3d537 Fixed display of QR button in info profile when no usernames. 2025-11-24 17:46:49 +03:00
23rd
08800b68f4 Slightly improved subtext for topic link in info profile. 2025-11-24 17:46:49 +03:00
23rd
a59db6529c Added default guarded callback for simple QR box for peer username. 2025-11-24 17:46:49 +03:00
dependabot[bot]
02a54ceea6 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 17:59:24 +04:00
John Preston
f0a7c547e8 Add safe check for strange click reports. 2025-11-24 17:51:42 +04:00
John Preston
ecfb343690 Fix crash in going from saved music to a chat. 2025-11-24 17:38:54 +04:00
John Preston
c65472c9b3 Add more assertions to find a problem. 2025-11-24 17:27:29 +04:00
John Preston
c42864d35e Fix crash in several video removed from video call. 2025-11-24 17:08:10 +04:00
John Preston
b02ce599e6 Fix possible crash in usernames order saving. 2025-11-24 14:47:14 +04:00
John Preston
1ae5495b91 Add some more assertions for subsection tabs. 2025-11-24 14:20:10 +04:00
John Preston
c4fbb8c199 Fix possible crash in group call box closing. 2025-11-24 14:19:37 +04:00
John Preston
313872dacc Fix crash in live location message destruction. 2025-11-24 12:23:13 +04:00
John Preston
ef15136a3b Fix possible crash in conference box closing. 2025-11-24 10:12:38 +04:00
John Preston
4342c8d761 Fix Live stories display without OpenGL. 2025-11-24 09:59:34 +04:00
23rd
644744ac9e Improved phrase of original date in self. 2025-11-23 18:29:01 +03:00
23rd
cbc03d1e45 Fixed display of original date in self when original sender is hidden. 2025-11-23 18:13:34 +03:00
23rd
7f56192b97 Fixed handler of login email in intro section.
This occurs both log in with setting up of login email
and log in by code from login email (set before).
2025-11-23 16:25:01 +03:00
23rd
6590f3b741 Added simple handle of EMAIL_NOT_SETUP error from email login section. 2025-11-23 16:25:01 +03:00
23rd
c0bbb669e0 Fixed unwanted switching to default filter on creation of tabs strip. 2025-11-23 16:25:01 +03:00
23rd
63014adfef Fixed rpl trigger for back button in profile top bar. 2025-11-23 16:25:01 +03:00
23rd
1ad055c8c8 Updated libiconv to v1.18. 2025-11-22 12:36:36 +03:00
John Preston
9b558564e9 Show my place correctly. 2025-11-21 21:26:44 +04:00
125 changed files with 2039 additions and 478 deletions

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

View File

@@ -59,7 +59,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive

View File

@@ -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 }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 KiB

After

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

@@ -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 */
}
}

View File

@@ -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";

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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),

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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(),
});
}

View File

@@ -80,6 +80,7 @@ private:
Ui::RoundRect pinnedLight;
Ui::RoundRect pinnedDark;
Ui::RoundRect messageLight;
Ui::RoundRect priceDark;
Ui::RoundRect badgeDark;
};

View File

@@ -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);
}

View File

@@ -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,
});
}

View File

@@ -39,6 +39,7 @@ struct VideoStreamStarsBoxArgs {
int min = 0;
int current = 0;
bool sending = false;
bool admin = false;
Fn<void(int)> save;
QString name;
};

View File

@@ -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);

View File

@@ -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();

View File

@@ -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());

View File

@@ -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),

View File

@@ -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(

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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()) {

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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),

View File

@@ -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,

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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());

View File

@@ -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;

View File

@@ -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

View File

@@ -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",

View File

@@ -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;

View File

@@ -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);

View File

@@ -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"

View File

@@ -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

View File

@@ -99,4 +99,9 @@ enum class ToggleCommentsState {
WithNew,
};
struct SendStarButtonEffect {
not_null<PeerData*> from;
int stars = 0;
};
} // namespace HistoryView::Controls

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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();
});
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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,
});

View File

@@ -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 &section = sections.back();
section.badges = item.badges;

View File

@@ -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)

View File

@@ -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);

View File

@@ -78,7 +78,6 @@ private:
Ui::Text::String _nameLine;
Ui::Text::String _phoneLine;
Ui::Text::String _infoLine;
Fn<void(not_null<Ui::GenericBox*>)> _vcardBoxFactory;

View File

@@ -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();

View File

@@ -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(

View File

@@ -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());

View File

@@ -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);
});
}
}

View File

@@ -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())) {
}

View File

@@ -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;
});
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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());
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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 &) {

View File

@@ -65,7 +65,8 @@ struct Data {
EmailStatus emailStatus = EmailStatus::None;
QString email;
QString emailPattern;
QString emailPatternSetup;
QString emailPatternLogin;
Core::CloudPasswordState pwdState;

View 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;
}

View File

@@ -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(

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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);

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