Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4247fd0c0f | ||
|
|
50f2e93623 | ||
|
|
fbfa6966f4 | ||
|
|
42eb452de8 | ||
|
|
9b3692ca22 | ||
|
|
7463aad755 | ||
|
|
9cf419999d | ||
|
|
7557a20679 | ||
|
|
91e6c42fcf | ||
|
|
3573b84e8b | ||
|
|
bb900c195c | ||
|
|
e61c058eb5 | ||
|
|
b48dee0af7 | ||
|
|
6b6afd38ac | ||
|
|
98e8a20f5f | ||
|
|
aeeb9fe761 | ||
|
|
c65b45460b | ||
|
|
1940edd6ee | ||
|
|
96ef82272b | ||
|
|
29d93d348c | ||
|
|
879df6e6a3 | ||
|
|
9d3715a36a | ||
|
|
1cb0d7c2dc | ||
|
|
1da635a5dd | ||
|
|
d0c2bec925 | ||
|
|
13a9920c11 | ||
|
|
0b100884fc | ||
|
|
6e89d41d58 | ||
|
|
20f946f657 | ||
|
|
9e49e32702 | ||
|
|
6834cdb969 | ||
|
|
36bf54b0d1 | ||
|
|
8f908ab9c0 | ||
|
|
cbd9dd0c2c | ||
|
|
a8decf154f | ||
|
|
f2ed77649e | ||
|
|
369862a3a7 | ||
|
|
e6b24a49f6 | ||
|
|
08644a9c31 | ||
|
|
9090b8ce6b | ||
|
|
646bb2ff71 | ||
|
|
7fa229537d | ||
|
|
c314e43a44 | ||
|
|
ee1162faff | ||
|
|
84b4ab1c3c | ||
|
|
9659cb5b6f | ||
|
|
eee800b6d0 | ||
|
|
41d9a9fcbd | ||
|
|
fec80c0c64 | ||
|
|
ed9ba07a32 | ||
|
|
2f1c674401 | ||
|
|
663e89662b | ||
|
|
afd717b36e | ||
|
|
ddfcf9f1df | ||
|
|
4a37846605 | ||
|
|
64f4e0dd52 | ||
|
|
d889cd0e72 | ||
|
|
d7aa18cb0a | ||
|
|
e486cf1afa | ||
|
|
5f3e7235a5 | ||
|
|
26ff3148d6 | ||
|
|
f95610edfc | ||
|
|
0d134f2b89 | ||
|
|
1a9e217c3e | ||
|
|
c4402c717a | ||
|
|
0bdd0689c0 | ||
|
|
671b3bc94e | ||
|
|
0943d3aac1 | ||
|
|
405230c0c6 | ||
|
|
af8c1f77c7 | ||
|
|
81fb4046d1 | ||
|
|
fb283c4828 | ||
|
|
6f797a17ad | ||
|
|
326b4eb10d | ||
|
|
713889aa9c | ||
|
|
a6aa759947 | ||
|
|
985db8aacf | ||
|
|
dff1568cb2 |
5
.github/workflows/mac.yml
vendored
5
.github/workflows/mac.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
- name: First set up.
|
||||
run: |
|
||||
sudo chown -R `whoami`:admin /usr/local/share
|
||||
brew install automake
|
||||
brew install automake ninja
|
||||
|
||||
# Disable spotlight.
|
||||
sudo mdutil -a -i off
|
||||
@@ -113,13 +113,14 @@ jobs:
|
||||
./configure.sh \
|
||||
-D CMAKE_C_FLAGS="-Werror" \
|
||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
||||
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
|
||||
-D TDESKTOP_API_TEST=ON \
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||
$DEFINE
|
||||
|
||||
cd ../out
|
||||
|
||||
xcoderun='xcodebuild build -project Telegram.xcodeproj -scheme Telegram -destination "platform=macOS,arch=x86_64" -configuration Debug CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO'
|
||||
xcoderun='xcodebuild build -project Telegram.xcodeproj -scheme Telegram -destination "platform=macOS,arch=x86_64" -configuration Debug'
|
||||
bash -c "$xcoderun" || bash -c "$xcoderun" || bash -c "$xcoderun"
|
||||
|
||||
- name: Move artifact.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
add_executable(Telegram WIN32 MACOSX_BUNDLE)
|
||||
init_non_host_target(Telegram ltcg)
|
||||
init_non_host_target(Telegram)
|
||||
|
||||
add_subdirectory(lib_rpl)
|
||||
add_subdirectory(lib_crl)
|
||||
|
||||
BIN
Telegram/Resources/icons/emoji_empty.png
Normal file
BIN
Telegram/Resources/icons/emoji_empty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/emoji_empty@2x.png
Normal file
BIN
Telegram/Resources/icons/emoji_empty@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Telegram/Resources/icons/emoji_empty@3x.png
Normal file
BIN
Telegram/Resources/icons/emoji_empty@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
@@ -35,7 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"cloud_lng_passport_in_it" = "Italian";
|
||||
"cloud_lng_passport_in_ja" = "Japanese";
|
||||
"cloud_lng_passport_in_ka" = "Georgian";
|
||||
"cloud_lng_passport_in_km" = "Khmer";
|
||||
// "cloud_lng_passport_in_km" = "Khmer";
|
||||
"cloud_lng_passport_in_ko" = "Korean";
|
||||
"cloud_lng_passport_in_lo" = "Lao";
|
||||
"cloud_lng_passport_in_lt" = "Lithuanian";
|
||||
@@ -58,3 +58,99 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"cloud_lng_passport_in_uk" = "Ukrainian";
|
||||
"cloud_lng_passport_in_uz" = "Uzbek";
|
||||
"cloud_lng_passport_in_vi" = "Vietnamese";
|
||||
|
||||
"cloud_lng_translate_to_ar" = "Arabic";
|
||||
"cloud_lng_translate_to_az" = "Azerbaijani";
|
||||
"cloud_lng_translate_to_bg" = "Bulgarian";
|
||||
// "cloud_lng_translate_to_bn" = "Bangla";
|
||||
"cloud_lng_translate_to_cs" = "Czech";
|
||||
"cloud_lng_translate_to_da" = "Danish";
|
||||
"cloud_lng_translate_to_de" = "German";
|
||||
// "cloud_lng_translate_to_dv" = "Divehi";
|
||||
// "cloud_lng_translate_to_dz" = "Dzongkha";
|
||||
"cloud_lng_translate_to_el" = "Greek";
|
||||
"cloud_lng_translate_to_en" = "English";
|
||||
"cloud_lng_translate_to_es" = "Spanish";
|
||||
"cloud_lng_translate_to_et" = "Estonian";
|
||||
"cloud_lng_translate_to_fa" = "Persian";
|
||||
"cloud_lng_translate_to_fr" = "French";
|
||||
"cloud_lng_translate_to_he" = "Hebrew";
|
||||
"cloud_lng_translate_to_hr" = "Croatian";
|
||||
"cloud_lng_translate_to_hu" = "Hungarian";
|
||||
"cloud_lng_translate_to_hy" = "Armenian";
|
||||
"cloud_lng_translate_to_id" = "Indonesian";
|
||||
"cloud_lng_translate_to_is" = "Icelandic";
|
||||
"cloud_lng_translate_to_it" = "Italian";
|
||||
"cloud_lng_translate_to_ja" = "Japanese";
|
||||
"cloud_lng_translate_to_ka" = "Georgian";
|
||||
// "cloud_lng_translate_to_km" = "Khmer";
|
||||
"cloud_lng_translate_to_ko" = "Korean";
|
||||
"cloud_lng_translate_to_lo" = "Lao";
|
||||
"cloud_lng_translate_to_lt" = "Lithuanian";
|
||||
"cloud_lng_translate_to_lv" = "Latvian";
|
||||
"cloud_lng_translate_to_mk" = "Macedonian";
|
||||
"cloud_lng_translate_to_mn" = "Mongolian";
|
||||
"cloud_lng_translate_to_ms" = "Malay";
|
||||
"cloud_lng_translate_to_my" = "Burmese";
|
||||
"cloud_lng_translate_to_ne" = "Nepali";
|
||||
"cloud_lng_translate_to_nl" = "Dutch";
|
||||
"cloud_lng_translate_to_pl" = "Polish";
|
||||
"cloud_lng_translate_to_pt" = "Portuguese";
|
||||
"cloud_lng_translate_to_ro" = "Romanian";
|
||||
"cloud_lng_translate_to_ru" = "Russian";
|
||||
"cloud_lng_translate_to_sk" = "Slovak";
|
||||
"cloud_lng_translate_to_sl" = "Slovenian";
|
||||
"cloud_lng_translate_to_th" = "Thai";
|
||||
"cloud_lng_translate_to_tk" = "Turkmen";
|
||||
"cloud_lng_translate_to_tr" = "Turkish";
|
||||
"cloud_lng_translate_to_uk" = "Ukrainian";
|
||||
"cloud_lng_translate_to_uz" = "Uzbek";
|
||||
"cloud_lng_translate_to_vi" = "Vietnamese";
|
||||
|
||||
"cloud_lng_language_ar" = "Arabic";
|
||||
"cloud_lng_language_az" = "Azerbaijani";
|
||||
"cloud_lng_language_bg" = "Bulgarian";
|
||||
// "cloud_lng_language_bn" = "Bangla";
|
||||
"cloud_lng_language_cs" = "Czech";
|
||||
"cloud_lng_language_da" = "Danish";
|
||||
"cloud_lng_language_de" = "German";
|
||||
// "cloud_lng_language_dv" = "Divehi";
|
||||
// "cloud_lng_language_dz" = "Dzongkha";
|
||||
"cloud_lng_language_el" = "Greek";
|
||||
"cloud_lng_language_en" = "English";
|
||||
"cloud_lng_language_es" = "Spanish";
|
||||
"cloud_lng_language_et" = "Estonian";
|
||||
"cloud_lng_language_fa" = "Persian";
|
||||
"cloud_lng_language_fr" = "French";
|
||||
"cloud_lng_language_he" = "Hebrew";
|
||||
"cloud_lng_language_hr" = "Croatian";
|
||||
"cloud_lng_language_hu" = "Hungarian";
|
||||
"cloud_lng_language_hy" = "Armenian";
|
||||
"cloud_lng_language_id" = "Indonesian";
|
||||
"cloud_lng_language_is" = "Icelandic";
|
||||
"cloud_lng_language_it" = "Italian";
|
||||
"cloud_lng_language_ja" = "Japanese";
|
||||
"cloud_lng_language_ka" = "Georgian";
|
||||
// "cloud_lng_language_km" = "Khmer";
|
||||
"cloud_lng_language_ko" = "Korean";
|
||||
"cloud_lng_language_lo" = "Lao";
|
||||
"cloud_lng_language_lt" = "Lithuanian";
|
||||
"cloud_lng_language_lv" = "Latvian";
|
||||
"cloud_lng_language_mk" = "Macedonian";
|
||||
"cloud_lng_language_mn" = "Mongolian";
|
||||
"cloud_lng_language_ms" = "Malay";
|
||||
"cloud_lng_language_my" = "Burmese";
|
||||
"cloud_lng_language_ne" = "Nepali";
|
||||
"cloud_lng_language_nl" = "Dutch";
|
||||
"cloud_lng_language_pl" = "Polish";
|
||||
"cloud_lng_language_pt" = "Portuguese";
|
||||
"cloud_lng_language_ro" = "Romanian";
|
||||
"cloud_lng_language_ru" = "Russian";
|
||||
"cloud_lng_language_sk" = "Slovak";
|
||||
"cloud_lng_language_sl" = "Slovenian";
|
||||
"cloud_lng_language_th" = "Thai";
|
||||
"cloud_lng_language_tk" = "Turkmen";
|
||||
"cloud_lng_language_tr" = "Turkish";
|
||||
"cloud_lng_language_uk" = "Ukrainian";
|
||||
"cloud_lng_language_uz" = "Uzbek";
|
||||
"cloud_lng_language_vi" = "Vietnamese";
|
||||
|
||||
@@ -1877,6 +1877,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_masks_archive_pack" = "Archive Masks";
|
||||
"lng_masks_has_been_archived" = "Mask pack has been archived.";
|
||||
"lng_masks_installed" = "Mask pack has been installed.";
|
||||
"lng_emoji_nothing_found" = "No emoji found";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
@@ -2233,8 +2234,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
"lng_translate_menu_to" = "Translate To";
|
||||
"lng_translate_menu_dont" = "Don't translate {name}";
|
||||
"lng_translate_menu_dont_other" = "Don't translate {name}";
|
||||
"lng_translate_menu_hide" = "Hide";
|
||||
"lng_translate_hidden_user" = "Translation bar is now hidden for this chat.";
|
||||
"lng_translate_hidden_group" = "Translation bar is now hidden for this group.";
|
||||
@@ -3389,6 +3392,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_translate_settings_chat" = "Translate Entire Chat";
|
||||
"lng_translate_settings_choose" = "Do Not Translate";
|
||||
"lng_translate_settings_about" = "The 'Translate' button will appear when you open a context menu on a text message.";
|
||||
"lng_translate_settings_one" = "Please choose at least one language so that it can be used as the \"Translate to\" language.";
|
||||
|
||||
"lng_launch_exe_warning" = "This file has a {extension} extension.\nAre you sure you want to run it?";
|
||||
"lng_launch_svg_warning" = "Opening this file can potentially expose your IP address to its sender. Continue?";
|
||||
|
||||
Binary file not shown.
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.6.0.0" />
|
||||
Version="4.6.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,6,0,0
|
||||
PRODUCTVERSION 4,6,0,0
|
||||
FILEVERSION 4,6,3,0
|
||||
PRODUCTVERSION 4,6,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "4.6.0.0"
|
||||
VALUE "FileVersion", "4.6.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.6.0.0"
|
||||
VALUE "ProductVersion", "4.6.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,6,0,0
|
||||
PRODUCTVERSION 4,6,0,0
|
||||
FILEVERSION 4,6,3,0
|
||||
PRODUCTVERSION 4,6,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "4.6.0.0"
|
||||
VALUE "FileVersion", "4.6.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.6.0.0"
|
||||
VALUE "ProductVersion", "4.6.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -26,7 +26,9 @@ void openLog() {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDateFormatter *fmt = [[NSDateFormatter alloc] initWithDateFormat:@"DebugLogs/%Y%m%d_%H%M%S_upd.txt" allowNaturalLanguage:NO];
|
||||
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
|
||||
[fmt setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
[fmt setDateFormat:@"'DebugLogs/'yyyyMMdd'_'HHmmss'_update.txt'"];
|
||||
NSString *logPath = [workDir stringByAppendingString:[fmt stringFromDate:[NSDate date]]];
|
||||
[[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil];
|
||||
_logFile = [NSFileHandle fileHandleForWritingAtPath:logPath];
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -125,7 +126,11 @@ constexpr auto kSharedMediaLimit = 100;
|
||||
colors,
|
||||
ranges::back_inserter(mtpColors),
|
||||
[&](const QColor &c) { return MTP_int(serializeColor(c)); });
|
||||
if (sticker->set.id && sticker->set.accessHash) {
|
||||
if (sticker->setType == Data::StickersType::Emoji) {
|
||||
return MTP_videoSizeEmojiMarkup(
|
||||
MTP_long(document->id),
|
||||
MTP_vector(mtpColors));
|
||||
} else if (sticker->set.id && sticker->set.accessHash) {
|
||||
return MTP_videoSizeStickerMarkup(
|
||||
MTP_inputStickerSetID(
|
||||
MTP_long(sticker->set.id),
|
||||
|
||||
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Api {
|
||||
|
||||
constexpr auto kDiscountDivider = 5.;
|
||||
constexpr auto kDiscountDivider = 1.;
|
||||
|
||||
Data::SubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
@@ -20,7 +20,7 @@ Data::SubscriptionOption CreateSubscriptionOption(
|
||||
const QString ¤cy,
|
||||
const QString &botUrl) {
|
||||
const auto discount = [&] {
|
||||
const auto percent = monthlyAmount * months / float64(amount) - 1.;
|
||||
const auto percent = 1. - float64(amount) / (monthlyAmount * months);
|
||||
return std::round(percent * 100. / kDiscountDivider)
|
||||
* kDiscountDivider;
|
||||
}();
|
||||
|
||||
@@ -1727,6 +1727,29 @@ void ApiWrap::leaveChannel(not_null<ChannelData*> channel) {
|
||||
}
|
||||
|
||||
void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
|
||||
const auto bad = peer.match([](const MTPDinputNotifyUsers &) {
|
||||
return false;
|
||||
}, [](const MTPDinputNotifyChats &) {
|
||||
return false;
|
||||
}, [](const MTPDinputNotifyBroadcasts &) {
|
||||
return false;
|
||||
}, [&](const MTPDinputNotifyPeer &data) {
|
||||
if (data.vpeer().type() == mtpc_inputPeerEmpty) {
|
||||
LOG(("Api Error: Requesting settings for empty peer."));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [&](const MTPDinputNotifyForumTopic &data) {
|
||||
if (data.vpeer().type() == mtpc_inputPeerEmpty) {
|
||||
LOG(("Api Error: Requesting settings for empty peer topic."));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (bad) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto peerFromInput = [&](const MTPInputPeer &inputPeer) {
|
||||
return inputPeer.match([&](const MTPDinputPeerSelf &) {
|
||||
return _session->userPeerId();
|
||||
|
||||
@@ -140,6 +140,19 @@ void MustBePublicFailed(
|
||||
MustBePublicDestroy(channel);
|
||||
}
|
||||
|
||||
[[nodiscard]] Fn<void(not_null<PeerData*>)> WrapPeerDoneFromChannelDone(
|
||||
Fn<void(not_null<ChannelData*>)> channelDone) {
|
||||
if (!channelDone) {
|
||||
return nullptr;
|
||||
}
|
||||
return [=](not_null<PeerData*> peer) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
const auto onstack = channelDone;
|
||||
onstack(channel);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextWithEntities PeerFloodErrorText(
|
||||
@@ -482,13 +495,7 @@ GroupInfoBox::GroupInfoBox(
|
||||
, _api(&_navigation->session().mtp())
|
||||
, _type(type)
|
||||
, _initialTitle(title)
|
||||
, _done([channelDone = std::move(channelDone)](not_null<PeerData*> peer) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (const auto onstack = channelDone) {
|
||||
onstack(channel);
|
||||
}
|
||||
}
|
||||
}) {
|
||||
, _done(WrapPeerDoneFromChannelDone(std::move(channelDone))) {
|
||||
}
|
||||
|
||||
GroupInfoBox::GroupInfoBox(
|
||||
|
||||
@@ -572,9 +572,6 @@ changePhoneError: FlatLabel(changePhoneLabel) {
|
||||
textFg: boxTextFgError;
|
||||
}
|
||||
|
||||
blockedUsersListSubtitleAddPadding: margins(0px, 1px, 0px, -14px);
|
||||
blockedUsersListIconPadding: margins(0px, 34px, 0px, 5px);
|
||||
|
||||
adminLogFilterUserpicLeft: 15px;
|
||||
adminLogFilterLittleSkip: 16px;
|
||||
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
|
||||
@@ -1232,7 +1232,10 @@ void LanguageBox::setupTop(not_null<Ui::VerticalLayout*> container) {
|
||||
translateSkipWrap->toggle(
|
||||
translateEnabled->toggled(),
|
||||
anim::type::normal);
|
||||
translateSkipWrap->toggleOn(translateEnabled->toggledValue());
|
||||
translateSkipWrap->toggleOn(rpl::combine(
|
||||
translateEnabled->toggledValue(),
|
||||
translateChat->toggledValue(),
|
||||
rpl::mappers::_1 || rpl::mappers::_2));
|
||||
const auto translateSkip = Settings::AddButtonWithLabel(
|
||||
translateSkipWrap->entity(),
|
||||
tr::lng_translate_settings_choose(),
|
||||
|
||||
@@ -295,10 +295,11 @@ void AddParticipantsBoxController::Start(
|
||||
not_null<ChatData*> chat) {
|
||||
auto controller = std::make_unique<AddParticipantsBoxController>(chat);
|
||||
const auto weak = controller.get();
|
||||
const auto parent = navigation->parentController();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_participant_invite(), [=] {
|
||||
weak->inviteSelectedUsers(box, [=] {
|
||||
navigation->parentController()->showPeerHistory(
|
||||
parent->showPeerHistory(
|
||||
chat,
|
||||
Window::SectionShow::Way::ClearStack,
|
||||
ShowAtTheEndMsgId);
|
||||
@@ -320,11 +321,12 @@ void AddParticipantsBoxController::Start(
|
||||
channel,
|
||||
std::move(alreadyIn));
|
||||
const auto weak = controller.get();
|
||||
const auto parent = navigation->parentController();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_participant_invite(), [=] {
|
||||
weak->inviteSelectedUsers(box, [=] {
|
||||
if (channel->isMegagroup()) {
|
||||
navigation->parentController()->showPeerHistory(
|
||||
parent->showPeerHistory(
|
||||
channel,
|
||||
Window::SectionShow::Way::ClearStack,
|
||||
ShowAtTheEndMsgId);
|
||||
@@ -337,13 +339,16 @@ void AddParticipantsBoxController::Start(
|
||||
justCreated ? tr::lng_create_group_skip() : tr::lng_cancel(),
|
||||
[=] { box->closeBox(); });
|
||||
if (justCreated) {
|
||||
const auto weak = base::make_weak(parent);
|
||||
box->boxClosing() | rpl::start_with_next([=] {
|
||||
auto params = Window::SectionShow();
|
||||
params.activation = anim::activation::background;
|
||||
navigation->parentController()->showPeerHistory(
|
||||
channel,
|
||||
params,
|
||||
ShowAtTheEndMsgId);
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showPeerHistory(
|
||||
channel,
|
||||
params,
|
||||
ShowAtTheEndMsgId);
|
||||
}
|
||||
}, box->lifetime());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@ struct NestedRestrictionLabels {
|
||||
second.erase(
|
||||
ranges::remove(
|
||||
second,
|
||||
Flag::CreateTopics,
|
||||
Flag::CreateTopics | Flag(),
|
||||
&RestrictionLabel::flags),
|
||||
end(second));
|
||||
}
|
||||
@@ -1002,7 +1002,7 @@ std::vector<AdminRightLabel> AdminRightLabels(
|
||||
result.erase(
|
||||
ranges::remove(
|
||||
result,
|
||||
Flag::ManageTopics,
|
||||
Flag::ManageTopics | Flag(),
|
||||
&AdminRightLabel::flags),
|
||||
end(result));
|
||||
}
|
||||
|
||||
@@ -854,7 +854,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
|
||||
if (((*i)->index() * _rowHeight) >= yTo) {
|
||||
break;
|
||||
}
|
||||
(*i)->entry()->loadUserpic();
|
||||
(*i)->entry()->chatListPreloadData();
|
||||
}
|
||||
}
|
||||
} else if (!_filtered.empty()) {
|
||||
@@ -865,7 +865,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
|
||||
if (to > _filtered.size()) to = _filtered.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_filtered[from]->entry()->loadUserpic();
|
||||
_filtered[from]->entry()->chatListPreloadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/translate_box.h"
|
||||
|
||||
#include "api/api_text_entities.h" // Api::EntitiesToMTP / EntitiesFromMTP.
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -20,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/choose_language_box.h"
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -37,6 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSkipAtLeastOneDuration = 3 * crl::time(1000);
|
||||
|
||||
class ShowButton final : public RpWidget {
|
||||
public:
|
||||
ShowButton(not_null<Ui::RpWidget*> parent);
|
||||
@@ -96,15 +103,15 @@ void TranslateBox(
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
auto id = Core::App().settings().translateToValue();
|
||||
const auto api = box->lifetime().make_state<MTP::Sender>(
|
||||
&peer->session().mtp());
|
||||
struct State {
|
||||
State(not_null<Main::Session*> session) : api(&session->mtp()) {
|
||||
}
|
||||
|
||||
text.entities = ranges::views::all(
|
||||
text.entities
|
||||
) | ranges::views::filter([](const EntityInText &e) {
|
||||
return e.type() != EntityType::Spoiler;
|
||||
}) | ranges::to<EntitiesInText>();
|
||||
MTP::Sender api;
|
||||
rpl::variable<LanguageId> to;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>(&peer->session());
|
||||
state->to = ChooseTranslateTo(peer->owner().history(peer));
|
||||
|
||||
if (!IsServerMsgId(msgId)) {
|
||||
msgId = 0;
|
||||
@@ -174,10 +181,10 @@ void TranslateBox(
|
||||
const auto padding = st::settingsSubsectionTitlePadding;
|
||||
const auto subtitle = Settings::AddSubsectionTitle(
|
||||
container,
|
||||
rpl::duplicate(id) | rpl::map(LanguageName));
|
||||
state->to.value() | rpl::map(LanguageName));
|
||||
|
||||
// Workaround.
|
||||
rpl::duplicate(id) | rpl::start_with_next([=] {
|
||||
state->to.value() | rpl::start_with_next([=] {
|
||||
subtitle->resizeToWidth(container->width()
|
||||
- padding.left()
|
||||
- padding.right());
|
||||
@@ -197,12 +204,18 @@ void TranslateBox(
|
||||
box,
|
||||
st::aboutLabel,
|
||||
std::min(original->entity()->height() / lineHeight, kMaxLines),
|
||||
rpl::duplicate(id) | rpl::map([=](LanguageId id) {
|
||||
state->to.value() | rpl::map([=](LanguageId id) {
|
||||
return id.locale().textDirection() == Qt::RightToLeft;
|
||||
}))));
|
||||
|
||||
const auto showText = [=](const QString &text) {
|
||||
translated->entity()->setText(text);
|
||||
const auto showText = [=](TextWithEntities text) {
|
||||
const auto label = translated->entity();
|
||||
label->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = &peer->session(),
|
||||
.customEmojiRepaint = [=] { label->update(); },
|
||||
});
|
||||
translated->show(anim::type::instant);
|
||||
loading->hide(anim::type::instant);
|
||||
};
|
||||
@@ -210,7 +223,7 @@ void TranslateBox(
|
||||
const auto send = [=](LanguageId to) {
|
||||
loading->show(anim::type::instant);
|
||||
translated->hide(anim::type::instant);
|
||||
api->request(MTPmessages_TranslateText(
|
||||
state->api.request(MTPmessages_TranslateText(
|
||||
MTP_flags(flags),
|
||||
msgId ? peer->input : MTP_inputPeerEmpty(),
|
||||
(msgId
|
||||
@@ -220,25 +233,39 @@ void TranslateBox(
|
||||
? MTPVector<MTPTextWithEntities>()
|
||||
: MTP_vector<MTPTextWithEntities>(1, MTP_textWithEntities(
|
||||
MTP_string(text.text),
|
||||
MTP_vector<MTPMessageEntity>()))),
|
||||
MTP_string(to.locale().name().mid(0, 2))
|
||||
Api::EntitiesToMTP(
|
||||
&peer->session(),
|
||||
text.entities,
|
||||
Api::ConvertOption::SkipLocal)))),
|
||||
MTP_string(to.twoLetterCode())
|
||||
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &list = data.vresult().v;
|
||||
showText(list.isEmpty()
|
||||
? tr::lng_translate_box_error(tr::now)
|
||||
: qs(list.front().data().vtext()));
|
||||
if (list.isEmpty()) {
|
||||
showText(
|
||||
Ui::Text::Italic(tr::lng_translate_box_error(tr::now)));
|
||||
} else {
|
||||
showText(TextWithEntities{
|
||||
.text = qs(list.front().data().vtext()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
&peer->session(),
|
||||
list.front().data().ventities().v),
|
||||
});
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
showText(tr::lng_translate_box_error(tr::now));
|
||||
showText(
|
||||
Ui::Text::Italic(tr::lng_translate_box_error(tr::now)));
|
||||
}).send();
|
||||
};
|
||||
std::move(id) | rpl::start_with_next(send, box->lifetime());
|
||||
state->to.value() | rpl::start_with_next(send, box->lifetime());
|
||||
|
||||
box->addLeftButton(tr::lng_settings_language(), [=] {
|
||||
if (loading->toggled()) {
|
||||
return;
|
||||
}
|
||||
Ui::BoxShow(box).showBox(ChooseTranslateToBox());
|
||||
Ui::BoxShow(box).showBox(ChooseTranslateToBox(
|
||||
state->to.current(),
|
||||
crl::guard(box, [=](LanguageId id) { state->to = id; })));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -275,25 +302,88 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
|
||||
|
||||
object_ptr<BoxContent> EditSkipTranslationLanguages() {
|
||||
auto title = tr::lng_translate_settings_choose();
|
||||
return Box(ChooseLanguageBox, std::move(title), [=](
|
||||
const auto selected = std::make_shared<std::vector<LanguageId>>(
|
||||
Core::App().settings().skipTranslationLanguages());
|
||||
const auto weak = std::make_shared<QPointer<BoxContent>>();
|
||||
const auto check = [=](LanguageId id) {
|
||||
const auto already = ranges::contains(*selected, id);
|
||||
if (already) {
|
||||
selected->erase(ranges::remove(*selected, id), selected->end());
|
||||
} else {
|
||||
selected->push_back(id);
|
||||
}
|
||||
if (already && selected->empty()) {
|
||||
if (const auto strong = weak->data()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = BoxShow(strong).toastParent(),
|
||||
.text = { tr::lng_translate_settings_one(tr::now) },
|
||||
.duration = kSkipAtLeastOneDuration,
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto result = Box(ChooseLanguageBox, std::move(title), [=](
|
||||
std::vector<LanguageId> &&list) {
|
||||
Core::App().settings().setSkipTranslationLanguages(
|
||||
std::move(list));
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, Core::App().settings().skipTranslationLanguages(), true);
|
||||
}, *selected, true, check);
|
||||
*weak = result.data();
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<BoxContent> ChooseTranslateToBox() {
|
||||
const auto selected = std::vector<LanguageId>{
|
||||
Core::App().settings().translateTo(),
|
||||
object_ptr<BoxContent> ChooseTranslateToBox(
|
||||
LanguageId bringUp,
|
||||
Fn<void(LanguageId)> callback) {
|
||||
auto &settings = Core::App().settings();
|
||||
auto selected = std::vector<LanguageId>{
|
||||
settings.translateTo(),
|
||||
};
|
||||
for (const auto &id : settings.skipTranslationLanguages()) {
|
||||
if (id != selected.front()) {
|
||||
selected.push_back(id);
|
||||
}
|
||||
}
|
||||
if (bringUp && ranges::contains(selected, bringUp)) {
|
||||
selected.push_back(bringUp);
|
||||
}
|
||||
return Box(ChooseLanguageBox, tr::lng_languages(), [=](
|
||||
const std::vector<LanguageId> &ids) {
|
||||
Expects(!ids.empty());
|
||||
|
||||
Core::App().settings().setTranslateTo(ids.front());
|
||||
const auto id = ids.front();
|
||||
Core::App().settings().setTranslateTo(id);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, selected, false);
|
||||
callback(id);
|
||||
}, selected, false, nullptr);
|
||||
}
|
||||
|
||||
LanguageId ChooseTranslateTo(not_null<History*> history) {
|
||||
return ChooseTranslateTo(history->translateOfferedFrom());
|
||||
}
|
||||
|
||||
LanguageId ChooseTranslateTo(LanguageId offeredFrom) {
|
||||
auto &settings = Core::App().settings();
|
||||
return ChooseTranslateTo(
|
||||
offeredFrom,
|
||||
settings.translateTo(),
|
||||
settings.skipTranslationLanguages());
|
||||
}
|
||||
|
||||
LanguageId ChooseTranslateTo(
|
||||
not_null<History*> history,
|
||||
LanguageId savedTo,
|
||||
const std::vector<LanguageId> &skip) {
|
||||
return ChooseTranslateTo(history->translateOfferedFrom(), savedTo, skip);
|
||||
}
|
||||
|
||||
LanguageId ChooseTranslateTo(
|
||||
LanguageId offeredFrom,
|
||||
LanguageId savedTo,
|
||||
const std::vector<LanguageId> &skip) {
|
||||
return (offeredFrom != savedTo) ? savedTo : skip.front();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
class History;
|
||||
class PeerData;
|
||||
struct LanguageId;
|
||||
|
||||
@@ -27,6 +28,19 @@ void TranslateBox(
|
||||
[[nodiscard]] bool SkipTranslate(TextWithEntities textWithEntities);
|
||||
|
||||
[[nodiscard]] object_ptr<BoxContent> EditSkipTranslationLanguages();
|
||||
[[nodiscard]] object_ptr<BoxContent> ChooseTranslateToBox();
|
||||
[[nodiscard]] object_ptr<BoxContent> ChooseTranslateToBox(
|
||||
LanguageId bringUp,
|
||||
Fn<void(LanguageId)> callback);
|
||||
|
||||
[[nodiscard]] LanguageId ChooseTranslateTo(not_null<History*> history);
|
||||
[[nodiscard]] LanguageId ChooseTranslateTo(LanguageId offeredFrom);
|
||||
[[nodiscard]] LanguageId ChooseTranslateTo(
|
||||
not_null<History*> history,
|
||||
LanguageId savedTo,
|
||||
const std::vector<LanguageId> &skip);
|
||||
[[nodiscard]] LanguageId ChooseTranslateTo(
|
||||
LanguageId offeredFrom,
|
||||
LanguageId savedTo,
|
||||
const std::vector<LanguageId> &skip);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -335,6 +335,7 @@ stickersToast: Toast(defaultToast) {
|
||||
}
|
||||
|
||||
stickersEmpty: icon {{ "stickers_empty", windowSubTextFg }};
|
||||
emojiEmpty: icon {{ "emoji_empty", windowSubTextFg }};
|
||||
|
||||
inlineBotsScroll: ScrollArea(defaultSolidScroll) {
|
||||
deltat: stickerPanPadding;
|
||||
@@ -396,7 +397,7 @@ reactStripBubble: icon{
|
||||
{ "chat/reactions_bubble", windowBg },
|
||||
};
|
||||
reactStripBubbleRight: 20px;
|
||||
reactPanelEmojiPan: EmojiPan(statusEmojiPan) {
|
||||
userpicBuilderEmojiPan: EmojiPan(statusEmojiPan) {
|
||||
margin: margins(reactStripSkip, 0px, reactStripSkip, 0px);
|
||||
padding: margins(reactStripSkip, 0px, reactStripSkip, reactStripSkip);
|
||||
desiredSize: reactStripSize;
|
||||
@@ -405,6 +406,8 @@ reactPanelEmojiPan: EmojiPan(statusEmojiPan) {
|
||||
search: TabbedSearch(defaultTabbedSearch) {
|
||||
defaultFieldWidth: 88px;
|
||||
}
|
||||
}
|
||||
reactPanelEmojiPan: EmojiPan(userpicBuilderEmojiPan) {
|
||||
searchMargin: margins(1px, 10px, 2px, 6px);
|
||||
}
|
||||
emojiScroll: ScrollArea(defaultSolidScroll) {
|
||||
|
||||
@@ -485,14 +485,21 @@ void EmojiListWidget::applyNextSearchQuery() {
|
||||
if (!_searchMode && !searching) {
|
||||
return;
|
||||
}
|
||||
_searchMode = searching;
|
||||
const auto modeChanged = (_searchMode != searching);
|
||||
clearSelection();
|
||||
if (modeChanged) {
|
||||
_searchMode = searching;
|
||||
}
|
||||
if (!searching) {
|
||||
_searchResults.clear();
|
||||
_searchCustomIds.clear();
|
||||
}
|
||||
clearSelection();
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
if (modeChanged) {
|
||||
visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
|
||||
}
|
||||
updateSelected();
|
||||
};
|
||||
if (_searchQuery.empty()) {
|
||||
finish(false);
|
||||
@@ -615,7 +622,7 @@ void EmojiListWidget::prepareExpanding() {
|
||||
}
|
||||
|
||||
void EmojiListWidget::paintExpanding(
|
||||
QPainter &p,
|
||||
Painter &p,
|
||||
QRect clip,
|
||||
int finalBottom,
|
||||
float64 progress,
|
||||
@@ -968,7 +975,7 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
|
||||
}
|
||||
|
||||
void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
auto p = Painter(this);
|
||||
|
||||
const auto clip = e ? e->rect() : rect();
|
||||
|
||||
@@ -1012,7 +1019,7 @@ void EmojiListWidget::validateEmojiPaintContext(
|
||||
}
|
||||
|
||||
void EmojiListWidget::paint(
|
||||
QPainter &p,
|
||||
Painter &p,
|
||||
ExpandingContext context,
|
||||
QRect clip) {
|
||||
validateEmojiPaintContext(context);
|
||||
@@ -1036,6 +1043,9 @@ void EmojiListWidget::paint(
|
||||
auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed)
|
||||
? &_pressed
|
||||
: &_selected);
|
||||
if (_searchResults.empty() && _searchMode) {
|
||||
paintEmptySearchResults(p);
|
||||
}
|
||||
enumerateSections([&](const SectionInfo &info) {
|
||||
if (clip.top() >= info.rowsBottom) {
|
||||
return true;
|
||||
@@ -2001,6 +2011,13 @@ int EmojiListWidget::paintButtonGetWidth(
|
||||
return emojiRight() - rect.x();
|
||||
}
|
||||
|
||||
void EmojiListWidget::paintEmptySearchResults(Painter &p) {
|
||||
Inner::paintEmptySearchResults(
|
||||
p,
|
||||
st::emojiEmpty,
|
||||
tr::lng_emoji_nothing_found(tr::now));
|
||||
}
|
||||
|
||||
bool EmojiListWidget::eventHook(QEvent *e) {
|
||||
if (e->type() == QEvent::ParentChange) {
|
||||
if (_picker->parentWidget() != parentWidget()) {
|
||||
@@ -2190,10 +2207,13 @@ void EmojiListWidget::showSet(uint64 setId) {
|
||||
}
|
||||
|
||||
uint64 EmojiListWidget::sectionSetId(int section) const {
|
||||
Expects(section < _staticCount
|
||||
Expects(_searchMode
|
||||
|| section < _staticCount
|
||||
|| (section - _staticCount) < _custom.size());
|
||||
|
||||
return (section < _staticCount)
|
||||
return _searchMode
|
||||
? SearchEmojiSectionSetId()
|
||||
: (section < _staticCount)
|
||||
? EmojiSectionSetId(static_cast<Section>(section))
|
||||
: _custom[section - _staticCount].id;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
|
||||
void prepareExpanding();
|
||||
void paintExpanding(
|
||||
QPainter &p,
|
||||
Painter &p,
|
||||
QRect clip,
|
||||
int finalBottom,
|
||||
float64 progress,
|
||||
@@ -271,7 +271,7 @@ private:
|
||||
Api::SendOptions options = Api::SendOptions());
|
||||
void selectEmoji(EmojiChosen data);
|
||||
void selectCustom(FileChosen data);
|
||||
void paint(QPainter &p, ExpandingContext context, QRect clip);
|
||||
void paint(Painter &p, ExpandingContext context, QRect clip);
|
||||
void drawCollapsedBadge(QPainter &p, QPoint position, int count);
|
||||
void drawRecent(
|
||||
QPainter &p,
|
||||
@@ -313,6 +313,7 @@ private:
|
||||
const SectionInfo &info,
|
||||
bool selected,
|
||||
QRect clip) const;
|
||||
void paintEmptySearchResults(Painter &p);
|
||||
|
||||
void displaySet(uint64 setId);
|
||||
void removeSet(uint64 setId);
|
||||
|
||||
@@ -161,6 +161,7 @@ object_ptr<TabbedSelector::InnerFooter> GifsListWidget::createFooter() {
|
||||
.st = &st(),
|
||||
});
|
||||
_footer = result;
|
||||
_chosenSetId = Data::Stickers::RecentSetId;
|
||||
|
||||
GifSectionsValue(
|
||||
&session()
|
||||
@@ -171,6 +172,9 @@ object_ptr<TabbedSelector::InnerFooter> GifsListWidget::createFooter() {
|
||||
|
||||
_footer->setChosen(
|
||||
) | rpl::start_with_next([=](uint64 setId) {
|
||||
if (_search) {
|
||||
_search->cancel();
|
||||
}
|
||||
_chosenSetId = setId;
|
||||
refreshIcons();
|
||||
const auto i = ranges::find(_sections, setId, [](GifSection value) {
|
||||
@@ -791,13 +795,16 @@ bool GifsListWidget::refreshInlineRows(int32 *added) {
|
||||
void GifsListWidget::setupSearch() {
|
||||
const auto session = &_controller->session();
|
||||
_search = MakeSearch(this, st(), [=](std::vector<QString> &&query) {
|
||||
_chosenSetId = Data::Stickers::RecentSetId;
|
||||
refreshIcons();
|
||||
searchForGifs(ranges::accumulate(query, QString(), [](
|
||||
const auto accumulated = ranges::accumulate(query, QString(), [](
|
||||
QString a,
|
||||
QString b) {
|
||||
return a.isEmpty() ? b : (a + ' ' + b);
|
||||
}));
|
||||
});
|
||||
_chosenSetId = accumulated.isEmpty()
|
||||
? Data::Stickers::RecentSetId
|
||||
: SearchEmojiSectionSetId();
|
||||
refreshIcons();
|
||||
searchForGifs(accumulated);
|
||||
}, session);
|
||||
}
|
||||
|
||||
|
||||
@@ -417,6 +417,16 @@ void StickersListFooter::enumerateSubicons(
|
||||
}
|
||||
|
||||
auto StickersListFooter::iconInfo(int index) const -> IconInfo {
|
||||
if (index < 0) {
|
||||
const auto iconsX = int(base::SafeRound(_iconState.x.current()));
|
||||
return {
|
||||
.index = -1,
|
||||
.left = -_singleWidth - _iconsLeft,
|
||||
.adjustedLeft = -_singleWidth - _iconsLeft - iconsX,
|
||||
.width = _singleWidth,
|
||||
.visible = false,
|
||||
};
|
||||
}
|
||||
auto result = IconInfo();
|
||||
enumerateIcons([&](const IconInfo &info) {
|
||||
if (info.index == index) {
|
||||
@@ -473,7 +483,8 @@ void StickersListFooter::validateSelectedIcon(
|
||||
&& setId == Data::Stickers::RecentSetId)) {
|
||||
newSelected = i;
|
||||
break;
|
||||
} else if (_icons[i].setId == Data::Stickers::FavedSetId) {
|
||||
} else if (_icons[i].setId == Data::Stickers::FavedSetId
|
||||
&& setId != SearchEmojiSectionSetId()) {
|
||||
favedIconIndex = i;
|
||||
} else if (isEmojiSection && _icons[i].setId == allEmojiSetId) {
|
||||
newSelected = i;
|
||||
@@ -483,7 +494,9 @@ void StickersListFooter::validateSelectedIcon(
|
||||
setSelectedIcon(
|
||||
(newSelected >= 0
|
||||
? newSelected
|
||||
: (favedIconIndex >= 0) ? favedIconIndex : 0),
|
||||
: (favedIconIndex >= 0)
|
||||
? favedIconIndex
|
||||
: -1),
|
||||
animations);
|
||||
setSelectedSubicon(
|
||||
(newSubSelected >= 0 ? newSubSelected : 0),
|
||||
@@ -522,6 +535,9 @@ void StickersListFooter::setSelectedIcon(
|
||||
if (_iconState.selected == newSelected) {
|
||||
return;
|
||||
}
|
||||
if ((_iconState.selected < 0) != (newSelected < 0)) {
|
||||
animations = ValidateIconAnimations::None;
|
||||
}
|
||||
_iconState.selected = newSelected;
|
||||
updateEmojiSectionWidth();
|
||||
const auto info = iconInfo(_iconState.selected);
|
||||
@@ -1113,7 +1129,7 @@ void StickersListFooter::refreshIconsGeometry(
|
||||
(st().footer - st().iconArea) / 2);
|
||||
refreshScrollableDimensions();
|
||||
refreshSubiconsGeometry();
|
||||
_iconState.selected = _subiconState.selected = -1;
|
||||
_iconState.selected = _subiconState.selected = -2;
|
||||
validateSelectedIcon(activeSetId, animations);
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -165,17 +165,18 @@ StickersListWidget::StickersListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level,
|
||||
bool masks)
|
||||
Mode mode)
|
||||
: Inner(
|
||||
parent,
|
||||
st::defaultEmojiPan,
|
||||
&controller->session(),
|
||||
Window::PausedIn(controller, level))
|
||||
, _mode(mode)
|
||||
, _controller(controller)
|
||||
, _api(&session().mtp())
|
||||
, _localSetsManager(std::make_unique<LocalStickersManager>(&session()))
|
||||
, _section(Section::Stickers)
|
||||
, _isMasks(masks)
|
||||
, _isMasks(mode == Mode::Masks)
|
||||
, _updateItemsTimer([=] { updateItems(); })
|
||||
, _updateSetsTimer([=] { updateSets(); })
|
||||
, _trendingAddBgOver(
|
||||
@@ -1079,20 +1080,10 @@ void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) {
|
||||
}
|
||||
|
||||
void StickersListWidget::paintEmptySearchResults(Painter &p) {
|
||||
const auto iconLeft = (width() - st::stickersEmpty.width()) / 2;
|
||||
const auto iconTop = (height() / 3) - (st::stickersEmpty.height() / 2);
|
||||
st::stickersEmpty.paint(p, iconLeft, iconTop, width());
|
||||
|
||||
const auto text = tr::lng_stickers_nothing_found(tr::now);
|
||||
const auto textWidth = st::normalFont->width(text);
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::windowSubTextFg);
|
||||
p.drawTextLeft(
|
||||
(width() - textWidth) / 2,
|
||||
iconTop + st::stickersEmpty.height() - st::normalFont->height,
|
||||
width(),
|
||||
text,
|
||||
textWidth);
|
||||
Inner::paintEmptySearchResults(
|
||||
p,
|
||||
st::stickersEmpty,
|
||||
tr::lng_stickers_nothing_found(tr::now));
|
||||
}
|
||||
|
||||
int StickersListWidget::megagroupSetInfoLeft() const {
|
||||
@@ -2462,7 +2453,18 @@ auto StickersListWidget::getLottieRenderer()
|
||||
}
|
||||
|
||||
void StickersListWidget::showStickerSet(uint64 setId) {
|
||||
if (_showingSetById) {
|
||||
return;
|
||||
}
|
||||
_showingSetById = true;
|
||||
const auto guard = gsl::finally([&] { _showingSetById = false; });
|
||||
|
||||
clearSelection();
|
||||
if (_search
|
||||
&& (!_searchQuery.isEmpty() || !_searchNextQuery.isEmpty())) {
|
||||
_search->cancel();
|
||||
cancelSetsSearch();
|
||||
}
|
||||
|
||||
if (setId == Data::Stickers::FeaturedSetId) {
|
||||
if (_section != Section::Featured) {
|
||||
@@ -2565,7 +2567,7 @@ void StickersListWidget::setupSearch() {
|
||||
return a.isEmpty() ? b : (a + ' ' + b);
|
||||
});
|
||||
searchForSets(std::move(text), SearchEmoji(query, set));
|
||||
}, session);
|
||||
}, session, false, (_mode == Mode::UserpicBuilder));
|
||||
}
|
||||
|
||||
void StickersListWidget::displaySet(uint64 setId) {
|
||||
|
||||
@@ -59,13 +59,21 @@ enum class ValidateIconAnimations;
|
||||
class StickersListFooter;
|
||||
class LocalStickersManager;
|
||||
|
||||
enum class StickersListMode {
|
||||
Full,
|
||||
Masks,
|
||||
UserpicBuilder,
|
||||
};
|
||||
|
||||
class StickersListWidget final : public TabbedSelector::Inner {
|
||||
public:
|
||||
using Mode = StickersListMode;
|
||||
|
||||
StickersListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level,
|
||||
bool masks = false);
|
||||
Mode mode = Mode::Full);
|
||||
|
||||
rpl::producer<FileChosen> chosen() const;
|
||||
rpl::producer<> scrollUpdated() const;
|
||||
@@ -331,6 +339,8 @@ private:
|
||||
int index,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
const Mode _mode;
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
std::unique_ptr<Ui::TabbedSearch> _search;
|
||||
MTP::Sender _api;
|
||||
@@ -345,6 +355,7 @@ private:
|
||||
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
|
||||
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
||||
|
||||
bool _showingSetById = false;
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastFullUpdatedAt = 0;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/painter.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
@@ -500,7 +501,7 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
||||
this,
|
||||
_controller,
|
||||
_level,
|
||||
true);
|
||||
StickersListWidget::Mode::Masks);
|
||||
}
|
||||
Unexpected("Type in TabbedSelector::createTab.");
|
||||
};
|
||||
@@ -1283,6 +1284,30 @@ void TabbedSelector::Inner::checkHideWithBox(QPointer<Ui::BoxContent> box) {
|
||||
});
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::paintEmptySearchResults(
|
||||
Painter &p,
|
||||
const style::icon &icon,
|
||||
const QString &text) const {
|
||||
const auto iconLeft = (width() - icon.width()) / 2;
|
||||
const auto iconTop = std::max(
|
||||
(height() / 3) - (icon.height() / 2),
|
||||
st::normalFont->height);
|
||||
icon.paint(p, iconLeft, iconTop, width());
|
||||
|
||||
const auto textWidth = st::normalFont->width(text);
|
||||
const auto textTop = std::min(
|
||||
iconTop + icon.height() - st::normalFont->height,
|
||||
height() - 2 * st::normalFont->height);
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::windowSubTextFg);
|
||||
p.drawTextLeft(
|
||||
(width() - textWidth) / 2,
|
||||
textTop,
|
||||
width(),
|
||||
text,
|
||||
textWidth);
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -376,6 +376,11 @@ protected:
|
||||
|
||||
void checkHideWithBox(QPointer<Ui::BoxContent> box);
|
||||
|
||||
void paintEmptySearchResults(
|
||||
Painter &p,
|
||||
const style::icon &icon,
|
||||
const QString &text) const;
|
||||
|
||||
private:
|
||||
const style::EmojiPan &_st;
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
@@ -347,9 +347,7 @@ void Application::run() {
|
||||
|
||||
_lastActivePrimaryWindow->widget()->show();
|
||||
|
||||
const auto current = _lastActivePrimaryWindow->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
|
||||
startMediaView();
|
||||
|
||||
DEBUG_LOG(("Application Info: showing."));
|
||||
_lastActivePrimaryWindow->finishFirstShow();
|
||||
@@ -492,7 +490,38 @@ void Application::processCreatedWindow(
|
||||
) | rpl::start_to_stream(_openInMediaViewRequests, window->lifetime());
|
||||
}
|
||||
|
||||
void Application::startMediaView() {
|
||||
#ifdef Q_OS_MAC
|
||||
// On macOS we create some windows async, otherwise they're
|
||||
// added to the Dock Menu as a visible window and are removed
|
||||
// only after first show and then hide.
|
||||
InvokeQueued(this, [=] {
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
});
|
||||
#else // Q_OS_MAC
|
||||
// On Windows we needed such hack for the main window, otherwise
|
||||
// somewhere inside the media viewer creating code its geometry
|
||||
// was broken / lost to some invalid values.
|
||||
const auto current = _lastActivePrimaryWindow->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
|
||||
#endif // Q_OS_MAC
|
||||
}
|
||||
|
||||
void Application::startTray() {
|
||||
#ifdef Q_OS_MAC
|
||||
// On macOS we create some windows async, otherwise they're
|
||||
// added to the Dock Menu as a visible window and are removed
|
||||
// only after first show and then hide, tray icon being "Item-0".
|
||||
InvokeQueued(this, [=] {
|
||||
createTray();
|
||||
});
|
||||
#else // Q_OS_MAC
|
||||
createTray();
|
||||
#endif // Q_OS_MAC
|
||||
}
|
||||
|
||||
void Application::createTray() {
|
||||
using WindowRaw = not_null<Window::Controller*>;
|
||||
_tray->create();
|
||||
_tray->aboutToShowRequests(
|
||||
@@ -1089,10 +1118,11 @@ void Application::updateWindowTitles() {
|
||||
}
|
||||
|
||||
void Application::lockByPasscode() {
|
||||
_passcodeLock = true;
|
||||
enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
_passcodeLock = true;
|
||||
w->setupPasscodeLock();
|
||||
});
|
||||
hideMediaView();
|
||||
}
|
||||
|
||||
void Application::maybeLockByPasscode() {
|
||||
|
||||
@@ -333,8 +333,10 @@ private:
|
||||
void startDomain();
|
||||
void startEmojiImageLoader();
|
||||
void startSystemDarkModeViewer();
|
||||
void startMediaView();
|
||||
void startTray();
|
||||
|
||||
void createTray();
|
||||
void updateWindowTitles();
|
||||
void setLastActiveWindow(Window::Controller *window);
|
||||
void showAccount(not_null<Main::Account*> account);
|
||||
|
||||
@@ -426,7 +426,7 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
}
|
||||
|
||||
_pleaseSendReport.setText(u"Please send us a crash report."_q);
|
||||
_yourReportName.setText(u"Your Report Tag: %1\nYour User Tag: %2"_q.arg(QString(_minidumpName).replace(".dmp", "")).arg(launcher->installationTag(), 0, 16));
|
||||
_yourReportName.setText(u"Crash ID: %1"_q.arg(QString(_minidumpName).replace(".dmp", "")));
|
||||
_yourReportName.setCursor(style::cur_text);
|
||||
_yourReportName.setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
|
||||
|
||||
@@ -190,9 +190,6 @@ void WriteReportInfo(int signum, const char *name) {
|
||||
} else {
|
||||
dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
|
||||
}
|
||||
|
||||
dump() << "\nBacktrace omitted.\n";
|
||||
dump() << "\n";
|
||||
}
|
||||
|
||||
const int HandledSignals[] = {
|
||||
|
||||
@@ -320,7 +320,14 @@ void Launcher::init() {
|
||||
}
|
||||
|
||||
void Launcher::initHighDpi() {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
|
||||
qputenv("QT_DPI_ADJUSTMENT_POLICY", "AdjustDpi");
|
||||
#endif // Qt < 6.2.0
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
#endif // Qt < 6.0.0
|
||||
|
||||
if (OptionFractionalScalingEnabled.value()) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
|
||||
@@ -239,17 +239,17 @@ void Sandbox::setupScreenScale() {
|
||||
const auto basePair = screen->handle()->logicalBaseDpi();
|
||||
const auto base = (basePair.first + basePair.second) * 0.5;
|
||||
const auto screenScaleExact = dpi / base;
|
||||
const auto screenScale = int(base::SafeRound(screenScaleExact * 4)) * 25;
|
||||
const auto screenScale = int(base::SafeRound(screenScaleExact * 20)) * 5;
|
||||
LOG(("Primary screen DPI: %1, Base: %2.").arg(dpi).arg(base));
|
||||
LOG(("Computed screen scale: %1").arg(screenScale));
|
||||
if (Platform::IsMac()) {
|
||||
// 110% for Retina screens by default.
|
||||
cSetScreenScale((useRatio == 2) ? 110 : 100);
|
||||
cSetScreenScale((useRatio == 2) ? 110 : style::kScaleDefault);
|
||||
} else {
|
||||
const auto clamped = std::clamp(
|
||||
screenScale * useRatio,
|
||||
50 * useRatio,
|
||||
300);
|
||||
style::kScaleMin * useRatio,
|
||||
style::kScaleMax);
|
||||
cSetScreenScale(int(base::SafeRound(clamped * 1. / useRatio)));
|
||||
}
|
||||
LOG(("DevicePixelRatio: %1").arg(useRatio));
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 4006000;
|
||||
constexpr auto AppVersionStr = "4.6";
|
||||
constexpr auto AppVersion = 4006003;
|
||||
constexpr auto AppVersionStr = "4.6.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -148,18 +148,19 @@ auto ProcessAlternativeName(Info &&info) {
|
||||
Manager::Manager(not_null<Main::Domain*> domain)
|
||||
: _path(cWorkingDir() + "tdata/countries") {
|
||||
read();
|
||||
|
||||
const auto mtpLifetime = _lifetime.make_state<rpl::lifetime>();
|
||||
domain->activeValue(
|
||||
) | rpl::map([=](Main::Account *account) {
|
||||
if (!account) {
|
||||
_api.reset();
|
||||
}
|
||||
return account
|
||||
? account->mtpMainSessionValue()
|
||||
: rpl::never<not_null<MTP::Instance*>>();
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
|
||||
_api.emplace(instance);
|
||||
request();
|
||||
) | rpl::filter([=](Main::Account *account) {
|
||||
return (account != nullptr);
|
||||
}) | rpl::start_with_next_done([=](Main::Account *account) {
|
||||
*mtpLifetime = account->mtpMainSessionValue(
|
||||
) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
|
||||
_api.emplace(instance);
|
||||
request();
|
||||
});
|
||||
}, [=] {
|
||||
_api.reset();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ ChatRestrictions TabbedPanelSendRestrictions() {
|
||||
|
||||
// Duplicated in CanSendAnyOfValue().
|
||||
bool CanSendAnyOf(
|
||||
not_null<Thread*> thread,
|
||||
not_null<const Thread*> thread,
|
||||
ChatRestrictions rights,
|
||||
bool forbidInForums) {
|
||||
const auto peer = thread->peer();
|
||||
@@ -107,7 +107,7 @@ bool CanSendAnyOf(
|
||||
|
||||
// Duplicated in CanSendAnyOfValue().
|
||||
bool CanSendAnyOf(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const PeerData*> peer,
|
||||
ChatRestrictions rights,
|
||||
bool forbidInForums) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
|
||||
@@ -131,43 +131,43 @@ struct RestrictionsSetOptions {
|
||||
[[nodiscard]] ChatRestrictions TabbedPanelSendRestrictions();
|
||||
|
||||
[[nodiscard]] bool CanSendAnyOf(
|
||||
not_null<Thread*> thread,
|
||||
not_null<const Thread*> thread,
|
||||
ChatRestrictions rights,
|
||||
bool forbidInForums = true);
|
||||
[[nodiscard]] bool CanSendAnyOf(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const PeerData*> peer,
|
||||
ChatRestrictions rights,
|
||||
bool forbidInForums = true);
|
||||
|
||||
[[nodiscard]] inline bool CanSend(
|
||||
not_null<Thread*> thread,
|
||||
not_null<const Thread*> thread,
|
||||
ChatRestriction right,
|
||||
bool forbidInForums = true) {
|
||||
return CanSendAnyOf(thread, right, forbidInForums);
|
||||
}
|
||||
[[nodiscard]] inline bool CanSend(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const PeerData*> peer,
|
||||
ChatRestriction right,
|
||||
bool forbidInForums = true) {
|
||||
return CanSendAnyOf(peer, right, forbidInForums);
|
||||
}
|
||||
[[nodiscard]] inline bool CanSendTexts(
|
||||
not_null<Thread*> thread,
|
||||
not_null<const Thread*> thread,
|
||||
bool forbidInForums = true) {
|
||||
return CanSend(thread, ChatRestriction::SendOther, forbidInForums);
|
||||
}
|
||||
[[nodiscard]] inline bool CanSendTexts(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const PeerData*> peer,
|
||||
bool forbidInForums = true) {
|
||||
return CanSend(peer, ChatRestriction::SendOther, forbidInForums);
|
||||
}
|
||||
[[nodiscard]] inline bool CanSendAnything(
|
||||
not_null<Thread*> thread,
|
||||
not_null<const Thread*> thread,
|
||||
bool forbidInForums = true) {
|
||||
return CanSendAnyOf(thread, AllSendRestrictions(), forbidInForums);
|
||||
}
|
||||
[[nodiscard]] inline bool CanSendAnything(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const PeerData*> peer,
|
||||
bool forbidInForums = true) {
|
||||
return CanSendAnyOf(peer, AllSendRestrictions(), forbidInForums);
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ void EmojiStatuses::requestProfilePhotoGroups() {
|
||||
const auto &data = group.data();
|
||||
auto emoticons = ranges::views::all(
|
||||
data.vemoticons().v
|
||||
) | ranges::view::transform([](const MTPstring &emoticon) {
|
||||
) | ranges::views::transform([](const MTPstring &emoticon) {
|
||||
return qs(emoticon);
|
||||
}) | ranges::to_vector;
|
||||
result.push_back({
|
||||
|
||||
@@ -211,7 +211,7 @@ not_null<Dialogs::MainList*> Folder::chatsList() {
|
||||
return &_chatsList;
|
||||
}
|
||||
|
||||
void Folder::loadUserpic() {
|
||||
void Folder::chatListPreloadData() {
|
||||
}
|
||||
|
||||
void Folder::paintUserpic(
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
void loadUserpic() override;
|
||||
void chatListPreloadData() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
|
||||
@@ -471,17 +471,7 @@ void ForumTopic::applyTopicTopMessage(MsgId topMessageId) {
|
||||
const auto itemId = FullMsgId(channel()->id, topMessageId);
|
||||
if (const auto item = owner().message(itemId)) {
|
||||
setLastServerMessage(item);
|
||||
|
||||
// If we set a single album part, request the full album.
|
||||
if (item->groupId() != MessageGroupId()) {
|
||||
if (owner().groups().isGroupOfOne(item)
|
||||
&& !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty()
|
||||
&& _requestedGroups.emplace(item->fullId()).second) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
}
|
||||
resolveChatListMessageGroup();
|
||||
} else {
|
||||
setLastServerMessage(nullptr);
|
||||
}
|
||||
@@ -490,6 +480,23 @@ void ForumTopic::applyTopicTopMessage(MsgId topMessageId) {
|
||||
}
|
||||
}
|
||||
|
||||
void ForumTopic::resolveChatListMessageGroup() {
|
||||
if (!(_flags & Flag::ResolveChatListMessage)) {
|
||||
return;
|
||||
}
|
||||
// If we set a single album part, request the full album.
|
||||
const auto item = _lastServerMessage.value_or(nullptr);
|
||||
if (item && item->groupId() != MessageGroupId()) {
|
||||
if (owner().groups().isGroupOfOne(item)
|
||||
&& !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty()
|
||||
&& _requestedGroups.emplace(item->fullId()).second) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ForumTopic::growLastKnownServerMessageId(MsgId id) {
|
||||
_lastKnownServerMessageId = std::max(_lastKnownServerMessageId, id);
|
||||
}
|
||||
@@ -548,10 +555,11 @@ void ForumTopic::setChatListMessage(HistoryItem *item) {
|
||||
_forum->listMessageChanged(was, item);
|
||||
}
|
||||
|
||||
void ForumTopic::loadUserpic() {
|
||||
void ForumTopic::chatListPreloadData() {
|
||||
if (_icon) {
|
||||
[[maybe_unused]] const auto preload = _icon->ready();
|
||||
}
|
||||
allowChatListMessageResolve();
|
||||
}
|
||||
|
||||
void ForumTopic::paintUserpic(
|
||||
@@ -840,6 +848,14 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
|
||||
return result;
|
||||
}
|
||||
|
||||
void ForumTopic::allowChatListMessageResolve() {
|
||||
if (_flags & Flag::ResolveChatListMessage) {
|
||||
return;
|
||||
}
|
||||
_flags |= Flag::ResolveChatListMessage;
|
||||
resolveChatListMessageGroup();
|
||||
}
|
||||
|
||||
HistoryItem *ForumTopic::chatListMessage() const {
|
||||
return _lastMessage.value_or(nullptr);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ public:
|
||||
return _notify;
|
||||
}
|
||||
|
||||
void loadUserpic() override;
|
||||
void chatListPreloadData() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
@@ -169,6 +169,7 @@ private:
|
||||
HasPinnedMessages = (1 << 3),
|
||||
GeneralIconActive = (1 << 4),
|
||||
GeneralIconSelected = (1 << 5),
|
||||
ResolveChatListMessage = (1 << 6),
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; }
|
||||
using Flags = base::flags<Flag>;
|
||||
@@ -183,6 +184,8 @@ private:
|
||||
void setLastMessage(HistoryItem *item);
|
||||
void setLastServerMessage(HistoryItem *item);
|
||||
void setChatListMessage(HistoryItem *item);
|
||||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
int chatListNameVersion() const override;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_participant_status.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_photo.h"
|
||||
@@ -475,6 +476,13 @@ bool PeerData::canPinMessages() const {
|
||||
Unexpected("Peer type in PeerData::canPinMessages.");
|
||||
}
|
||||
|
||||
bool PeerData::canCreatePolls() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->isBot() && !user->isSupport();
|
||||
}
|
||||
return Data::CanSend(this, ChatRestriction::SendPolls);
|
||||
}
|
||||
|
||||
bool PeerData::canCreateTopics() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->isForum()
|
||||
@@ -940,10 +948,6 @@ Data::RestrictionCheckResult PeerData::amRestricted(
|
||||
? ((user->flags() & UserDataFlag::VoiceMessagesForbidden)
|
||||
? Result::Explicit()
|
||||
: Result::Allowed())
|
||||
: (right == ChatRestriction::SendPolls)
|
||||
? ((!user->isBot() || user->isSupport())
|
||||
? Result::Explicit()
|
||||
: Result::Allowed())
|
||||
: (right == ChatRestriction::PinMessages)
|
||||
? ((user->flags() & UserDataFlag::CanPinMessages)
|
||||
? Result::Allowed()
|
||||
|
||||
@@ -317,6 +317,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool canPinMessages() const;
|
||||
[[nodiscard]] bool canEditMessagesIndefinitely() const;
|
||||
[[nodiscard]] bool canCreatePolls() const;
|
||||
[[nodiscard]] bool canCreateTopics() const;
|
||||
[[nodiscard]] bool canManageTopics() const;
|
||||
[[nodiscard]] bool canExportChatHistory() const;
|
||||
|
||||
@@ -215,19 +215,11 @@ inline auto DefaultRestrictionValue(
|
||||
return rpl::single(false);
|
||||
}
|
||||
using namespace rpl::mappers;
|
||||
const auto other = rights & ~(ChatRestriction::SendPolls
|
||||
| ChatRestriction::SendVoiceMessages
|
||||
const auto other = rights & ~(ChatRestriction::SendVoiceMessages
|
||||
| ChatRestriction::SendVideoMessages);
|
||||
if (other) {
|
||||
return PeerFlagValue(user, UserDataFlag::Deleted)
|
||||
| rpl::map(!_1);
|
||||
} else if (rights & ChatRestriction::SendPolls) {
|
||||
if (CanSend(user, ChatRestriction::SendPolls)) {
|
||||
return PeerFlagValue(user, UserDataFlag::Deleted)
|
||||
| rpl::map(!_1);
|
||||
} else if (rights == ChatRestriction::SendPolls) {
|
||||
return rpl::single(false);
|
||||
}
|
||||
}
|
||||
const auto mask = UserDataFlag::Deleted
|
||||
| UserDataFlag::VoiceMessagesForbidden;
|
||||
|
||||
@@ -381,6 +381,9 @@ void Session::clear() {
|
||||
cSetRecentInlineBots(RecentInlineBots());
|
||||
cSetRecentStickers(RecentStickerPack());
|
||||
HistoryView::Element::ClearGlobal();
|
||||
_contactsNoChatsList.clear();
|
||||
_contactsList.clear();
|
||||
_chatsList.clear();
|
||||
_histories->clearAll();
|
||||
_webpages.clear();
|
||||
_locations.clear();
|
||||
|
||||
@@ -425,10 +425,6 @@ CustomEmojiManager::CustomEmojiManager(not_null<Session*> owner)
|
||||
QString()).toULongLong();
|
||||
if (setId) {
|
||||
_coloredSetId = setId;
|
||||
auto pending = base::take(_coloredSetPending);
|
||||
for (const auto &instance : pending[setId]) {
|
||||
instance->setColored();
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
@@ -460,12 +456,6 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||
}, std::move(repaint))).first;
|
||||
if (colored) {
|
||||
i->second->setColored();
|
||||
} else if (_coloredSetId) {
|
||||
if (_coloredSetId == setId) {
|
||||
i->second->setColored();
|
||||
}
|
||||
} else if (setId) {
|
||||
_coloredSetPending[setId].emplace(i->second.get());
|
||||
}
|
||||
} else if (!i->second->hasImagePreview()) {
|
||||
auto preview = prepareNonExactPreview(documentId, tag, sizeOverride);
|
||||
@@ -693,32 +683,14 @@ void CustomEmojiManager::request() {
|
||||
}
|
||||
|
||||
void CustomEmojiManager::fillColoredFlags(not_null<DocumentData*> document) {
|
||||
const auto id = document->id;
|
||||
const auto setColored = [&] {
|
||||
if (document->emojiUsesTextColor()) {
|
||||
const auto id = document->id;
|
||||
for (auto &instances : _instances) {
|
||||
const auto i = instances.find(id);
|
||||
if (i != end(instances)) {
|
||||
i->second->setColored();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (document->emojiUsesTextColor()) {
|
||||
setColored();
|
||||
return;
|
||||
}
|
||||
const auto sticker = document->sticker();
|
||||
const auto setId = sticker ? sticker->set.id : uint64();
|
||||
if (!setId || (_coloredSetId && setId != _coloredSetId)) {
|
||||
return;
|
||||
} else if (setId == _coloredSetId) {
|
||||
setColored();
|
||||
} else {
|
||||
for (auto &instances : _instances) {
|
||||
const auto i = instances.find(id);
|
||||
if (i != end(instances)) {
|
||||
_coloredSetPending[setId].emplace(i->second.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,10 +152,6 @@ private:
|
||||
not_null<Listener*>,
|
||||
base::flat_set<DocumentId>> _listeners;
|
||||
base::flat_set<DocumentId> _pendingForRequest;
|
||||
base::flat_map<
|
||||
uint64,
|
||||
base::flat_set<
|
||||
not_null<Ui::CustomEmoji::Instance*>>> _coloredSetPending;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void loadUserpic() = 0;
|
||||
virtual void chatListPreloadData() = 0;
|
||||
virtual void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
|
||||
@@ -205,6 +205,7 @@ void IndexedList::remove(Key key, Row *replacedBy) {
|
||||
}
|
||||
|
||||
void IndexedList::clear() {
|
||||
_list.clear();
|
||||
_index.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -2506,7 +2506,7 @@ void InnerWidget::visibleTopBottomUpdated(
|
||||
int visibleBottom) {
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
loadPeerPhotos();
|
||||
preloadRowsData();
|
||||
const auto loadTill = _visibleTop
|
||||
+ PreloadHeightsCount * (_visibleBottom - _visibleTop);
|
||||
if (_state == WidgetState::Filtered && loadTill >= peerSearchOffset()) {
|
||||
@@ -2726,7 +2726,7 @@ void InnerWidget::refresh(bool toTop) {
|
||||
if (toTop) {
|
||||
stopReorderPinned();
|
||||
_mustScrollTo.fire({ 0, 0 });
|
||||
loadPeerPhotos();
|
||||
preloadRowsData();
|
||||
}
|
||||
_controller->setDialogsListDisplayForced(
|
||||
_searchInChat || !_filter.isEmpty());
|
||||
@@ -3115,8 +3115,10 @@ void InnerWidget::scrollToDefaultSelected() {
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::loadPeerPhotos() {
|
||||
if (!parentWidget()) return;
|
||||
void InnerWidget::preloadRowsData() {
|
||||
if (!parentWidget()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto yFrom = _visibleTop;
|
||||
auto yTo = _visibleTop + (_visibleBottom - _visibleTop) * (PreloadHeightsCount + 1);
|
||||
@@ -3129,7 +3131,7 @@ void InnerWidget::loadPeerPhotos() {
|
||||
if (((*i)->index() * _st->height) >= yTo) {
|
||||
break;
|
||||
}
|
||||
(*i)->entry()->loadUserpic();
|
||||
(*i)->entry()->chatListPreloadData();
|
||||
}
|
||||
yFrom = 0;
|
||||
} else {
|
||||
@@ -3144,7 +3146,7 @@ void InnerWidget::loadPeerPhotos() {
|
||||
if (to > _filterResults.size()) to = _filterResults.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_filterResults[from].key().entry()->loadUserpic();
|
||||
_filterResults[from].key().entry()->chatListPreloadData();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ private:
|
||||
Qt::KeyboardModifiers modifiers);
|
||||
void clearIrrelevantState();
|
||||
void selectByMouse(QPoint globalPosition);
|
||||
void loadPeerPhotos();
|
||||
void preloadRowsData();
|
||||
void scrollToItem(int top, int height);
|
||||
void scrollToDefaultSelected();
|
||||
void setCollapsedPressed(int pressed);
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
List &operator=(List &&other) = default;
|
||||
~List() = default;
|
||||
|
||||
void clear() {
|
||||
_rows.clear();
|
||||
_rowByKey.clear();
|
||||
}
|
||||
[[nodiscard]] int size() const {
|
||||
return _rows.size();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ void MainList::clear() {
|
||||
recomputeFullListSize();
|
||||
});
|
||||
const auto notifier = unreadStateChangeNotifier(true);
|
||||
_pinned.clear();
|
||||
_all.clear();
|
||||
_unreadState = UnreadState();
|
||||
_cloudUnreadState = UnreadState();
|
||||
|
||||
@@ -743,6 +743,7 @@ void RowPainter::Paint(
|
||||
const auto thread = row->thread();
|
||||
const auto peer = history ? history->peer.get() : nullptr;
|
||||
const auto badgesState = entry->chatListBadgesState();
|
||||
entry->chatListPreloadData(); // Allow chat list message resolve.
|
||||
const auto item = entry->chatListMessage();
|
||||
const auto cloudDraft = [&]() -> const Data::Draft*{
|
||||
if (!thread) {
|
||||
|
||||
@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include <gsl/gsl_util>
|
||||
#include <gsl/util>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/view/history_view_translate_tracker.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "history/history_inner_widget.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -606,16 +607,6 @@ not_null<HistoryItem*> History::addNewItem(
|
||||
} else {
|
||||
addNewToBack(item, unread);
|
||||
checkForLoadedAtTop(item);
|
||||
if (!unread) {
|
||||
// When we add just one last item, like we do while loading dialogs,
|
||||
// we want to remove a single added grouped media, otherwise it will
|
||||
// jump once we open the message history (first we show only that
|
||||
// media, then we load the rest of the group and show the group).
|
||||
//
|
||||
// That way when we open the message history we show nothing until a
|
||||
// whole history part is loaded, it certainly will contain the group.
|
||||
removeOrphanMediaGroupPart();
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@@ -2240,6 +2231,44 @@ Dialogs::UnreadState History::computeUnreadState() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
void History::allowChatListMessageResolve() {
|
||||
if (_flags & Flag::ResolveChatListMessage) {
|
||||
return;
|
||||
}
|
||||
_flags |= Flag::ResolveChatListMessage;
|
||||
if (!chatListMessageKnown()) {
|
||||
requestChatListMessage();
|
||||
} else {
|
||||
resolveChatListMessageGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void History::resolveChatListMessageGroup() {
|
||||
const auto item = _chatListMessage.value_or(nullptr);
|
||||
if (!(_flags & Flag::ResolveChatListMessage)
|
||||
|| !item
|
||||
|| !hasOrphanMediaGroupPart()) {
|
||||
return;
|
||||
}
|
||||
// If we set a single album part, request the full album.
|
||||
const auto withImages = !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty();
|
||||
if (withImages) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
if (unreadCountKnown() && !unreadCount()) {
|
||||
// When we add just one last item, like we do while loading dialogs,
|
||||
// we want to remove a single added grouped media, otherwise it will
|
||||
// jump once we open the message history (first we show only that
|
||||
// media, then we load the rest of the group and show the group).
|
||||
//
|
||||
// That way when we open the message history we show nothing until a
|
||||
// whole history part is loaded, it certainly will contain the group.
|
||||
clear(ClearType::Unload);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *History::chatListMessage() const {
|
||||
return _chatListMessage.value_or(nullptr);
|
||||
}
|
||||
@@ -2268,8 +2297,9 @@ const base::flat_set<QChar> &History::chatListFirstLetters() const {
|
||||
return peer->nameFirstLetters();
|
||||
}
|
||||
|
||||
void History::loadUserpic() {
|
||||
void History::chatListPreloadData() {
|
||||
peer->loadUserpic();
|
||||
allowChatListMessageResolve();
|
||||
}
|
||||
|
||||
void History::paintUserpic(
|
||||
@@ -2451,14 +2481,7 @@ void History::setChatListMessage(HistoryItem *item) {
|
||||
}
|
||||
_chatListMessage = item;
|
||||
setChatListTimeId(item->date());
|
||||
|
||||
// If we have a single message from a group, request the full album.
|
||||
if (hasOrphanMediaGroupPart()
|
||||
&& !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty()) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
resolveChatListMessageGroup();
|
||||
} else if (!_chatListMessage || *_chatListMessage) {
|
||||
_chatListMessage = nullptr;
|
||||
updateChatListEntry();
|
||||
@@ -2559,13 +2582,21 @@ void History::requestChatListMessage() {
|
||||
}
|
||||
|
||||
void History::setFakeChatListMessage() {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
if (!(_flags & Flag::ResolveChatListMessage)) {
|
||||
if (!chatListTimeId()) {
|
||||
if (const auto last = lastMessage()) {
|
||||
setChatListTimeId(last->date());
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
// In chats we try to take the item before the 'last', which
|
||||
// is the empty-displayed migration message.
|
||||
owner().histories().requestFakeChatListMessage(this);
|
||||
} else if (const auto from = migrateFrom()) {
|
||||
// In megagroups we just try to use
|
||||
// the message from the original group.
|
||||
from->allowChatListMessageResolve();
|
||||
from->requestChatListMessage();
|
||||
}
|
||||
}
|
||||
@@ -3308,14 +3339,6 @@ bool History::hasOrphanMediaGroupPart() const {
|
||||
return last->groupId() != MessageGroupId();
|
||||
}
|
||||
|
||||
bool History::removeOrphanMediaGroupPart() {
|
||||
if (hasOrphanMediaGroupPart()) {
|
||||
clear(ClearType::Unload);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<MsgId> History::collectMessagesFromParticipantToDelete(
|
||||
not_null<PeerData*> participant) const {
|
||||
auto result = std::vector<MsgId>();
|
||||
|
||||
@@ -111,7 +111,6 @@ public:
|
||||
Element *findLastNonEmpty() const;
|
||||
Element *findLastDisplayed() const;
|
||||
bool hasOrphanMediaGroupPart() const;
|
||||
bool removeOrphanMediaGroupPart();
|
||||
[[nodiscard]] std::vector<MsgId> collectMessagesFromParticipantToDelete(
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
@@ -387,7 +386,7 @@ public:
|
||||
const QString &chatListNameSortKey() const override;
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
void loadUserpic() override;
|
||||
void chatListPreloadData() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
@@ -467,6 +466,7 @@ private:
|
||||
IsForum = (1 << 3),
|
||||
FakeUnreadWhileOpened = (1 << 4),
|
||||
HasPinnedMessages = (1 << 5),
|
||||
ResolveChatListMessage = (1 << 6),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) {
|
||||
@@ -553,6 +553,8 @@ private:
|
||||
void setChatListMessageFromLast();
|
||||
void setChatListMessageUnknown();
|
||||
void setFakeChatListMessage();
|
||||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
// Add all items to the unread mentions if we were not loaded at bottom and now are.
|
||||
void checkAddAllToUnreadMentions();
|
||||
|
||||
@@ -555,6 +555,9 @@ void HistoryInner::messagesReceived(
|
||||
const QVector<MTPMessage> &messages) {
|
||||
if (_history->peer == peer) {
|
||||
_history->addOlderSlice(messages);
|
||||
if (!messages.isEmpty()) {
|
||||
_translateTracker->addBunchFromBlocks();
|
||||
}
|
||||
} else if (_migrated && _migrated->peer == peer) {
|
||||
const auto newLoaded = _migrated
|
||||
&& _migrated->isEmpty()
|
||||
@@ -1757,6 +1760,9 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pinnedItem == item) {
|
||||
_pinnedItem = nullptr;
|
||||
}
|
||||
if (_reactionsItem.current() == item) {
|
||||
_reactionsItem = nullptr;
|
||||
}
|
||||
@@ -3940,8 +3946,17 @@ void HistoryInner::notifyIsBotChanged() {
|
||||
}
|
||||
|
||||
void HistoryInner::notifyMigrateUpdated() {
|
||||
_migrated = _history->migrateFrom();
|
||||
_migrated->translateTo(_history->translatedTo());
|
||||
const auto migrated = _history->migrateFrom();
|
||||
if (_migrated != migrated) {
|
||||
if (_migrated) {
|
||||
_migrated->delegateMixin()->setCurrent(nullptr);
|
||||
}
|
||||
_migrated = migrated;
|
||||
if (_migrated) {
|
||||
_migrated->delegateMixin()->setCurrent(this);
|
||||
_migrated->translateTo(_history->translatedTo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryInner::applyDragSelection() {
|
||||
|
||||
@@ -491,7 +491,13 @@ HistoryWidget::HistoryWidget(
|
||||
_botCommandStart->hide();
|
||||
|
||||
session().attachWebView().requestBots();
|
||||
session().attachWebView().attachBotsUpdates(
|
||||
rpl::merge(
|
||||
session().attachWebView().attachBotsUpdates(),
|
||||
session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::Rights
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return update.peer == _peer;
|
||||
}) | rpl::to_empty
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshAttachBotsMenu();
|
||||
}, lifetime());
|
||||
@@ -505,7 +511,16 @@ HistoryWidget::HistoryWidget(
|
||||
_attachDragAreas = DragArea::SetupDragAreaToContainer(
|
||||
this,
|
||||
crl::guard(this, [=](not_null<const QMimeData*> d) {
|
||||
return _history && _canSendMessages && !isRecording();
|
||||
if (!_peer || isRecording()) {
|
||||
return false;
|
||||
}
|
||||
const auto replyTo = (_replyToId && !_editMsgId)
|
||||
? _replyEditMsg
|
||||
: 0;
|
||||
const auto topic = replyTo ? replyTo->topic() : nullptr;
|
||||
return topic
|
||||
? Data::CanSendAnyOf(topic, Data::FilesSendRestrictions())
|
||||
: Data::CanSendAnyOf(_peer, Data::FilesSendRestrictions());
|
||||
}),
|
||||
crl::guard(this, [=](bool f) { _field->setAcceptDrops(f); }),
|
||||
crl::guard(this, [=] { updateControlsGeometry(); }));
|
||||
@@ -1867,11 +1882,11 @@ void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
InvokeQueued(this, [=] { updateStickersByEmoji(); });
|
||||
|
||||
if (_voiceRecordBar->isActive() || !_canSendTexts) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto editDraft = _history ? _history->localEditDraft({}) : nullptr;
|
||||
@@ -1893,7 +1908,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
refreshTopBarActiveChat();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
_textUpdateEvents = 0;
|
||||
@@ -1930,6 +1945,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
_processingReplyId = draft ? draft->msgId : MsgId();
|
||||
processReply();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryWidget::applyCloudDraft(History *history) {
|
||||
@@ -2261,7 +2277,9 @@ void HistoryWidget::showHistory(
|
||||
handlePeerUpdate();
|
||||
|
||||
session().local().readDraftsWithCursors(_history);
|
||||
applyDraft();
|
||||
if (!applyDraft()) {
|
||||
clearFieldText();
|
||||
}
|
||||
_send->finishAnimating();
|
||||
|
||||
updateControlsGeometry();
|
||||
@@ -2330,9 +2348,12 @@ void HistoryWidget::showHistory(
|
||||
updateOverStates(mapFromGlobal(QCursor::pos()));
|
||||
|
||||
if (_history) {
|
||||
const auto msgId = (_showAtMsgId == ShowAtTheEndMsgId)
|
||||
? ShowAtUnreadMsgId
|
||||
: _showAtMsgId;
|
||||
controller()->setActiveChatEntry({
|
||||
_history,
|
||||
FullMsgId(_history->peer->id, _showAtMsgId) });
|
||||
FullMsgId(_history->peer->id, msgId) });
|
||||
}
|
||||
update();
|
||||
controller()->floatPlayerAreaUpdated();
|
||||
@@ -4055,6 +4076,7 @@ void HistoryWidget::showFinished() {
|
||||
|
||||
void HistoryWidget::doneShow() {
|
||||
_topBar->setAnimatingMode(false);
|
||||
updateCanSendMessage();
|
||||
updateBotKeyboard();
|
||||
updateControlsVisibility();
|
||||
if (!_historyInited) {
|
||||
@@ -7359,6 +7381,9 @@ void HistoryWidget::handlePeerUpdate() {
|
||||
}
|
||||
|
||||
bool HistoryWidget::updateCanSendMessage() {
|
||||
if (!_peer) {
|
||||
return false;
|
||||
}
|
||||
const auto replyTo = (_replyToId && !_editMsgId) ? _replyEditMsg : 0;
|
||||
const auto topic = replyTo ? replyTo->topic() : nullptr;
|
||||
const auto allWithoutPolls = Data::AllSendRestrictions()
|
||||
|
||||
@@ -213,7 +213,7 @@ public:
|
||||
void botCallbackSent(not_null<HistoryItem*> item);
|
||||
|
||||
void fastShowAtEnd(not_null<History*> history);
|
||||
void applyDraft(
|
||||
bool applyDraft(
|
||||
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
|
||||
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
|
||||
void setChooseReportMessagesDetails(
|
||||
|
||||
@@ -523,9 +523,9 @@ void BottomInfo::layoutReactionsText() {
|
||||
_reactions.clear();
|
||||
return;
|
||||
}
|
||||
auto sorted = ranges::view::all(
|
||||
auto sorted = ranges::views::all(
|
||||
_data.reactions
|
||||
) | ranges::view::transform([](const MessageReaction &reaction) {
|
||||
) | ranges::views::transform([](const MessageReaction &reaction) {
|
||||
return not_null{ &reaction };
|
||||
}) | ranges::to_vector;
|
||||
ranges::sort(
|
||||
|
||||
@@ -523,6 +523,9 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_translateTracker) {
|
||||
_translateTracker->addBunchFrom(_items);
|
||||
}
|
||||
for (auto e = end(_items), i = e - addedToEndCount; i != e; ++i) {
|
||||
_itemRevealPending.emplace(*i);
|
||||
}
|
||||
|
||||
@@ -355,6 +355,11 @@ protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
private:
|
||||
using ScrollTopState = ListMemento::ScrollTopState;
|
||||
using PointState = HistoryView::PointState;
|
||||
using CursorState = HistoryView::CursorState;
|
||||
using ChosenReaction = HistoryView::Reactions::ChosenReaction;
|
||||
|
||||
struct MouseState {
|
||||
MouseState();
|
||||
MouseState(
|
||||
@@ -405,10 +410,6 @@ private:
|
||||
Selecting,
|
||||
Deselecting,
|
||||
};
|
||||
using ScrollTopState = ListMemento::ScrollTopState;
|
||||
using PointState = HistoryView::PointState;
|
||||
using CursorState = HistoryView::CursorState;
|
||||
using ChosenReaction = HistoryView::Reactions::ChosenReaction;
|
||||
|
||||
void onTouchSelect();
|
||||
void onTouchScrollTimer();
|
||||
|
||||
@@ -2616,9 +2616,18 @@ void RepliesWidget::clearSelected() {
|
||||
}
|
||||
|
||||
void RepliesWidget::setupDragArea() {
|
||||
const auto filter = [=](const auto &d) {
|
||||
if (!_history || _composeControls->isRecording()) {
|
||||
return false;
|
||||
}
|
||||
const auto peer = _history->peer;
|
||||
return _topic
|
||||
? Data::CanSendAnyOf(_topic, Data::FilesSendRestrictions())
|
||||
: Data::CanSendAnyOf(peer, Data::FilesSendRestrictions());
|
||||
};
|
||||
const auto areas = DragArea::SetupDragAreaToContainer(
|
||||
this,
|
||||
[=](auto d) { return _history && !_composeControls->isRecording(); },
|
||||
filter,
|
||||
nullptr,
|
||||
[=] { updateControlsGeometry(); });
|
||||
|
||||
|
||||
@@ -124,7 +124,9 @@ TopBarWidget::TopBarWidget(
|
||||
_groupCall->setClickedCallback([=] { groupCall(); });
|
||||
_menuToggle->setClickedCallback([=] { showPeerMenu(); });
|
||||
_infoToggle->setClickedCallback([=] { toggleInfoSection(); });
|
||||
_back->addClickHandler([=] { backClicked(); });
|
||||
_back->setClickedCallback([=] {
|
||||
InvokeQueued(_back.data(), [=] { backClicked(); });
|
||||
});
|
||||
_cancelChoose->setClickedCallback(
|
||||
[=] { _cancelChooseForReport.fire({}); });
|
||||
|
||||
@@ -1033,7 +1035,7 @@ void TopBarWidget::updateControlsVisibility() {
|
||||
const auto section = _activeChat.section;
|
||||
const auto historyMode = (section == Section::History);
|
||||
const auto hasPollsMenu = (_activeChat.key.peer()
|
||||
&& Data::CanSend(_activeChat.key.peer(), ChatRestriction::SendPolls))
|
||||
&& _activeChat.key.peer()->canCreatePolls())
|
||||
|| (topic && Data::CanSend(topic, ChatRestriction::SendPolls));
|
||||
const auto hasTopicMenu = [&] {
|
||||
if (!topic || section != Section::Replies) {
|
||||
|
||||
@@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/boxes/choose_language_box.h" // EditSkipTranslationLanguages.
|
||||
#include "ui/layers/box_content.h"
|
||||
@@ -280,7 +279,7 @@ void TranslateBar::setup(not_null<History*> history) {
|
||||
}
|
||||
};
|
||||
const auto button = static_cast<Ui::AbstractButton*>(_wrap.entity());
|
||||
button->resize(0, st::historyComposeButton.height);
|
||||
button->resize(0, st::historyTranslateBarHeight);
|
||||
button->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
button->paintRequest(
|
||||
@@ -289,19 +288,9 @@ void TranslateBar::setup(not_null<History*> history) {
|
||||
}, button->lifetime());
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
translateTo(history->translatedTo()
|
||||
? LanguageId()
|
||||
: Core::App().settings().translateTo());
|
||||
translateTo(history->translatedTo() ? LanguageId() : _to.current());
|
||||
});
|
||||
|
||||
Core::App().settings().translateToValue(
|
||||
) | rpl::filter([=](LanguageId should) {
|
||||
const auto now = history->translatedTo();
|
||||
return now && (now != should);
|
||||
}) | rpl::start_with_next([=](LanguageId should) {
|
||||
translateTo(should);
|
||||
}, _wrap.lifetime());
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
st::historyTranslateLabel);
|
||||
@@ -343,8 +332,34 @@ void TranslateBar::setup(not_null<History*> history) {
|
||||
updateLabelGeometry();
|
||||
}, lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_overridenTo = history->translatedTo();
|
||||
_to = rpl::combine(
|
||||
Core::App().settings().translateToValue(),
|
||||
Core::App().settings().skipTranslationLanguagesValue(),
|
||||
history->session().changes().historyFlagsValue(
|
||||
history,
|
||||
Data::HistoryUpdate::Flag::TranslateFrom),
|
||||
_overridenTo.value()
|
||||
) | rpl::map([=](
|
||||
LanguageId to,
|
||||
const std::vector<LanguageId> &skip,
|
||||
const auto &,
|
||||
LanguageId overridenTo) {
|
||||
return overridenTo
|
||||
? overridenTo
|
||||
: Ui::ChooseTranslateTo(history, to, skip);
|
||||
}) | rpl::distinct_until_changed();
|
||||
|
||||
_to.value(
|
||||
) | rpl::filter([=](LanguageId should) {
|
||||
const auto now = history->translatedTo();
|
||||
return now && (now != should);
|
||||
}) | rpl::start_with_next([=](LanguageId should) {
|
||||
translateTo(should);
|
||||
}, _wrap.lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_to.value(),
|
||||
history->session().changes().historyFlagsValue(
|
||||
history,
|
||||
(Data::HistoryUpdate::Flag::TranslatedTo
|
||||
@@ -352,16 +367,17 @@ void TranslateBar::setup(not_null<History*> history) {
|
||||
history->session().changes().peerFlagsValue(
|
||||
history->peer,
|
||||
Data::PeerUpdate::Flag::TranslationDisabled)
|
||||
) | rpl::map([=](LanguageId to, const auto&, const auto&) {
|
||||
) | rpl::map([=](
|
||||
LanguageId to,
|
||||
const auto&,
|
||||
const auto&) {
|
||||
using Flag = PeerData::TranslationFlag;
|
||||
return (history->peer->translationFlag() != Flag::Enabled)
|
||||
? rpl::single(QString())
|
||||
: history->translatedTo()
|
||||
? tr::lng_translate_show_original()
|
||||
: history->translateOfferedFrom()
|
||||
? tr::lng_translate_bar_to(
|
||||
lt_name,
|
||||
rpl::single(Ui::LanguageName(to)))
|
||||
? Ui::TranslateBarTo(to)
|
||||
: rpl::single(QString());
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::distinct_until_changed(
|
||||
@@ -408,20 +424,25 @@ void TranslateBar::showMenu(base::unique_qptr<Ui::PopupMenu> menu) {
|
||||
_menu = std::move(menu);
|
||||
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
||||
|
||||
const auto guard = Ui::MakeWeak(&_wrap);
|
||||
const auto now = _history->translatedTo();
|
||||
const auto to = now ? now : Ui::ChooseTranslateTo(_history);
|
||||
const auto weak = base::make_weak(_controller);
|
||||
const auto chooseCallback = [=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->show(Ui::ChooseTranslateToBox());
|
||||
strong->show(Ui::ChooseTranslateToBox(
|
||||
to,
|
||||
crl::guard(guard, [=](LanguageId id) { _overridenTo = id; })
|
||||
));
|
||||
}
|
||||
};
|
||||
_menu->addAction(MakeTranslateToItem(
|
||||
_menu->menu(),
|
||||
Ui::LanguageName(Core::App().settings().translateTo()),
|
||||
Ui::LanguageName(to ? to : Ui::ChooseTranslateTo(_history)),
|
||||
chooseCallback));
|
||||
_menu->addSeparator();
|
||||
const auto history = _history;
|
||||
if (const auto translateOfferedFrom = _history->translateOfferedFrom()) {
|
||||
const auto name = Ui::LanguageName(translateOfferedFrom);
|
||||
const auto addToIgnoreList = [=] {
|
||||
showSettingsToast(history->peer, translateOfferedFrom);
|
||||
|
||||
@@ -436,7 +457,7 @@ void TranslateBar::showMenu(base::unique_qptr<Ui::PopupMenu> menu) {
|
||||
Core::App().saveSettingsDelayed();
|
||||
};
|
||||
_menu->addAction(
|
||||
tr::lng_translate_menu_dont(tr::now, lt_name, name),
|
||||
Ui::TranslateMenuDont(tr::now, translateOfferedFrom),
|
||||
addToIgnoreList,
|
||||
&st::menuIconBlock);
|
||||
}
|
||||
@@ -596,7 +617,7 @@ int TranslateBar::height() const {
|
||||
return !_forceHidden
|
||||
? _wrap.height()
|
||||
: _shouldBeShown
|
||||
? st::historyComposeButton.height
|
||||
? st::historyTranslateBarHeight
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
|
||||
class History;
|
||||
struct LanguageId;
|
||||
@@ -68,6 +69,8 @@ private:
|
||||
std::unique_ptr<Ui::PlainShadow> _shadow;
|
||||
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
rpl::variable<LanguageId> _overridenTo;
|
||||
rpl::variable<LanguageId> _to;
|
||||
bool _shouldBeShown = false;
|
||||
bool _forceHidden = false;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace {
|
||||
|
||||
constexpr auto kEnoughForRecognition = 10;
|
||||
constexpr auto kEnoughForTranslation = 6;
|
||||
constexpr auto kMaxCheckInBunch = 100;
|
||||
constexpr auto kRequestLengthLimit = 24 * 1024;
|
||||
constexpr auto kRequestCountLimit = 20;
|
||||
|
||||
@@ -82,20 +83,20 @@ void TranslateTracker::startBunch() {
|
||||
++_generation;
|
||||
}
|
||||
|
||||
void TranslateTracker::add(not_null<Element*> view) {
|
||||
bool TranslateTracker::add(not_null<Element*> view) {
|
||||
const auto item = view->data();
|
||||
const auto only = view->isOnlyEmojiAndSpaces();
|
||||
if (only != OnlyEmojiAndSpaces::Unknown) {
|
||||
item->cacheOnlyEmojiAndSpaces(only == OnlyEmojiAndSpaces::Yes);
|
||||
}
|
||||
add(item, false);
|
||||
return add(item, false);
|
||||
}
|
||||
|
||||
void TranslateTracker::add(not_null<HistoryItem*> item) {
|
||||
add(item, false);
|
||||
bool TranslateTracker::add(not_null<HistoryItem*> item) {
|
||||
return add(item, false);
|
||||
}
|
||||
|
||||
void TranslateTracker::add(
|
||||
bool TranslateTracker::add(
|
||||
not_null<HistoryItem*> item,
|
||||
bool skipDependencies) {
|
||||
Expects(_addedInBunch >= 0);
|
||||
@@ -104,7 +105,7 @@ void TranslateTracker::add(
|
||||
|| item->isService()
|
||||
|| !item->isRegular()
|
||||
|| item->isOnlyEmojiAndSpaces()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (item->translationShowRequiresCheck(_bunchTranslatedTo)) {
|
||||
_switchTranslations[item] = _bunchTranslatedTo;
|
||||
@@ -131,7 +132,7 @@ void TranslateTracker::add(
|
||||
const auto i = _itemsForRecognize.find(id);
|
||||
if (i != end(_itemsForRecognize)) {
|
||||
i->second.generation = _generation;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
const auto &text = item->originalText().text;
|
||||
_itemsForRecognize.emplace(id, ItemForRecognize{
|
||||
@@ -141,6 +142,7 @@ void TranslateTracker::add(
|
||||
: MaybeLanguageId{ text }),
|
||||
});
|
||||
++_addedInBunch;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TranslateTracker::switchTranslation(
|
||||
@@ -173,6 +175,43 @@ void TranslateTracker::finishBunch() {
|
||||
requestSome();
|
||||
}
|
||||
|
||||
void TranslateTracker::addBunchFromBlocks() {
|
||||
if (enoughForRecognition()) {
|
||||
return;
|
||||
}
|
||||
startBunch();
|
||||
const auto guard = gsl::finally([&] {
|
||||
finishBunch();
|
||||
});
|
||||
|
||||
auto check = kMaxCheckInBunch;
|
||||
for (const auto &block : _history->blocks) {
|
||||
for (const auto &view : block->messages) {
|
||||
if (!check-- || (add(view.get()) && enoughForRecognition())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::addBunchFrom(
|
||||
const std::vector<not_null<Element*>> &views) {
|
||||
if (enoughForRecognition()) {
|
||||
return;
|
||||
}
|
||||
startBunch();
|
||||
const auto guard = gsl::finally([&] {
|
||||
finishBunch();
|
||||
});
|
||||
|
||||
auto check = kMaxCheckInBunch;
|
||||
for (const auto &view : views) {
|
||||
if (!check-- || (add(view.get()) && enoughForRecognition())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateTracker::cancelToRequest() {
|
||||
if (!_itemsToRequest.empty()) {
|
||||
const auto owner = &_history->owner();
|
||||
@@ -234,7 +273,7 @@ void TranslateTracker::requestSome() {
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(list),
|
||||
MTPVector<MTPTextWithEntities>(),
|
||||
MTP_string(to.locale().name().mid(0, 2))
|
||||
MTP_string(to.twoLetterCode())
|
||||
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||
requestDone(to, result.data().vresult().v);
|
||||
}).fail([=] {
|
||||
|
||||
@@ -23,10 +23,13 @@ public:
|
||||
|
||||
[[nodiscard]] bool enoughForRecognition() const;
|
||||
void startBunch();
|
||||
void add(not_null<Element*> view);
|
||||
void add(not_null<HistoryItem*> item);
|
||||
bool add(not_null<Element*> view);
|
||||
bool add(not_null<HistoryItem*> item);
|
||||
void finishBunch();
|
||||
|
||||
void addBunchFromBlocks();
|
||||
void addBunchFrom(const std::vector<not_null<Element*>> &views);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> trackingLanguage() const;
|
||||
|
||||
private:
|
||||
@@ -40,7 +43,7 @@ private:
|
||||
};
|
||||
|
||||
void setup();
|
||||
void add(not_null<HistoryItem*> item, bool skipDependencies);
|
||||
bool add(not_null<HistoryItem*> item, bool skipDependencies);
|
||||
void recognizeCollected();
|
||||
void trackSkipLanguages();
|
||||
void checkRecognized();
|
||||
|
||||
@@ -111,9 +111,9 @@ void InlineList::layoutButtons() {
|
||||
_buttons.clear();
|
||||
return;
|
||||
}
|
||||
auto sorted = ranges::view::all(
|
||||
auto sorted = ranges::views::all(
|
||||
_data.reactions
|
||||
) | ranges::view::transform([](const MessageReaction &reaction) {
|
||||
) | ranges::views::transform([](const MessageReaction &reaction) {
|
||||
return not_null{ &reaction };
|
||||
}) | ranges::to_vector;
|
||||
const auto &list = _owner->list(::Data::Reactions::Type::All);
|
||||
@@ -643,7 +643,7 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
|
||||
result.recent.reserve(recent.size());
|
||||
for (const auto &[id, list] : recent) {
|
||||
result.recent.emplace(id).first->second = list
|
||||
| ranges::view::transform(&Data::RecentReaction::peer)
|
||||
| ranges::views::transform(&Data::RecentReaction::peer)
|
||||
| ranges::to_vector;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,9 +220,9 @@ void Controller::showReaction(const ReactionId &reaction) {
|
||||
appendRow(peer, reaction);
|
||||
}
|
||||
} else {
|
||||
_filtered = _all | ranges::view::filter([&](const AllEntry &entry) {
|
||||
_filtered = _all | ranges::views::filter([&](const AllEntry &entry) {
|
||||
return (entry.second == reaction);
|
||||
}) | ranges::view::transform(
|
||||
}) | ranges::views::transform(
|
||||
&AllEntry::first
|
||||
) | ranges::to_vector;
|
||||
for (const auto peer : _filtered) {
|
||||
|
||||
@@ -18,7 +18,7 @@ userpicBuilderEmojiSubtitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
userpicBuilderEmojiSubtitlePadding: margins(0px, 9px, 0px, 2px);
|
||||
userpicBuilderEmojiBubblePaletteSize: size(356px, 56px);
|
||||
userpicBuilderEmojiBubblePaletteWidth: 356px;
|
||||
userpicBuilderEmojiBubblePalettePadding: margins(12px, 8px, 12px, 8px);
|
||||
|
||||
userpicBuilderEmojiSelectorLeft: 5px;
|
||||
|
||||
@@ -104,7 +104,11 @@ void ColorsLine::fillButtons() {
|
||||
container,
|
||||
st::userpicBuilderEmojiColorMinus));
|
||||
_wraps.push_back(minus);
|
||||
minus->toggle(_colors->size() > 1, anim::type::instant);
|
||||
minus->entity()->setClickedCallback([=] {
|
||||
if (_colors->size() < 2) {
|
||||
return;
|
||||
}
|
||||
const auto wasColors = *_colors;
|
||||
_colors->erase(_colors->end() - 1);
|
||||
const auto nowColors = *_colors;
|
||||
@@ -148,7 +152,11 @@ void ColorsLine::fillButtons() {
|
||||
container,
|
||||
st::userpicBuilderEmojiColorPlus));
|
||||
_wraps.push_back(plus);
|
||||
plus->toggle(_colors->size() < kMaxColors, anim::type::instant);
|
||||
plus->entity()->setClickedCallback([=] {
|
||||
if (_colors->size() >= kMaxColors) {
|
||||
return;
|
||||
}
|
||||
const auto wasColors = *_colors;
|
||||
_colors->push_back(RandomColor(_colors->back()));
|
||||
const auto nowColors = *_colors;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/random.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/userpic/info_userpic_emoji_builder.h"
|
||||
@@ -27,6 +28,128 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <random>
|
||||
|
||||
namespace UserpicBuilder {
|
||||
namespace {
|
||||
|
||||
constexpr auto kTimeout = crl::time(1500);
|
||||
|
||||
class StickerProvider final {
|
||||
public:
|
||||
StickerProvider(not_null<Data::Session*> owner);
|
||||
|
||||
void setDocuments(std::vector<DocumentId> documents);
|
||||
[[nodiscard]] DocumentId documentId() const;
|
||||
[[nodiscard]] auto documentChanged() const
|
||||
-> rpl::producer<not_null<DocumentData*>>;
|
||||
|
||||
private:
|
||||
void processDocumentIndex(int documentIndex);
|
||||
[[nodiscard]] DocumentData *lookupAndRememberSticker(int documentIndex);
|
||||
[[nodiscard]] std::pair<DocumentData*, int> lookupSticker(
|
||||
int documentIndex) const;
|
||||
|
||||
const not_null<Data::Session*> _owner;
|
||||
|
||||
int _documentIndex = 0;
|
||||
std::vector<DocumentId> _shuffledDocuments;
|
||||
|
||||
base::Timer _timer;
|
||||
|
||||
rpl::event_stream<not_null<DocumentData*>> _documentChanged;
|
||||
rpl::lifetime _resolvingLifetime;
|
||||
rpl::lifetime _downloadFinishedLifetime;
|
||||
|
||||
};
|
||||
|
||||
StickerProvider::StickerProvider(not_null<Data::Session*> owner)
|
||||
: _owner(owner) {
|
||||
_timer.setCallback([=] {
|
||||
_documentIndex++;
|
||||
if (_documentIndex >= _shuffledDocuments.size()) {
|
||||
_documentIndex = 0;
|
||||
}
|
||||
processDocumentIndex(_documentIndex);
|
||||
});
|
||||
}
|
||||
|
||||
DocumentId StickerProvider::documentId() const {
|
||||
const auto &[document, index] = lookupSticker(_documentIndex);
|
||||
return document ? document->id : DocumentId(0);
|
||||
}
|
||||
|
||||
void StickerProvider::setDocuments(std::vector<DocumentId> documents) {
|
||||
if (documents.empty()) {
|
||||
return;
|
||||
}
|
||||
auto rd = std::random_device();
|
||||
ranges::shuffle(documents, std::mt19937(rd()));
|
||||
_shuffledDocuments = std::move(documents);
|
||||
_documentIndex = 0;
|
||||
processDocumentIndex(_documentIndex);
|
||||
}
|
||||
|
||||
auto StickerProvider::documentChanged() const
|
||||
-> rpl::producer<not_null<DocumentData*>> {
|
||||
return _documentChanged.events();
|
||||
}
|
||||
|
||||
void StickerProvider::processDocumentIndex(int documentIndex) {
|
||||
if (const auto document = lookupAndRememberSticker(documentIndex)) {
|
||||
_resolvingLifetime.destroy();
|
||||
_owner->customEmojiManager().resolve(
|
||||
document->id
|
||||
) | rpl::start_with_next([=](not_null<DocumentData*> d) {
|
||||
_resolvingLifetime.destroy();
|
||||
_downloadFinishedLifetime.destroy();
|
||||
|
||||
const auto mediaView = d->createMediaView();
|
||||
_downloadFinishedLifetime.add([=] {
|
||||
[[maybe_unused]] const auto copy = mediaView;
|
||||
});
|
||||
mediaView->checkStickerLarge();
|
||||
mediaView->goodThumbnailWanted();
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
_owner->session().downloaderTaskFinished()
|
||||
) | rpl::start_with_next([=] {
|
||||
if (mediaView->loaded()) {
|
||||
_timer.callOnce(kTimeout);
|
||||
_documentChanged.fire_copy(mediaView->owner());
|
||||
_downloadFinishedLifetime.destroy();
|
||||
}
|
||||
}, _downloadFinishedLifetime);
|
||||
}, _resolvingLifetime);
|
||||
} else if (!_resolvingLifetime) {
|
||||
_timer.callOnce(kTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
DocumentData *StickerProvider::lookupAndRememberSticker(int documentIndex) {
|
||||
const auto &[document, index] = lookupSticker(documentIndex);
|
||||
if (document) {
|
||||
_documentIndex = index;
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
std::pair<DocumentData*, int> StickerProvider::lookupSticker(
|
||||
int documentIndex) const {
|
||||
const auto size = _shuffledDocuments.size();
|
||||
for (auto i = 0; i < size; i++) {
|
||||
const auto unrestrictedIndex = documentIndex + i;
|
||||
const auto index = (unrestrictedIndex >= size)
|
||||
? (unrestrictedIndex - size)
|
||||
: unrestrictedIndex;
|
||||
const auto id = _shuffledDocuments[index];
|
||||
const auto document = _owner->document(id);
|
||||
if (document->sticker()) {
|
||||
return { document, index };
|
||||
}
|
||||
}
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddEmojiBuilderAction(
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -34,35 +157,23 @@ void AddEmojiBuilderAction(
|
||||
rpl::producer<std::vector<DocumentId>> documents,
|
||||
Fn<void(UserpicBuilder::Result)> &&done,
|
||||
bool isForum) {
|
||||
constexpr auto kTimeout = crl::time(1500);
|
||||
struct State final {
|
||||
State() {
|
||||
colorIndex = base::RandomIndex(std::numeric_limits<int>::max());
|
||||
}
|
||||
void next() {
|
||||
auto nextIndex = documentIndex.current() + 1;
|
||||
if (nextIndex >= shuffledDocuments.size()) {
|
||||
nextIndex = 0;
|
||||
}
|
||||
documentIndex = nextIndex;
|
||||
}
|
||||
void documentShown() {
|
||||
if (!firstDocumentShown) {
|
||||
firstDocumentShown = true;
|
||||
} else {
|
||||
colorIndex = base::RandomIndex(
|
||||
std::numeric_limits<int>::max());
|
||||
}
|
||||
timer.callOnce(kTimeout);
|
||||
}
|
||||
rpl::variable<int> documentIndex;
|
||||
rpl::variable<int> colorIndex;
|
||||
std::vector<DocumentId> shuffledDocuments;
|
||||
bool firstDocumentShown = false;
|
||||
|
||||
base::Timer timer;
|
||||
struct State final {
|
||||
State(not_null<Window::SessionController*> controller)
|
||||
: manager(&controller->session().data())
|
||||
, colorIndex(rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
manager.documentChanged() | rpl::skip(1) | rpl::to_empty
|
||||
) | rpl::map([] {
|
||||
return base::RandomIndex(std::numeric_limits<int>::max());
|
||||
})) {
|
||||
}
|
||||
|
||||
StickerProvider manager;
|
||||
rpl::variable<int> colorIndex;
|
||||
};
|
||||
const auto state = menu->lifetime().make_state<State>();
|
||||
const auto state = menu->lifetime().make_state<State>(controller);
|
||||
auto item = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu.get(),
|
||||
menu->st().menu,
|
||||
@@ -70,10 +181,7 @@ void AddEmojiBuilderAction(
|
||||
menu.get(),
|
||||
tr::lng_attach_profile_emoji(tr::now),
|
||||
[=, done = std::move(done), docs = rpl::duplicate(documents)] {
|
||||
const auto index = state->documentIndex.current();
|
||||
const auto id = index < state->shuffledDocuments.size()
|
||||
? state->shuffledDocuments[index]
|
||||
: 0;
|
||||
const auto id = state->manager.documentId();
|
||||
UserpicBuilder::ShowLayer(
|
||||
controller,
|
||||
{ id, state->colorIndex.current(), docs, {}, isForum },
|
||||
@@ -81,29 +189,10 @@ void AddEmojiBuilderAction(
|
||||
}),
|
||||
nullptr,
|
||||
nullptr);
|
||||
state->timer.setCallback([=] { state->next(); });
|
||||
const auto icon = UserpicBuilder::CreateEmojiUserpic(
|
||||
item.get(),
|
||||
st::restoreUserpicIcon.size,
|
||||
state->documentIndex.value(
|
||||
) | rpl::filter([=](int index) {
|
||||
if (index >= state->shuffledDocuments.size()) {
|
||||
state->next();
|
||||
return false;
|
||||
}
|
||||
const auto id = state->shuffledDocuments[index];
|
||||
if (!controller->session().data().document(id)->sticker()) {
|
||||
state->next();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}) | rpl::map([=](int index) {
|
||||
return controller->session().data().customEmojiManager().resolve(
|
||||
state->shuffledDocuments[index]);
|
||||
}) | rpl::flatten_latest() | rpl::map([=](not_null<DocumentData*> d) {
|
||||
state->documentShown();
|
||||
return d;
|
||||
}),
|
||||
state->manager.documentChanged(),
|
||||
state->colorIndex.value(),
|
||||
isForum);
|
||||
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
@@ -115,13 +204,7 @@ void AddEmojiBuilderAction(
|
||||
rpl::duplicate(
|
||||
documents
|
||||
) | rpl::start_with_next([=](std::vector<DocumentId> documents) {
|
||||
if (documents.empty()) {
|
||||
return;
|
||||
}
|
||||
auto rd = std::random_device();
|
||||
ranges::shuffle(documents, std::mt19937(rd()));
|
||||
state->shuffledDocuments = std::move(documents);
|
||||
state->documentIndex.force_assign(0);
|
||||
state->manager.setDocuments(std::move(documents));
|
||||
}, item->lifetime());
|
||||
|
||||
menu->addAction(std::move(item));
|
||||
|
||||
@@ -118,12 +118,10 @@ void PreviewPainter::paintBackground(QPainter &p, const QImage &image) {
|
||||
|
||||
bool PreviewPainter::paintForeground(QPainter &p) {
|
||||
if (_player && _player->ready()) {
|
||||
// resolveIsColored();
|
||||
const auto c = _media->owner()->emojiUsesTextColor() ? 255 : 0;
|
||||
auto frame = _player->frame(
|
||||
Size(_emojiSize),
|
||||
(/*_isColored
|
||||
? st::profileVerifiedCheckBg->c
|
||||
: */QColor(0, 0, 0, 0)),
|
||||
QColor(c, c, c, c),
|
||||
false,
|
||||
crl::now(),
|
||||
_paused);
|
||||
|
||||
@@ -97,7 +97,7 @@ void AlignChildren(not_null<Ui::RpWidget*> widget, int fullWidth) {
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::vector<QColor>> PaletteGradients() {
|
||||
return std::vector<std::vector<QColor>>{
|
||||
auto v = std::vector<std::vector<QColor>>{
|
||||
{
|
||||
QColor(32, 226, 205),
|
||||
QColor(14, 225, 241),
|
||||
@@ -141,6 +141,12 @@ void AlignChildren(not_null<Ui::RpWidget*> widget, int fullWidth) {
|
||||
QColor(176, 99, 255),
|
||||
},
|
||||
};
|
||||
for (auto &g : v) {
|
||||
// Rotate 180 degrees.
|
||||
std::swap(g[0], g[2]);
|
||||
std::swap(g[1], g[3]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void ShowGradientEditor(
|
||||
@@ -244,7 +250,7 @@ EmojiSelector::Selector EmojiSelector::createEmojiList(
|
||||
.customRecentFactory = [=](DocumentId id, Fn<void()> repaint) {
|
||||
return manager->create(id, std::move(repaint), tag);
|
||||
},
|
||||
.st = &st::reactPanelEmojiPan,
|
||||
.st = &st::userpicBuilderEmojiPan,
|
||||
};
|
||||
const auto list = scroll->setOwnedWidget(
|
||||
object_ptr<ChatHelpers::EmojiListWidget>(scroll, std::move(args)));
|
||||
@@ -268,7 +274,8 @@ EmojiSelector::Selector EmojiSelector::createStickersList(
|
||||
object_ptr<ChatHelpers::StickersListWidget>(
|
||||
scroll,
|
||||
_controller,
|
||||
Window::GifPauseReason::Any));
|
||||
Window::GifPauseReason::Any,
|
||||
ChatHelpers::StickersListMode::UserpicBuilder));
|
||||
const auto footer = list->createFooter().data();
|
||||
list->refreshRecent();
|
||||
list->chosen(
|
||||
@@ -412,7 +419,8 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
|
||||
data.isForum)),
|
||||
st::userpicBuilderEmojiPreviewPadding)->entity();
|
||||
if (const auto id = data.documentId) {
|
||||
if (const auto document = controller->session().data().document(id)) {
|
||||
const auto document = controller->session().data().document(id);
|
||||
if (document && document->sticker()) {
|
||||
preview->setDocument(document);
|
||||
}
|
||||
}
|
||||
@@ -428,8 +436,13 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
|
||||
|
||||
const auto paletteBg = Ui::AddBubbleWrap(
|
||||
container,
|
||||
st::userpicBuilderEmojiBubblePaletteSize);
|
||||
const auto palette = Ui::CreateChild<Ui::RpWidget>(paletteBg.get());
|
||||
QSize(
|
||||
st::userpicBuilderEmojiBubblePaletteWidth,
|
||||
std::abs(Ui::BubbleWrapInnerRect(QRect(0, 0, 0, 0)).height())
|
||||
+ st::userpicBuilderEmojiAccentColorSize
|
||||
+ rect::m::sum::v(
|
||||
st::userpicBuilderEmojiBubblePalettePadding)));
|
||||
const auto palette = Ui::CreateChild<Ui::VerticalLayout>(paletteBg.get());
|
||||
{
|
||||
constexpr auto kColorsCount = int(7);
|
||||
const auto checkIsSpecial = [=](int i) {
|
||||
@@ -516,7 +529,7 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
|
||||
const auto selectorBg = Ui::AddBubbleWrap(
|
||||
container,
|
||||
QSize(
|
||||
st::userpicBuilderEmojiBubblePaletteSize.width(),
|
||||
st::userpicBuilderEmojiBubblePaletteWidth,
|
||||
st::userpicBuilderEmojiSelectorMinHeight));
|
||||
const auto selector = Ui::CreateChild<EmojiSelector>(
|
||||
selectorBg.get(),
|
||||
|
||||
@@ -1044,17 +1044,32 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<Api::SendAction()> actionFactory,
|
||||
Fn<void(bool)> attach) {
|
||||
if (!Data::CanSend(peer, ChatRestriction::SendInline)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Ui::DropdownMenu>(
|
||||
parent,
|
||||
st::dropdownMenuWithIcons);
|
||||
const auto bots = &peer->session().attachWebView();
|
||||
const auto raw = result.get();
|
||||
raw->addAction(tr::lng_attach_photo_or_video(tr::now), [=] {
|
||||
attach(true);
|
||||
}, &st::menuIconPhoto);
|
||||
raw->addAction(tr::lng_attach_document(tr::now), [=] {
|
||||
attach(false);
|
||||
}, &st::menuIconFile);
|
||||
auto minimal = 0;
|
||||
if (Data::CanSend(peer, ChatRestriction::SendPhotos, false)) {
|
||||
++minimal;
|
||||
raw->addAction(tr::lng_attach_photo_or_video(tr::now), [=] {
|
||||
attach(true);
|
||||
}, &st::menuIconPhoto);
|
||||
}
|
||||
const auto fileTypes = ChatRestriction::SendVideos
|
||||
| ChatRestriction::SendGifs
|
||||
| ChatRestriction::SendStickers
|
||||
| ChatRestriction::SendMusic
|
||||
| ChatRestriction::SendFiles;
|
||||
if (Data::CanSendAnyOf(peer, fileTypes)) {
|
||||
++minimal;
|
||||
raw->addAction(tr::lng_attach_document(tr::now), [=] {
|
||||
attach(false);
|
||||
}, &st::menuIconFile);
|
||||
}
|
||||
for (const auto &bot : bots->attachBots()) {
|
||||
if (!PeerMatchesTypes(peer, bot.user, bot.types)) {
|
||||
continue;
|
||||
@@ -1082,7 +1097,7 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
||||
}, action->lifetime());
|
||||
raw->addAction(std::move(action));
|
||||
}
|
||||
if (raw->actions().size() < 3) {
|
||||
if (raw->actions().size() <= minimal) {
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -158,18 +158,18 @@ Language ParseLanguage(const MTPLangPackLanguage &data) {
|
||||
|
||||
CloudManager::CloudManager(Instance &langpack)
|
||||
: _langpack(langpack) {
|
||||
const auto mtpLifetime = _lifetime.make_state<rpl::lifetime>();
|
||||
Core::App().domain().activeValue(
|
||||
) | rpl::map([=](Main::Account *account) {
|
||||
if (!account) {
|
||||
_api.reset();
|
||||
}
|
||||
return account
|
||||
? account->mtpMainSessionValue()
|
||||
: rpl::never<not_null<MTP::Instance*>>();
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
|
||||
_api.emplace(instance);
|
||||
resendRequests();
|
||||
) | rpl::filter([=](Main::Account *account) {
|
||||
return (account != nullptr);
|
||||
}) | rpl::start_with_next_done([=](Main::Account *account) {
|
||||
*mtpLifetime = account->mtpMainSessionValue(
|
||||
) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
|
||||
_api.emplace(instance);
|
||||
resendRequests();
|
||||
});
|
||||
}, [=] {
|
||||
_api.reset();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
|
||||
@@ -202,8 +202,6 @@ Account &Domain::active() const {
|
||||
return *_active.current();
|
||||
}
|
||||
|
||||
|
||||
|
||||
rpl::producer<not_null<Account*>> Domain::activeChanges() const {
|
||||
return _active.changes() | rpl::map([](Account *value) {
|
||||
return not_null{ value };
|
||||
|
||||
@@ -79,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/download_path_box.h"
|
||||
#include "boxes/connection_box.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "media/player/media_player_panel.h"
|
||||
#include "media/player/media_player_widget.h"
|
||||
@@ -1265,6 +1266,8 @@ bool MainWidget::showHistoryInDifferentWindow(
|
||||
const SectionShow ¶ms,
|
||||
MsgId showAtMsgId) {
|
||||
const auto peer = session().data().peer(peerId);
|
||||
const auto account = &session().account();
|
||||
auto primary = Core::App().separateWindowForAccount(account);
|
||||
if (const auto separate = Core::App().separateWindowForPeer(peer)) {
|
||||
if (separate == &_controller->window()) {
|
||||
return false;
|
||||
@@ -1276,8 +1279,6 @@ bool MainWidget::showHistoryInDifferentWindow(
|
||||
separate->activate();
|
||||
return true;
|
||||
} else if (isPrimary()) {
|
||||
const auto primary = Core::App().separateWindowForAccount(
|
||||
&peer->account());
|
||||
if (primary && primary != &_controller->window()) {
|
||||
primary->sessionController()->showPeerHistory(
|
||||
peerId,
|
||||
@@ -1291,18 +1292,17 @@ bool MainWidget::showHistoryInDifferentWindow(
|
||||
return true;
|
||||
} else if (singlePeer()->id == peerId) {
|
||||
return false;
|
||||
} else if (!primary) {
|
||||
Core::App().domain().activate(account);
|
||||
primary = Core::App().separateWindowForAccount(account);
|
||||
}
|
||||
const auto primary = Core::App().activePrimaryWindow();
|
||||
if (&primary->account() != &session().account()) {
|
||||
primary->showAccount(&session().account());
|
||||
}
|
||||
if (&primary->account() == &session().account()) {
|
||||
if (primary && &primary->account() == account) {
|
||||
primary->sessionController()->showPeerHistory(
|
||||
peerId,
|
||||
params,
|
||||
showAtMsgId);
|
||||
primary->activate();
|
||||
}
|
||||
primary->activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,6 @@ void MainWindow::setupPasscodeLock() {
|
||||
_passcodeLock.create(bodyWidget(), &controller());
|
||||
updateControlsGeometry();
|
||||
|
||||
Core::App().hideMediaView();
|
||||
ui_hideSettingsAndLayer(anim::type::instant);
|
||||
if (_main) {
|
||||
_main->hide();
|
||||
|
||||
@@ -3380,6 +3380,13 @@ void OverlayWidget::switchToPip() {
|
||||
}) | rpl::start_with_next([=] {
|
||||
_pip = nullptr;
|
||||
}, _pip->lifetime);
|
||||
|
||||
Core::App().passcodeLockChanges(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1
|
||||
) | rpl::start_with_next([=] {
|
||||
_pip = nullptr;
|
||||
}, _pip->lifetime);
|
||||
}
|
||||
|
||||
if (isHidden()) {
|
||||
|
||||
@@ -65,7 +65,7 @@ QByteArray DnsUserAgent() {
|
||||
static const auto kResult = QByteArray(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/108.0.5359.98 Safari/537.36");
|
||||
"Chrome/109.0.5414.74 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -611,6 +611,10 @@ void CheckoutProcess::panelWebviewMessage(
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<QDate> CheckoutProcess::panelOverrideExpireDateThreshold() {
|
||||
return _form->overrideExpireDateThreshold();
|
||||
}
|
||||
|
||||
bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) {
|
||||
if (Core::TryConvertUrlToLocal(uri) == uri) {
|
||||
return true;
|
||||
|
||||
@@ -151,6 +151,8 @@ private:
|
||||
|
||||
QString panelWebviewDataPath() override;
|
||||
|
||||
std::optional<QDate> panelOverrideExpireDateThreshold() override;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const std::unique_ptr<Form> _form;
|
||||
const std::unique_ptr<Ui::Panel> _panel;
|
||||
|
||||
@@ -682,6 +682,13 @@ void Form::submit(const Core::CloudPasswordResult &result) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
std::optional<QDate> Form::overrideExpireDateThreshold() const {
|
||||
const auto phone = _session->user()->phone();
|
||||
return phone.startsWith('7')
|
||||
? QDate(2022, 2, 1)
|
||||
: std::optional<QDate>();
|
||||
}
|
||||
|
||||
void Form::validateInformation(const Ui::RequestedInformation &information) {
|
||||
if (_validateRequestId) {
|
||||
if (_validatedInformation == information) {
|
||||
@@ -795,7 +802,7 @@ void Form::validateCard(
|
||||
bool saveInformation) {
|
||||
Expects(!v::is_null(_paymentMethod.native.data));
|
||||
|
||||
if (!validateCardLocal(details)) {
|
||||
if (!validateCardLocal(details, overrideExpireDateThreshold())) {
|
||||
return;
|
||||
}
|
||||
const auto &native = _paymentMethod.native.data;
|
||||
@@ -809,15 +816,19 @@ void Form::validateCard(
|
||||
}
|
||||
}
|
||||
|
||||
bool Form::validateCardLocal(const Ui::UncheckedCardDetails &details) const {
|
||||
if (auto error = cardErrorLocal(details)) {
|
||||
bool Form::validateCardLocal(
|
||||
const Ui::UncheckedCardDetails &details,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) const {
|
||||
if (auto error = cardErrorLocal(details, overrideExpireDateThreshold)) {
|
||||
_updates.fire(std::move(error));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Error Form::cardErrorLocal(const Ui::UncheckedCardDetails &details) const {
|
||||
Error Form::cardErrorLocal(
|
||||
const Ui::UncheckedCardDetails &details,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) const {
|
||||
using namespace Stripe;
|
||||
|
||||
auto errors = QStringList();
|
||||
@@ -830,7 +841,8 @@ Error Form::cardErrorLocal(const Ui::UncheckedCardDetails &details) const {
|
||||
}
|
||||
if (ValidateParsedExpireDate(
|
||||
details.expireMonth,
|
||||
details.expireYear
|
||||
details.expireYear,
|
||||
overrideExpireDateThreshold
|
||||
) != kValid) {
|
||||
push(u"LOCAL_CARD_EXPIRE_DATE_INVALID"_q);
|
||||
}
|
||||
|
||||
@@ -218,6 +218,8 @@ public:
|
||||
return _updates.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<QDate> overrideExpireDateThreshold() const;
|
||||
|
||||
void validateInformation(const Ui::RequestedInformation &information);
|
||||
void validateCard(
|
||||
const Ui::UncheckedCardDetails &details,
|
||||
@@ -284,9 +286,11 @@ private:
|
||||
const Ui::RequestedInformation &information) const;
|
||||
|
||||
bool validateCardLocal(
|
||||
const Ui::UncheckedCardDetails &details) const;
|
||||
const Ui::UncheckedCardDetails &details,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) const;
|
||||
[[nodiscard]] Error cardErrorLocal(
|
||||
const Ui::UncheckedCardDetails &details) const;
|
||||
const Ui::UncheckedCardDetails &details,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) const;
|
||||
|
||||
const InvoiceId _id;
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
@@ -193,11 +193,12 @@ CardValidationResult ValidateCard(const QString &number) {
|
||||
}
|
||||
const auto range = MostSpecificBinRangeForNumber(sanitized);
|
||||
const auto brand = range.brand;
|
||||
if (sanitized.size() > range.length) {
|
||||
return { .state = ValidationState::Invalid, .brand = brand };
|
||||
} else if (sanitized.size() < range.length) {
|
||||
return { .state = ValidationState::Incomplete, .brand = brand };
|
||||
} else if (!IsValidLuhn(sanitized)) {
|
||||
//if (sanitized.size() > range.length) {
|
||||
// return { .state = ValidationState::Invalid, .brand = brand };
|
||||
//} else if (sanitized.size() < range.length) {
|
||||
// return { .state = ValidationState::Incomplete, .brand = brand };
|
||||
//} else
|
||||
if (!IsValidLuhn(sanitized)) {
|
||||
return { .state = ValidationState::Invalid, .brand = brand };
|
||||
}
|
||||
return {
|
||||
@@ -207,7 +208,9 @@ CardValidationResult ValidateCard(const QString &number) {
|
||||
};
|
||||
}
|
||||
|
||||
ExpireDateValidationResult ValidateExpireDate(const QString &date) {
|
||||
ExpireDateValidationResult ValidateExpireDate(
|
||||
const QString &date,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) {
|
||||
const auto sanitized = RemoveWhitespaces(date).replace('/', QString());
|
||||
if (!IsNumeric(sanitized)) {
|
||||
return { ValidationState::Invalid };
|
||||
@@ -225,12 +228,13 @@ ExpireDateValidationResult ValidateExpireDate(const QString &date) {
|
||||
}
|
||||
const auto year = 2000 + normalized.mid(2).toInt();
|
||||
|
||||
const auto currentDate = QDate::currentDate();
|
||||
const auto currentMonth = currentDate.month();
|
||||
const auto currentYear = currentDate.year();
|
||||
if (year < currentYear) {
|
||||
const auto thresholdDate = overrideExpireDateThreshold.value_or(
|
||||
QDate::currentDate());
|
||||
const auto thresholdMonth = thresholdDate.month();
|
||||
const auto thresholdYear = thresholdDate.year();
|
||||
if (year < thresholdYear) {
|
||||
return { ValidationState::Invalid };
|
||||
} else if (year == currentYear && month < currentMonth) {
|
||||
} else if (year == thresholdYear && month < thresholdMonth) {
|
||||
return { ValidationState::Invalid };
|
||||
}
|
||||
return { ValidationState::Valid, true };
|
||||
@@ -238,15 +242,16 @@ ExpireDateValidationResult ValidateExpireDate(const QString &date) {
|
||||
|
||||
ValidationState ValidateParsedExpireDate(
|
||||
quint32 month,
|
||||
quint32 year) {
|
||||
quint32 year,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) {
|
||||
if ((year / 100) != 20) {
|
||||
return ValidationState::Invalid;
|
||||
}
|
||||
return ValidateExpireDate(
|
||||
QString("%1%2"
|
||||
).arg(month, 2, 10, QChar('0')
|
||||
).arg(year % 100, 2, 10, QChar('0'))
|
||||
).state;
|
||||
const auto date = QString("%1%2"
|
||||
).arg(month, 2, 10, QChar('0')
|
||||
).arg(year % 100, 2, 10, QChar('0'));
|
||||
|
||||
return ValidateExpireDate(date, overrideExpireDateThreshold).state;
|
||||
}
|
||||
|
||||
CvcValidationResult ValidateCvc(
|
||||
|
||||
@@ -9,6 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "stripe/stripe_card.h"
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
class QDate;
|
||||
|
||||
namespace Stripe {
|
||||
|
||||
@@ -32,11 +35,13 @@ struct ExpireDateValidationResult {
|
||||
};
|
||||
|
||||
[[nodiscard]] ExpireDateValidationResult ValidateExpireDate(
|
||||
const QString &date);
|
||||
const QString &date,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold);
|
||||
|
||||
[[nodiscard]] ValidationState ValidateParsedExpireDate(
|
||||
quint32 month,
|
||||
quint32 year);
|
||||
quint32 year,
|
||||
const std::optional<QDate> &overrideExpireDateThreshold);
|
||||
|
||||
struct CvcValidationResult {
|
||||
ValidationState state = ValidationState::Invalid;
|
||||
|
||||
@@ -165,10 +165,11 @@ template <
|
||||
PostprocessCardValidateResult);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ExpireDateValidator() {
|
||||
return ComplexNumberValidator(
|
||||
Stripe::ValidateExpireDate,
|
||||
PostprocessExpireDateValidateResult);
|
||||
[[nodiscard]] auto ExpireDateValidator(
|
||||
const std::optional<QDate> &overrideExpireDateThreshold) {
|
||||
return ComplexNumberValidator([=](const QString &date) {
|
||||
return Stripe::ValidateExpireDate(date, overrideExpireDateThreshold);
|
||||
}, PostprocessExpireDateValidateResult);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto CvcValidator(Fn<QString()> number) {
|
||||
@@ -306,7 +307,8 @@ not_null<RpWidget*> EditCard::setupContent() {
|
||||
_expire = make(container, {
|
||||
.type = FieldType::CardExpireDate,
|
||||
.placeholder = tr::lng_payments_card_expire_date(),
|
||||
.validator = ExpireDateValidator(),
|
||||
.validator = ExpireDateValidator(
|
||||
_delegate->panelOverrideExpireDateThreshold()),
|
||||
});
|
||||
_cvc = make(container, {
|
||||
.type = FieldType::CardCVC,
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
virtual QVariant panelClickHandlerContext() = 0;
|
||||
|
||||
virtual QString panelWebviewDataPath() = 0;
|
||||
|
||||
virtual std::optional<QDate> panelOverrideExpireDateThreshold() = 0;
|
||||
};
|
||||
|
||||
} // namespace Payments::Ui
|
||||
|
||||
@@ -17,7 +17,6 @@ public:
|
||||
|
||||
private:
|
||||
void initHook() override;
|
||||
void initHighDpi() override;
|
||||
|
||||
bool launchUpdater(UpdaterLaunch action) override;
|
||||
|
||||
|
||||
@@ -27,11 +27,6 @@ void Launcher::initHook() {
|
||||
base::RegisterBundledResources(u"Telegram.rcc"_q);
|
||||
}
|
||||
|
||||
void Launcher::initHighDpi() {
|
||||
// macOS Retina display support is working fine.
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
}
|
||||
|
||||
bool Launcher::launchUpdater(UpdaterLaunch action) {
|
||||
if (cExeName().isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -226,7 +226,7 @@ MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
void MainWindow::closeWithoutDestroy() {
|
||||
NSWindow *nsWindow = [reinterpret_cast<NSView*>(winId()) window];
|
||||
|
||||
auto isFullScreen = (([nsWindow styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask);
|
||||
auto isFullScreen = (([nsWindow styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen);
|
||||
if (isFullScreen) {
|
||||
_hideAfterFullScreenTimer.callOnce(kHideAfterFullscreenTimeoutMs);
|
||||
[nsWindow toggleFullScreen:nsWindow];
|
||||
@@ -266,7 +266,7 @@ bool MainWindow::preventsQuit(Core::QuitReason reason) {
|
||||
// chromium.org/developers/design-documents/confirm-to-quit-experiment
|
||||
return (reason == Core::QuitReason::QtQuitEvent)
|
||||
&& Core::App().settings().macWarnBeforeQuit()
|
||||
&& ([[NSApp currentEvent] type] == NSKeyDown)
|
||||
&& ([[NSApp currentEvent] type] == NSEventTypeKeyDown)
|
||||
&& !Platform::ConfirmQuit::RunModal(
|
||||
tr::lng_mac_hold_to_quit(
|
||||
tr::now,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user