Compare commits
20 Commits
7236ecc034
...
6a2896e730
Author | SHA1 | Date |
---|---|---|
detiam | 6a2896e730 | |
John Preston | c0db5ee98a | |
John Preston | 372b3da09c | |
John Preston | 79532954dc | |
23rd | aff2be605e | |
John Preston | 363c191a6e | |
John Preston | 2949cdab61 | |
John Preston | 7addcf2d25 | |
John Preston | a272807a99 | |
23rd | c803603de4 | |
23rd | e6c22ec1ca | |
John Preston | b3ae843f0e | |
John Preston | 12a78c1f45 | |
23rd | 612b81ee87 | |
23rd | e5b91d2f3d | |
23rd | 82293c98eb | |
23rd | 629da68cfc | |
23rd | 643ecd2c2c | |
Ilya Fedin | dd1cb00c62 | |
detiam | 9409fb344f |
|
@ -273,6 +273,8 @@ PRIVATE
|
||||||
boxes/local_storage_box.h
|
boxes/local_storage_box.h
|
||||||
boxes/max_invite_box.cpp
|
boxes/max_invite_box.cpp
|
||||||
boxes/max_invite_box.h
|
boxes/max_invite_box.h
|
||||||
|
boxes/moderate_messages_box.cpp
|
||||||
|
boxes/moderate_messages_box.h
|
||||||
boxes/peer_list_box.cpp
|
boxes/peer_list_box.cpp
|
||||||
boxes/peer_list_box.h
|
boxes/peer_list_box.h
|
||||||
boxes/peer_list_controllers.cpp
|
boxes/peer_list_controllers.cpp
|
||||||
|
|
|
@ -134,9 +134,32 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
.page-slide {
|
.page-slide {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
margin-left: 0%;
|
margin-left: 0%;
|
||||||
transition: margin 240ms ease-in-out;
|
transition: margin 240ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
.page-footer {
|
||||||
|
height: 32px;
|
||||||
|
margin-top: -32px;
|
||||||
|
background: var(--td-window-bg-over);
|
||||||
|
}
|
||||||
|
.page-footer .content {
|
||||||
|
padding: 3px 18px;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--td-window-sub-text-fg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.page-footer .wrong {
|
||||||
|
position: relative;
|
||||||
|
padding: 5px;
|
||||||
|
margin: -5px;
|
||||||
|
color: var(--td-window-sub-text-fg);
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.page-footer .wrong:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.hidden-left,
|
.hidden-left,
|
||||||
.hidden-right {
|
.hidden-right {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -148,7 +171,7 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
margin-left: 100%;
|
margin-left: 100%;
|
||||||
}
|
}
|
||||||
article {
|
article {
|
||||||
padding-bottom: 12px;
|
padding-bottom: 40px;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
@ -893,6 +916,9 @@ section.related a.related-link:after {
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
section.related a.related-link:last-child:after {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
section.related .related-link-url {
|
section.related .related-link-url {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
@ -1027,6 +1053,9 @@ section.channel > a > h4 {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
.media-outer {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
.photo-wrap,
|
.photo-wrap,
|
||||||
.video-wrap {
|
.video-wrap {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -26,7 +26,7 @@ var IV = {
|
||||||
}
|
}
|
||||||
target = target.parentNode;
|
target = target.parentNode;
|
||||||
}
|
}
|
||||||
if (!target || !target.hasAttribute('href')) {
|
if (!target || (context === '' && !target.hasAttribute('href'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var base = document.createElement('A');
|
var base = document.createElement('A');
|
||||||
|
@ -413,9 +413,12 @@ var IV = {
|
||||||
var article = function (el) {
|
var article = function (el) {
|
||||||
return el.getElementsByTagName('article')[0];
|
return el.getElementsByTagName('article')[0];
|
||||||
};
|
};
|
||||||
var from = article(IV.findPageScroll());
|
var footer = function (el) {
|
||||||
var to = article(IV.makeScrolledContent(data.html));
|
return el.getElementsByClassName('page-footer')[0];
|
||||||
morphdom(from, to, {
|
};
|
||||||
|
var from = IV.findPageScroll();
|
||||||
|
var to = IV.makeScrolledContent(data.html);
|
||||||
|
morphdom(article(from), article(to), {
|
||||||
onBeforeElUpdated: function (fromEl, toEl) {
|
onBeforeElUpdated: function (fromEl, toEl) {
|
||||||
if (fromEl.classList.contains('video')
|
if (fromEl.classList.contains('video')
|
||||||
&& toEl.classList.contains('video')
|
&& toEl.classList.contains('video')
|
||||||
|
@ -439,6 +442,7 @@ var IV = {
|
||||||
return !fromEl.isEqualNode(toEl);
|
return !fromEl.isEqualNode(toEl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
morphdom(footer(from), footer(to));
|
||||||
IV.initMedia();
|
IV.initMedia();
|
||||||
eval(data.js);
|
eval(data.js);
|
||||||
},
|
},
|
||||||
|
@ -477,9 +481,7 @@ var IV = {
|
||||||
var result = document.createElement('div');
|
var result = document.createElement('div');
|
||||||
result.className = 'page-scroll';
|
result.className = 'page-scroll';
|
||||||
result.tabIndex = '-1';
|
result.tabIndex = '-1';
|
||||||
result.innerHTML = '<div class="page-slide"><article>'
|
result.innerHTML = html.trim();
|
||||||
+ html
|
|
||||||
+ '</article></div>';
|
|
||||||
result.onscroll = IV.frameScrolled;
|
result.onscroll = IV.frameScrolled;
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
|
@ -2854,7 +2854,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_in_dlg_audio_count#other" = "{count} audio";
|
"lng_in_dlg_audio_count#other" = "{count} audio";
|
||||||
|
|
||||||
"lng_ban_user" = "Ban User";
|
"lng_ban_user" = "Ban User";
|
||||||
|
"lng_ban_users" = "Ban users";
|
||||||
|
"lng_restrict_users" = "Restrict users";
|
||||||
"lng_delete_all_from_user" = "Delete all from {user}";
|
"lng_delete_all_from_user" = "Delete all from {user}";
|
||||||
|
"lng_delete_all_from_users" = "Delete all from users";
|
||||||
|
"lng_restrict_user_part" = "Partially restrict this user {emoji}";
|
||||||
|
"lng_restrict_users_part" = "Partially restrict users {emoji}";
|
||||||
|
"lng_restrict_user_full" = "Fully ban this user {emoji}";
|
||||||
|
"lng_restrict_users_full" = "Fully ban users {emoji}";
|
||||||
|
"lng_restrict_users_part_single_header" = "What can this user do?";
|
||||||
|
"lng_restrict_users_part_header#one" = "What can {count} selected user do?";
|
||||||
|
"lng_restrict_users_part_header#other" = "What can {count} selected users do?";
|
||||||
"lng_report_spam" = "Report Spam";
|
"lng_report_spam" = "Report Spam";
|
||||||
"lng_report_spam_and_leave" = "Report spam and leave";
|
"lng_report_spam_and_leave" = "Report spam and leave";
|
||||||
"lng_report_spam_done" = "Thank you for your report.";
|
"lng_report_spam_done" = "Thank you for your report.";
|
||||||
|
@ -5101,6 +5111,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_iv_share" = "Share";
|
"lng_iv_share" = "Share";
|
||||||
"lng_iv_join_channel" = "Join";
|
"lng_iv_join_channel" = "Join";
|
||||||
"lng_iv_window_title" = "Instant View";
|
"lng_iv_window_title" = "Instant View";
|
||||||
|
"lng_iv_wrong_layout" = "Wrong layout?";
|
||||||
|
|
||||||
"lng_limit_download_title" = "Download speed limited";
|
"lng_limit_download_title" = "Download speed limited";
|
||||||
"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
|
"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="4.16.9.0" />
|
Version="4.16.10.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,16,9,0
|
FILEVERSION 4,16,10,0
|
||||||
PRODUCTVERSION 4,16,9,0
|
PRODUCTVERSION 4,16,10,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||||
VALUE "FileDescription", "Telegram Desktop"
|
VALUE "FileDescription", "Telegram Desktop"
|
||||||
VALUE "FileVersion", "4.16.9.0"
|
VALUE "FileVersion", "4.16.10.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||||
VALUE "ProductName", "Telegram Desktop"
|
VALUE "ProductName", "Telegram Desktop"
|
||||||
VALUE "ProductVersion", "4.16.9.0"
|
VALUE "ProductVersion", "4.16.10.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,16,9,0
|
FILEVERSION 4,16,10,0
|
||||||
PRODUCTVERSION 4,16,9,0
|
PRODUCTVERSION 4,16,10,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||||
VALUE "FileVersion", "4.16.9.0"
|
VALUE "FileVersion", "4.16.10.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||||
VALUE "ProductName", "Telegram Desktop"
|
VALUE "ProductName", "Telegram Desktop"
|
||||||
VALUE "ProductVersion", "4.16.9.0"
|
VALUE "ProductVersion", "4.16.10.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -592,6 +592,33 @@ ChatParticipants::Parsed ChatParticipants::ParseRecent(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatParticipants::Restrict(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
not_null<PeerData*> participant,
|
||||||
|
ChatRestrictionsInfo oldRights,
|
||||||
|
ChatRestrictionsInfo newRights,
|
||||||
|
Fn<void()> onDone,
|
||||||
|
Fn<void()> onFail) {
|
||||||
|
channel->session().api().request(MTPchannels_EditBanned(
|
||||||
|
channel->inputChannel,
|
||||||
|
participant->input,
|
||||||
|
MTP_chatBannedRights(
|
||||||
|
MTP_flags(MTPDchatBannedRights::Flags::from_raw(
|
||||||
|
uint32(newRights.flags))),
|
||||||
|
MTP_int(newRights.until))
|
||||||
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
channel->session().api().applyUpdates(result);
|
||||||
|
channel->applyEditBanned(participant, oldRights, newRights);
|
||||||
|
if (onDone) {
|
||||||
|
onDone();
|
||||||
|
}
|
||||||
|
}).fail([=] {
|
||||||
|
if (onFail) {
|
||||||
|
onFail();
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
void ChatParticipants::requestSelf(not_null<ChannelData*> channel) {
|
void ChatParticipants::requestSelf(not_null<ChannelData*> channel) {
|
||||||
if (_selfParticipantRequests.contains(channel)) {
|
if (_selfParticipantRequests.contains(channel)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -100,6 +100,13 @@ public:
|
||||||
static Parsed ParseRecent(
|
static Parsed ParseRecent(
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
const TLMembers &data);
|
const TLMembers &data);
|
||||||
|
static void Restrict(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
not_null<PeerData*> participant,
|
||||||
|
ChatRestrictionsInfo oldRights,
|
||||||
|
ChatRestrictionsInfo newRights,
|
||||||
|
Fn<void()> onDone,
|
||||||
|
Fn<void()> onFail);
|
||||||
void add(
|
void add(
|
||||||
std::shared_ptr<Ui::Show> show,
|
std::shared_ptr<Ui::Show> show,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
|
|
@ -62,6 +62,10 @@ void ConfirmPhone::resolve(
|
||||||
return bad("FirebaseSms");
|
return bad("FirebaseSms");
|
||||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||||
return bad("EmailCode");
|
return bad("EmailCode");
|
||||||
|
}, [&](const MTPDauth_sentCodeTypeSmsWord &) {
|
||||||
|
return bad("SmsWord");
|
||||||
|
}, [&](const MTPDauth_sentCodeTypeSmsPhrase &) {
|
||||||
|
return bad("SmsPhrase");
|
||||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||||
return bad("SetUpEmailRequired");
|
return bad("SetUpEmailRequired");
|
||||||
});
|
});
|
||||||
|
|
|
@ -691,6 +691,10 @@ createPollOptionField: InputField(createPollField) {
|
||||||
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||||
heightMax: 68px;
|
heightMax: 68px;
|
||||||
}
|
}
|
||||||
|
createPollOptionFieldPremium: InputField(createPollOptionField) {
|
||||||
|
textMargins: margins(22px, 11px, 68px, 11px);
|
||||||
|
}
|
||||||
|
createPollOptionFieldPremiumEmojiPosition: point(15px, -1px);
|
||||||
createPollSolutionField: InputField(createPollField) {
|
createPollSolutionField: InputField(createPollField) {
|
||||||
textMargins: margins(0px, 4px, 0px, 4px);
|
textMargins: margins(0px, 4px, 0px, 4px);
|
||||||
border: 1px;
|
border: 1px;
|
||||||
|
@ -704,7 +708,7 @@ createPollOptionRemove: CrossButton {
|
||||||
cross: CrossAnimation {
|
cross: CrossAnimation {
|
||||||
size: 22px;
|
size: 22px;
|
||||||
skip: 6px;
|
skip: 6px;
|
||||||
stroke: 1.;
|
stroke: 1.5;
|
||||||
minScale: 0.3;
|
minScale: 0.3;
|
||||||
}
|
}
|
||||||
crossFg: boxTitleCloseFg;
|
crossFg: boxTitleCloseFg;
|
||||||
|
@ -718,6 +722,7 @@ createPollOptionRemove: CrossButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createPollOptionRemovePosition: point(11px, 9px);
|
createPollOptionRemovePosition: point(11px, 9px);
|
||||||
|
createPollOptionEmojiPositionSkip: 4px;
|
||||||
createPollWarning: FlatLabel(defaultFlatLabel) {
|
createPollWarning: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: windowSubTextFg;
|
textFg: windowSubTextFg;
|
||||||
palette: TextPalette(defaultTextPalette) {
|
palette: TextPalette(defaultTextPalette) {
|
||||||
|
@ -1074,3 +1079,23 @@ collectibleBox: Box(defaultBox) {
|
||||||
buttonHeight: 36px;
|
buttonHeight: 36px;
|
||||||
button: collectibleCopy;
|
button: collectibleCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moderateBoxUserpic: UserpicButton(defaultUserpicButton) {
|
||||||
|
size: size(34px, 42px);
|
||||||
|
photoSize: 34px;
|
||||||
|
photoPosition: point(0px, 4px);
|
||||||
|
}
|
||||||
|
moderateBoxExpand: icon {{ "chat/reply_type_group", boxTextFg }};
|
||||||
|
moderateBoxExpandHeight: 20px;
|
||||||
|
moderateBoxExpandRight: 10px;
|
||||||
|
moderateBoxExpandInnerSkip: 2px;
|
||||||
|
moderateBoxExpandFont: font(11px);
|
||||||
|
moderateBoxExpandToggleSize: 4px;
|
||||||
|
moderateBoxExpandToggleFourStrokes: 3px;
|
||||||
|
moderateBoxExpandIcon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }};
|
||||||
|
moderateBoxExpandIconDown: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }};
|
||||||
|
moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
|
||||||
|
palette: TextPalette(defaultTextPalette) {
|
||||||
|
selectLinkFg: windowActiveTextFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,33 +7,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "boxes/create_poll_box.h"
|
#include "boxes/create_poll_box.h"
|
||||||
|
|
||||||
#include "lang/lang_keys.h"
|
#include "base/call_delayed.h"
|
||||||
#include "data/data_poll.h"
|
#include "base/event_filter.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "base/random.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
|
||||||
#include "ui/wrap/fade_wrap.h"
|
|
||||||
#include "ui/widgets/fields/input_field.h"
|
|
||||||
#include "ui/widgets/shadow.h"
|
|
||||||
#include "ui/widgets/labels.h"
|
|
||||||
#include "ui/widgets/buttons.h"
|
|
||||||
#include "ui/widgets/checkbox.h"
|
|
||||||
#include "ui/text/text_utilities.h"
|
|
||||||
#include "ui/vertical_list.h"
|
|
||||||
#include "main/main_session.h"
|
|
||||||
#include "core/application.h"
|
|
||||||
#include "core/core_settings.h"
|
|
||||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
#include "menu/menu_send.h"
|
#include "chat_helpers/tabbed_panel.h"
|
||||||
|
#include "chat_helpers/tabbed_selector.h"
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/core_settings.h"
|
||||||
|
#include "data/data_poll.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "history/view/history_view_schedule_box.h"
|
#include "history/view/history_view_schedule_box.h"
|
||||||
#include "base/unique_qptr.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "base/event_filter.h"
|
#include "main/main_session.h"
|
||||||
#include "base/call_delayed.h"
|
#include "menu/menu_send.h"
|
||||||
#include "base/random.h"
|
#include "ui/controls/emoji_button.h"
|
||||||
|
#include "ui/rect.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/toast/toast.h"
|
||||||
|
#include "ui/vertical_list.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "ui/widgets/fields/input_field.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/wrap/fade_wrap.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_layers.h"
|
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_chat_helpers.h" // defaultComposeFiles.
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -46,12 +54,107 @@ constexpr auto kSolutionLimit = 200;
|
||||||
constexpr auto kWarnSolutionLimit = 60;
|
constexpr auto kWarnSolutionLimit = 60;
|
||||||
constexpr auto kErrorLimit = 99;
|
constexpr auto kErrorLimit = 99;
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<Ui::EmojiButton*> AddEmojiToggleToField(
|
||||||
|
not_null<Ui::InputField*> field,
|
||||||
|
not_null<Ui::BoxContent*> box,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
not_null<ChatHelpers::TabbedPanel*> emojiPanel,
|
||||||
|
QPoint shift) {
|
||||||
|
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
|
||||||
|
field->parentWidget(),
|
||||||
|
st::defaultComposeFiles.emoji);
|
||||||
|
const auto fade = Ui::CreateChild<Ui::FadeAnimation>(
|
||||||
|
emojiToggle,
|
||||||
|
emojiToggle,
|
||||||
|
0.5);
|
||||||
|
{
|
||||||
|
const auto fadeTarget = Ui::CreateChild<Ui::RpWidget>(emojiToggle);
|
||||||
|
fadeTarget->resize(emojiToggle->size());
|
||||||
|
fadeTarget->paintRequest(
|
||||||
|
) | rpl::start_with_next([=](const QRect &rect) {
|
||||||
|
auto p = QPainter(fadeTarget);
|
||||||
|
if (fade->animating()) {
|
||||||
|
p.fillRect(fadeTarget->rect(), st::boxBg);
|
||||||
|
}
|
||||||
|
fade->paint(p);
|
||||||
|
}, fadeTarget->lifetime());
|
||||||
|
rpl::single(false) | rpl::then(
|
||||||
|
field->focusedChanges()
|
||||||
|
) | rpl::start_with_next([=](bool shown) {
|
||||||
|
if (shown) {
|
||||||
|
fade->fadeIn(st::universalDuration);
|
||||||
|
} else {
|
||||||
|
fade->fadeOut(st::universalDuration);
|
||||||
|
}
|
||||||
|
}, emojiToggle->lifetime());
|
||||||
|
fade->fadeOut(1);
|
||||||
|
fade->finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const auto outer = box->getDelegate()->outerContainer();
|
||||||
|
const auto allow = [](not_null<DocumentData*>) { return true; };
|
||||||
|
InitMessageFieldHandlers(
|
||||||
|
controller,
|
||||||
|
field,
|
||||||
|
Window::GifPauseReason::Layer,
|
||||||
|
allow);
|
||||||
|
Ui::Emoji::SuggestionsController::Init(
|
||||||
|
outer,
|
||||||
|
field,
|
||||||
|
&controller->session(),
|
||||||
|
Ui::Emoji::SuggestionsController::Options{
|
||||||
|
.suggestCustomEmoji = true,
|
||||||
|
.allowCustomWithoutPremium = allow,
|
||||||
|
});
|
||||||
|
const auto updateEmojiPanelGeometry = [=] {
|
||||||
|
const auto parent = emojiPanel->parentWidget();
|
||||||
|
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
|
||||||
|
const auto local = parent->mapFromGlobal(global);
|
||||||
|
const auto right = local.x() + emojiToggle->width() * 3;
|
||||||
|
const auto isDropDown = local.y() < parent->height() / 2;
|
||||||
|
emojiPanel->setDropDown(isDropDown);
|
||||||
|
if (isDropDown) {
|
||||||
|
emojiPanel->moveTopRight(
|
||||||
|
local.y() + emojiToggle->height(),
|
||||||
|
right);
|
||||||
|
} else {
|
||||||
|
emojiPanel->moveBottomRight(local.y(), right);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rpl::combine(
|
||||||
|
box->sizeValue(),
|
||||||
|
field->geometryValue()
|
||||||
|
) | rpl::start_with_next([=](QSize outer, QRect inner) {
|
||||||
|
emojiToggle->moveToLeft(
|
||||||
|
rect::right(inner) + shift.x(),
|
||||||
|
inner.y() + shift.y());
|
||||||
|
emojiToggle->update();
|
||||||
|
}, emojiToggle->lifetime());
|
||||||
|
|
||||||
|
emojiToggle->installEventFilter(emojiPanel);
|
||||||
|
emojiToggle->addClickHandler([=] {
|
||||||
|
updateEmojiPanelGeometry();
|
||||||
|
emojiPanel->toggleAnimated();
|
||||||
|
});
|
||||||
|
const auto filterCallback = [=](not_null<QEvent*> event) {
|
||||||
|
if (event->type() == QEvent::Enter) {
|
||||||
|
updateEmojiPanelGeometry();
|
||||||
|
}
|
||||||
|
return base::EventFilterResult::Continue;
|
||||||
|
};
|
||||||
|
base::install_event_filter(emojiToggle, filterCallback);
|
||||||
|
|
||||||
|
return emojiToggle;
|
||||||
|
}
|
||||||
|
|
||||||
class Options {
|
class Options {
|
||||||
public:
|
public:
|
||||||
Options(
|
Options(
|
||||||
not_null<QWidget*> outer,
|
not_null<Ui::BoxContent*> box,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Main::Session*> session,
|
not_null<Window::SessionController*> controller,
|
||||||
|
ChatHelpers::TabbedPanel *emojiPanel,
|
||||||
bool chooseCorrectEnabled);
|
bool chooseCorrectEnabled);
|
||||||
|
|
||||||
[[nodiscard]] bool hasOptions() const;
|
[[nodiscard]] bool hasOptions() const;
|
||||||
|
@ -140,9 +243,10 @@ private:
|
||||||
[[nodiscard]] auto createChooseCorrectGroup()
|
[[nodiscard]] auto createChooseCorrectGroup()
|
||||||
-> std::shared_ptr<Ui::RadiobuttonGroup>;
|
-> std::shared_ptr<Ui::RadiobuttonGroup>;
|
||||||
|
|
||||||
not_null<QWidget*> _outer;
|
not_null<Ui::BoxContent*> _box;
|
||||||
not_null<Ui::VerticalLayout*> _container;
|
not_null<Ui::VerticalLayout*> _container;
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
ChatHelpers::TabbedPanel * const _emojiPanel;
|
||||||
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
|
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
|
||||||
int _position = 0;
|
int _position = 0;
|
||||||
std::vector<std::unique_ptr<Option>> _list;
|
std::vector<std::unique_ptr<Option>> _list;
|
||||||
|
@ -154,6 +258,7 @@ private:
|
||||||
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
||||||
rpl::event_stream<> _backspaceInFront;
|
rpl::event_stream<> _backspaceInFront;
|
||||||
rpl::event_stream<> _tabbed;
|
rpl::event_stream<> _tabbed;
|
||||||
|
rpl::lifetime _emojiPanelLifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -193,8 +298,9 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
result->setText(QString::number(value));
|
result->setText(QString::number(value));
|
||||||
} else {
|
} else {
|
||||||
|
constexpr auto kMinus = QChar(0x2212);
|
||||||
result->setMarkedText(Ui::Text::Colorized(
|
result->setMarkedText(Ui::Text::Colorized(
|
||||||
QString::number(value)));
|
kMinus + QString::number(std::abs(value))));
|
||||||
}
|
}
|
||||||
result->setVisible(shown);
|
result->setVisible(shown);
|
||||||
}));
|
}));
|
||||||
|
@ -223,7 +329,9 @@ Options::Option::Option(
|
||||||
, _field(
|
, _field(
|
||||||
Ui::CreateChild<Ui::InputField>(
|
Ui::CreateChild<Ui::InputField>(
|
||||||
_content.get(),
|
_content.get(),
|
||||||
st::createPollOptionField,
|
session->user()->isPremium()
|
||||||
|
? st::createPollOptionFieldPremium
|
||||||
|
: st::createPollOptionField,
|
||||||
Ui::InputField::Mode::NoNewlines,
|
Ui::InputField::Mode::NoNewlines,
|
||||||
tr::lng_polls_create_option_add())) {
|
tr::lng_polls_create_option_add())) {
|
||||||
InitField(outer, _field, session);
|
InitField(outer, _field, session);
|
||||||
|
@ -299,7 +407,7 @@ void Options::Option::createRemove() {
|
||||||
const auto remove = Ui::CreateChild<Ui::CrossButton>(
|
const auto remove = Ui::CreateChild<Ui::CrossButton>(
|
||||||
field.get(),
|
field.get(),
|
||||||
st::createPollOptionRemove);
|
st::createPollOptionRemove);
|
||||||
remove->hide(anim::type::instant);
|
remove->show(anim::type::instant);
|
||||||
|
|
||||||
const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
|
const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
|
||||||
_removeAlways = lifetime.make_state<rpl::variable<bool>>(false);
|
_removeAlways = lifetime.make_state<rpl::variable<bool>>(false);
|
||||||
|
@ -309,6 +417,7 @@ void Options::Option::createRemove() {
|
||||||
// Don't capture 'this'! Because Option is a value type.
|
// Don't capture 'this'! Because Option is a value type.
|
||||||
*toggle = !field->getLastText().isEmpty();
|
*toggle = !field->getLastText().isEmpty();
|
||||||
}, field->lifetime());
|
}, field->lifetime());
|
||||||
|
#if 0
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
toggle->value(),
|
toggle->value(),
|
||||||
_removeAlways->value(),
|
_removeAlways->value(),
|
||||||
|
@ -316,6 +425,7 @@ void Options::Option::createRemove() {
|
||||||
) | rpl::start_with_next([=](bool shown) {
|
) | rpl::start_with_next([=](bool shown) {
|
||||||
remove->toggle(shown, anim::type::normal);
|
remove->toggle(shown, anim::type::normal);
|
||||||
}, remove->lifetime());
|
}, remove->lifetime());
|
||||||
|
#endif
|
||||||
|
|
||||||
field->widthValue(
|
field->widthValue(
|
||||||
) | rpl::start_with_next([=](int width) {
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
@ -456,10 +566,16 @@ void Options::Option::removePlaceholder() const {
|
||||||
PollAnswer Options::Option::toPollAnswer(int index) const {
|
PollAnswer Options::Option::toPollAnswer(int index) const {
|
||||||
Expects(index >= 0 && index < kMaxOptionsCount);
|
Expects(index >= 0 && index < kMaxOptionsCount);
|
||||||
|
|
||||||
|
const auto text = field()->getTextWithTags();
|
||||||
|
|
||||||
auto result = PollAnswer{
|
auto result = PollAnswer{
|
||||||
field()->getLastText().trimmed(),
|
TextWithEntities{
|
||||||
QByteArray(1, ('0' + index))
|
.text = text.text,
|
||||||
|
.entities = TextUtilities::ConvertTextTagsToEntities(text.tags),
|
||||||
|
},
|
||||||
|
QByteArray(1, ('0' + index)),
|
||||||
};
|
};
|
||||||
|
TextUtilities::Trim(result.text);
|
||||||
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
|
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -469,13 +585,15 @@ rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Options::Options(
|
Options::Options(
|
||||||
not_null<QWidget*> outer,
|
not_null<Ui::BoxContent*> box,
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Main::Session*> session,
|
not_null<Window::SessionController*> controller,
|
||||||
|
ChatHelpers::TabbedPanel *emojiPanel,
|
||||||
bool chooseCorrectEnabled)
|
bool chooseCorrectEnabled)
|
||||||
: _outer(outer)
|
: _box(box)
|
||||||
, _container(container)
|
, _container(container)
|
||||||
, _session(session)
|
, _controller(controller)
|
||||||
|
, _emojiPanel(emojiPanel)
|
||||||
, _chooseCorrectGroup(chooseCorrectEnabled
|
, _chooseCorrectGroup(chooseCorrectEnabled
|
||||||
? createChooseCorrectGroup()
|
? createChooseCorrectGroup()
|
||||||
: nullptr)
|
: nullptr)
|
||||||
|
@ -645,12 +763,40 @@ void Options::addEmptyOption() {
|
||||||
(*(_list.end() - 2))->toggleRemoveAlways(true);
|
(*(_list.end() - 2))->toggleRemoveAlways(true);
|
||||||
}
|
}
|
||||||
_list.push_back(std::make_unique<Option>(
|
_list.push_back(std::make_unique<Option>(
|
||||||
_outer,
|
_box,
|
||||||
_container,
|
_container,
|
||||||
_session,
|
&_controller->session(),
|
||||||
_position + _list.size() + _destroyed.size(),
|
_position + _list.size() + _destroyed.size(),
|
||||||
_chooseCorrectGroup));
|
_chooseCorrectGroup));
|
||||||
const auto field = _list.back()->field();
|
const auto field = _list.back()->field();
|
||||||
|
if (const auto emojiPanel = _emojiPanel) {
|
||||||
|
const auto emojiToggle = AddEmojiToggleToField(
|
||||||
|
field,
|
||||||
|
_box,
|
||||||
|
_controller,
|
||||||
|
emojiPanel,
|
||||||
|
QPoint(
|
||||||
|
-st::createPollOptionFieldPremium.textMargins.right(),
|
||||||
|
st::createPollOptionEmojiPositionSkip));
|
||||||
|
emojiToggle->shownValue() | rpl::start_with_next([=](bool shown) {
|
||||||
|
if (!shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_emojiPanelLifetime.destroy();
|
||||||
|
emojiPanel->selector()->emojiChosen(
|
||||||
|
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||||
|
if (field->hasFocus()) {
|
||||||
|
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
|
||||||
|
}
|
||||||
|
}, _emojiPanelLifetime);
|
||||||
|
emojiPanel->selector()->customEmojiChosen(
|
||||||
|
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||||
|
if (field->hasFocus()) {
|
||||||
|
Data::InsertCustomEmoji(field, data.document);
|
||||||
|
}
|
||||||
|
}, _emojiPanelLifetime);
|
||||||
|
}, emojiToggle->lifetime());
|
||||||
|
}
|
||||||
field->submits(
|
field->submits(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
const auto index = findField(field);
|
const auto index = findField(field);
|
||||||
|
@ -697,7 +843,7 @@ void Options::addEmptyOption() {
|
||||||
});
|
});
|
||||||
|
|
||||||
_list.back()->removeClicks(
|
_list.back()->removeClicks(
|
||||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
Ui::PostponeCall(crl::guard(field, [=] {
|
Ui::PostponeCall(crl::guard(field, [=] {
|
||||||
Expects(!_list.empty());
|
Expects(!_list.empty());
|
||||||
|
|
||||||
|
@ -789,19 +935,63 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
|
||||||
using namespace Settings;
|
using namespace Settings;
|
||||||
|
|
||||||
const auto session = &_controller->session();
|
const auto session = &_controller->session();
|
||||||
|
const auto isPremium = session->user()->isPremium();
|
||||||
Ui::AddSubsectionTitle(container, tr::lng_polls_create_question());
|
Ui::AddSubsectionTitle(container, tr::lng_polls_create_question());
|
||||||
|
|
||||||
const auto question = container->add(
|
const auto question = container->add(
|
||||||
object_ptr<Ui::InputField>(
|
object_ptr<Ui::InputField>(
|
||||||
container,
|
container,
|
||||||
st::createPollField,
|
st::createPollField,
|
||||||
Ui::InputField::Mode::MultiLine,
|
Ui::InputField::Mode::MultiLine,
|
||||||
tr::lng_polls_create_question_placeholder()),
|
tr::lng_polls_create_question_placeholder()),
|
||||||
st::createPollFieldPadding);
|
st::createPollFieldPadding
|
||||||
|
+ (isPremium
|
||||||
|
? QMargins(0, 0, st::defaultComposeFiles.emoji.inner.width, 0)
|
||||||
|
: QMargins()));
|
||||||
InitField(getDelegate()->outerContainer(), question, session);
|
InitField(getDelegate()->outerContainer(), question, session);
|
||||||
question->setMaxLength(kQuestionLimit + kErrorLimit);
|
question->setMaxLength(kQuestionLimit + kErrorLimit);
|
||||||
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||||
question->customTab(true);
|
question->customTab(true);
|
||||||
|
|
||||||
|
if (isPremium) {
|
||||||
|
using Selector = ChatHelpers::TabbedSelector;
|
||||||
|
const auto outer = getDelegate()->outerContainer();
|
||||||
|
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||||
|
outer,
|
||||||
|
_controller,
|
||||||
|
object_ptr<Selector>(
|
||||||
|
nullptr,
|
||||||
|
_controller->uiShow(),
|
||||||
|
Window::GifPauseReason::Layer,
|
||||||
|
Selector::Mode::EmojiOnly));
|
||||||
|
const auto emojiPanel = _emojiPanel.get();
|
||||||
|
emojiPanel->setDesiredHeightValues(
|
||||||
|
1.,
|
||||||
|
st::emojiPanMinHeight / 2,
|
||||||
|
st::emojiPanMinHeight);
|
||||||
|
emojiPanel->hide();
|
||||||
|
emojiPanel->selector()->setCurrentPeer(session->user());
|
||||||
|
|
||||||
|
const auto emojiToggle = AddEmojiToggleToField(
|
||||||
|
question,
|
||||||
|
this,
|
||||||
|
_controller,
|
||||||
|
emojiPanel,
|
||||||
|
st::createPollOptionFieldPremiumEmojiPosition);
|
||||||
|
emojiPanel->selector()->emojiChosen(
|
||||||
|
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||||
|
if (question->hasFocus()) {
|
||||||
|
Ui::InsertEmojiAtCursor(question->textCursor(), data.emoji);
|
||||||
|
}
|
||||||
|
}, emojiToggle->lifetime());
|
||||||
|
emojiPanel->selector()->customEmojiChosen(
|
||||||
|
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||||
|
if (question->hasFocus()) {
|
||||||
|
Data::InsertCustomEmoji(question, data.document);
|
||||||
|
}
|
||||||
|
}, emojiToggle->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
const auto warning = CreateWarningLabel(
|
const auto warning = CreateWarningLabel(
|
||||||
container,
|
container,
|
||||||
question,
|
question,
|
||||||
|
@ -910,9 +1100,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
st::defaultSubsectionTitle),
|
st::defaultSubsectionTitle),
|
||||||
st::createPollFieldTitlePadding);
|
st::createPollFieldTitlePadding);
|
||||||
const auto options = lifetime().make_state<Options>(
|
const auto options = lifetime().make_state<Options>(
|
||||||
getDelegate()->outerContainer(),
|
this,
|
||||||
container,
|
container,
|
||||||
&_controller->session(),
|
_controller,
|
||||||
|
_emojiPanel ? _emojiPanel.get() : nullptr,
|
||||||
(_chosen & PollData::Flag::Quiz));
|
(_chosen & PollData::Flag::Quiz));
|
||||||
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
||||||
setCloseByEscape(!count);
|
setCloseByEscape(!count);
|
||||||
|
@ -1029,9 +1220,13 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto collectResult = [=] {
|
const auto collectResult = [=] {
|
||||||
|
const auto textWithTags = question->getTextWithTags();
|
||||||
using Flag = PollData::Flag;
|
using Flag = PollData::Flag;
|
||||||
auto result = PollData(&_controller->session().data(), id);
|
auto result = PollData(&_controller->session().data(), id);
|
||||||
result.question = question->getLastText().trimmed();
|
result.question.text = textWithTags.text;
|
||||||
|
result.question.entities = TextUtilities::ConvertTextTagsToEntities(
|
||||||
|
textWithTags.tags);
|
||||||
|
TextUtilities::Trim(result.question);
|
||||||
result.answers = options->toPollAnswers();
|
result.answers = options->toPollAnswers();
|
||||||
const auto solutionWithTags = quiz->checked()
|
const auto solutionWithTags = quiz->checked()
|
||||||
? solution->getTextWithAppliedMarkdown()
|
? solution->getTextWithAppliedMarkdown()
|
||||||
|
|
|
@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
struct PollData;
|
struct PollData;
|
||||||
|
|
||||||
|
namespace ChatHelpers {
|
||||||
|
class TabbedPanel;
|
||||||
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class VerticalLayout;
|
class VerticalLayout;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
@ -72,6 +76,7 @@ private:
|
||||||
const PollData::Flags _disabled = PollData::Flags();
|
const PollData::Flags _disabled = PollData::Flags();
|
||||||
const Api::SendType _sendType = Api::SendType();
|
const Api::SendType _sendType = Api::SendType();
|
||||||
const SendMenu::Type _sendMenuType;
|
const SendMenu::Type _sendMenuType;
|
||||||
|
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||||
Fn<void()> _setInnerFocus;
|
Fn<void()> _setInnerFocus;
|
||||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||||
rpl::event_stream<Result> _submitRequests;
|
rpl::event_stream<Result> _submitRequests;
|
||||||
|
|
|
@ -0,0 +1,649 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "boxes/moderate_messages_box.h"
|
||||||
|
|
||||||
|
#include "api/api_chat_participants.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "base/timer.h"
|
||||||
|
#include "boxes/delete_messages_box.h"
|
||||||
|
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||||
|
#include "core/ui_integration.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_chat.h"
|
||||||
|
#include "data/data_chat_participant_status.h"
|
||||||
|
#include "data/data_histories.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "ui/controls/userpic_button.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/effects/toggle_arrow.h"
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "ui/rect.h"
|
||||||
|
#include "ui/rect_part.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/vertical_list.h"
|
||||||
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class ModerateOption {
|
||||||
|
Ban = (1 << 0),
|
||||||
|
DeleteAll = (1 << 1),
|
||||||
|
};
|
||||||
|
inline constexpr bool is_flag_type(ModerateOption) { return true; }
|
||||||
|
using ModerateOptions = base::flags<ModerateOption>;
|
||||||
|
|
||||||
|
ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
||||||
|
Expects(!items.empty());
|
||||||
|
|
||||||
|
const auto peer = items.front()->history()->peer;
|
||||||
|
auto allCanBan = true;
|
||||||
|
auto allCanDelete = true;
|
||||||
|
for (const auto &item : items) {
|
||||||
|
if (!allCanBan && !allCanDelete) {
|
||||||
|
return ModerateOptions(0);
|
||||||
|
}
|
||||||
|
if (peer != item->history()->peer) {
|
||||||
|
return ModerateOptions(0);
|
||||||
|
}
|
||||||
|
if (!item->suggestBanReport()) {
|
||||||
|
allCanBan = false;
|
||||||
|
}
|
||||||
|
if (!item->suggestDeleteAllReport()) {
|
||||||
|
allCanDelete = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ModerateOptions(0)
|
||||||
|
| (allCanBan ? ModerateOption::Ban : ModerateOptions(0))
|
||||||
|
| (allCanDelete ? ModerateOption::DeleteAll : ModerateOptions(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Button final : public Ui::RippleButton {
|
||||||
|
public:
|
||||||
|
Button(not_null<QWidget*> parent, int count);
|
||||||
|
|
||||||
|
void setChecked(bool checked);
|
||||||
|
[[nodiscard]] bool checked() const;
|
||||||
|
|
||||||
|
[[nodiscard]] static QSize ComputeSize(int);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
|
||||||
|
const int _count;
|
||||||
|
const QString _text;
|
||||||
|
bool _checked = false;
|
||||||
|
|
||||||
|
Ui::Animations::Simple _animation;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Button::Button(not_null<QWidget*> parent, int count)
|
||||||
|
: RippleButton(parent, st::defaultRippleAnimation)
|
||||||
|
, _count(count)
|
||||||
|
, _text(QString::number(std::abs(_count))) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize Button::ComputeSize(int count) {
|
||||||
|
return QSize(
|
||||||
|
st::moderateBoxExpandHeight
|
||||||
|
+ st::moderateBoxExpand.width()
|
||||||
|
+ st::moderateBoxExpandInnerSkip * 4
|
||||||
|
+ st::moderateBoxExpandFont->width(
|
||||||
|
QString::number(std::abs(count)))
|
||||||
|
+ st::moderateBoxExpandToggleSize,
|
||||||
|
st::moderateBoxExpandHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::setChecked(bool checked) {
|
||||||
|
if (_checked == checked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_checked = checked;
|
||||||
|
_animation.stop();
|
||||||
|
_animation.start(
|
||||||
|
[=] { update(); },
|
||||||
|
checked ? 0 : 1,
|
||||||
|
checked ? 1 : 0,
|
||||||
|
st::slideWrapDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Button::checked() const {
|
||||||
|
return _checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::paintEvent(QPaintEvent *event) {
|
||||||
|
auto p = Painter(this);
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
Ui::RippleButton::paintRipple(p, QPoint());
|
||||||
|
const auto radius = height() / 2;
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
st::moderateBoxExpand.paint(
|
||||||
|
p,
|
||||||
|
radius,
|
||||||
|
(height() - st::moderateBoxExpand.height()) / 2,
|
||||||
|
width());
|
||||||
|
|
||||||
|
const auto innerSkip = st::moderateBoxExpandInnerSkip;
|
||||||
|
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
p.setPen(st::boxTextFg);
|
||||||
|
p.setFont(st::moderateBoxExpandFont);
|
||||||
|
p.drawText(
|
||||||
|
QRect(
|
||||||
|
innerSkip + radius + st::moderateBoxExpand.width(),
|
||||||
|
0,
|
||||||
|
width(),
|
||||||
|
height()),
|
||||||
|
_text,
|
||||||
|
style::al_left);
|
||||||
|
|
||||||
|
const auto path = Ui::ToggleUpDownArrowPath(
|
||||||
|
width() - st::moderateBoxExpandToggleSize - radius,
|
||||||
|
height() / 2,
|
||||||
|
st::moderateBoxExpandToggleSize,
|
||||||
|
st::moderateBoxExpandToggleFourStrokes,
|
||||||
|
_animation.value(_checked ? 1. : 0.));
|
||||||
|
p.fillPath(path, st::boxTextFg);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Button::prepareRippleMask() const {
|
||||||
|
return Ui::RippleAnimation::RoundRectMask(size(), size().height() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint Button::prepareRippleStartPosition() const {
|
||||||
|
return mapFromGlobal(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void CreateModerateMessagesBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
const HistoryItemsList &items,
|
||||||
|
Fn<void()> confirmed) {
|
||||||
|
using Users = std::vector<not_null<UserData*>>;
|
||||||
|
struct Controller final {
|
||||||
|
rpl::event_stream<bool> toggleRequestsFromTop;
|
||||||
|
rpl::event_stream<bool> toggleRequestsFromInner;
|
||||||
|
rpl::event_stream<bool> checkAllRequests;
|
||||||
|
Fn<Users()> collectRequests;
|
||||||
|
};
|
||||||
|
constexpr auto kSmallDelayMs = 5;
|
||||||
|
const auto options = CalculateModerateOptions(items);
|
||||||
|
const auto inner = box->verticalLayout();
|
||||||
|
|
||||||
|
const auto users = [&] {
|
||||||
|
auto result = Users();
|
||||||
|
for (const auto &item : items) {
|
||||||
|
if (const auto user = item->from()->asUser()) {
|
||||||
|
if (!ranges::contains(result, not_null{ user })) {
|
||||||
|
result.push_back(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
Assert(!users.empty());
|
||||||
|
|
||||||
|
const auto confirms = inner->lifetime().make_state<rpl::event_stream<>>();
|
||||||
|
|
||||||
|
const auto isSingle = users.size() == 1;
|
||||||
|
const auto buttonPadding = isSingle
|
||||||
|
? QMargins()
|
||||||
|
: QMargins(0, 0, Button::ComputeSize(users.size()).width(), 0);
|
||||||
|
|
||||||
|
using Request = Fn<void(not_null<UserData*>, not_null<ChannelData*>)>;
|
||||||
|
const auto sequentiallyRequest = [=](Request request, Users users) {
|
||||||
|
const auto session = &items.front()->history()->session();
|
||||||
|
const auto history = items.front()->history();
|
||||||
|
const auto peerId = history->peer->id;
|
||||||
|
const auto userIds = ranges::views::all(
|
||||||
|
users
|
||||||
|
) | ranges::views::transform([](not_null<UserData*> user) {
|
||||||
|
return user->id;
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||||
|
const auto counter = lifetime->make_state<int>(0);
|
||||||
|
const auto timer = lifetime->make_state<base::Timer>();
|
||||||
|
timer->setCallback(crl::guard(session, [=] {
|
||||||
|
if ((*counter) < userIds.size()) {
|
||||||
|
const auto peer = session->data().peer(peerId);
|
||||||
|
const auto channel = peer ? peer->asChannel() : nullptr;
|
||||||
|
const auto from = session->data().peer(userIds[*counter]);
|
||||||
|
if (const auto user = from->asUser(); channel && user) {
|
||||||
|
request(user, channel);
|
||||||
|
}
|
||||||
|
(*counter)++;
|
||||||
|
} else {
|
||||||
|
lifetime->destroy();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
timer->callEach(kSmallDelayMs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto handleConfirmation = [=](
|
||||||
|
not_null<Ui::Checkbox*> checkbox,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
Request request) {
|
||||||
|
confirms->events() | rpl::start_with_next([=] {
|
||||||
|
if (checkbox->checked()) {
|
||||||
|
if (isSingle) {
|
||||||
|
const auto item = items.front();
|
||||||
|
const auto channel = item->history()->peer->asChannel();
|
||||||
|
request(users.front(), channel);
|
||||||
|
} else if (const auto collect = controller->collectRequests) {
|
||||||
|
sequentiallyRequest(request, collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, checkbox->lifetime());
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto createUsersList = [&](not_null<Controller*> controller) {
|
||||||
|
const auto wrap = inner->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
inner,
|
||||||
|
object_ptr<Ui::VerticalLayout>(inner)));
|
||||||
|
wrap->toggle(false, anim::type::instant);
|
||||||
|
|
||||||
|
controller->toggleRequestsFromTop.events(
|
||||||
|
) | rpl::start_with_next([=](bool toggled) {
|
||||||
|
wrap->toggle(toggled, anim::type::normal);
|
||||||
|
}, wrap->lifetime());
|
||||||
|
|
||||||
|
const auto container = wrap->entity();
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
|
||||||
|
auto &lifetime = wrap->lifetime();
|
||||||
|
const auto clicks = lifetime.make_state<rpl::event_stream<>>();
|
||||||
|
const auto checkboxes = ranges::views::all(
|
||||||
|
users
|
||||||
|
) | ranges::views::transform([&](not_null<UserData*> user) {
|
||||||
|
const auto line = container->add(
|
||||||
|
object_ptr<Ui::AbstractButton>(container));
|
||||||
|
const auto &st = st::moderateBoxUserpic;
|
||||||
|
line->resize(line->width(), st.size.height());
|
||||||
|
|
||||||
|
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||||
|
line,
|
||||||
|
user,
|
||||||
|
st);
|
||||||
|
const auto checkbox = Ui::CreateChild<Ui::Checkbox>(
|
||||||
|
line,
|
||||||
|
user->name(),
|
||||||
|
false,
|
||||||
|
st::defaultBoxCheckbox);
|
||||||
|
line->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
userpic->moveToLeft(
|
||||||
|
st::boxRowPadding.left()
|
||||||
|
+ checkbox->checkRect().width()
|
||||||
|
+ st::defaultBoxCheckbox.textPosition.x(),
|
||||||
|
0);
|
||||||
|
const auto skip = st::defaultBoxCheckbox.textPosition.x();
|
||||||
|
checkbox->resizeToWidth(width
|
||||||
|
- rect::right(userpic)
|
||||||
|
- skip
|
||||||
|
- st::boxRowPadding.right());
|
||||||
|
checkbox->moveToLeft(
|
||||||
|
rect::right(userpic) + skip,
|
||||||
|
((userpic->height() - checkbox->height()) / 2)
|
||||||
|
+ st::defaultBoxCheckbox.margin.top());
|
||||||
|
}, checkbox->lifetime());
|
||||||
|
|
||||||
|
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
line->setClickedCallback([=] {
|
||||||
|
checkbox->setChecked(!checkbox->checked());
|
||||||
|
clicks->fire({});
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkbox;
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
clicks->events(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
controller->toggleRequestsFromInner.fire_copy(
|
||||||
|
ranges::any_of(checkboxes, &Ui::Checkbox::checked));
|
||||||
|
}, container->lifetime());
|
||||||
|
|
||||||
|
controller->checkAllRequests.events(
|
||||||
|
) | rpl::start_with_next([=](bool checked) {
|
||||||
|
for (const auto &c : checkboxes) {
|
||||||
|
c->setChecked(checked);
|
||||||
|
}
|
||||||
|
}, container->lifetime());
|
||||||
|
|
||||||
|
controller->collectRequests = [=] {
|
||||||
|
auto result = Users();
|
||||||
|
for (auto i = 0; i < checkboxes.size(); i++) {
|
||||||
|
if (checkboxes[i]->checked()) {
|
||||||
|
result.push_back(users[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto appendList = [&](
|
||||||
|
not_null<Ui::Checkbox*> checkbox,
|
||||||
|
not_null<Controller*> controller) {
|
||||||
|
const auto button = Ui::CreateChild<Button>(inner, users.size());
|
||||||
|
button->resize(Button::ComputeSize(users.size()));
|
||||||
|
|
||||||
|
const auto overlay = Ui::CreateChild<Ui::AbstractButton>(inner);
|
||||||
|
|
||||||
|
checkbox->geometryValue(
|
||||||
|
) | rpl::start_with_next([=](const QRect &rect) {
|
||||||
|
overlay->setGeometry(rect);
|
||||||
|
overlay->raise();
|
||||||
|
|
||||||
|
button->moveToRight(
|
||||||
|
st::moderateBoxExpandRight,
|
||||||
|
rect.top() + (rect.height() - button->height()) / 2,
|
||||||
|
box->width());
|
||||||
|
button->raise();
|
||||||
|
}, button->lifetime());
|
||||||
|
|
||||||
|
controller->toggleRequestsFromInner.events(
|
||||||
|
) | rpl::start_with_next([=](bool toggled) {
|
||||||
|
checkbox->setChecked(toggled);
|
||||||
|
}, checkbox->lifetime());
|
||||||
|
button->setClickedCallback([=] {
|
||||||
|
button->setChecked(!button->checked());
|
||||||
|
controller->toggleRequestsFromTop.fire_copy(button->checked());
|
||||||
|
});
|
||||||
|
overlay->setClickedCallback([=] {
|
||||||
|
checkbox->setChecked(!checkbox->checked());
|
||||||
|
controller->checkAllRequests.fire_copy(checkbox->checked());
|
||||||
|
});
|
||||||
|
createUsersList(controller);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
(items.size() == 1)
|
||||||
|
? tr::lng_selected_delete_sure_this()
|
||||||
|
: tr::lng_selected_delete_sure(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(items.size()) | tr::to_count()),
|
||||||
|
st::boxLabel));
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
{
|
||||||
|
const auto report = box->addRow(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
box,
|
||||||
|
tr::lng_report_spam(tr::now),
|
||||||
|
false,
|
||||||
|
st::defaultBoxCheckbox),
|
||||||
|
st::boxRowPadding + buttonPadding);
|
||||||
|
const auto controller = box->lifetime().make_state<Controller>();
|
||||||
|
if (!isSingle) {
|
||||||
|
appendList(report, controller);
|
||||||
|
}
|
||||||
|
const auto ids = items.front()->from()->owner().itemsToIds(items);
|
||||||
|
handleConfirmation(report, controller, [=](
|
||||||
|
not_null<UserData*> u,
|
||||||
|
not_null<ChannelData*> c) {
|
||||||
|
auto filtered = QVector<MTPint>();
|
||||||
|
for (const auto &id : ids) {
|
||||||
|
if (const auto item = u->session().data().message(id)) {
|
||||||
|
if (item->from()->asUser() == u) {
|
||||||
|
filtered.push_back(MTP_int(item->fullId().msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u->session().api().request(
|
||||||
|
MTPchannels_ReportSpam(
|
||||||
|
c->inputChannel,
|
||||||
|
u->input,
|
||||||
|
MTP_vector<MTPint>(std::move(filtered)))
|
||||||
|
).send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options & ModerateOption::DeleteAll) {
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
|
||||||
|
const auto deleteAll = inner->add(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
inner,
|
||||||
|
!(isSingle)
|
||||||
|
? tr::lng_delete_all_from_users(
|
||||||
|
tr::now,
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: tr::lng_delete_all_from_user(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
Ui::Text::Bold(items.front()->from()->name()),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
false,
|
||||||
|
st::defaultBoxCheckbox),
|
||||||
|
st::boxRowPadding + buttonPadding);
|
||||||
|
|
||||||
|
const auto controller = box->lifetime().make_state<Controller>();
|
||||||
|
if (!isSingle) {
|
||||||
|
appendList(deleteAll, controller);
|
||||||
|
}
|
||||||
|
handleConfirmation(deleteAll, controller, [=](
|
||||||
|
not_null<UserData*> u,
|
||||||
|
not_null<ChannelData*> c) {
|
||||||
|
u->session().api().deleteAllFromParticipant(c, u);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (options & ModerateOption::Ban) {
|
||||||
|
auto ownedWrap = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
inner,
|
||||||
|
object_ptr<Ui::VerticalLayout>(inner));
|
||||||
|
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
const auto ban = inner->add(
|
||||||
|
object_ptr<Ui::Checkbox>(
|
||||||
|
box,
|
||||||
|
rpl::conditional(
|
||||||
|
ownedWrap->toggledValue(),
|
||||||
|
tr::lng_context_restrict_user(),
|
||||||
|
rpl::conditional(
|
||||||
|
rpl::single(isSingle),
|
||||||
|
tr::lng_ban_user(),
|
||||||
|
tr::lng_ban_users())),
|
||||||
|
false,
|
||||||
|
st::defaultBoxCheckbox),
|
||||||
|
st::boxRowPadding + buttonPadding);
|
||||||
|
const auto controller = box->lifetime().make_state<Controller>();
|
||||||
|
if (!isSingle) {
|
||||||
|
appendList(ban, controller);
|
||||||
|
}
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
|
||||||
|
const auto wrap = inner->add(std::move(ownedWrap));
|
||||||
|
const auto container = wrap->entity();
|
||||||
|
wrap->toggle(false, anim::type::instant);
|
||||||
|
|
||||||
|
const auto session = &users.front()->session();
|
||||||
|
const auto emojiMargin = QMargins(
|
||||||
|
-st::moderateBoxExpandInnerSkip,
|
||||||
|
-st::moderateBoxExpandInnerSkip / 2,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
const auto emojiUp = Ui::Text::SingleCustomEmoji(
|
||||||
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
|
st::moderateBoxExpandIcon,
|
||||||
|
emojiMargin,
|
||||||
|
false));
|
||||||
|
const auto emojiDown = Ui::Text::SingleCustomEmoji(
|
||||||
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
|
st::moderateBoxExpandIconDown,
|
||||||
|
emojiMargin,
|
||||||
|
false));
|
||||||
|
|
||||||
|
auto label = object_ptr<Ui::FlatLabel>(
|
||||||
|
inner,
|
||||||
|
QString(),
|
||||||
|
st::moderateBoxDividerLabel);
|
||||||
|
const auto raw = label.data();
|
||||||
|
|
||||||
|
auto &lifetime = wrap->lifetime();
|
||||||
|
const auto scrollLifetime = lifetime.make_state<rpl::lifetime>();
|
||||||
|
label->setClickHandlerFilter([=](
|
||||||
|
const ClickHandlerPtr &handler,
|
||||||
|
Qt::MouseButton button) {
|
||||||
|
if (button != Qt::LeftButton) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
||||||
|
{
|
||||||
|
inner->heightValue() | rpl::start_with_next([=] {
|
||||||
|
if (!wrap->animating()) {
|
||||||
|
scrollLifetime->destroy();
|
||||||
|
Ui::PostponeCall(crl::guard(box, [=] {
|
||||||
|
box->scrollToY(std::numeric_limits<int>::max());
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
box->scrollToY(std::numeric_limits<int>::max());
|
||||||
|
}
|
||||||
|
}, *scrollLifetime);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
wrap->toggledValue(
|
||||||
|
) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) {
|
||||||
|
return ((toggled && isSingle)
|
||||||
|
? tr::lng_restrict_user_part
|
||||||
|
: (toggled && !isSingle)
|
||||||
|
? tr::lng_restrict_users_part
|
||||||
|
: isSingle
|
||||||
|
? tr::lng_restrict_user_full
|
||||||
|
: tr::lng_restrict_users_full)(
|
||||||
|
lt_emoji,
|
||||||
|
rpl::single(toggled ? emojiUp : emojiDown),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
}) | rpl::flatten_latest(
|
||||||
|
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||||
|
raw->setMarkedText(
|
||||||
|
Ui::Text::Link(text, u"internal:"_q),
|
||||||
|
Core::MarkedTextContext{
|
||||||
|
.session = session,
|
||||||
|
.customEmojiRepaint = [=] { raw->update(); },
|
||||||
|
});
|
||||||
|
}, label->lifetime());
|
||||||
|
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
inner->add(object_ptr<Ui::DividerLabel>(
|
||||||
|
inner,
|
||||||
|
std::move(label),
|
||||||
|
st::defaultBoxDividerLabelPadding,
|
||||||
|
RectPart::Top | RectPart::Bottom));
|
||||||
|
|
||||||
|
using Flag = ChatRestriction;
|
||||||
|
using Flags = ChatRestrictions;
|
||||||
|
const auto peer = items.front()->history()->peer;
|
||||||
|
const auto chat = peer->asChat();
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
const auto defaultRestrictions = chat
|
||||||
|
? chat->defaultRestrictions()
|
||||||
|
: channel->defaultRestrictions();
|
||||||
|
const auto prepareFlags = FixDependentRestrictions(
|
||||||
|
defaultRestrictions
|
||||||
|
| ((channel && channel->isPublic())
|
||||||
|
? (Flag::ChangeInfo | Flag::PinMessages)
|
||||||
|
: Flags(0)));
|
||||||
|
const auto disabledMessages = [&] {
|
||||||
|
auto result = base::flat_map<Flags, QString>();
|
||||||
|
{
|
||||||
|
const auto disabled = FixDependentRestrictions(
|
||||||
|
defaultRestrictions
|
||||||
|
| ((channel && channel->isPublic())
|
||||||
|
? (Flag::ChangeInfo | Flag::PinMessages)
|
||||||
|
: Flags(0)));
|
||||||
|
result.emplace(
|
||||||
|
disabled,
|
||||||
|
tr::lng_rights_restriction_for_all(tr::now));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||||
|
box,
|
||||||
|
rpl::conditional(
|
||||||
|
rpl::single(isSingle),
|
||||||
|
tr::lng_restrict_users_part_single_header(),
|
||||||
|
tr::lng_restrict_users_part_header(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(users.size()) | tr::to_count())),
|
||||||
|
prepareFlags,
|
||||||
|
disabledMessages,
|
||||||
|
{ .isForum = peer->isForum() });
|
||||||
|
std::move(changes) | rpl::start_with_next([=] {
|
||||||
|
ban->setChecked(true);
|
||||||
|
}, ban->lifetime());
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
Ui::AddDivider(container);
|
||||||
|
Ui::AddSkip(container);
|
||||||
|
container->add(std::move(checkboxes));
|
||||||
|
|
||||||
|
handleConfirmation(ban, controller, [=](
|
||||||
|
not_null<UserData*> user,
|
||||||
|
not_null<ChannelData*> channel) {
|
||||||
|
if (wrap->toggled()) {
|
||||||
|
Api::ChatParticipants::Restrict(
|
||||||
|
channel,
|
||||||
|
user,
|
||||||
|
ChatRestrictionsInfo(), // Unused.
|
||||||
|
ChatRestrictionsInfo(getRestrictions(), 0),
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
} else {
|
||||||
|
channel->session().api().chatParticipants().kick(
|
||||||
|
channel,
|
||||||
|
user,
|
||||||
|
{ channel->restrictions(), 0 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto close = crl::guard(box, [=] { box->closeBox(); });
|
||||||
|
box->addButton(tr::lng_box_delete(), [=] {
|
||||||
|
confirms->fire({});
|
||||||
|
box->closeBox();
|
||||||
|
const auto data = &users.front()->session().data();
|
||||||
|
const auto ids = data->itemsToIds(items);
|
||||||
|
if (confirmed) {
|
||||||
|
confirmed();
|
||||||
|
}
|
||||||
|
data->histories().deleteMessages(ids, true);
|
||||||
|
data->sendHistoryChangeNotifications();
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
box->addButton(tr::lng_cancel(), close);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanCreateModerateMessagesBox(const HistoryItemsList &items) {
|
||||||
|
const auto options = CalculateModerateOptions(items);
|
||||||
|
return (options & ModerateOption::Ban)
|
||||||
|
|| (options & ModerateOption::DeleteAll);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class GenericBox;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
void CreateModerateMessagesBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
const HistoryItemsList &items,
|
||||||
|
Fn<void()> confirmed);
|
||||||
|
|
||||||
|
[[nodiscard]] bool CanCreateModerateMessagesBox(const HistoryItemsList &);
|
|
@ -166,33 +166,6 @@ void SaveChannelAdmin(
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveChannelRestriction(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
not_null<PeerData*> participant,
|
|
||||||
ChatRestrictionsInfo oldRights,
|
|
||||||
ChatRestrictionsInfo newRights,
|
|
||||||
Fn<void()> onDone,
|
|
||||||
Fn<void()> onFail) {
|
|
||||||
channel->session().api().request(MTPchannels_EditBanned(
|
|
||||||
channel->inputChannel,
|
|
||||||
participant->input,
|
|
||||||
MTP_chatBannedRights(
|
|
||||||
MTP_flags(MTPDchatBannedRights::Flags::from_raw(
|
|
||||||
uint32(newRights.flags))),
|
|
||||||
MTP_int(newRights.until))
|
|
||||||
)).done([=](const MTPUpdates &result) {
|
|
||||||
channel->session().api().applyUpdates(result);
|
|
||||||
channel->applyEditBanned(participant, oldRights, newRights);
|
|
||||||
if (onDone) {
|
|
||||||
onDone();
|
|
||||||
}
|
|
||||||
}).fail([=] {
|
|
||||||
if (onFail) {
|
|
||||||
onFail();
|
|
||||||
}
|
|
||||||
}).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SaveChatParticipantKick(
|
void SaveChatParticipantKick(
|
||||||
not_null<ChatData*> chat,
|
not_null<ChatData*> chat,
|
||||||
not_null<UserData*> user,
|
not_null<UserData*> user,
|
||||||
|
@ -275,7 +248,7 @@ Fn<void(
|
||||||
ChatRestrictionsInfo newRights) {
|
ChatRestrictionsInfo newRights) {
|
||||||
const auto done = [=] { if (onDone) onDone(newRights); };
|
const auto done = [=] { if (onDone) onDone(newRights); };
|
||||||
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
|
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
|
||||||
SaveChannelRestriction(
|
Api::ChatParticipants::Restrict(
|
||||||
channel,
|
channel,
|
||||||
participant,
|
participant,
|
||||||
oldRights,
|
oldRights,
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||||
constexpr auto AppFile = "Telegram"_cs;
|
constexpr auto AppFile = "Telegram"_cs;
|
||||||
constexpr auto AppVersion = 4016009;
|
constexpr auto AppVersion = 4016010;
|
||||||
constexpr auto AppVersionStr = "4.16.9";
|
constexpr auto AppVersionStr = "4.16.10";
|
||||||
constexpr auto AppBetaVersion = true;
|
constexpr auto AppBetaVersion = true;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -1857,23 +1857,21 @@ TextWithEntities MediaPoll::notificationText() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MediaPoll::pinnedTextSubstring() const {
|
QString MediaPoll::pinnedTextSubstring() const {
|
||||||
return QChar(171) + _poll->question + QChar(187);
|
return QChar(171) + _poll->question.text + QChar(187);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextForMimeData MediaPoll::clipboardText() const {
|
TextForMimeData MediaPoll::clipboardText() const {
|
||||||
const auto text = u"[ "_q
|
auto result = TextWithEntities();
|
||||||
+ tr::lng_in_dlg_poll(tr::now)
|
result
|
||||||
+ u" : "_q
|
.append(u"[ "_q)
|
||||||
+ _poll->question
|
.append(tr::lng_in_dlg_poll(tr::now))
|
||||||
+ u" ]"_q
|
.append(u" : "_q)
|
||||||
+ ranges::accumulate(
|
.append(_poll->question)
|
||||||
ranges::views::all(
|
.append(u" ]"_q);
|
||||||
_poll->answers
|
for (const auto &answer : _poll->answers) {
|
||||||
) | ranges::views::transform([](const PollAnswer &answer) {
|
result.append(u"\n- "_q).append(answer.text);
|
||||||
return "\n- " + answer.text;
|
}
|
||||||
}),
|
return TextForMimeData::Rich(std::move(result));
|
||||||
QString());
|
|
||||||
return TextForMimeData::Simple(text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) {
|
bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
#include "main/session/send_as_peers.h"
|
#include "main/session/send_as_peers.h"
|
||||||
|
@ -137,7 +138,14 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto result = PossibleItemReactionsRef();
|
auto result = PossibleItemReactionsRef();
|
||||||
const auto peer = item->history()->peer;
|
auto peer = item->history()->peer;
|
||||||
|
if (item->isDiscussionPost()) {
|
||||||
|
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||||
|
if (forwarded->savedFromPeer) {
|
||||||
|
peer = forwarded->savedFromPeer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto session = &peer->session();
|
const auto session = &peer->session();
|
||||||
const auto reactions = &session->data().reactions();
|
const auto reactions = &session->data().reactions();
|
||||||
const auto &full = reactions->list(Reactions::Type::Active);
|
const auto &full = reactions->list(Reactions::Type::Active);
|
||||||
|
|
|
@ -533,7 +533,9 @@ bool PeerData::canPinMessages() const {
|
||||||
|
|
||||||
bool PeerData::canCreatePolls() const {
|
bool PeerData::canCreatePolls() const {
|
||||||
if (const auto user = asUser()) {
|
if (const auto user = asUser()) {
|
||||||
return user->isBot() && !user->isSupport();
|
return user->isBot()
|
||||||
|
&& !user->isSupport()
|
||||||
|
&& !user->isRepliesChat();
|
||||||
}
|
}
|
||||||
return Data::CanSend(this, ChatRestriction::SendPolls);
|
return Data::CanSend(this, ChatRestriction::SendPolls);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
|
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
|
@ -69,7 +70,12 @@ bool PollData::closeByTimer() {
|
||||||
bool PollData::applyChanges(const MTPDpoll &poll) {
|
bool PollData::applyChanges(const MTPDpoll &poll) {
|
||||||
Expects(poll.vid().v == id);
|
Expects(poll.vid().v == id);
|
||||||
|
|
||||||
const auto newQuestion = qs(poll.vquestion());
|
const auto newQuestion = TextWithEntities{
|
||||||
|
.text = qs(poll.vquestion().data().vtext()),
|
||||||
|
.entities = Api::EntitiesFromMTP(
|
||||||
|
&session(),
|
||||||
|
poll.vquestion().data().ventities().v),
|
||||||
|
};
|
||||||
const auto newFlags = (poll.is_closed() ? Flag::Closed : Flag(0))
|
const auto newFlags = (poll.is_closed() ? Flag::Closed : Flag(0))
|
||||||
| (poll.is_public_voters() ? Flag::PublicVotes : Flag(0))
|
| (poll.is_public_voters() ? Flag::PublicVotes : Flag(0))
|
||||||
| (poll.is_multiple_choice() ? Flag::MultiChoice : Flag(0))
|
| (poll.is_multiple_choice() ? Flag::MultiChoice : Flag(0))
|
||||||
|
@ -78,11 +84,16 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
|
||||||
const auto newClosePeriod = poll.vclose_period().value_or_empty();
|
const auto newClosePeriod = poll.vclose_period().value_or_empty();
|
||||||
auto newAnswers = ranges::views::all(
|
auto newAnswers = ranges::views::all(
|
||||||
poll.vanswers().v
|
poll.vanswers().v
|
||||||
) | ranges::views::transform([](const MTPPollAnswer &data) {
|
) | ranges::views::transform([&](const MTPPollAnswer &data) {
|
||||||
return data.match([](const MTPDpollAnswer &answer) {
|
return data.match([&](const MTPDpollAnswer &answer) {
|
||||||
auto result = PollAnswer();
|
auto result = PollAnswer();
|
||||||
result.option = answer.voption().v;
|
result.option = answer.voption().v;
|
||||||
result.text = qs(answer.vtext());
|
result.text = TextWithEntities{
|
||||||
|
.text = qs(answer.vtext().data().vtext()),
|
||||||
|
.entities = Api::EntitiesFromMTP(
|
||||||
|
&session(),
|
||||||
|
answer.vtext().data().ventities().v),
|
||||||
|
};
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}) | ranges::views::take(
|
}) | ranges::views::take(
|
||||||
|
@ -251,9 +262,11 @@ bool PollData::quiz() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close) {
|
MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close) {
|
||||||
const auto convert = [](const PollAnswer &answer) {
|
const auto convert = [&](const PollAnswer &answer) {
|
||||||
return MTP_pollAnswer(
|
return MTP_pollAnswer(
|
||||||
MTP_string(answer.text),
|
MTP_textWithEntities(
|
||||||
|
MTP_string(answer.text.text),
|
||||||
|
Api::EntitiesToMTP(&poll->session(), answer.text.entities)),
|
||||||
MTP_bytes(answer.option));
|
MTP_bytes(answer.option));
|
||||||
};
|
};
|
||||||
auto answers = QVector<MTPPollAnswer>();
|
auto answers = QVector<MTPPollAnswer>();
|
||||||
|
@ -272,7 +285,9 @@ MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close) {
|
||||||
return MTP_poll(
|
return MTP_poll(
|
||||||
MTP_long(poll->id),
|
MTP_long(poll->id),
|
||||||
MTP_flags(flags),
|
MTP_flags(flags),
|
||||||
MTP_string(poll->question),
|
MTP_textWithEntities(
|
||||||
|
MTP_string(poll->question.text),
|
||||||
|
Api::EntitiesToMTP(&poll->session(), poll->question.entities)),
|
||||||
MTP_vector<MTPPollAnswer>(answers),
|
MTP_vector<MTPPollAnswer>(answers),
|
||||||
MTP_int(poll->closePeriod),
|
MTP_int(poll->closePeriod),
|
||||||
MTP_int(poll->closeDate));
|
MTP_int(poll->closeDate));
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
struct PollAnswer {
|
struct PollAnswer {
|
||||||
QString text;
|
TextWithEntities text;
|
||||||
QByteArray option;
|
QByteArray option;
|
||||||
int votes = 0;
|
int votes = 0;
|
||||||
bool chosen = false;
|
bool chosen = false;
|
||||||
|
@ -65,7 +65,7 @@ struct PollData {
|
||||||
[[nodiscard]] bool quiz() const;
|
[[nodiscard]] bool quiz() const;
|
||||||
|
|
||||||
PollId id = 0;
|
PollId id = 0;
|
||||||
QString question;
|
TextWithEntities question;
|
||||||
std::vector<PollAnswer> answers;
|
std::vector<PollAnswer> answers;
|
||||||
std::vector<not_null<PeerData*>> recentVoters;
|
std::vector<not_null<PeerData*>> recentVoters;
|
||||||
std::vector<QByteArray> sendingVotes;
|
std::vector<QByteArray> sendingVotes;
|
||||||
|
|
|
@ -3264,13 +3264,7 @@ void Widget::keyPressEvent(QKeyEvent *e) {
|
||||||
} else {
|
} else {
|
||||||
_inner->selectSkipPage(_scroll->height(), -1);
|
_inner->selectSkipPage(_scroll->height(), -1);
|
||||||
}
|
}
|
||||||
} else if (!(e->modifiers() & ~Qt::ShiftModifier)
|
} else if (redirectKeyToSearch(e)) {
|
||||||
&& e->key() != Qt::Key_Shift
|
|
||||||
&& !_openedFolder
|
|
||||||
&& !_openedForum
|
|
||||||
&& _search->isVisible()
|
|
||||||
&& !_search->hasFocus()
|
|
||||||
&& !e->text().isEmpty()) {
|
|
||||||
// This delay in search focus processing allows us not to create
|
// This delay in search focus processing allows us not to create
|
||||||
// _suggestions in case the event inserts some non-whitespace search
|
// _suggestions in case the event inserts some non-whitespace search
|
||||||
// query while still show _suggestions animated, if it is a space.
|
// query while still show _suggestions animated, if it is a space.
|
||||||
|
@ -3284,6 +3278,31 @@ void Widget::keyPressEvent(QKeyEvent *e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Widget::redirectKeyToSearch(QKeyEvent *e) const {
|
||||||
|
if (_openedFolder
|
||||||
|
|| _openedForum
|
||||||
|
|| !_search->isVisible()
|
||||||
|
|| _search->hasFocus()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto character = !(e->modifiers() & ~Qt::ShiftModifier)
|
||||||
|
&& (e->key() != Qt::Key_Shift)
|
||||||
|
&& !e->text().isEmpty();
|
||||||
|
if (character) {
|
||||||
|
return true;
|
||||||
|
} else if (e != QKeySequence::Paste) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto useSelectionMode = (e->key() == Qt::Key_Insert)
|
||||||
|
&& (e->modifiers() == (Qt::CTRL | Qt::SHIFT))
|
||||||
|
&& QGuiApplication::clipboard()->supportsSelection();
|
||||||
|
const auto pasteMode = useSelectionMode
|
||||||
|
? QClipboard::Selection
|
||||||
|
: QClipboard::Clipboard;
|
||||||
|
const auto data = QGuiApplication::clipboard()->mimeData(pasteMode);
|
||||||
|
return data && data->hasText();
|
||||||
|
}
|
||||||
|
|
||||||
void Widget::paintEvent(QPaintEvent *e) {
|
void Widget::paintEvent(QPaintEvent *e) {
|
||||||
if (controller()->contentOverlapped(this, e)) {
|
if (controller()->contentOverlapped(this, e)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -250,6 +250,8 @@ private:
|
||||||
void updateSuggestions(anim::type animated);
|
void updateSuggestions(anim::type animated);
|
||||||
void processSearchFocusChange();
|
void processSearchFocusChange();
|
||||||
|
|
||||||
|
[[nodiscard]] bool redirectKeyToSearch(QKeyEvent *e) const;
|
||||||
|
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
bool _dragInScroll = false;
|
bool _dragInScroll = false;
|
||||||
|
|
|
@ -668,14 +668,14 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
|
||||||
auto result = Poll();
|
auto result = Poll();
|
||||||
data.vpoll().match([&](const MTPDpoll &poll) {
|
data.vpoll().match([&](const MTPDpoll &poll) {
|
||||||
result.id = poll.vid().v;
|
result.id = poll.vid().v;
|
||||||
result.question = ParseString(poll.vquestion());
|
result.question = ParseString(poll.vquestion().data().vtext());
|
||||||
result.closed = poll.is_closed();
|
result.closed = poll.is_closed();
|
||||||
result.answers = ranges::views::all(
|
result.answers = ranges::views::all(
|
||||||
poll.vanswers().v
|
poll.vanswers().v
|
||||||
) | ranges::views::transform([](const MTPPollAnswer &answer) {
|
) | ranges::views::transform([](const MTPPollAnswer &answer) {
|
||||||
return answer.match([](const MTPDpollAnswer &answer) {
|
return answer.match([](const MTPDpollAnswer &answer) {
|
||||||
auto result = Poll::Answer();
|
auto result = Poll::Answer();
|
||||||
result.text = ParseString(answer.vtext());
|
result.text = ParseString(answer.vtext().data().vtext());
|
||||||
result.option = answer.voption().v;
|
result.option = answer.voption().v;
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item_helpers.h"
|
#include "history/history_item_helpers.h"
|
||||||
#include "history/view/controls/history_view_forward_panel.h"
|
#include "history/view/controls/history_view_forward_panel.h"
|
||||||
#include "history/view/controls/history_view_draft_options.h"
|
#include "history/view/controls/history_view_draft_options.h"
|
||||||
|
#include "boxes/moderate_messages_box.h"
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "history/view/media/history_view_web_page.h"
|
#include "history/view/media/history_view_web_page.h"
|
||||||
#include "history/view/reactions/history_view_reactions_button.h"
|
#include "history/view/reactions/history_view_reactions_button.h"
|
||||||
|
@ -4269,8 +4270,13 @@ void HistoryInner::deleteItem(not_null<HistoryItem*> item) {
|
||||||
_controller->cancelUploadLayer(item);
|
_controller->cancelUploadLayer(item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto suggestModerateActions = true;
|
const auto list = HistoryItemsList{ item };
|
||||||
_controller->show(Box<DeleteMessagesBox>(item, suggestModerateActions));
|
if (CanCreateModerateMessagesBox(list)) {
|
||||||
|
_controller->show(Box(CreateModerateMessagesBox, list, nullptr));
|
||||||
|
} else {
|
||||||
|
const auto suggestModerate = false;
|
||||||
|
_controller->show(Box<DeleteMessagesBox>(item, suggestModerate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryInner::hasPendingResizedItems() const {
|
bool HistoryInner::hasPendingResizedItems() const {
|
||||||
|
|
|
@ -3427,6 +3427,14 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
|
||||||
forwarded->savedFromMsgId = config.savedFromMsgId;
|
forwarded->savedFromMsgId = config.savedFromMsgId;
|
||||||
forwarded->savedFromSender = _history->owner().peerLoaded(
|
forwarded->savedFromSender = _history->owner().peerLoaded(
|
||||||
config.savedFromSenderId);
|
config.savedFromSenderId);
|
||||||
|
if (forwarded->savedFromPeer
|
||||||
|
&& !forwarded->savedFromPeer->isFullLoaded()
|
||||||
|
&& forwarded->savedFromPeer->isChannel()) {
|
||||||
|
_history->session().api().requestFullPeer(forwarded->savedFromPeer);
|
||||||
|
} else if (config.savedFromPeer) {
|
||||||
|
_history->session().api().requestFullPeer(
|
||||||
|
_history->owner().peer(config.savedFromPeer));
|
||||||
|
}
|
||||||
forwarded->savedFromOutgoing = config.savedFromOutgoing;
|
forwarded->savedFromOutgoing = config.savedFromOutgoing;
|
||||||
if (!forwarded->savedFromSender
|
if (!forwarded->savedFromSender
|
||||||
&& !config.savedFromSenderName.isEmpty()) {
|
&& !config.savedFromSenderName.isEmpty()) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/send_files_box.h"
|
#include "boxes/send_files_box.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "boxes/edit_caption_box.h"
|
#include "boxes/edit_caption_box.h"
|
||||||
|
#include "boxes/moderate_messages_box.h"
|
||||||
#include "boxes/premium_limits_box.h"
|
#include "boxes/premium_limits_box.h"
|
||||||
#include "boxes/premium_preview_box.h"
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
|
#include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
|
||||||
|
@ -7953,15 +7954,23 @@ void HistoryWidget::forwardSelected() {
|
||||||
void HistoryWidget::confirmDeleteSelected() {
|
void HistoryWidget::confirmDeleteSelected() {
|
||||||
if (!_list) return;
|
if (!_list) return;
|
||||||
|
|
||||||
auto items = _list->getSelectedItems();
|
auto ids = _list->getSelectedItems();
|
||||||
if (items.empty()) {
|
if (ids.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto box = Box<DeleteMessagesBox>(&session(), std::move(items));
|
const auto items = session().data().idsToItems(ids);
|
||||||
box->setDeleteConfirmedCallback(crl::guard(this, [=] {
|
if (CanCreateModerateMessagesBox(items)) {
|
||||||
clearSelected();
|
controller()->show(Box(
|
||||||
}));
|
CreateModerateMessagesBox,
|
||||||
controller()->show(std::move(box));
|
items,
|
||||||
|
crl::guard(this, [=] { clearSelected(); })));
|
||||||
|
} else {
|
||||||
|
auto box = Box<DeleteMessagesBox>(&session(), std::move(ids));
|
||||||
|
box->setDeleteConfirmedCallback(crl::guard(this, [=] {
|
||||||
|
clearSelected();
|
||||||
|
}));
|
||||||
|
controller()->show(std::move(box));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::escape() {
|
void HistoryWidget::escape() {
|
||||||
|
|
|
@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/fields/input_field.h"
|
#include "ui/widgets/fields/input_field.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
#include "boxes/delete_messages_box.h"
|
#include "boxes/delete_messages_box.h"
|
||||||
|
#include "boxes/moderate_messages_box.h"
|
||||||
#include "boxes/report_messages_box.h"
|
#include "boxes/report_messages_box.h"
|
||||||
#include "boxes/sticker_set_box.h"
|
#include "boxes/sticker_set_box.h"
|
||||||
#include "boxes/stickers_box.h"
|
#include "boxes/stickers_box.h"
|
||||||
|
@ -828,9 +829,15 @@ bool AddDeleteMessageAction(
|
||||||
controller->cancelUploadLayer(item);
|
controller->cancelUploadLayer(item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto suggestModerateActions = true;
|
const auto list = HistoryItemsList{ item };
|
||||||
controller->show(
|
if (CanCreateModerateMessagesBox(list)) {
|
||||||
Box<DeleteMessagesBox>(item, suggestModerateActions));
|
controller->show(
|
||||||
|
Box(CreateModerateMessagesBox, list, nullptr));
|
||||||
|
} else {
|
||||||
|
const auto suggestModerateActions = false;
|
||||||
|
controller->show(
|
||||||
|
Box<DeleteMessagesBox>(item, suggestModerateActions));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (item->isUploading()) {
|
if (item->isUploading()) {
|
||||||
|
@ -1300,15 +1307,15 @@ void AddPollActions(
|
||||||
const auto radio = QString::fromUtf8(kRadio);
|
const auto radio = QString::fromUtf8(kRadio);
|
||||||
auto text = poll->question;
|
auto text = poll->question;
|
||||||
for (const auto &answer : poll->answers) {
|
for (const auto &answer : poll->answers) {
|
||||||
text += '\n' + radio + answer.text;
|
text.append('\n').append(radio).append(answer.text);
|
||||||
}
|
}
|
||||||
if (!Ui::SkipTranslate({ text })) {
|
if (!Ui::SkipTranslate(text)) {
|
||||||
menu->addAction(tr::lng_context_translate(tr::now), [=] {
|
menu->addAction(tr::lng_context_translate(tr::now), [=] {
|
||||||
controller->show(Box(
|
controller->show(Box(
|
||||||
Ui::TranslateBox,
|
Ui::TranslateBox,
|
||||||
item->history()->peer,
|
item->history()->peer,
|
||||||
MsgId(),
|
MsgId(),
|
||||||
TextWithEntities{ .text = text },
|
std::move(text),
|
||||||
item->forbidsForward()));
|
item->forbidsForward()));
|
||||||
}, &st::menuIconTranslate);
|
}, &st::menuIconTranslate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "history/view/media/history_view_poll.h"
|
#include "history/view/media/history_view_poll.h"
|
||||||
|
|
||||||
|
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
@ -154,7 +155,10 @@ struct Poll::SendingAnimation {
|
||||||
struct Poll::Answer {
|
struct Poll::Answer {
|
||||||
Answer();
|
Answer();
|
||||||
|
|
||||||
void fillData(not_null<PollData*> poll, const PollAnswer &original);
|
void fillData(
|
||||||
|
not_null<PollData*> poll,
|
||||||
|
const PollAnswer &original,
|
||||||
|
Core::MarkedTextContext context);
|
||||||
|
|
||||||
Ui::Text::String text;
|
Ui::Text::String text;
|
||||||
QByteArray option;
|
QByteArray option;
|
||||||
|
@ -201,16 +205,18 @@ Poll::Answer::Answer() : text(st::msgMinWidth / 2) {
|
||||||
|
|
||||||
void Poll::Answer::fillData(
|
void Poll::Answer::fillData(
|
||||||
not_null<PollData*> poll,
|
not_null<PollData*> poll,
|
||||||
const PollAnswer &original) {
|
const PollAnswer &original,
|
||||||
|
Core::MarkedTextContext context) {
|
||||||
chosen = original.chosen;
|
chosen = original.chosen;
|
||||||
correct = poll->quiz() ? original.correct : chosen;
|
correct = poll->quiz() ? original.correct : chosen;
|
||||||
if (!text.isEmpty() && text.toString() == original.text) {
|
if (!text.isEmpty() && text.toTextWithEntities() == original.text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
text.setText(
|
text.setMarkedText(
|
||||||
st::historyPollAnswerStyle,
|
st::historyPollAnswerStyle,
|
||||||
original.text,
|
original.text,
|
||||||
Ui::WebpageTextTitleOptions());
|
Ui::WebpageTextTitleOptions(),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::CloseInformation::CloseInformation(
|
Poll::CloseInformation::CloseInformation(
|
||||||
|
@ -383,13 +389,18 @@ void Poll::updateTexts() {
|
||||||
const auto willStartAnimation = checkAnimationStart();
|
const auto willStartAnimation = checkAnimationStart();
|
||||||
const auto voted = _voted;
|
const auto voted = _voted;
|
||||||
|
|
||||||
if (_question.toString() != _poll->question) {
|
if (_question.toTextWithEntities() != _poll->question) {
|
||||||
auto options = Ui::WebpageTextTitleOptions();
|
auto options = Ui::WebpageTextTitleOptions();
|
||||||
options.maxw = options.maxh = 0;
|
options.maxw = options.maxh = 0;
|
||||||
_question.setText(
|
_question.setMarkedText(
|
||||||
st::historyPollQuestionStyle,
|
st::historyPollQuestionStyle,
|
||||||
_poll->question,
|
_poll->question,
|
||||||
options);
|
options,
|
||||||
|
Core::MarkedTextContext{
|
||||||
|
.session = &_poll->session(),
|
||||||
|
.customEmojiRepaint = [=] { repaint(); },
|
||||||
|
.customEmojiLoopLimit = 2,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (_flags != _poll->flags() || _subtitle.isEmpty()) {
|
if (_flags != _poll->flags() || _subtitle.isEmpty()) {
|
||||||
using Flag = PollData::Flag;
|
using Flag = PollData::Flag;
|
||||||
|
@ -514,6 +525,11 @@ void Poll::updateRecentVoters() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poll::updateAnswers() {
|
void Poll::updateAnswers() {
|
||||||
|
const auto context = Core::MarkedTextContext{
|
||||||
|
.session = &_poll->session(),
|
||||||
|
.customEmojiRepaint = [=] { repaint(); },
|
||||||
|
.customEmojiLoopLimit = 2,
|
||||||
|
};
|
||||||
const auto changed = !ranges::equal(
|
const auto changed = !ranges::equal(
|
||||||
_answers,
|
_answers,
|
||||||
_poll->answers,
|
_poll->answers,
|
||||||
|
@ -523,7 +539,7 @@ void Poll::updateAnswers() {
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
auto &&answers = ranges::views::zip(_answers, _poll->answers);
|
auto &&answers = ranges::views::zip(_answers, _poll->answers);
|
||||||
for (auto &&[answer, original] : answers) {
|
for (auto &&[answer, original] : answers) {
|
||||||
answer.fillData(_poll, original);
|
answer.fillData(_poll, original, context);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -532,7 +548,7 @@ void Poll::updateAnswers() {
|
||||||
) | ranges::views::transform([&](const PollAnswer &answer) {
|
) | ranges::views::transform([&](const PollAnswer &answer) {
|
||||||
auto result = Answer();
|
auto result = Answer();
|
||||||
result.option = answer.option;
|
result.option = answer.option;
|
||||||
result.fillData(_poll, answer);
|
result.fillData(_poll, answer, context);
|
||||||
return result;
|
return result;
|
||||||
}) | ranges::to_vector;
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/polls/info_polls_results_inner_widget.h"
|
#include "info/polls/info_polls_results_inner_widget.h"
|
||||||
|
|
||||||
#include "info/polls/info_polls_results_widget.h"
|
#include "info/polls/info_polls_results_widget.h"
|
||||||
#include "info/info_controller.h"
|
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
#include "data/data_peer.h"
|
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "ui/controls/peer_list_dummy.h"
|
#include "ui/controls/peer_list_dummy.h"
|
||||||
#include "ui/widgets/labels.h"
|
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "boxes/peer_list_box.h"
|
#include "boxes/peer_list_box.h"
|
||||||
|
@ -26,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_boxes.h"
|
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
namespace Info {
|
namespace Info {
|
||||||
|
@ -461,10 +456,11 @@ ListController *CreateAnswerRows(
|
||||||
container.get(),
|
container.get(),
|
||||||
object_ptr<Ui::FlatLabel>(
|
object_ptr<Ui::FlatLabel>(
|
||||||
container,
|
container,
|
||||||
(answer.text
|
rpl::single(
|
||||||
+ QString::fromUtf8(" \xe2\x80\x94 ")
|
TextWithEntities(answer.text)
|
||||||
+ QString::number(percent)
|
.append(QString::fromUtf8(" \xe2\x80\x94 "))
|
||||||
+ "%"),
|
.append(QString::number(percent))
|
||||||
|
.append('%')),
|
||||||
st::boxDividerLabel),
|
st::boxDividerLabel),
|
||||||
style::margins(
|
style::margins(
|
||||||
st::pollResultsHeaderPadding.left(),
|
st::pollResultsHeaderPadding.left(),
|
||||||
|
@ -613,7 +609,7 @@ void InnerWidget::setupContent() {
|
||||||
_content->add(
|
_content->add(
|
||||||
object_ptr<Ui::FlatLabel>(
|
object_ptr<Ui::FlatLabel>(
|
||||||
_content,
|
_content,
|
||||||
_poll->question,
|
rpl::single(_poll->question),
|
||||||
st::pollResultsQuestion),
|
st::pollResultsQuestion),
|
||||||
style::margins{
|
style::margins{
|
||||||
st::boxRowPadding.left(),
|
st::boxRowPadding.left(),
|
||||||
|
|
|
@ -369,6 +369,10 @@ void Step::fillSentCodeData(const MTPDauth_sentCode &data) {
|
||||||
bad("FirebaseSms");
|
bad("FirebaseSms");
|
||||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||||
bad("EmailCode");
|
bad("EmailCode");
|
||||||
|
}, [&](const MTPDauth_sentCodeTypeSmsWord &) {
|
||||||
|
bad("SmsWord");
|
||||||
|
}, [&](const MTPDauth_sentCodeTypeSmsPhrase &) {
|
||||||
|
bad("SmsPhrase");
|
||||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||||
bad("SetUpEmailRequired");
|
bad("SetUpEmailRequired");
|
||||||
});
|
});
|
||||||
|
|
|
@ -155,10 +155,6 @@ namespace {
|
||||||
+ "IV.init();"
|
+ "IV.init();"
|
||||||
+ page.script;
|
+ page.script;
|
||||||
|
|
||||||
const auto contentAttributes = page.rtl
|
|
||||||
? " dir=\"rtl\" class=\"rtl\""_q
|
|
||||||
: QByteArray();
|
|
||||||
|
|
||||||
return R"(<!DOCTYPE html>
|
return R"(<!DOCTYPE html>
|
||||||
<html)"_q
|
<html)"_q
|
||||||
+ classAttribute
|
+ classAttribute
|
||||||
|
@ -179,9 +175,7 @@ namespace {
|
||||||
<path d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6" transform="translate(11.997236, 12.000000) scale(-1, -1) rotate(-90.000000) translate(-11.997236, -12.000000) "></path>
|
<path d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6" transform="translate(11.997236, 12.000000) scale(-1, -1) rotate(-90.000000) translate(-11.997236, -12.000000) "></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="page-scroll" tabindex="-1"><div class="page-slide">
|
<div class="page-scroll" tabindex="-1">)"_q + page.content.trimmed() + R"(</div>
|
||||||
<article)"_q + contentAttributes + ">"_q + page.content + R"(</article>
|
|
||||||
</div></div>
|
|
||||||
<script>)"_q + js + R"(</script>
|
<script>)"_q + js + R"(</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -646,7 +640,12 @@ void Controller::processLink(const QString &url, const QString &context) {
|
||||||
const auto joinPrefix = u"join_link"_q;
|
const auto joinPrefix = u"join_link"_q;
|
||||||
const auto webpagePrefix = u"webpage"_q;
|
const auto webpagePrefix = u"webpage"_q;
|
||||||
const auto viewerPrefix = u"viewer"_q;
|
const auto viewerPrefix = u"viewer"_q;
|
||||||
if (context.startsWith(channelPrefix)) {
|
if (context == u"report-iv") {
|
||||||
|
_events.fire({
|
||||||
|
.type = Event::Type::Report,
|
||||||
|
.context = QString::number(compuseCurrentPageId()),
|
||||||
|
});
|
||||||
|
} else if (context.startsWith(channelPrefix)) {
|
||||||
_events.fire({
|
_events.fire({
|
||||||
.type = Event::Type::OpenChannel,
|
.type = Event::Type::OpenChannel,
|
||||||
.context = context.mid(channelPrefix.size()),
|
.context = context.mid(channelPrefix.size()),
|
||||||
|
@ -701,6 +700,13 @@ QString Controller::composeCurrentUrl() const {
|
||||||
+ (_hash.isEmpty() ? u""_q : ('#' + _hash));
|
+ (_hash.isEmpty() ? u""_q : ('#' + _hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 Controller::compuseCurrentPageId() const {
|
||||||
|
const auto index = _index.current();
|
||||||
|
Assert(index >= 0 && index < _pages.size());
|
||||||
|
|
||||||
|
return _pages[index].pageId;
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::showMenu() {
|
void Controller::showMenu() {
|
||||||
const auto index = _index.current();
|
const auto index = _index.current();
|
||||||
if (_menu || index < 0 || index > _pages.size()) {
|
if (_menu || index < 0 || index > _pages.size()) {
|
||||||
|
|
|
@ -63,6 +63,7 @@ public:
|
||||||
OpenLink,
|
OpenLink,
|
||||||
OpenLinkExternal,
|
OpenLinkExternal,
|
||||||
OpenMedia,
|
OpenMedia,
|
||||||
|
Report,
|
||||||
};
|
};
|
||||||
Type type = Type::Close;
|
Type type = Type::Close;
|
||||||
QString url;
|
QString url;
|
||||||
|
@ -116,6 +117,7 @@ private:
|
||||||
void quit();
|
void quit();
|
||||||
|
|
||||||
[[nodiscard]] QString composeCurrentUrl() const;
|
[[nodiscard]] QString composeCurrentUrl() const;
|
||||||
|
[[nodiscard]] uint64 compuseCurrentPageId() const;
|
||||||
void showShareMenu();
|
void showShareMenu();
|
||||||
void destroyShareMenu();
|
void destroyShareMenu();
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ struct Options {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Prepared {
|
struct Prepared {
|
||||||
|
uint64 pageId = 0;
|
||||||
QString name;
|
QString name;
|
||||||
QByteArray content;
|
QByteArray content;
|
||||||
QByteArray script;
|
QByteArray script;
|
||||||
|
|
|
@ -742,9 +742,7 @@ void Instance::show(
|
||||||
not_null<Data*> data,
|
not_null<Data*> data,
|
||||||
QString hash) {
|
QString hash) {
|
||||||
const auto guard = gsl::finally([&] {
|
const auto guard = gsl::finally([&] {
|
||||||
if (data->partial()) {
|
requestFull(session, data->id());
|
||||||
requestFull(session, data->id());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (_shown && _shownSession == session) {
|
if (_shown && _shownSession == session) {
|
||||||
_shown->moveTo(data, hash);
|
_shown->moveTo(data, hash);
|
||||||
|
@ -815,6 +813,7 @@ void Instance::show(
|
||||||
if (!urlChecked) {
|
if (!urlChecked) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
_fullRequested[_shownSession].emplace(event.url);
|
||||||
_shownSession->api().request(MTPmessages_GetWebPage(
|
_shownSession->api().request(MTPmessages_GetWebPage(
|
||||||
MTP_string(event.url),
|
MTP_string(event.url),
|
||||||
MTP_int(0)
|
MTP_int(0)
|
||||||
|
@ -834,6 +833,17 @@ void Instance::show(
|
||||||
UrlClickHandler::Open(event.url);
|
UrlClickHandler::Open(event.url);
|
||||||
}).send();
|
}).send();
|
||||||
break;
|
break;
|
||||||
|
case Type::Report:
|
||||||
|
if (const auto controller = _shownSession->tryResolveWindow()) {
|
||||||
|
controller->window().activate();
|
||||||
|
controller->showPeerByLink(Window::PeerByLinkInfo{
|
||||||
|
.usernameOrId = "previews",
|
||||||
|
.resolveType = Window::ResolveType::BotStart,
|
||||||
|
.startToken = ("webpage"
|
||||||
|
+ QString::number(event.context.toULongLong())),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}, _shown->lifetime());
|
}, _shown->lifetime());
|
||||||
|
|
||||||
|
@ -938,6 +948,7 @@ void Instance::openWithIvPreferred(
|
||||||
};
|
};
|
||||||
_ivRequestSession = session;
|
_ivRequestSession = session;
|
||||||
_ivRequestUri = uri;
|
_ivRequestUri = uri;
|
||||||
|
_fullRequested[session].emplace(url);
|
||||||
_ivRequestId = session->api().request(MTPmessages_GetWebPage(
|
_ivRequestId = session->api().request(MTPmessages_GetWebPage(
|
||||||
MTP_string(url),
|
MTP_string(url),
|
||||||
MTP_int(0)
|
MTP_int(0)
|
||||||
|
|
|
@ -142,6 +142,8 @@ private:
|
||||||
[[nodiscard]] QByteArray block(
|
[[nodiscard]] QByteArray block(
|
||||||
const MTPDpageListOrderedItemBlocks &data);
|
const MTPDpageListOrderedItemBlocks &data);
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray wrap(const QByteArray &content, int views);
|
||||||
|
|
||||||
[[nodiscard]] QByteArray tag(
|
[[nodiscard]] QByteArray tag(
|
||||||
const QByteArray &name,
|
const QByteArray &name,
|
||||||
const QByteArray &body = {});
|
const QByteArray &body = {});
|
||||||
|
@ -223,9 +225,13 @@ Parser::Parser(const Source &source, const Options &options)
|
||||||
: /*_options(options)
|
: /*_options(options)
|
||||||
, */_fileOriginPostfix('/' + Number(source.pageId)) {
|
, */_fileOriginPostfix('/' + Number(source.pageId)) {
|
||||||
process(source);
|
process(source);
|
||||||
|
_result.pageId = source.pageId;
|
||||||
_result.name = source.name;
|
_result.name = source.name;
|
||||||
_result.rtl = source.page.data().is_rtl();
|
_result.rtl = source.page.data().is_rtl();
|
||||||
_result.content = list(source.page.data().vblocks());
|
|
||||||
|
const auto views = source.page.data().vviews().value_or_empty();
|
||||||
|
const auto content = list(source.page.data().vblocks());
|
||||||
|
_result.content = wrap(content, views);
|
||||||
}
|
}
|
||||||
|
|
||||||
Prepared Parser::result() {
|
Prepared Parser::result() {
|
||||||
|
@ -514,6 +520,9 @@ QByteArray Parser::block(
|
||||||
}, result);
|
}, result);
|
||||||
if (!slideshow) {
|
if (!slideshow) {
|
||||||
result += caption(data.vcaption());
|
result += caption(data.vcaption());
|
||||||
|
if (!collage) {
|
||||||
|
result = tag("div", { { "class", "media-outer" } }, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -579,6 +588,9 @@ QByteArray Parser::block(
|
||||||
}
|
}
|
||||||
if (!slideshow) {
|
if (!slideshow) {
|
||||||
result += caption(data.vcaption());
|
result += caption(data.vcaption());
|
||||||
|
if (!collage) {
|
||||||
|
result = tag("div", { { "class", "media-outer" } }, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -925,6 +937,26 @@ QByteArray Parser::utf(const tl::conditional<MTPstring> &text) {
|
||||||
return text ? utf(*text) : QByteArray();
|
return text ? utf(*text) : QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray Parser::wrap(const QByteArray &content, int views) {
|
||||||
|
const auto sep = " \xE2\x80\xA2 ";
|
||||||
|
const auto viewsText = views
|
||||||
|
? (tr::lng_stories_views(tr::now, lt_count_decimal, views) + sep)
|
||||||
|
: QString();
|
||||||
|
return R"(
|
||||||
|
<div class="page-slide">
|
||||||
|
<article>)"_q + content + R"(</article>
|
||||||
|
</div>
|
||||||
|
<div class="page-footer">
|
||||||
|
<div class="content">
|
||||||
|
)"_q
|
||||||
|
+ viewsText.toUtf8()
|
||||||
|
+ R"(<a class="wrong" data-context="report-iv">)"_q
|
||||||
|
+ tr::lng_iv_wrong_layout(tr::now).toUtf8()
|
||||||
|
+ R"(</a>
|
||||||
|
</div>
|
||||||
|
</div>)"_q;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray Parser::tag(
|
QByteArray Parser::tag(
|
||||||
const QByteArray &name,
|
const QByteArray &name,
|
||||||
const QByteArray &body) {
|
const QByteArray &body) {
|
||||||
|
|
|
@ -5190,27 +5190,43 @@ void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||||
void OverlayWidget::handleWheelEvent(not_null<QWheelEvent*> e) {
|
void OverlayWidget::handleWheelEvent(not_null<QWheelEvent*> e) {
|
||||||
constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep);
|
constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep);
|
||||||
|
|
||||||
|
const auto _thisWheelDelta = e->angleDelta().y();
|
||||||
const auto acceptForJump = !_stories
|
const auto acceptForJump = !_stories
|
||||||
&& ((e->source() == Qt::MouseEventNotSynthesized)
|
&& ((e->source() == Qt::MouseEventNotSynthesized)
|
||||||
|| (e->source() == Qt::MouseEventSynthesizedBySystem));
|
|| (e->source() == Qt::MouseEventSynthesizedBySystem));
|
||||||
_verticalWheelDelta += e->angleDelta().y();
|
|
||||||
while (qAbs(_verticalWheelDelta) >= step) {
|
const bool directionChanges =
|
||||||
if (_verticalWheelDelta < 0) {
|
std::signbit(_lastWheelDelta) != std::signbit(_thisWheelDelta);
|
||||||
_verticalWheelDelta += step;
|
|
||||||
|
if ((qAbs(_thisWheelDelta) != step) && directionChanges) {
|
||||||
|
// linux: first scroll after direction changes on hi-res wheel in
|
||||||
|
// libinput is unreliable. offen lost first half of it's value,
|
||||||
|
// or even only remain one with 15 delta, so we just hardcode it
|
||||||
|
// to same as step here, other system should be fine too.
|
||||||
|
_absWheelDelta = _thisWheelDelta > 0 ? step : step * -1;
|
||||||
|
} else {
|
||||||
|
_absWheelDelta += _thisWheelDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (qAbs(_absWheelDelta) >= step) {
|
||||||
|
if (_absWheelDelta < 0) {
|
||||||
|
// _absWheelDelta += step;
|
||||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||||
zoomOut();
|
zoomOut();
|
||||||
} else if (acceptForJump) {
|
} else if (acceptForJump) {
|
||||||
moveToNext(1);
|
moveToNext(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_verticalWheelDelta -= step;
|
// _absWheelDelta -= step;
|
||||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||||
zoomIn();
|
zoomIn();
|
||||||
} else if (acceptForJump) {
|
} else if (acceptForJump) {
|
||||||
moveToNext(-1);
|
moveToNext(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_absWheelDelta = 0; // reset to 0 to reduce pic move jump or skip.
|
||||||
}
|
}
|
||||||
|
_lastWheelDelta = _thisWheelDelta; // record last wheel delta
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::setZoomLevel(int newZoom, bool force) {
|
void OverlayWidget::setZoomLevel(int newZoom, bool force) {
|
||||||
|
|
|
@ -725,7 +725,8 @@ private:
|
||||||
rpl::event_stream<TouchBarItemType> _touchbarDisplay;
|
rpl::event_stream<TouchBarItemType> _touchbarDisplay;
|
||||||
rpl::event_stream<bool> _touchbarFullscreenToggled;
|
rpl::event_stream<bool> _touchbarFullscreenToggled;
|
||||||
|
|
||||||
int _verticalWheelDelta = 0;
|
int _absWheelDelta = 0;
|
||||||
|
int _lastWheelDelta = 0;
|
||||||
|
|
||||||
bool _themePreviewShown = false;
|
bool _themePreviewShown = false;
|
||||||
uint64 _themePreviewId = 0;
|
uint64 _themePreviewId = 0;
|
||||||
|
|
|
@ -758,6 +758,8 @@ auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true go
|
||||||
auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType;
|
auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType;
|
||||||
auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType;
|
auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType;
|
||||||
auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType;
|
auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType;
|
||||||
|
auth.sentCodeTypeSmsWord#a416ac81 flags:# beginning:flags.0?string = auth.SentCodeType;
|
||||||
|
auth.sentCodeTypeSmsPhrase#b37794af flags:# beginning:flags.0?string = auth.SentCodeType;
|
||||||
|
|
||||||
messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
|
messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
|
||||||
|
|
||||||
|
@ -1149,9 +1151,9 @@ help.supportName#8c05f1c9 name:string = help.SupportName;
|
||||||
help.userInfoEmpty#f3ae2eed = help.UserInfo;
|
help.userInfoEmpty#f3ae2eed = help.UserInfo;
|
||||||
help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:string date:int = help.UserInfo;
|
help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:string date:int = help.UserInfo;
|
||||||
|
|
||||||
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
|
pollAnswer#ff16e2ca text:TextWithEntities option:bytes = PollAnswer;
|
||||||
|
|
||||||
poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
|
poll#58747131 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:TextWithEntities answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
|
||||||
|
|
||||||
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
|
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
|
||||||
|
|
||||||
|
@ -1808,6 +1810,7 @@ auth.checkRecoveryPassword#d36bf79 code:string = Bool;
|
||||||
auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization;
|
auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization;
|
||||||
auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool;
|
auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool;
|
||||||
auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode;
|
auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode;
|
||||||
|
auth.reportMissingCode#cb9deff6 phone_number:string phone_code_hash:string mnc:string = Bool;
|
||||||
|
|
||||||
account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<long> = Bool;
|
account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<long> = Bool;
|
||||||
account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector<long> = Bool;
|
account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector<long> = Bool;
|
||||||
|
@ -2424,4 +2427,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
|
||||||
|
|
||||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
||||||
|
|
||||||
// LAYER 178
|
// LAYER 179
|
||||||
|
|
|
@ -2217,6 +2217,10 @@ void FormController::startPhoneVerification(not_null<Value*> value) {
|
||||||
bad("FirebaseSms");
|
bad("FirebaseSms");
|
||||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||||
bad("EmailCode");
|
bad("EmailCode");
|
||||||
|
}, [&](const MTPDauth_sentCodeTypeSmsWord &) {
|
||||||
|
bad("SmsWord");
|
||||||
|
}, [&](const MTPDauth_sentCodeTypeSmsPhrase &) {
|
||||||
|
bad("SmsPhrase");
|
||||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||||
bad("SetUpEmailRequired");
|
bad("SetUpEmailRequired");
|
||||||
});
|
});
|
||||||
|
|
|
@ -994,7 +994,7 @@ TextWithEntities Manager::ComposeReactionNotification(
|
||||||
lt_reaction,
|
lt_reaction,
|
||||||
reactionWithEntities,
|
reactionWithEntities,
|
||||||
lt_title,
|
lt_title,
|
||||||
Ui::Text::WithEntities(poll->question),
|
poll->question,
|
||||||
Ui::Text::WithEntities);
|
Ui::Text::WithEntities);
|
||||||
} else if (media->game()) {
|
} else if (media->game()) {
|
||||||
return simple(tr::lng_reaction_game);
|
return simple(tr::lng_reaction_game);
|
||||||
|
|
|
@ -1085,6 +1085,34 @@ void Filler::addViewStatistics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Filler::addCreatePoll() {
|
void Filler::addCreatePoll() {
|
||||||
|
const auto isJoinChannel = [&] {
|
||||||
|
if (_request.section != Section::Replies) {
|
||||||
|
if (const auto c = _peer->asChannel(); c && !c->amIn()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}();
|
||||||
|
const auto isBotStart = [&] {
|
||||||
|
const auto user = _peer ? _peer->asUser() : nullptr;
|
||||||
|
if (!user || !user->isBot()) {
|
||||||
|
return false;
|
||||||
|
} else if (!user->botInfo->startToken.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto history = _peer->owner().history(_peer);
|
||||||
|
if (history && history->isEmpty() && !history->lastMessage()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}();
|
||||||
|
const auto isBlocked = [&] {
|
||||||
|
return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
|
||||||
|
}();
|
||||||
|
if (isBlocked || isJoinChannel || isBotStart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto can = _topic
|
const auto can = _topic
|
||||||
? Data::CanSend(_topic, ChatRestriction::SendPolls)
|
? Data::CanSend(_topic, ChatRestriction::SendPolls)
|
||||||
: _peer->canCreatePolls();
|
: _peer->canCreatePolls();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
AppVersion 4016009
|
AppVersion 4016010
|
||||||
AppVersionStrMajor 4.16
|
AppVersionStrMajor 4.16
|
||||||
AppVersionStrSmall 4.16.9
|
AppVersionStrSmall 4.16.10
|
||||||
AppVersionStr 4.16.9
|
AppVersionStr 4.16.10
|
||||||
BetaChannel 1
|
BetaChannel 1
|
||||||
AlphaVersion 0
|
AlphaVersion 0
|
||||||
AppVersionOriginal 4.16.9.beta
|
AppVersionOriginal 4.16.10.beta
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 69e474ea775f115afb3e4afeb80d3227325dfcc4
|
Subproject commit cb57bef3f01b7ec60eb0eae0ee68cd56cb3a9b1f
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9f9bcaaec922644406faadda4d37014c9dec2dd9
|
Subproject commit 2ccbfa5f3443274e40deb761674b536e2e8eedae
|
|
@ -1,3 +1,10 @@
|
||||||
|
4.16.10 beta (26.04.24)
|
||||||
|
|
||||||
|
- Group admins can mass-moderate many messages.
|
||||||
|
- Premium users can use animated emoji in polls.
|
||||||
|
- Revert the default "Open Sans" font to 1.10.
|
||||||
|
- Several crash fixes and small improvements.
|
||||||
|
|
||||||
4.16.9 beta (23.04.24)
|
4.16.9 beta (23.04.24)
|
||||||
|
|
||||||
- Show "Frequent contacts" when you focus the search field.
|
- Show "Frequent contacts" when you focus the search field.
|
||||||
|
|
|
@ -2,7 +2,7 @@ name: telegram-desktop
|
||||||
adopt-info: telegram
|
adopt-info: telegram
|
||||||
icon: Telegram/Resources/art/icon512@2x.png
|
icon: Telegram/Resources/art/icon512@2x.png
|
||||||
|
|
||||||
base: core22
|
base: core24
|
||||||
grade: stable
|
grade: stable
|
||||||
confinement: strict
|
confinement: strict
|
||||||
compression: lzo
|
compression: lzo
|
||||||
|
@ -70,12 +70,14 @@ layout:
|
||||||
bind: $SNAP/usr/share/X11
|
bind: $SNAP/usr/share/X11
|
||||||
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/gtk-3.0:
|
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/gtk-3.0:
|
||||||
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/gtk-3.0
|
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/gtk-3.0
|
||||||
|
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/gtk-4.0:
|
||||||
|
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/gtk-4.0
|
||||||
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pipewire-0.3:
|
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pipewire-0.3:
|
||||||
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pipewire-0.3
|
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pipewire-0.3
|
||||||
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/spa-0.2:
|
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/spa-0.2:
|
||||||
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/spa-0.2
|
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/spa-0.2
|
||||||
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/webkit2gtk-4.1:
|
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/webkitgtk-6.0:
|
||||||
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/webkit2gtk-4.1
|
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/webkitgtk-6.0
|
||||||
|
|
||||||
package-repositories:
|
package-repositories:
|
||||||
- type: apt
|
- type: apt
|
||||||
|
@ -94,14 +96,17 @@ parts:
|
||||||
- clang
|
- clang
|
||||||
- libtool-bin
|
- libtool-bin
|
||||||
- python3
|
- python3
|
||||||
|
- protobuf-compiler
|
||||||
- libasound2-dev
|
- libasound2-dev
|
||||||
- libavif-dev
|
- libavif-dev
|
||||||
- libboost-regex1.74-dev
|
- libboost-regex-dev
|
||||||
- libfmt-dev
|
- libfmt-dev
|
||||||
- libgirepository1.0-dev
|
- libgirepository1.0-dev
|
||||||
- libglib2.0-dev
|
- libglib2.0-dev
|
||||||
- libheif-dev
|
- libheif-dev
|
||||||
|
- libopenal-dev
|
||||||
- libopus-dev
|
- libopus-dev
|
||||||
|
- libprotobuf-dev
|
||||||
- libpulse-dev
|
- libpulse-dev
|
||||||
- libssl-dev
|
- libssl-dev
|
||||||
- libwayland-dev
|
- libwayland-dev
|
||||||
|
@ -112,15 +117,17 @@ parts:
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libasound2
|
- libasound2
|
||||||
- libavif13
|
- libavif16
|
||||||
- libboost-regex1.74.0
|
- libboost-regex1.83.0
|
||||||
- libglib2.0-0
|
- libglib2.0-0
|
||||||
- libheif1
|
- libheif1
|
||||||
|
- libopenal1
|
||||||
- libopus0
|
- libopus0
|
||||||
|
- libprotobuf-lite32
|
||||||
- libpulse0
|
- libpulse0
|
||||||
- libssl3
|
- libssl3
|
||||||
- libwayland-client0
|
- libwayland-client0
|
||||||
- libwebkit2gtk-4.1-0
|
- libwebkitgtk-6.0-4
|
||||||
- libxcb1
|
- libxcb1
|
||||||
- libxcb-keysyms1
|
- libxcb-keysyms1
|
||||||
- libxcb-record0
|
- libxcb-record0
|
||||||
|
@ -157,8 +164,6 @@ parts:
|
||||||
after:
|
after:
|
||||||
- ffmpeg
|
- ffmpeg
|
||||||
- libjxl
|
- libjxl
|
||||||
- openal
|
|
||||||
- protobuf
|
|
||||||
- qt
|
- qt
|
||||||
- rnnoise
|
- rnnoise
|
||||||
- webrtc
|
- webrtc
|
||||||
|
@ -220,12 +225,12 @@ parts:
|
||||||
- libswresample-dev
|
- libswresample-dev
|
||||||
- libswscale-dev
|
- libswscale-dev
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libavcodec58
|
- libavcodec60
|
||||||
- libavfilter7
|
- libavfilter9
|
||||||
- libavformat58
|
- libavformat60
|
||||||
- libavutil56
|
- libavutil58
|
||||||
- libswresample3
|
- libswresample4
|
||||||
- libswscale5
|
- libswscale7
|
||||||
- va-driver-all
|
- va-driver-all
|
||||||
- vdpau-driver-all
|
- vdpau-driver-all
|
||||||
override-build: |
|
override-build: |
|
||||||
|
@ -273,60 +278,6 @@ parts:
|
||||||
after:
|
after:
|
||||||
- patches
|
- patches
|
||||||
|
|
||||||
openal:
|
|
||||||
source: https://github.com/kcat/openal-soft.git
|
|
||||||
source-depth: 1
|
|
||||||
source-tag: 1.23.1
|
|
||||||
plugin: cmake
|
|
||||||
build-environment:
|
|
||||||
- LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s
|
|
||||||
build-packages:
|
|
||||||
- libasound2-dev
|
|
||||||
- libdbus-1-dev
|
|
||||||
- libpipewire-0.3-dev
|
|
||||||
- libpulse-dev
|
|
||||||
stage-packages:
|
|
||||||
- libasound2
|
|
||||||
- libdbus-1-3
|
|
||||||
- libpulse0
|
|
||||||
- pipewire
|
|
||||||
cmake-generator: Ninja
|
|
||||||
cmake-parameters:
|
|
||||||
- -DCMAKE_BUILD_TYPE=Release
|
|
||||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
|
||||||
- -DALSOFT_EXAMPLES=OFF
|
|
||||||
- -DALSOFT_UTILS=OFF
|
|
||||||
- -DALSOFT_INSTALL_CONFIG=OFF
|
|
||||||
prime:
|
|
||||||
- -./usr/include
|
|
||||||
- -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/cmake
|
|
||||||
- -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pkgconfig
|
|
||||||
- -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.so
|
|
||||||
|
|
||||||
protobuf:
|
|
||||||
source: https://github.com/protocolbuffers/protobuf.git
|
|
||||||
source-depth: 1
|
|
||||||
source-tag: v24.3
|
|
||||||
plugin: cmake
|
|
||||||
build-environment:
|
|
||||||
- LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s
|
|
||||||
build-packages:
|
|
||||||
- zlib1g-dev
|
|
||||||
stage-packages:
|
|
||||||
- zlib1g
|
|
||||||
cmake-generator: Ninja
|
|
||||||
cmake-parameters:
|
|
||||||
- -DCMAKE_BUILD_TYPE=Release
|
|
||||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
|
||||||
- -DBUILD_SHARED_LIBS=ON
|
|
||||||
- -Dprotobuf_BUILD_TESTS=OFF
|
|
||||||
prime:
|
|
||||||
- -./usr/bin
|
|
||||||
- -./usr/include
|
|
||||||
- -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/cmake
|
|
||||||
- -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/pkgconfig
|
|
||||||
- -./usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.so
|
|
||||||
|
|
||||||
qt:
|
qt:
|
||||||
plugin: nil
|
plugin: nil
|
||||||
build-environment:
|
build-environment:
|
||||||
|
@ -380,7 +331,7 @@ parts:
|
||||||
- libgtk-3-0
|
- libgtk-3-0
|
||||||
- libharfbuzz0b
|
- libharfbuzz0b
|
||||||
- libice6
|
- libice6
|
||||||
- libicu70
|
- libicu74
|
||||||
- liblcms2-2
|
- liblcms2-2
|
||||||
- libopengl0
|
- libopengl0
|
||||||
- libpcre2-16-0
|
- libpcre2-16-0
|
||||||
|
@ -413,6 +364,7 @@ parts:
|
||||||
- libxkbcommon-x11-0
|
- libxkbcommon-x11-0
|
||||||
- zlib1g
|
- zlib1g
|
||||||
- mesa-vulkan-drivers
|
- mesa-vulkan-drivers
|
||||||
|
- xkb-data
|
||||||
override-pull: |
|
override-pull: |
|
||||||
QT=6.7.0
|
QT=6.7.0
|
||||||
|
|
||||||
|
@ -518,10 +470,10 @@ parts:
|
||||||
- libgbm1
|
- libgbm1
|
||||||
- libgl1
|
- libgl1
|
||||||
- libglib2.0-0
|
- libglib2.0-0
|
||||||
- libopenh264-6
|
- libopenh264-7
|
||||||
- libopus0
|
- libopus0
|
||||||
- libssl3
|
- libssl3
|
||||||
- libvpx7
|
- libvpx8
|
||||||
- libx11-6
|
- libx11-6
|
||||||
- libxcomposite1
|
- libxcomposite1
|
||||||
- libxdamage1
|
- libxdamage1
|
||||||
|
|
Loading…
Reference in New Issue