Compare commits

...

78 Commits

Author SHA1 Message Date
John Preston
4247fd0c0f Version 4.6.3.
- Optimize chats list initial loading.
- Various crash fixes.
2023-02-16 07:03:13 +04:00
John Preston
50f2e93623 Replace "Your Report Tag" with "Crash ID". 2023-02-15 22:32:11 +04:00
Vitaly Zaitsev
fbfa6966f4 Updated internal submodules. 2023-02-15 21:17:16 +04:00
Vitaly Zaitsev
42eb452de8 Updated GSL submodule to version 4.0.0. 2023-02-15 21:17:16 +04:00
Vitaly Zaitsev
9b3692ca22 Removed deprecated GSL headers usage. 2023-02-15 21:17:16 +04:00
Daniel Novomeský
7463aad755 Upgrade libde265, libheif, libjxl in Windows build 2023-02-15 20:19:13 +04:00
Daniel Novomeský
9cf419999d Update kimageformats submodule
Contains fix in HEIF Qt plug-in
2023-02-15 20:19:13 +04:00
John Preston
7557a20679 Close PiP when a passcode lock is enabled. 2023-02-15 10:47:10 +04:00
John Preston
91e6c42fcf Fix channel creation flow. 2023-02-15 10:14:50 +04:00
John Preston
3573b84e8b Fix drag area in comments appearance. 2023-02-15 10:02:14 +04:00
John Preston
bb900c195c Fix possible crash in top bar back button.
Fixes #25882.
2023-02-15 09:43:42 +04:00
John Preston
e61c058eb5 Fix account activation from main menu. 2023-02-15 08:44:24 +04:00
Vitaly Zaitsev
b48dee0af7 Fixed build with GCC 13.
Signed-off-by: Vitaly Zaitsev <vitaly@easycoding.org>
2023-02-14 21:00:37 +04:00
Ilya Fedin
6b6afd38ac Update submodules 2023-02-14 18:37:01 +04:00
Ilya Fedin
98e8a20f5f Fix missing include in settings_main 2023-02-14 18:37:01 +04:00
Ilya Fedin
aeeb9fe761 Fix macOS GH action 2023-02-14 18:37:01 +04:00
Ilya Fedin
c65b45460b Fix range-v3 deprecation warnings 2023-02-14 18:37:01 +04:00
Ilya Fedin
1940edd6ee Fix deprecated declarations warnings on macOS 2023-02-14 18:37:01 +04:00
Ilya Fedin
96ef82272b Use QT_DEPRECATED_WARNINGS_SINCE 2023-02-14 18:37:01 +04:00
John Preston
29d93d348c Fix possible crash in migration tracking. 2023-02-14 10:00:10 +04:00
Ilya Fedin
879df6e6a3 Present window's devicePixelRatio in the UI
It's a more valid value when Qt's rater downscaling is in effect

Also round it
2023-02-13 11:48:51 +04:00
John Preston
9d3715a36a Fix possible crash in members invite.
In some cases Info::Controller (public Window::SessionNavigation)
can be destroyed without the box being destroyed, for example when
group topics are turned on and off.
2023-02-13 06:09:55 +04:00
John Preston
1cb0d7c2dc Fix crash in emoji panel search mode toggle. 2023-02-12 15:04:57 +04:00
John Preston
1da635a5dd Fix crash in cloud lang manager destructor. 2023-02-12 15:01:50 +04:00
John Preston
d0c2bec925 Fix crash clearing chats list before histories. 2023-02-12 14:39:06 +04:00
Ilya Fedin
13a9920c11 Set QT_DPI_ADJUSTMENT_POLICY for older Qt to the new default value 2023-02-11 15:40:00 +04:00
Ilya Fedin
0b100884fc Round system scale in step of 5 points, just like in settings 2023-02-11 15:40:00 +04:00
Ilya Fedin
6e89d41d58 Use variables from style namespace instead of hardcoded scale limits 2023-02-11 15:40:00 +04:00
John Preston
20f946f657 Add empty emoji search icon. 2023-02-10 20:12:28 +04:00
John Preston
9e49e32702 Fix system buttons padding on Windows. 2023-02-10 20:12:17 +04:00
John Preston
6834cdb969 Check restrictions in attach menu bots. 2023-02-09 13:15:45 +04:00
John Preston
36bf54b0d1 Fix layout in blocked users list. 2023-02-09 13:05:21 +04:00
John Preston
8f908ab9c0 Show empty search results in emoji panel. 2023-02-09 12:55:44 +04:00
John Preston
cbd9dd0c2c Allow either category or set being selected. 2023-02-09 12:17:16 +04:00
Joe Kappus
a8decf154f Fix build with GCC12 2023-02-09 11:18:02 +04:00
John Preston
f2ed77649e Preload complex last message on demand. 2023-02-09 09:56:54 +04:00
John Preston
369862a3a7 Always use 64 bit build of dump_syms. 2023-02-09 09:56:54 +04:00
23rd
e6b24a49f6 Moved sticker randomization to class in menu item of userpic builder. 2023-02-09 08:36:59 +03:00
23rd
08644a9c31 Fixed search categories in list of stickers from userpic emoji builder. 2023-02-09 08:36:59 +03:00
23rd
9090b8ce6b Fixed color buttons in userpic emoji builder with different scales. 2023-02-09 08:36:59 +03:00
John Preston
646bb2ff71 Upload sources-full first in release script. 2023-02-09 08:12:38 +04:00
John Preston
7fa229537d Version 4.6.2: Fix translating to fallback language. 2023-02-08 18:12:08 +04:00
John Preston
c314e43a44 Fix sending / sent image icon in night-green theme. 2023-02-08 16:59:00 +04:00
John Preston
ee1162faff Version 4.6.2: Fix ghost drafts appearing in channels. 2023-02-08 15:48:05 +04:00
John Preston
84b4ab1c3c Version 4.6.2: Re-enable global /LTCG on Windows.
With /LTCGOUT: empty path it should not try generating .iobj file,
and work on both 32 bit and 64 bit build with all static libs.
2023-02-08 13:20:01 +04:00
John Preston
9659cb5b6f Version 4.6.2: Fix build on macOS. 2023-02-08 12:55:57 +04:00
Ilya Fedin
eee800b6d0 Use window widget's devicePixelRatio when displaying interface scale value
This is less confusing in multi-monitor environments
2023-02-08 12:18:21 +04:00
John Preston
41d9a9fcbd Version 4.6.2.
- One more attempt to fix fonts on Windows.
- Fix polls forwarding to private chats.
- Improve translations bar appearance.
- Improve userpic editor presets.
2023-02-07 21:27:33 +04:00
John Preston
fec80c0c64 Support text-color-ed emoji in userpic editor. 2023-02-07 20:43:50 +04:00
John Preston
ed9ba07a32 Disable local card number validation.
Some cards are reported invalid there that are valid.
2023-02-07 20:18:18 +04:00
John Preston
2f1c674401 Support markup in TranslateBox.
Server will translate with markup for premium users.
2023-02-07 20:06:21 +04:00
John Preston
663e89662b Bring "Do Not Translate" to "Translate To" list top. 2023-02-07 19:45:18 +04:00
John Preston
afd717b36e Ease local card expiry date check. 2023-02-07 19:13:07 +04:00
John Preston
ddfcf9f1df Don't allow empty "Do Not Translate" list. 2023-02-07 18:38:10 +04:00
John Preston
4a37846605 Feed initial messages slice to translation tracker. 2023-02-07 17:29:30 +04:00
John Preston
64f4e0dd52 Don't offer translate from / to the same language. 2023-02-07 16:38:16 +04:00
John Preston
d889cd0e72 Fix polls forwarding to private chats. 2023-02-07 16:28:59 +04:00
23rd
d7aa18cb0a Moved light part of palette gradients to top in userpic emoji builder. 2023-02-07 11:35:23 +04:00
23rd
e486cf1afa Fixed calculate of discount in premium subscription options. 2023-02-07 11:35:21 +04:00
John Preston
5f3e7235a5 Update submodules. 2023-02-07 11:35:17 +04:00
John Preston
26ff3148d6 One more fonts fix and fix paste from clipboard. 2023-02-07 11:31:19 +04:00
John Preston
f95610edfc Version 4.6.1: Add one more patch on Windows. 2023-02-06 23:04:40 +04:00
John Preston
0d134f2b89 Version 4.6.1.
- Fix fonts fallback on Windows.
- Fix crash in userpic editor.
- Fix some crashes on 32 bit Window build.
- Bug fixes and other minor improvements.
2023-02-06 20:42:51 +04:00
John Preston
1a9e217c3e Remove duplicate LTCG flags in td_scheme. 2023-02-06 20:41:33 +04:00
John Preston
c4402c717a Rebuild Qt with a patch for fonts fallback. 2023-02-06 20:39:00 +04:00
John Preston
0bdd0689c0 Create tray / mediaview queued on macOS.
This removes some strange redundant entries like "Item-0" from the Dock menu.
2023-02-06 13:34:03 +04:00
John Preston
671b3bc94e Improve cursor coordinate rounding.
Fixes #25838.
2023-02-06 12:44:30 +04:00
Joe Kappus
0943d3aac1 Add missing include to fix GCC12 build
Signed-off-by: Joe Kappus <joe@wt.gd>
2023-02-06 12:43:55 +04:00
John Preston
405230c0c6 Don't request settings of unknown peers. 2023-02-06 12:24:08 +04:00
John Preston
af8c1f77c7 Change td_ui to a static library.
This (temporarily) fixes LTCG linking on Windows 32 bit.

I hope this fixes #25832.
2023-02-06 10:37:52 +04:00
John Preston
81fb4046d1 Fix possible crash with pinned message deletion. 2023-02-06 10:36:21 +04:00
John Preston
fb283c4828 Fix bot buttons disappearing on chat switch. 2023-02-06 09:12:06 +04:00
John Preston
6f797a17ad Fix activating chat list entry from global search. 2023-02-04 21:19:46 +04:00
Ilya Fedin
326b4eb10d Add the package with C.UTF-8 locale to snap 2023-02-04 11:29:00 +04:00
John Preston
713889aa9c Make sure the device model isn't empty.
I hope this fixes #25824.
2023-02-04 10:23:26 +04:00
GitHub Action
a6aa759947 Update User-Agent for DNS to Chrome 109.0.5414.74. 2023-02-04 09:56:19 +04:00
John Preston
985db8aacf Fix a possible crash in userpic editor. 2023-02-04 09:56:01 +04:00
John Preston
dff1568cb2 Fix "Do Not Translate" button visibility. 2023-02-04 09:37:36 +04:00
133 changed files with 1265 additions and 537 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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[] = {

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -211,7 +211,7 @@ not_null<Dialogs::MainList*> Folder::chatsList() {
return &_chatsList;
}
void Folder::loadUserpic() {
void Folder::chatListPreloadData() {
}
void Folder::paintUserpic(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -230,7 +230,7 @@ public:
return nullptr;
}
virtual void loadUserpic() = 0;
virtual void chatListPreloadData() = 0;
virtual void paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,

View File

@@ -205,6 +205,7 @@ void IndexedList::remove(Key key, Row *replacedBy) {
}
void IndexedList::clear() {
_list.clear();
_index.clear();
}

View File

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

View File

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

View File

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

View File

@@ -81,6 +81,7 @@ void MainList::clear() {
recomputeFullListSize();
});
const auto notifier = unreadStateChangeNotifier(true);
_pinned.clear();
_all.clear();
_unreadState = UnreadState();
_cloudUnreadState = UnreadState();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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([=] {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -194,7 +194,6 @@ void MainWindow::setupPasscodeLock() {
_passcodeLock.create(bodyWidget(), &controller());
updateControlsGeometry();
Core::App().hideMediaView();
ui_hideSettingsAndLayer(anim::type::instant);
if (_main) {
_main->hide();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,8 @@ public:
virtual QVariant panelClickHandlerContext() = 0;
virtual QString panelWebviewDataPath() = 0;
virtual std::optional<QDate> panelOverrideExpireDateThreshold() = 0;
};
} // namespace Payments::Ui

View File

@@ -17,7 +17,6 @@ public:
private:
void initHook() override;
void initHighDpi() override;
bool launchUpdater(UpdaterLaunch action) override;

View File

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

View File

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