Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae261fcede | ||
|
|
c04cdff7f7 | ||
|
|
466aa5a14d | ||
|
|
4aac633413 | ||
|
|
ad328d35a2 | ||
|
|
c5140f34a7 | ||
|
|
419f6345b3 | ||
|
|
c2c53df886 | ||
|
|
b3f73bb6a9 | ||
|
|
eda5cd47ad | ||
|
|
0c906a5e6d | ||
|
|
352768053d | ||
|
|
79b1cec4f3 | ||
|
|
8d09190439 | ||
|
|
5cd0a3719e | ||
|
|
8b7cd4a0c7 | ||
|
|
937c2d3dce | ||
|
|
1fa5d273cc | ||
|
|
24fa3dbf8f | ||
|
|
c9b782fd63 | ||
|
|
e7cf560da0 | ||
|
|
86e07518ad | ||
|
|
8c71d03959 | ||
|
|
967e86f4ab | ||
|
|
730412fefe | ||
|
|
576883ddc8 | ||
|
|
992d636680 | ||
|
|
8cdd2f113f | ||
|
|
d5f935b73d | ||
|
|
84f561b251 | ||
|
|
21ac2b8f3a | ||
|
|
1790828b01 | ||
|
|
792b9090a7 | ||
|
|
8c21fad642 | ||
|
|
5136cc3c9c | ||
|
|
b78b27f517 | ||
|
|
85760ea92c | ||
|
|
c2212c719e | ||
|
|
fc8a0d0efd | ||
|
|
c052c37621 | ||
|
|
21f7cec781 | ||
|
|
64af456d29 | ||
|
|
7751c4ac1f | ||
|
|
ececdcb9c0 | ||
|
|
cb8f49aea0 | ||
|
|
e3ef7d6631 | ||
|
|
21aa1f49d7 | ||
|
|
51e80170e2 | ||
|
|
b2526ab7f6 | ||
|
|
e220447bdd | ||
|
|
ead695b101 | ||
|
|
4ea72f8f89 | ||
|
|
4ef550da9b | ||
|
|
1e660fc2a2 | ||
|
|
6adf791b3b | ||
|
|
d2a41a42e0 | ||
|
|
315549b5f8 | ||
|
|
fd4a543bab | ||
|
|
d525e56053 | ||
|
|
dab5d1f994 | ||
|
|
de3b52425c | ||
|
|
844fd58a97 | ||
|
|
de2bad51d3 | ||
|
|
1424ea3540 | ||
|
|
a8efd0ef3d | ||
|
|
1204e282d3 | ||
|
|
6588242793 | ||
|
|
b1ba9a42c6 | ||
|
|
ab0d2bf9c6 | ||
|
|
80028e41f3 | ||
|
|
2c581adc55 | ||
|
|
f0e8c1e325 | ||
|
|
a2db9de4d7 | ||
|
|
a228c62286 | ||
|
|
37d940eca6 | ||
|
|
f7c24c54a1 | ||
|
|
19ce1edc16 | ||
|
|
21b10cebe0 | ||
|
|
50435f7783 | ||
|
|
1b789de4f4 | ||
|
|
a50310f0c1 | ||
|
|
eb02a7861a | ||
|
|
8759ca4577 | ||
|
|
d5c6d9a231 | ||
|
|
63f179e93e | ||
|
|
cfcc1b1ce7 | ||
|
|
da1945d0ca | ||
|
|
12252ef1aa | ||
|
|
1eef94e8d9 | ||
|
|
0984e631fa | ||
|
|
ec064a904d | ||
|
|
b47692e920 | ||
|
|
132f127f3f | ||
|
|
5c44b851fe | ||
|
|
2f5bed2899 | ||
|
|
cf76933352 | ||
|
|
eaa4c5e5b1 | ||
|
|
a4b5b6e370 | ||
|
|
c1be1ca4ae | ||
|
|
b2df781b76 | ||
|
|
38815c1ca8 | ||
|
|
2ec92f541c | ||
|
|
7ce8b42216 | ||
|
|
17511749de | ||
|
|
4f6c7657bf | ||
|
|
54085c70a4 | ||
|
|
e6c4b96c54 | ||
|
|
b75221737a | ||
|
|
c336d725ea | ||
|
|
d0fcc40d25 | ||
|
|
422bfd973b | ||
|
|
d4db679ce8 | ||
|
|
2c7d8858c0 | ||
|
|
155bbed3f4 | ||
|
|
b1517c68fb | ||
|
|
d206ba7e1d | ||
|
|
af100c2d13 | ||
|
|
1f25777929 | ||
|
|
a566405598 | ||
|
|
b02967a44e | ||
|
|
e0135e509d | ||
|
|
8274fddcbc | ||
|
|
82c45871c7 | ||
|
|
2164caaab7 | ||
|
|
f4b162cbaf | ||
|
|
4bc4584868 | ||
|
|
890a126423 | ||
|
|
42cc24e167 |
15
.github/workflows/win.yml
vendored
@@ -259,12 +259,17 @@ jobs:
|
||||
- name: Opus.
|
||||
if: steps.cache-opus.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/opus.git
|
||||
git clone -b v1.3.1 %GIT%/xiph/opus.git
|
||||
cd opus
|
||||
git checkout tdesktop
|
||||
cd win32\VS2015
|
||||
msbuild -m opus.sln /property:Configuration=Debug /property:Platform="Win32"
|
||||
msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32"
|
||||
git cherry-pick 927de8453c
|
||||
cmake -B out . ^
|
||||
-A Win32 ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LibrariesPath%/local/opus ^
|
||||
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
|
||||
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
|
||||
cmake --build out --config Debug
|
||||
cmake --build out --config Release
|
||||
cmake --install out --config Release
|
||||
|
||||
- name: Rnnoise.
|
||||
run: |
|
||||
|
||||
@@ -42,7 +42,12 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
generate_midl(Telegram ${src_loc}
|
||||
platform/win/windows_quiethours.idl
|
||||
platform/win/windows_toastactivator.idl
|
||||
)
|
||||
|
||||
nuget_add_winrt(Telegram)
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
@@ -137,6 +142,8 @@ PRIVATE
|
||||
api/api_updates.h
|
||||
api/api_user_privacy.cpp
|
||||
api/api_user_privacy.h
|
||||
api/api_views.cpp
|
||||
api/api_views.h
|
||||
api/api_who_read.cpp
|
||||
api/api_who_read.h
|
||||
boxes/filters/edit_filter_box.cpp
|
||||
@@ -167,6 +174,8 @@ PRIVATE
|
||||
boxes/peers/edit_peer_permissions_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
boxes/about_sponsored_box.h
|
||||
boxes/abstract_box.cpp
|
||||
boxes/abstract_box.h
|
||||
boxes/add_contact_box.cpp
|
||||
@@ -413,6 +422,7 @@ PRIVATE
|
||||
data/data_media_types.h
|
||||
data/data_messages.cpp
|
||||
data/data_messages.h
|
||||
data/data_msg_id.h
|
||||
data/data_notify_settings.cpp
|
||||
data/data_notify_settings.h
|
||||
data/data_peer.cpp
|
||||
@@ -445,6 +455,8 @@ PRIVATE
|
||||
data/data_shared_media.h
|
||||
data/data_sparse_ids.cpp
|
||||
data/data_sparse_ids.h
|
||||
data/data_sponsored_messages.cpp
|
||||
data/data_sponsored_messages.h
|
||||
data/data_streaming.cpp
|
||||
data/data_streaming.h
|
||||
data/data_types.cpp
|
||||
@@ -465,8 +477,6 @@ PRIVATE
|
||||
dialogs/dialogs_inner_widget.h
|
||||
dialogs/dialogs_key.cpp
|
||||
dialogs/dialogs_key.h
|
||||
dialogs/dialogs_layout.cpp
|
||||
dialogs/dialogs_layout.h
|
||||
dialogs/dialogs_list.cpp
|
||||
dialogs/dialogs_list.h
|
||||
dialogs/dialogs_main_list.cpp
|
||||
@@ -479,6 +489,10 @@ PRIVATE
|
||||
dialogs/dialogs_search_from_controllers.h
|
||||
dialogs/dialogs_widget.cpp
|
||||
dialogs/dialogs_widget.h
|
||||
dialogs/ui/dialogs_layout.cpp
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
editor/color_picker.cpp
|
||||
editor/color_picker.h
|
||||
editor/controllers/controllers.h
|
||||
@@ -619,6 +633,8 @@ PRIVATE
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
history/view/history_view_top_bar_widget.h
|
||||
history/view/history_view_view_button.cpp
|
||||
history/view/history_view_view_button.h
|
||||
history/view/history_view_webpage_preview.cpp
|
||||
history/view/history_view_webpage_preview.h
|
||||
history/history.cpp
|
||||
@@ -629,6 +645,8 @@ PRIVATE
|
||||
history/history_item.h
|
||||
history/history_item_components.cpp
|
||||
history/history_item_components.h
|
||||
history/history_item_reply_markup.cpp
|
||||
history/history_item_reply_markup.h
|
||||
history/history_item_text.cpp
|
||||
history/history_item_text.h
|
||||
history/history_inner_widget.cpp
|
||||
@@ -941,6 +959,8 @@ PRIVATE
|
||||
platform/win/windows_dlls.h
|
||||
platform/win/windows_event_filter.cpp
|
||||
platform/win/windows_event_filter.h
|
||||
platform/win/windows_toast_activator.cpp
|
||||
platform/win/windows_toast_activator.h
|
||||
platform/platform_audio.h
|
||||
platform/platform_file_utilities.h
|
||||
platform/platform_launcher.h
|
||||
@@ -1038,6 +1058,8 @@ PRIVATE
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
ui/chat/attach/attach_item_single_media_preview.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
@@ -1366,12 +1388,14 @@ if (WIN32)
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
BIN
Telegram/Resources/day-custom-base.tdesktop-theme
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play.png
Normal file
|
After Width: | Height: | Size: 165 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@2x.png
Normal file
|
After Width: | Height: | Size: 200 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@3x.png
Normal file
|
After Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 453 B |
|
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 973 B |
|
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 651 B After Width: | Height: | Size: 710 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1335,6 +1335,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forwarded_imported" = "This message was imported from another app. It may not be real.";
|
||||
"lng_signed_author" = "Author: {user}";
|
||||
"lng_in_reply_to" = "In reply to";
|
||||
"lng_sponsored" = "sponsored";
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
@@ -1618,7 +1619,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_user_action_upload_file" = "{user} is sending a file";
|
||||
"lng_send_action_choose_sticker" = "choosing a sticker";
|
||||
"lng_user_action_choose_sticker" = "{user} is choosing a sticker";
|
||||
"lng_user_action_watching_animations" = "watching {emoji} animations";
|
||||
"lng_user_action_watching_animations" = "watching {emoji}";
|
||||
"lng_unread_bar#one" = "{count} unread message";
|
||||
"lng_unread_bar#other" = "{count} unread messages";
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
@@ -2239,7 +2240,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_recording_start_checkbox" = "Also record video";
|
||||
"lng_group_call_recording_start_audio_subtitle" = "This chat will be recorded into an audio file";
|
||||
"lng_group_call_recording_start_video_subtitle" = "Choose video orientation";
|
||||
"lng_group_call_is_recorded" = "Voice chat is being recorded.";
|
||||
"lng_group_call_is_recorded" = "The audio stream is being recorded.";
|
||||
"lng_group_call_is_recorded_video" = "The video stream is being recorded.";
|
||||
"lng_group_call_is_recorded_channel" = "Live stream is being recorded.";
|
||||
"lng_group_call_can_speak_here" = "You can now speak.";
|
||||
"lng_group_call_can_speak" = "You can now speak in {chat}.";
|
||||
@@ -2870,6 +2872,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
|
||||
"lng_filters_remove_yes" = "Remove";
|
||||
|
||||
"lng_chat_theme_change" = "Change colors";
|
||||
"lng_chat_theme_none" = "No\nTheme";
|
||||
"lng_chat_theme_apply" = "Apply Theme";
|
||||
"lng_chat_theme_reset" = "Reset Theme";
|
||||
"lng_chat_theme_dont" = "Do Not Set Theme";
|
||||
"lng_chat_theme_title" = "Select theme";
|
||||
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
|
||||
|
||||
"lng_photo_editor_menu_delete" = "Delete";
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
@@ -2879,6 +2889,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_voice_speed_fast" = "Fast";
|
||||
"lng_voice_speed_very_fast" = "Very fast";
|
||||
|
||||
"lng_view_button_user" = "View user";
|
||||
"lng_view_button_bot" = "View bot";
|
||||
"lng_view_button_group" = "View group";
|
||||
"lng_view_button_channel" = "View channel";
|
||||
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to-many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.";
|
||||
"lng_sponsored_info_description2" = "Unlike other apps, Telegram doesn\'t track whether you tapped on a sponsored message and doesn\'t profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.";
|
||||
"lng_sponsored_info_description3" = "Telegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
|
||||
"lng_sponsored_info_description4" = "Ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together.";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
BIN
Telegram/Resources/night-custom-base.tdesktop-theme
Normal file
@@ -60,6 +60,8 @@
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
<file alias="day-custom-base.tdesktop-theme">../../day-custom-base.tdesktop-theme</file>
|
||||
<file alias="night-custom-base.tdesktop-theme">../../night-custom-base.tdesktop-theme</file>
|
||||
<file alias="icons/calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
<file alias="icons/calls/voice.lottie">../../icons/calls/voice.lottie</file>
|
||||
<file alias="recording/info_audio.svg">../../art/recording/recording_info_audio.svg</file>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.0.5.0" />
|
||||
Version="3.1.7.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,0,5,0
|
||||
PRODUCTVERSION 3,0,5,0
|
||||
FILEVERSION 3,1,7,0
|
||||
PRODUCTVERSION 3,1,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.0.5.0"
|
||||
VALUE "FileVersion", "3.1.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.5.0"
|
||||
VALUE "ProductVersion", "3.1.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,0,5,0
|
||||
PRODUCTVERSION 3,0,5,0
|
||||
FILEVERSION 3,1,7,0
|
||||
PRODUCTVERSION 3,1,7,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", "3.0.5.0"
|
||||
VALUE "FileVersion", "3.1.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.5.0"
|
||||
VALUE "ProductVersion", "3.1.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -128,7 +128,7 @@ void SendExistingMedia(
|
||||
messagePostAuthor,
|
||||
media,
|
||||
caption,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -288,7 +288,7 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
messagePostAuthor,
|
||||
TextWithEntities(),
|
||||
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
@@ -439,7 +439,7 @@ void SendConfirmedFile(
|
||||
| (localEntities.v.isEmpty()
|
||||
? MTPDmessage::Flag()
|
||||
: MTPDmessage::Flag::f_entities)),
|
||||
MTP_int(newId.msg),
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(messageFromId),
|
||||
peerToMTP(file->to.peer),
|
||||
MTPMessageFwdHeader(),
|
||||
@@ -471,7 +471,7 @@ void SendConfirmedFile(
|
||||
messagePostAuthor,
|
||||
caption,
|
||||
media,
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupId);
|
||||
}
|
||||
|
||||
|
||||
135
Telegram/SourceFiles/api/api_views.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_views.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
// Send channel views each second.
|
||||
constexpr auto kSendViewsTimeout = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ViewsManager::ViewsManager(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance())
|
||||
, _incrementTimer([=] { viewsIncrement(); }) {
|
||||
}
|
||||
|
||||
void ViewsManager::scheduleIncrement(not_null<HistoryItem*> item) {
|
||||
auto peer = item->history()->peer;
|
||||
auto i = _incremented.find(peer);
|
||||
if (i != _incremented.cend()) {
|
||||
if (i->second.contains(item->id)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
i = _incremented.emplace(peer).first;
|
||||
}
|
||||
i->second.emplace(item->id);
|
||||
auto j = _toIncrement.find(peer);
|
||||
if (j == _toIncrement.cend()) {
|
||||
j = _toIncrement.emplace(peer).first;
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
j->second.emplace(item->id);
|
||||
}
|
||||
|
||||
void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::viewsIncrement() {
|
||||
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
|
||||
if (_incrementRequests.contains(i->first)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<MTPint> ids;
|
||||
ids.reserve(i->second.size());
|
||||
for (const auto &msgId : i->second) {
|
||||
ids.push_back(MTP_int(msgId));
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_GetMessagesViews(
|
||||
i->first->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_bool(true)
|
||||
)).done([=](
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
done(ids, result, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
fail(error, requestId);
|
||||
}).afterDelay(5).send();
|
||||
|
||||
_incrementRequests.emplace(i->first, requestId);
|
||||
i = _toIncrement.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
const auto &data = result.c_messages_messageViews();
|
||||
auto &owner = _session->data();
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
auto &v = data.vviews().v;
|
||||
if (ids.size() == v.size()) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id != requestId) {
|
||||
continue;
|
||||
}
|
||||
const auto channel = peerToChannel(peer->id);
|
||||
for (auto j = 0, l = int(ids.size()); j < l; ++j) {
|
||||
if (const auto item = owner.message(channel, ids[j].v)) {
|
||||
v[j].match([&](const MTPDmessageViews &data) {
|
||||
if (const auto views = data.vviews()) {
|
||||
item->setViewsCount(views->v);
|
||||
}
|
||||
if (const auto forwards = data.vforwards()) {
|
||||
item->setForwardsCount(forwards->v);
|
||||
}
|
||||
if (const auto replies = data.vreplies()) {
|
||||
item->setReplies(*replies);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::fail(const MTP::Error &error, mtpRequestId requestId) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id == requestId) {
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
49
Telegram/SourceFiles/api/api_views.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class ViewsManager final {
|
||||
public:
|
||||
explicit ViewsManager(not_null<ApiWrap*> api);
|
||||
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
private:
|
||||
void viewsIncrement();
|
||||
|
||||
void done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId);
|
||||
void fail(const MTP::Error &error, mtpRequestId requestId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _incremented;
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _toIncrement;
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _incrementRequests;
|
||||
base::flat_map<mtpRequestId, not_null<PeerData*>> _incrementByRequest;
|
||||
base::Timer _incrementTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -355,6 +355,8 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
} else if (UpdateUserpics(state, item, peers)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "api/api_views.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -141,7 +142,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
|
||||
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
|
||||
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
|
||||
, _views(std::make_unique<Api::ViewsManager>(this)) {
|
||||
crl::on_main(session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
@@ -558,7 +560,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(nullptr, result, requestId);
|
||||
_session->data().processExistingMessages(nullptr, result);
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -584,7 +587,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(channel, result, requestId);
|
||||
_session->data().processExistingMessages(channel, result);
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -600,37 +604,6 @@ void ApiWrap::resolveMessageDatas() {
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
|
||||
const auto handleResult = [&](auto &&result) {
|
||||
_session->data().processUsers(result.vusers());
|
||||
_session->data().processChats(result.vchats());
|
||||
_session->data().processMessages(
|
||||
result.vmessages(),
|
||||
NewMessageType::Existing);
|
||||
};
|
||||
switch (msgs.type()) {
|
||||
case mtpc_messages_messages:
|
||||
handleResult(msgs.c_messages_messages());
|
||||
break;
|
||||
case mtpc_messages_messagesSlice:
|
||||
handleResult(msgs.c_messages_messagesSlice());
|
||||
break;
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = msgs.c_messages_channelMessages();
|
||||
if (channel) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
|
||||
}
|
||||
handleResult(d);
|
||||
} break;
|
||||
case mtpc_messages_messagesNotModified:
|
||||
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)"));
|
||||
break;
|
||||
}
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId) {
|
||||
@@ -662,8 +635,8 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto fallback = [&] {
|
||||
auto linkChannel = channel;
|
||||
auto linkItemId = item->id;
|
||||
auto linkCommentId = 0;
|
||||
auto linkThreadId = 0;
|
||||
auto linkCommentId = MsgId();
|
||||
auto linkThreadId = MsgId();
|
||||
if (inRepliesContext) {
|
||||
if (const auto rootId = item->replyToTop()) {
|
||||
const auto root = item->history()->owner().message(
|
||||
@@ -693,11 +666,11 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto query = base
|
||||
+ '/'
|
||||
+ QString::number(linkItemId)
|
||||
+ QString::number(linkItemId.bare)
|
||||
+ (linkCommentId
|
||||
? "?comment=" + QString::number(linkCommentId)
|
||||
? "?comment=" + QString::number(linkCommentId.bare)
|
||||
: linkThreadId
|
||||
? "?thread=" + QString::number(linkThreadId)
|
||||
? "?thread=" + QString::number(linkThreadId.bare)
|
||||
: "");
|
||||
if (linkChannel->hasUsername()
|
||||
&& !linkChannel->isMegagroup()
|
||||
@@ -1759,7 +1732,7 @@ void ApiWrap::deleteAllFromUser(
|
||||
? history->collectMessagesFromUserToDelete(from)
|
||||
: QVector<MsgId>();
|
||||
const auto channelId = peerToChannel(channel->id);
|
||||
for (const auto msgId : ids) {
|
||||
for (const auto &msgId : ids) {
|
||||
if (const auto item = _session->data().message(channelId, msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
@@ -3837,7 +3810,7 @@ void ApiWrap::sendSharedContact(
|
||||
MTP_string(lastName),
|
||||
MTP_string(), // vcard
|
||||
MTP_long(userId.bare)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto media = MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
@@ -4101,7 +4074,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
messagePostAuthor,
|
||||
sending,
|
||||
media,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_SendMessage(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -4733,6 +4706,10 @@ Api::InviteLinks &ApiWrap::inviteLinks() {
|
||||
return *_inviteLinks;
|
||||
}
|
||||
|
||||
Api::ViewsManager &ApiWrap::views() {
|
||||
return *_views;
|
||||
}
|
||||
|
||||
void ApiWrap::createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
|
||||
@@ -62,6 +62,7 @@ class SensitiveContent;
|
||||
class GlobalPrivacy;
|
||||
class UserPrivacy;
|
||||
class InviteLinks;
|
||||
class ViewsManager;
|
||||
|
||||
namespace details {
|
||||
|
||||
@@ -400,6 +401,7 @@ public:
|
||||
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
||||
[[nodiscard]] Api::UserPrivacy &userPrivacy();
|
||||
[[nodiscard]] Api::InviteLinks &inviteLinks();
|
||||
[[nodiscard]] Api::ViewsManager &views();
|
||||
|
||||
void createPoll(
|
||||
const PollData &data,
|
||||
@@ -456,7 +458,6 @@ private:
|
||||
void saveDraftsToCloud();
|
||||
|
||||
void resolveMessageDatas();
|
||||
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId);
|
||||
void finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId);
|
||||
@@ -720,6 +721,7 @@ private:
|
||||
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
||||
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
|
||||
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
|
||||
const std::unique_ptr<Api::ViewsManager> _views;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
|
||||
76
Telegram/SourceFiles/boxes/about_sponsored_box.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/about_sponsored_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kUrl = "https://telegram.org/ads"_cs;
|
||||
|
||||
} // namespace
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_sponsored_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
|
||||
const auto addUrl = [&] {
|
||||
const auto &st = st::sponsoredUrlButton;
|
||||
const auto row = box->addRow(object_ptr<RpWidget>(box));
|
||||
row->resize(0, st.height + st.padding.top() + st.padding.bottom());
|
||||
const auto button = Ui::CreateChild<RoundButton>(
|
||||
row,
|
||||
rpl::single<QString>(kUrl.utf8()),
|
||||
st);
|
||||
button->setBrushOverride(Qt::NoBrush);
|
||||
button->setPenOverride(QPen(st::historyLinkInFg));
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
rpl::combine(
|
||||
row->sizeValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QSize &rowSize,
|
||||
const QSize &buttonSize) {
|
||||
button->moveToLeft(
|
||||
(rowSize.width() - buttonSize.width()) / 2,
|
||||
(rowSize.height() - buttonSize.height()) / 2);
|
||||
}, row->lifetime());
|
||||
button->addClickHandler([=] {
|
||||
QDesktopServices::openUrl({ kUrl.utf8() });
|
||||
});
|
||||
};
|
||||
|
||||
const auto &stLabel = st::aboutLabel;
|
||||
const auto info1 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info1->setText(tr::lng_sponsored_info_description1(tr::now));
|
||||
box->addSkip(st::sponsoredInfoSkip);
|
||||
|
||||
const auto info2 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info2->setText(tr::lng_sponsored_info_description2(tr::now));
|
||||
box->addSkip(st::sponsoredInfoSkip);
|
||||
|
||||
const auto info3 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info3->setText(tr::lng_sponsored_info_description3(tr::now));
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
addUrl();
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
const auto info4 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info4->setText(tr::lng_sponsored_info_description4(tr::now));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
16
Telegram/SourceFiles/boxes/about_sponsored_box.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -210,7 +210,7 @@ void ServiceCheck::Generator::paintFrame(
|
||||
const auto frames = framesForStyle(st);
|
||||
auto &image = frames->image;
|
||||
const auto count = int(frames->ready.size());
|
||||
const auto index = int(std::round(toggled * (count - 1)));
|
||||
const auto index = int(base::SafeRound(toggled * (count - 1)));
|
||||
Assert(index >= 0 && index < count);
|
||||
if (!frames->ready[index]) {
|
||||
frames->ready[index] = true;
|
||||
@@ -288,7 +288,6 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
bool out) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3);
|
||||
const auto flags = MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| (out ? MessageFlag::Outgoing : MessageFlag(0));
|
||||
@@ -296,7 +295,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
const auto viaBotId = UserId();
|
||||
const auto groupedId = uint64();
|
||||
const auto item = history->makeMessage(
|
||||
++id,
|
||||
history->nextNonHistoryEntryId(),
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
@@ -305,7 +304,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
QString(),
|
||||
TextWithEntities{ TextUtilities::Clean(text) },
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupedId);
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
@@ -973,3 +973,22 @@ autolockTimeField: InputField(scheduleTimeField) {
|
||||
heightMin: 20px;
|
||||
}
|
||||
autolockTimeWidth: 52px;
|
||||
|
||||
sponsoredInfoSkip: 22px;
|
||||
sponsoredUrlButtonSkip: 11px;
|
||||
sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
height: 32px;
|
||||
width: -42px;
|
||||
textBg: transparent;
|
||||
textBgOver: transparent;
|
||||
radius: roundRadiusLarge;
|
||||
padding: margins(2px, 2px, 2px, 2px);
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,14 @@ void EditAdminBox::transferOwnership() {
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
_checkTransferRequestId = 0;
|
||||
if (!handleTransferPasswordError(error)) {
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto callback = crl::guard(this, [=] {
|
||||
transferOwnershipChecked();
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
});
|
||||
*box = getDelegate()->show(Box<ConfirmBox>(
|
||||
tr::lng_rights_transfer_about(
|
||||
tr::now,
|
||||
lt_group,
|
||||
@@ -462,7 +469,7 @@ void EditAdminBox::transferOwnership() {
|
||||
Ui::Text::Bold(user()->shortName()),
|
||||
Ui::Text::RichLangValue),
|
||||
tr::lng_rights_transfer_sure(tr::now),
|
||||
crl::guard(this, [=] { transferOwnershipChecked(); })));
|
||||
callback));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -268,7 +268,8 @@ void Row::update(const InviteLinkData &data, TimeId now) {
|
||||
|
||||
void Row::updateExpireProgress(TimeId now) {
|
||||
const auto updated = ComputeProgress(_data, now);
|
||||
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
|
||||
if (base::SafeRound(_progressTillExpire * 360)
|
||||
!= base::SafeRound(updated * 360)) {
|
||||
_progressTillExpire = updated;
|
||||
const auto color = ComputeColor(_data, _progressTillExpire);
|
||||
if (_color != color) {
|
||||
@@ -291,7 +292,8 @@ crl::time Row::updateExpireIn() const {
|
||||
if (_data.expireDate <= start) {
|
||||
return 0;
|
||||
}
|
||||
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
return base::SafeRound(
|
||||
(_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
|
||||
@@ -549,9 +549,6 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
block.takeWidget(),
|
||||
QMargins(0, _inner->count() ? st::sendMediaRowSkip : 0, 0, 0));
|
||||
|
||||
const auto preventDelete =
|
||||
widget->lifetime().make_state<rpl::event_stream<int>>();
|
||||
|
||||
block.itemDeleteRequest(
|
||||
) | rpl::filter([=] {
|
||||
return !_removingIndex;
|
||||
@@ -562,9 +559,9 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
if (index < 0 || index >= _list.files.size()) {
|
||||
return;
|
||||
}
|
||||
// Prevent item delete if it is the only one.
|
||||
// Just close the box if it is the only one.
|
||||
if (_list.files.size() == 1) {
|
||||
preventDelete->fire_copy(0);
|
||||
closeBox();
|
||||
return;
|
||||
}
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
@@ -572,9 +569,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
rpl::merge(
|
||||
block.itemReplaceRequest(),
|
||||
preventDelete->events()
|
||||
block.itemReplaceRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
|
||||
@@ -1113,7 +1113,7 @@ QString AppendShareGameScoreUrl(
|
||||
auto channelAccessHash = uint64(channel ? channel->access : 0);
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.channel.bare;
|
||||
shareHashDataInts[2] = fullId.msg;
|
||||
shareHashDataInts[2] = uint64(fullId.msg.bare);
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
@@ -1200,7 +1200,6 @@ void ShareGameScoreByHash(
|
||||
//}
|
||||
|
||||
if (((hashDataInts[1] >> 40) != 0)
|
||||
|| ((hashDataInts[2] >> 32) != 0)
|
||||
|| (!hashDataInts[1] && channelAccessHash)) {
|
||||
// If there is no channel id, there should be no channel access_hash.
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
@@ -1208,7 +1207,7 @@ void ShareGameScoreByHash(
|
||||
}
|
||||
|
||||
auto channelId = ChannelId(hashDataInts[1]);
|
||||
auto msgId = MsgId(hashDataInts[2]);
|
||||
auto msgId = MsgId(int64(hashDataInts[2]));
|
||||
if (const auto item = session->data().message(channelId, msgId)) {
|
||||
FastShareMessage(item);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
@@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -64,7 +64,7 @@ private:
|
||||
void setCounter(int counter);
|
||||
|
||||
QString _text;
|
||||
Dialogs::Layout::UnreadBadgeStyle _st;
|
||||
Dialogs::Ui::UnreadBadgeStyle _st;
|
||||
|
||||
};
|
||||
|
||||
@@ -303,7 +303,7 @@ StickersBox::CounterWidget::CounterWidget(
|
||||
: RpWidget(parent) {
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
|
||||
_st.sizeId = Dialogs::Ui::UnreadBadgeInStickersBox;
|
||||
_st.textTop = st::stickersFeaturedBadgeTextTop;
|
||||
_st.size = st::stickersFeaturedBadgeSize;
|
||||
_st.padding = st::stickersFeaturedBadgePadding;
|
||||
@@ -323,7 +323,7 @@ void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
Painter p(&dummy);
|
||||
|
||||
auto newWidth = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
|
||||
resize(newWidth, st::stickersFeaturedBadgeSize);
|
||||
}
|
||||
@@ -334,7 +334,7 @@ void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) {
|
||||
if (!_text.isEmpty()) {
|
||||
auto unreadRight = rtl() ? 0 : width();
|
||||
auto unreadTop = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ private:
|
||||
};
|
||||
|
||||
BoxController::Row::Row(not_null<HistoryItem*> item)
|
||||
: PeerListRow(item->history()->peer, item->id)
|
||||
: PeerListRow(item->history()->peer, item->id.bare)
|
||||
, _items(1, item)
|
||||
, _date(ItemDateTime(item).date())
|
||||
, _type(ComputeType(item))
|
||||
|
||||
@@ -209,7 +209,7 @@ void VideoBubble::updateSizeToFrame(QSize frame) {
|
||||
size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
|
||||
} else {
|
||||
const auto area = size.width() * size.height();
|
||||
const auto w = int(std::round(std::max(
|
||||
const auto w = int(base::SafeRound(std::max(
|
||||
std::sqrt((frame.width() * float64(area)) / (frame.height() * 1.)),
|
||||
1.)));
|
||||
const auto h = area / w;
|
||||
|
||||
@@ -2361,6 +2361,7 @@ bool GroupCall::tryCreateController() {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
descriptor.config.need_log = true;
|
||||
#ifdef Q_OS_WIN
|
||||
descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
@@ -2370,6 +2371,8 @@ bool GroupCall::tryCreateController() {
|
||||
#endif // Q_OS_WIN
|
||||
QFile(callLogPath).remove();
|
||||
QDir().mkpath(callLogFolder);
|
||||
} else {
|
||||
descriptor.config.need_log = false;
|
||||
}
|
||||
|
||||
LOG(("Call Info: Creating group instance"));
|
||||
|
||||
@@ -57,7 +57,7 @@ auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||
}
|
||||
|
||||
[[nodiscard]] QString StatusPercentString(float volume) {
|
||||
return QString::number(int(std::round(volume * 200))) + '%';
|
||||
return QString::number(int(base::SafeRound(volume * 200))) + '%';
|
||||
}
|
||||
|
||||
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
|
||||
@@ -492,7 +492,7 @@ int MembersRow::statusIconWidth(bool skipIcon) const {
|
||||
const auto full = iconWidth
|
||||
+ _statusIcon->percentWidth
|
||||
+ st::normalFont->spacew;
|
||||
return int(std::round(shown * full));
|
||||
return int(base::SafeRound(shown * full));
|
||||
}
|
||||
|
||||
int MembersRow::statusIconHeight() const {
|
||||
|
||||
@@ -1000,6 +1000,8 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_recordingMark->setClickedCallback([=] {
|
||||
showToast({ (livestream
|
||||
? tr::lng_group_call_is_recorded_channel
|
||||
: real->recordVideo()
|
||||
? tr::lng_group_call_is_recorded_video
|
||||
: tr::lng_group_call_is_recorded)(tr::now) });
|
||||
});
|
||||
const auto animate = [=] {
|
||||
|
||||
@@ -461,15 +461,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||
const auto columns = slices;
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(column * sizew + sizew - skip))
|
||||
- left;
|
||||
const auto rows = int(std::round((count - index)
|
||||
const auto left = int(base::SafeRound(column * sizew));
|
||||
const auto width = int(
|
||||
base::SafeRound(column * sizew + sizew - skip)) - left;
|
||||
const auto rows = int(base::SafeRound((count - index)
|
||||
/ float64(columns - column)));
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(
|
||||
const auto top = int(base::SafeRound(row * sizeh));
|
||||
const auto height = int(base::SafeRound(
|
||||
row * sizeh + sizeh - skip)) - top;
|
||||
auto &geometry = sizes[index];
|
||||
geometry.columns = {
|
||||
@@ -493,15 +493,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||
const auto rows = slices;
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(row * sizeh + sizeh - skip))
|
||||
- top;
|
||||
const auto columns = int(std::round((count - index)
|
||||
const auto top = int(base::SafeRound(row * sizeh));
|
||||
const auto height = int(
|
||||
base::SafeRound(row * sizeh + sizeh - skip)) - top;
|
||||
const auto columns = int(base::SafeRound((count - index)
|
||||
/ float64(rows - row)));
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(
|
||||
const auto left = int(base::SafeRound(column * sizew));
|
||||
const auto width = int(base::SafeRound(
|
||||
column * sizew + sizew - skip)) - left;
|
||||
auto &geometry = sizes[index];
|
||||
geometry.rows = {
|
||||
|
||||
@@ -242,7 +242,7 @@ vec4 background() {
|
||||
QSize outer,
|
||||
float factor) {
|
||||
factor *= kBlurTextureSizeFactor;
|
||||
const auto area = outer / int(std::round(factor * cScale() / 100));
|
||||
const auto area = outer / int(base::SafeRound(factor * cScale() / 100));
|
||||
const auto scaled = unscaled.scaled(area, Qt::KeepAspectRatio);
|
||||
return (scaled.width() > unscaled.width()
|
||||
|| scaled.height() > unscaled.height())
|
||||
|
||||
@@ -92,7 +92,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
|
||||
const auto volume = _localMuted
|
||||
? 0
|
||||
: std::round(_slider->value() * kMaxVolumePercent);
|
||||
: base::SafeRound(_slider->value() * kMaxVolumePercent);
|
||||
const auto muteProgress =
|
||||
_crossLineAnimation.value((!volume) ? 1. : 0.);
|
||||
|
||||
@@ -140,7 +140,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
};
|
||||
|
||||
_slider->setChangeFinishedCallback([=](float64 value) {
|
||||
const auto newVolume = std::round(value * _maxVolume);
|
||||
const auto newVolume = base::SafeRound(value * _maxVolume);
|
||||
const auto muted = (value == 0);
|
||||
|
||||
if (!_cloudMuted && muted) {
|
||||
@@ -175,7 +175,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
}
|
||||
if (_waitingForUpdateVolume) {
|
||||
const auto localVolume =
|
||||
std::round(_slider->value() * _maxVolume);
|
||||
base::SafeRound(_slider->value() * _maxVolume);
|
||||
if ((localVolume != newVolume)
|
||||
&& (_cloudVolume == newVolume)) {
|
||||
_changeVolumeRequests.fire(int(localVolume));
|
||||
|
||||
@@ -16,7 +16,7 @@ struct SendCommandRequest {
|
||||
not_null<PeerData*> peer;
|
||||
QString command;
|
||||
FullMsgId context;
|
||||
int replyTo = 0;
|
||||
MsgId replyTo = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] QString WrapCommandInChat(
|
||||
|
||||
@@ -199,9 +199,9 @@ bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (key >= Qt::Key_1 && key <= Qt::Key_2) {
|
||||
const auto index = int(key - Qt::Key_1);
|
||||
if (!markup->rows.empty()
|
||||
if (!markup->data.rows.empty()
|
||||
&& index >= 0
|
||||
&& index < int(markup->rows.front().size())) {
|
||||
&& index < int(markup->data.rows.front().size())) {
|
||||
App::activateBotCommand(_controller, item, 0, index);
|
||||
return true;
|
||||
}
|
||||
@@ -257,14 +257,14 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
_singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse);
|
||||
|
||||
if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
_placeholder = markup->placeholder;
|
||||
_placeholder = markup->data.placeholder;
|
||||
} else {
|
||||
_placeholder = QString();
|
||||
}
|
||||
|
||||
_impl = nullptr;
|
||||
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (!markup->rows.empty()) {
|
||||
if (!markup->data.rows.empty()) {
|
||||
_impl = std::make_unique<ReplyKeyboard>(
|
||||
to,
|
||||
std::make_unique<Style>(this, *_st));
|
||||
|
||||
@@ -117,7 +117,8 @@ void EmojiInteractions::startOutgoing(
|
||||
if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) {
|
||||
return;
|
||||
}
|
||||
const auto emoji = chooseInteractionEmoji(item);
|
||||
const auto emoticon = item->originalText().text;
|
||||
const auto emoji = chooseInteractionEmoji(emoticon);
|
||||
if (!emoji) {
|
||||
return;
|
||||
}
|
||||
@@ -145,6 +146,7 @@ void EmojiInteractions::startOutgoing(
|
||||
media->checkStickerLarge();
|
||||
const auto now = crl::now();
|
||||
animations.push_back({
|
||||
.emoticon = emoticon,
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
@@ -184,7 +186,7 @@ void EmojiInteractions::startIncoming(
|
||||
}
|
||||
const auto now = crl::now();
|
||||
for (const auto &single : bunch.interactions) {
|
||||
const auto at = now + crl::time(std::round(single.time * 1000));
|
||||
const auto at = now + crl::time(base::SafeRound(single.time * 1000));
|
||||
if (!animations.empty() && animations.back().scheduledAt >= at) {
|
||||
continue;
|
||||
}
|
||||
@@ -195,6 +197,7 @@ void EmojiInteractions::startIncoming(
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
animations.push_back({
|
||||
.emoticon = emoticon,
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
@@ -219,7 +222,7 @@ void EmojiInteractions::seenOutgoing(
|
||||
if (const auto j = i->second.find(emoji); j != end(i->second)) {
|
||||
const auto last = j->second.lastDoneReceivedAt;
|
||||
if (!last || last + kAcceptSeenSinceRequest > crl::now()) {
|
||||
_seen.fire({ peer, emoji });
|
||||
_seen.fire({ peer, emoticon });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +266,7 @@ auto EmojiInteractions::checkAnimations(
|
||||
} else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) {
|
||||
animation.startedAt = now;
|
||||
_playRequests.fire({
|
||||
animation.emoji->text(),
|
||||
animation.emoticon,
|
||||
item,
|
||||
animation.media,
|
||||
animation.scheduledAt,
|
||||
@@ -316,7 +319,7 @@ void EmojiInteractions::sendAccumulatedOutgoing(
|
||||
peer->input,
|
||||
MTPint(), // top_msg_id
|
||||
MTP_sendMessageEmojiInteraction(
|
||||
MTP_string(emoji->text()),
|
||||
MTP_string(from->emoticon),
|
||||
MTP_int(item->id),
|
||||
MTP_dataJSON(MTP_bytes(ToJson(bunch))))
|
||||
)).done([=](const MTPBool &result, mtpRequestId requestId) {
|
||||
|
||||
@@ -28,7 +28,7 @@ class Element;
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct EmojiInteractionPlayRequest {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
not_null<HistoryItem*> item;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time shouldHaveStartedAt = 0;
|
||||
@@ -45,7 +45,7 @@ struct EmojiInteractionsBunch {
|
||||
|
||||
struct EmojiInteractionSeen {
|
||||
not_null<PeerData*> peer;
|
||||
not_null<EmojiPtr> emoji;
|
||||
QString emoticon;
|
||||
};
|
||||
|
||||
class EmojiInteractions final {
|
||||
@@ -78,6 +78,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Animation {
|
||||
QString emoticon;
|
||||
not_null<EmojiPtr> emoji;
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
|
||||
@@ -168,11 +168,10 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
|
||||
hide();
|
||||
|
||||
connect(
|
||||
_scroll,
|
||||
&Ui::ScrollArea::geometryChanged,
|
||||
_inner,
|
||||
&Inner::onParentGeometryChanged);
|
||||
_scroll->geometryChanged(
|
||||
) | rpl::start_with_next(crl::guard(_inner, [=] {
|
||||
_inner->onParentGeometryChanged();
|
||||
}), lifetime());
|
||||
}
|
||||
|
||||
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
|
||||
|
||||
@@ -68,7 +68,7 @@ QImage EmojiImageLoader::prepare(EmojiPtr emoji) const {
|
||||
{ -1, 1 },
|
||||
{ 1, 1 },
|
||||
} };
|
||||
const auto corrected = int(std::round(delta / sqrt(2.)));
|
||||
const auto corrected = int(base::SafeRound(delta / sqrt(2.)));
|
||||
for (const auto &shift : diagonal) {
|
||||
for (auto i = 0; i != corrected; ++i) {
|
||||
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);
|
||||
|
||||
@@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
|
||||
@@ -60,12 +60,11 @@ auto LottieFromDocument(
|
||||
Method &&method,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
uint8 keyShift,
|
||||
QSize box,
|
||||
int cacheAreaLimit) {
|
||||
QSize box) {
|
||||
const auto document = media->owner();
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
if (box.width() * box.height() > cacheAreaLimit) {
|
||||
if (box.width() * box.height() > kDontCacheLottieAfterArea) {
|
||||
// Don't use frame caching for large stickers.
|
||||
return method(
|
||||
Lottie::ReadContent(data, filepath),
|
||||
@@ -114,12 +113,9 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
replacements,
|
||||
std::move(renderer));
|
||||
};
|
||||
const auto limit = (sizeTag == StickerLottieSize::EmojiInteraction)
|
||||
? (3 * kDontCacheLottieAfterArea)
|
||||
: kDontCacheLottieAfterArea;
|
||||
const auto tag = replacements ? replacements->tag : uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box, limit);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box);
|
||||
}
|
||||
|
||||
not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
@@ -130,8 +126,7 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
const auto method = [&](auto &&...args) {
|
||||
return player->append(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
const auto limit = kDontCacheLottieAfterArea;
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box, limit);
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box);
|
||||
}
|
||||
|
||||
bool HasLottieThumbnail(
|
||||
|
||||
@@ -45,6 +45,9 @@ enum class StickerLottieSize : uchar {
|
||||
SetsListThumbnail,
|
||||
InlineResults,
|
||||
EmojiInteraction,
|
||||
EmojiInteractionReserved1,
|
||||
EmojiInteractionReserved2,
|
||||
EmojiInteractionReserved3,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
|
||||
@@ -58,6 +58,44 @@ std::map<int, const char*> BetaLogs() {
|
||||
{
|
||||
3000005,
|
||||
"- Add support for Emoji 13.1."
|
||||
},
|
||||
{
|
||||
3001002,
|
||||
"- Control video in fullscreen mode using arrows and numbers.\n"
|
||||
|
||||
"- Open locations in browser if default Bing Maps is not installed.\n"
|
||||
|
||||
"- Reconnect without timeout when network availability changes.\n"
|
||||
|
||||
"- Crash fixes."
|
||||
},
|
||||
{
|
||||
3001005,
|
||||
"- Choose one of 8 new preset themes for any individual private chat.\n"
|
||||
|
||||
"- Click on '...' menu > 'Change Colors' to pick a theme.\n"
|
||||
|
||||
"- Both chat participants will see the same theme in that chat "
|
||||
"– on all their devices.\n"
|
||||
|
||||
"- Each new theme features colorful gradient message bubbles, "
|
||||
"beautifully animated backgrounds and unique background patterns.\n"
|
||||
|
||||
"- All chat themes have day and night versions and will follow "
|
||||
"your overall dark mode settings.\n"
|
||||
|
||||
"- Implement main window rounded corners on Windows 11.\n"
|
||||
|
||||
"- Fix audio capture from AirPods on macOS.\n"
|
||||
},
|
||||
{
|
||||
3001006,
|
||||
"- Show small media previews in chats list.\n"
|
||||
|
||||
"- Show media album previews and caption text in chats list.\n"
|
||||
|
||||
"- Add \"Quick Reply\" and \"Mark as Read\" "
|
||||
"to native Windows notifications.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -617,7 +617,7 @@ public:
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
return int(std::round(std::clamp(speed, 0.5, 2.0) * 100));
|
||||
return int(base::SafeRound(std::clamp(speed, 0.5, 2.0) * 100));
|
||||
}
|
||||
[[nodiscard]] static float64 DeserializePlaybackSpeed(qint32 speed) {
|
||||
if (speed < 10) {
|
||||
|
||||
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/themes/window_theme_editor_box.h" // GenerateSlug.
|
||||
#include "settings/settings_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -469,6 +470,100 @@ bool ShowInviteLink(
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
if (!theme->paper
|
||||
|| !theme->paper->isPattern()
|
||||
|| theme->paper->backgroundColors().empty()
|
||||
|| !theme->accentColor
|
||||
|| !theme->paper->hasShareUrl()) {
|
||||
Ui::Toast::Show("Something went wrong :(");
|
||||
return;
|
||||
}
|
||||
const auto &bg = theme->paper->backgroundColors();
|
||||
const auto url = theme->paper->shareUrl(session);
|
||||
const auto from = url.indexOf("bg/");
|
||||
const auto till = url.indexOf("?");
|
||||
if (from < 0 || till <= from) {
|
||||
Ui::Toast::Show("Bad WallPaper link: " + url);
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPaccount_CreateTheme::Flag;
|
||||
using Setting = MTPDinputThemeSettings::Flag;
|
||||
using Paper = MTPDwallPaperSettings::Flag;
|
||||
const auto color = [](const QColor &color) {
|
||||
const auto red = color.red();
|
||||
const auto green = color.green();
|
||||
const auto blue = color.blue();
|
||||
return int(((uint32(red) & 0xFFU) << 16)
|
||||
| ((uint32(green) & 0xFFU) << 8)
|
||||
| (uint32(blue) & 0xFFU));
|
||||
};
|
||||
const auto colors = [&](const std::vector<QColor> &colors) {
|
||||
auto result = QVector<MTPint>();
|
||||
result.reserve(colors.size());
|
||||
for (const auto &single : colors) {
|
||||
result.push_back(MTP_int(color(single)));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto slug = url.mid(from + 3, till - from - 3);
|
||||
const auto flags = Flag::f_settings;
|
||||
const auto settings = Setting::f_wallpaper
|
||||
| Setting::f_wallpaper_settings
|
||||
| (theme->outgoingAccentColor
|
||||
? Setting::f_outbox_accent_color
|
||||
: Setting(0))
|
||||
| (!theme->outgoingMessagesColors.empty()
|
||||
? Setting::f_message_colors
|
||||
: Setting(0));
|
||||
const auto papers = Paper::f_background_color
|
||||
| Paper::f_intensity
|
||||
| (bg.size() > 1
|
||||
? Paper::f_second_background_color
|
||||
: Paper(0))
|
||||
| (bg.size() > 2
|
||||
? Paper::f_third_background_color
|
||||
: Paper(0))
|
||||
| (bg.size() > 3
|
||||
? Paper::f_fourth_background_color
|
||||
: Paper(0));
|
||||
session->api().request(MTPaccount_CreateTheme(
|
||||
MTP_flags(flags),
|
||||
MTP_string(Window::Theme::GenerateSlug()),
|
||||
MTP_string(theme->title + " Desktop"),
|
||||
MTPInputDocument(),
|
||||
MTP_inputThemeSettings(
|
||||
MTP_flags(settings),
|
||||
(theme->basedOnDark
|
||||
? MTP_baseThemeTinted()
|
||||
: MTP_baseThemeClassic()),
|
||||
MTP_int(color(theme->accentColor.value_or(Qt::black))),
|
||||
MTP_int(color(theme->outgoingAccentColor.value_or(
|
||||
Qt::black))),
|
||||
MTP_vector<MTPint>(colors(
|
||||
theme->outgoingMessagesColors)),
|
||||
MTP_inputWallPaperSlug(MTP_string(slug)),
|
||||
MTP_wallPaperSettings(
|
||||
MTP_flags(papers),
|
||||
MTP_int(color(bg[0])),
|
||||
MTP_int(color(bg.size() > 1 ? bg[1] : Qt::black)),
|
||||
MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
|
||||
MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
|
||||
MTP_int(theme->paper->patternIntensity()),
|
||||
MTP_int(0)))
|
||||
)).done([=](const MTPTheme &result) {
|
||||
const auto slug = Data::CloudTheme::Parse(session, result, true).slug;
|
||||
QGuiApplication::clipboard()->setText(
|
||||
session->createInternalLinkFull("addtheme/" + slug));
|
||||
Ui::Toast::Show(tr::lng_background_link_copied(tr::now));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::Toast::Show("Error: " + error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool ResolveTestChatTheme(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
@@ -485,6 +580,9 @@ bool ResolveTestChatTheme(
|
||||
history->peer->themeEmoji(),
|
||||
params);
|
||||
if (theme) {
|
||||
if (!params["export"].isEmpty()) {
|
||||
ExportTestChatTheme(&controller->session(), &*theme);
|
||||
}
|
||||
[[maybe_unused]] auto value = controller->cachedChatThemeValue(
|
||||
*theme);
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ bool ParseCommonMap(
|
||||
}
|
||||
return string.toULongLong();
|
||||
} else if ((*version).isDouble()) {
|
||||
return uint64(std::round((*version).toDouble()));
|
||||
return uint64(base::SafeRound((*version).toDouble()));
|
||||
}
|
||||
return 0ULL;
|
||||
}();
|
||||
|
||||
@@ -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 = 3000005;
|
||||
constexpr auto AppVersionStr = "3.0.5";
|
||||
constexpr auto AppVersion = 3001007;
|
||||
constexpr auto AppVersionStr = "3.1.7";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -148,7 +148,7 @@ struct MessageUpdate {
|
||||
NewMaybeAdded = (1U << 7),
|
||||
RepliesUnreadCount = (1U << 8),
|
||||
|
||||
LastUsedBit = (1U << 7),
|
||||
LastUsedBit = (1U << 8),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -387,26 +387,29 @@ rpl::producer<> CloudThemes::chatThemesUpdated() const {
|
||||
}
|
||||
|
||||
std::optional<ChatTheme> CloudThemes::themeForEmoji(
|
||||
const QString &emoji) const {
|
||||
if (emoji.isEmpty()) {
|
||||
const QString &emoticon) const {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!emoji) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
|
||||
}
|
||||
|
||||
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
||||
const QString &emoji) {
|
||||
const QString &emoticon) {
|
||||
const auto testing = TestingColors();
|
||||
if (emoji.isEmpty()) {
|
||||
if (!Ui::Emoji::Find(emoticon)) {
|
||||
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
|
||||
} else if (auto result = themeForEmoji(emoji)) {
|
||||
} else if (auto result = themeForEmoji(emoticon)) {
|
||||
if (testing) {
|
||||
return rpl::single(
|
||||
std::move(result)
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}));
|
||||
@@ -419,7 +422,7 @@ rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
||||
std::nullopt
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}) | rpl::take(limit));
|
||||
@@ -433,7 +436,7 @@ void CloudThemes::SetTestingColors(bool testing) {
|
||||
IsTestingColors = testing;
|
||||
}
|
||||
|
||||
QString CloudThemes::PrepareTestingLink(const CloudTheme &theme) {
|
||||
QString CloudThemes::prepareTestingLink(const CloudTheme &theme) const {
|
||||
const auto hex = [](int value) {
|
||||
return QChar((value < 10) ? ('0' + value) : ('a' + (value - 10)));
|
||||
};
|
||||
@@ -460,6 +463,16 @@ QString CloudThemes::PrepareTestingLink(const CloudTheme &theme) {
|
||||
if (theme.paper && !theme.paper->backgroundColors().empty()) {
|
||||
arguments.push_back("bg=" + colors(theme.paper->backgroundColors()));
|
||||
}
|
||||
if (theme.paper/* && theme.paper->hasShareUrl()*/) {
|
||||
arguments.push_back("intensity="
|
||||
+ QString::number(theme.paper->patternIntensity()));
|
||||
//const auto url = theme.paper->shareUrl(_session);
|
||||
//const auto from = url.indexOf("bg/");
|
||||
//const auto till = url.indexOf("?");
|
||||
//if (from > 0 && till > from) {
|
||||
// arguments.push_back("slug=" + url.mid(from + 3, till - from - 3));
|
||||
//}
|
||||
}
|
||||
if (theme.outgoingAccentColor) {
|
||||
arguments.push_back("out_accent" + color(*theme.outgoingAccentColor));
|
||||
}
|
||||
@@ -472,12 +485,15 @@ QString CloudThemes::PrepareTestingLink(const CloudTheme &theme) {
|
||||
}
|
||||
|
||||
std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms) {
|
||||
if (!TestingColors()) {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!TestingColors() || !emoji) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
if (i == end(_chatThemes)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -525,7 +541,11 @@ std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
|
||||
const auto bg = colors(params["bg"]);
|
||||
applyTo.paper = (applyTo.paper && !bg.empty())
|
||||
? std::make_optional(applyTo.paper->withBackgroundColors(bg))
|
||||
: std::nullopt;
|
||||
: applyTo.paper;
|
||||
applyTo.paper = (applyTo.paper && params["intensity"].toInt())
|
||||
? std::make_optional(
|
||||
applyTo.paper->withPatternIntensity(params["intensity"].toInt()))
|
||||
: applyTo.paper;
|
||||
applyTo.outgoingAccentColor = color(params["out_accent"]);
|
||||
applyTo.outgoingMessagesColors = colors(params["out_bg"]);
|
||||
_chatThemesUpdates.fire({});
|
||||
@@ -538,7 +558,7 @@ void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
|
||||
for (const auto &theme : list) {
|
||||
theme.match([&](const MTPDchatTheme &data) {
|
||||
_chatThemes.push_back({
|
||||
.emoji = qs(data.vemoticon()),
|
||||
.emoticon = qs(data.vemoticon()),
|
||||
.light = CloudTheme::Parse(_session, data.vtheme(), true),
|
||||
.dark = CloudTheme::Parse(_session, data.vdark_theme(), true),
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ struct CloudTheme {
|
||||
};
|
||||
|
||||
struct ChatTheme {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
CloudTheme light;
|
||||
CloudTheme dark;
|
||||
};
|
||||
@@ -71,15 +71,15 @@ public:
|
||||
[[nodiscard]] const std::vector<ChatTheme> &chatThemes() const;
|
||||
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
|
||||
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
|
||||
const QString &emoji) const;
|
||||
const QString &emoticon) const;
|
||||
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
|
||||
const QString &emoji);
|
||||
const QString &emoticon);
|
||||
|
||||
[[nodiscard]] static bool TestingColors();
|
||||
static void SetTestingColors(bool testing);
|
||||
[[nodiscard]] static QString PrepareTestingLink(const CloudTheme &theme);
|
||||
[[nodiscard]] QString prepareTestingLink(const CloudTheme &theme) const;
|
||||
[[nodiscard]] std::optional<CloudTheme> updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms);
|
||||
|
||||
void applyUpdate(const MTPTheme &theme);
|
||||
|
||||
@@ -19,6 +19,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Data {
|
||||
|
||||
DraftKey DraftKey::FromSerializedOld(int32 value) {
|
||||
return !value
|
||||
? DraftKey::None()
|
||||
: (value == kLocalDraftIndex + kEditDraftShiftOld)
|
||||
? DraftKey::LocalEdit()
|
||||
: (value == kScheduledDraftIndex + kEditDraftShiftOld)
|
||||
? DraftKey::ScheduledEdit()
|
||||
: (value > 0 && value < 0x4000'0000)
|
||||
? DraftKey::Replies(int64(value))
|
||||
: (value > kEditDraftShiftOld
|
||||
&& value < kEditDraftShiftOld + 0x4000'000)
|
||||
? DraftKey::RepliesEdit(int64(value - kEditDraftShiftOld))
|
||||
: DraftKey::None();
|
||||
}
|
||||
|
||||
Draft::Draft(
|
||||
const TextWithTags &textWithTags,
|
||||
MsgId msgId,
|
||||
|
||||
@@ -75,19 +75,21 @@ public:
|
||||
return kScheduledDraftIndex + kEditDraftShift;
|
||||
}
|
||||
[[nodiscard]] static DraftKey Replies(MsgId rootId) {
|
||||
return rootId;
|
||||
return rootId.bare;
|
||||
}
|
||||
[[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) {
|
||||
return rootId + kEditDraftShift;
|
||||
return rootId.bare + kEditDraftShift;
|
||||
}
|
||||
|
||||
[[nodiscard]] static DraftKey FromSerialized(int32 value) {
|
||||
[[nodiscard]] static DraftKey FromSerialized(qint64 value) {
|
||||
return value;
|
||||
}
|
||||
[[nodiscard]] int32 serialize() const {
|
||||
[[nodiscard]] qint64 serialize() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
[[nodiscard]] static DraftKey FromSerializedOld(int32 value);
|
||||
|
||||
inline bool operator<(const DraftKey &other) const {
|
||||
return _value < other._value;
|
||||
}
|
||||
@@ -111,15 +113,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
DraftKey(int value) : _value(value) {
|
||||
DraftKey(int64 value) : _value(value) {
|
||||
}
|
||||
|
||||
static constexpr auto kLocalDraftIndex = -1;
|
||||
static constexpr auto kCloudDraftIndex = -2;
|
||||
static constexpr auto kScheduledDraftIndex = -3;
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId;
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId.bare;
|
||||
static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF;
|
||||
|
||||
int _value = 0;
|
||||
int64 _value = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_groups.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
|
||||
@@ -140,8 +142,13 @@ const Group *Groups::find(not_null<const HistoryItem*> item) const {
|
||||
}
|
||||
|
||||
void Groups::refreshViews(const HistoryItemsList &items) {
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto history = items.front()->history();
|
||||
for (const auto &item : items) {
|
||||
_data->requestItemViewRefresh(item);
|
||||
history->lastItemDialogsView.itemInvalidated(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ class Session;
|
||||
|
||||
struct Group {
|
||||
HistoryItemsList items;
|
||||
|
||||
};
|
||||
|
||||
class Groups {
|
||||
|
||||
@@ -72,13 +72,13 @@ void Histories::readInbox(not_null<History*> history) {
|
||||
if (history->lastServerMessageKnown()) {
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: last known, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
).arg(last ? last->id.bare : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
return;
|
||||
} else if (history->loadedAtBottom()) {
|
||||
if (const auto lastId = history->maxMsgId()) {
|
||||
DEBUG_LOG(("Reading: loaded at bottom, maxMsgId %1."
|
||||
).arg(lastId));
|
||||
).arg(lastId.bare));
|
||||
readInboxTill(history, lastId);
|
||||
return;
|
||||
} else if (history->loadedAtTop()) {
|
||||
@@ -93,7 +93,7 @@ void Histories::readInbox(not_null<History*> history) {
|
||||
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: got entry, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
).arg(last ? last->id.bare : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
});
|
||||
}
|
||||
@@ -147,7 +147,7 @@ void Histories::readInboxTill(
|
||||
Expects(IsServerMsgId(tillId) || (!tillId && !force));
|
||||
|
||||
DEBUG_LOG(("Reading: readInboxTill %1, force %2."
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(Logs::b(force)));
|
||||
|
||||
const auto syncGuard = gsl::finally([&] {
|
||||
@@ -156,8 +156,8 @@ void Histories::readInboxTill(
|
||||
if (history->unreadCount() > 0) {
|
||||
if (const auto last = history->lastServerMessage()) {
|
||||
DEBUG_LOG(("Reading: checking last %1 and %2."
|
||||
).arg(last->id
|
||||
).arg(tillId));
|
||||
).arg(last->id.bare
|
||||
).arg(tillId.bare));
|
||||
if (last->id == tillId) {
|
||||
DEBUG_LOG(("Reading: locally marked as read."));
|
||||
history->setUnreadCount(0);
|
||||
@@ -180,11 +180,11 @@ void Histories::readInboxTill(
|
||||
const auto maybeState = lookup(history);
|
||||
if (maybeState && maybeState->sentReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 3 with %1."
|
||||
).arg(maybeState->sentReadTill));
|
||||
).arg(maybeState->sentReadTill.bare));
|
||||
return;
|
||||
} else if (maybeState && maybeState->willReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 4 with %1 and force %2."
|
||||
).arg(maybeState->sentReadTill
|
||||
).arg(maybeState->sentReadTill.bare
|
||||
).arg(Logs::b(force)));
|
||||
if (force) {
|
||||
sendPendingReadInbox(history);
|
||||
@@ -200,7 +200,7 @@ void Histories::readInboxTill(
|
||||
&& history->unreadCountKnown()
|
||||
&& *stillUnread == history->unreadCount()) {
|
||||
DEBUG_LOG(("Reading: count didn't change so just update till %1"
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
history->setInboxReadTill(tillId);
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ void Histories::readInboxTill(
|
||||
state.willReadTill = tillId;
|
||||
if (force || !stillUnread || !*stillUnread) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with still unread %2"
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(stillUnread.value_or(-666)));
|
||||
state.willReadWhen = 0;
|
||||
sendReadRequests();
|
||||
@@ -216,17 +216,18 @@ void Histories::readInboxTill(
|
||||
return;
|
||||
}
|
||||
} else if (!state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed").arg(tillId));
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed"
|
||||
).arg(tillId.bare));
|
||||
state.willReadWhen = crl::now() + kReadRequestTimeout;
|
||||
if (!_readRequestsTimer.isActive()) {
|
||||
_readRequestsTimer.callOnce(kReadRequestTimeout);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("Reading: will read till %1 postponed already"
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
}
|
||||
DEBUG_LOG(("Reading: marking now with till %1 and still %2"
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(*stillUnread));
|
||||
history->setInboxReadTill(tillId);
|
||||
history->setUnreadCount(*stillUnread);
|
||||
@@ -437,10 +438,53 @@ void Histories::requestFakeChatListMessage(
|
||||
});
|
||||
}
|
||||
|
||||
void Histories::requestGroupAround(not_null<HistoryItem*> item) {
|
||||
const auto history = item->history();
|
||||
const auto id = item->id;
|
||||
const auto i = _chatListGroupRequests.find(history);
|
||||
if (i != end(_chatListGroupRequests)) {
|
||||
if (i->second.aroundId == id) {
|
||||
return;
|
||||
} else {
|
||||
cancelRequest(i->second.requestId);
|
||||
_chatListGroupRequests.erase(i);
|
||||
}
|
||||
}
|
||||
constexpr auto kMaxAlbumCount = 10;
|
||||
const auto requestId = sendRequest(history, RequestType::History, [=](
|
||||
Fn<void()> finish) {
|
||||
return session().api().request(MTPmessages_GetHistory(
|
||||
history->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_int(0), // offset_date
|
||||
MTP_int(-kMaxAlbumCount),
|
||||
MTP_int(2 * kMaxAlbumCount - 1),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_long(0) // hash
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_owner->processExistingMessages(
|
||||
history->peer->asChannel(),
|
||||
result);
|
||||
_chatListGroupRequests.remove(history);
|
||||
history->migrateToOrMe()->applyChatListGroup(
|
||||
history->channelId(),
|
||||
result);
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_chatListGroupRequests.remove(history);
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
_chatListGroupRequests.emplace(
|
||||
history,
|
||||
ChatListGroupRequest{ .aroundId = id, .requestId = requestId });
|
||||
}
|
||||
|
||||
void Histories::sendPendingReadInbox(not_null<History*> history) {
|
||||
if (const auto state = lookup(history)) {
|
||||
DEBUG_LOG(("Reading: send pending now with till %1 and when %2"
|
||||
).arg(state->willReadTill
|
||||
).arg(state->willReadTill.bare
|
||||
).arg(state->willReadWhen));
|
||||
if (state->willReadTill && state->willReadWhen) {
|
||||
state->willReadWhen = 0;
|
||||
@@ -462,7 +506,7 @@ void Histories::sendReadRequests() {
|
||||
continue;
|
||||
} else if (state.willReadWhen <= now) {
|
||||
DEBUG_LOG(("Reading: sending with till %1."
|
||||
).arg(state.willReadTill));
|
||||
).arg(state.willReadTill.bare));
|
||||
sendReadRequest(history, state);
|
||||
} else if (!next || *next > state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: scheduling for later send."));
|
||||
@@ -483,10 +527,10 @@ void Histories::sendReadRequest(not_null<History*> history, State &state) {
|
||||
state.willReadWhen = 0;
|
||||
state.sentReadDone = false;
|
||||
DEBUG_LOG(("Reading: sending request now with till %1."
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
|
||||
DEBUG_LOG(("Reading: sending request invoked with till %1."
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
const auto finished = [=] {
|
||||
const auto state = lookup(history);
|
||||
Assert(state != nullptr);
|
||||
|
||||
@@ -59,6 +59,8 @@ public:
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void requestGroupAround(not_null<HistoryItem*> item);
|
||||
|
||||
void deleteMessages(
|
||||
not_null<History*> history,
|
||||
const QVector<MTPint> &ids,
|
||||
@@ -95,6 +97,10 @@ private:
|
||||
bool sentReadDone = false;
|
||||
bool postponedRequestEntry = false;
|
||||
};
|
||||
struct ChatListGroupRequest {
|
||||
MsgId aroundId = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
void readInboxTill(not_null<History*> history, MsgId tillId, bool force);
|
||||
void sendReadRequests();
|
||||
@@ -130,6 +136,10 @@ private:
|
||||
|
||||
base::flat_set<not_null<History*>> _fakeChatListRequests;
|
||||
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
ChatListGroupRequest> _chatListGroupRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "history/view/media/history_view_slot_machine.h"
|
||||
#include "history/view/media/history_view_dice.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/text/format_values.h"
|
||||
@@ -37,23 +38,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_auto_download.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
constexpr auto kMaxPreviewImages = 3;
|
||||
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
|
||||
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
|
||||
auto result = Call();
|
||||
@@ -98,20 +108,23 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
|
||||
[[nodiscard]] QString WithCaptionDialogsText(
|
||||
const QString &attachType,
|
||||
const QString &caption) {
|
||||
const QString &caption,
|
||||
bool hasMiniImages) {
|
||||
if (caption.isEmpty()) {
|
||||
return textcmdLink(1, TextUtilities::Clean(attachType));
|
||||
}
|
||||
|
||||
return tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
return hasMiniImages
|
||||
? TextUtilities::Clean(caption)
|
||||
: tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption));
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString WithCaptionNotificationText(
|
||||
@@ -132,6 +145,151 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
caption);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PreparePreviewImage(
|
||||
not_null<const Image*> image,
|
||||
ImageRoundRadius radius = ImageRoundRadius::Small) {
|
||||
const auto original = image->original();
|
||||
if (original.width() * 10 < original.height()
|
||||
|| original.height() * 10 < original.width()) {
|
||||
return QImage();
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::dialogsMiniPreview * factor;
|
||||
const auto scaled = original.scaled(
|
||||
QSize(size, size),
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation);
|
||||
auto square = scaled.copy(
|
||||
(scaled.width() - size) / 2,
|
||||
(scaled.height() - size) / 2,
|
||||
size,
|
||||
size
|
||||
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
if (radius == ImageRoundRadius::Small) {
|
||||
struct Cache {
|
||||
base::flat_map<int, std::array<QImage, 4>> all;
|
||||
std::array<QImage, 4> *lastUsed = nullptr;
|
||||
int lastUsedRadius = 0;
|
||||
};
|
||||
static auto cache = Cache();
|
||||
const auto pxRadius = st::dialogsMiniPreviewRadius;
|
||||
if (!cache.lastUsed || cache.lastUsedRadius != pxRadius) {
|
||||
cache.lastUsedRadius = pxRadius;
|
||||
const auto i = cache.all.find(pxRadius);
|
||||
if (i != end(cache.all)) {
|
||||
cache.lastUsed = &i->second;
|
||||
} else {
|
||||
cache.lastUsed = &cache.all.emplace(
|
||||
pxRadius,
|
||||
Images::CornersMask(pxRadius)).first->second;
|
||||
}
|
||||
}
|
||||
Images::prepareRound(square, *cache.lastUsed);
|
||||
} else {
|
||||
Images::prepareRound(square, radius);
|
||||
}
|
||||
square.setDevicePixelRatio(factor);
|
||||
return square;
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<PhotoMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
const auto photo = media->owner();
|
||||
const auto readyCacheKey = reinterpret_cast<uint64>(photo.get());
|
||||
if (const auto small = media->image(PhotoSize::Small)) {
|
||||
return { PreparePreviewImage(small, radius), readyCacheKey };
|
||||
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
|
||||
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
|
||||
} else if (const auto large = media->image(PhotoSize::Large)) {
|
||||
return { PreparePreviewImage(large, radius), readyCacheKey };
|
||||
}
|
||||
const auto allowedToDownload = [&] {
|
||||
const auto photo = media->owner();
|
||||
if (media->loaded() || photo->cancelled()) {
|
||||
return false;
|
||||
}
|
||||
return photo->hasExact(PhotoSize::Small)
|
||||
|| photo->hasExact(PhotoSize::Thumbnail)
|
||||
|| AutoDownload::Should(
|
||||
photo->session().settings().autoDownload(),
|
||||
item->history()->peer,
|
||||
photo);
|
||||
}();
|
||||
const auto cacheKey = allowedToDownload ? 0 : readyCacheKey;
|
||||
if (allowedToDownload) {
|
||||
media->owner()->load(PhotoSize::Small, item->fullId());
|
||||
}
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), cacheKey };
|
||||
}
|
||||
return { QImage(), allowedToDownload ? 0 : cacheKey };
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
Expects(media->owner()->hasThumbnail());
|
||||
|
||||
const auto document = media->owner();
|
||||
const auto readyCacheKey = reinterpret_cast<uint64>(document.get());
|
||||
if (const auto thumbnail = media->thumbnail()) {
|
||||
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
|
||||
}
|
||||
document->loadThumbnail(item->fullId());
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), 0 };
|
||||
}
|
||||
return { QImage(), 0 };
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
|
||||
Expects(!preview.isNull());
|
||||
|
||||
{
|
||||
QPainter p(&preview);
|
||||
st::dialogsMiniPlay.paintInCenter(
|
||||
p,
|
||||
QRect(QPoint(), preview.size() / preview.devicePixelRatio()));
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PrepareFilePreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
auto result = PrepareFilePreviewImage(item, media, radius);
|
||||
const auto document = media->owner();
|
||||
if (!result.data.isNull()
|
||||
&& (document->isVideoFile() || document->isVideoMessage())) {
|
||||
result.data = PutPlayIcon(std::move(result.data));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool TryFilePreview(not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail()
|
||||
&& !document->sticker()
|
||||
&& !document->isAudioFile();
|
||||
}
|
||||
|
||||
template <typename MediaType>
|
||||
[[nodiscard]] ItemPreviewImage FindCachedPreview(
|
||||
const std::vector<ItemPreviewImage> *existing,
|
||||
not_null<MediaType*> data) {
|
||||
if (!existing) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(
|
||||
*existing,
|
||||
reinterpret_cast<uint64>(data.get()),
|
||||
&ItemPreviewImage::cacheKey);
|
||||
return (i != end(*existing)) ? *i : ItemPreviewImage();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextForMimeData WithCaptionClipboardText(
|
||||
@@ -201,11 +359,12 @@ bool Media::canBeGrouped() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Media::chatListText(DrawInDialog way) const {
|
||||
ItemPreview Media::toPreview(ToPreviewOptions options) const {
|
||||
auto result = notificationText();
|
||||
return result.isEmpty()
|
||||
auto text = result.isEmpty()
|
||||
? QString()
|
||||
: textcmdLink(1, TextUtilities::Clean(std::move(result)));
|
||||
return { .text = std::move(text) };
|
||||
}
|
||||
|
||||
bool Media::hasReplyPreview() const {
|
||||
@@ -270,6 +429,52 @@ std::unique_ptr<HistoryView::Media> Media::createView(
|
||||
return createView(message, message->data(), replacing);
|
||||
}
|
||||
|
||||
ItemPreview Media::toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const {
|
||||
const auto genericText = textcmdLink(
|
||||
1,
|
||||
TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
|
||||
auto result = ItemPreview();
|
||||
auto loadingContext = std::vector<std::any>();
|
||||
for (const auto &item : items) {
|
||||
if (const auto media = item->media()) {
|
||||
auto copy = options;
|
||||
copy.ignoreGroup = true;
|
||||
const auto already = int(result.images.size());
|
||||
const auto left = kMaxPreviewImages - already;
|
||||
auto single = left ? media->toPreview(copy) : ItemPreview();
|
||||
if (!single.images.empty()) {
|
||||
while (single.images.size() > left) {
|
||||
single.images.pop_back();
|
||||
}
|
||||
result.images.insert(
|
||||
end(result.images),
|
||||
std::make_move_iterator(begin(single.images)),
|
||||
std::make_move_iterator(end(single.images)));
|
||||
}
|
||||
if (single.loadingContext.has_value()) {
|
||||
loadingContext.push_back(std::move(single.loadingContext));
|
||||
}
|
||||
const auto original = item->originalText().text;
|
||||
if (!original.isEmpty()) {
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = TextUtilities::Clean(original);
|
||||
} else {
|
||||
result.text = genericText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = genericText;
|
||||
}
|
||||
if (!loadingContext.empty()) {
|
||||
result.loadingContext = std::move(loadingContext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MediaPhoto::MediaPhoto(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PhotoData*> photo)
|
||||
@@ -341,11 +546,40 @@ QString MediaPhoto::notificationText() const {
|
||||
parent()->originalText().text);
|
||||
}
|
||||
|
||||
QString MediaPhoto::chatListText(DrawInDialog way) const {
|
||||
const auto caption = (way == DrawInDialog::WithoutSenderAndCaption)
|
||||
ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||
const auto item = parent();
|
||||
if (!options.ignoreGroup && item->groupId()) {
|
||||
if (const auto group = item->history()->owner().groups().find(item)
|
||||
; group && group->items.size() > 1) {
|
||||
return toGroupPreview(group->items, options);
|
||||
}
|
||||
}
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto context = std::any();
|
||||
if (auto cached = FindCachedPreview(options.existing, _photo)) {
|
||||
images.push_back(std::move(cached));
|
||||
} else {
|
||||
const auto media = _photo->createMediaView();
|
||||
const auto radius = _chat
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (auto prepared = PreparePhotoPreview(parent(), media, radius)
|
||||
; prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto type = tr::lng_in_dlg_photo(tr::now);
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
return WithCaptionDialogsText(tr::lng_in_dlg_photo(tr::now), caption);
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, !images.empty()),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaPhoto::pinnedTextSubstring() const {
|
||||
@@ -511,16 +745,40 @@ bool MediaFile::replyPreviewLoaded() const {
|
||||
return _document->replyPreviewLoaded();
|
||||
}
|
||||
|
||||
QString MediaFile::chatListText(DrawInDialog way) const {
|
||||
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
const auto item = parent();
|
||||
if (!options.ignoreGroup && item->groupId()) {
|
||||
if (const auto group = item->history()->owner().groups().find(item)
|
||||
; group && group->items.size() > 1) {
|
||||
return toGroupPreview(group->items, options);
|
||||
}
|
||||
}
|
||||
if (const auto sticker = _document->sticker()) {
|
||||
return Media::chatListText(way);
|
||||
return Media::toPreview(options);
|
||||
}
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto context = std::any();
|
||||
if (auto cached = FindCachedPreview(options.existing, _document)) {
|
||||
images.push_back(std::move(cached));
|
||||
} else if (TryFilePreview(_document)) {
|
||||
const auto media = _document->createMediaView();
|
||||
const auto radius = _document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (auto prepared = PrepareFilePreview(parent(), media, radius)
|
||||
; prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto type = [&] {
|
||||
using namespace Ui::Text;
|
||||
if (_document->isVideoMessage()) {
|
||||
return tr::lng_in_dlg_video_message(tr::now);
|
||||
} else if (_document->isAnimation()) {
|
||||
return qsl("GIF");
|
||||
return u"GIF"_q;
|
||||
} else if (_document->isVideoFile()) {
|
||||
return tr::lng_in_dlg_video(tr::now);
|
||||
} else if (_document->isVoiceMessage()) {
|
||||
@@ -533,10 +791,14 @@ QString MediaFile::chatListText(DrawInDialog way) const {
|
||||
}
|
||||
return tr::lng_in_dlg_file(tr::now);
|
||||
}();
|
||||
const auto caption = (way == DrawInDialog::WithoutSenderAndCaption)
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
return WithCaptionDialogsText(type, caption);
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, !images.empty()),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaFile::notificationText() const {
|
||||
@@ -855,8 +1117,10 @@ Data::CloudImage *MediaLocation::location() const {
|
||||
return _location;
|
||||
}
|
||||
|
||||
QString MediaLocation::chatListText(DrawInDialog way) const {
|
||||
return WithCaptionDialogsText(tr::lng_maps_point(tr::now), _title);
|
||||
ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
|
||||
const auto type = tr::lng_maps_point(tr::now);
|
||||
const auto hasMiniImages = false;
|
||||
return { .text = WithCaptionDialogsText(type, _title, hasMiniImages) };
|
||||
}
|
||||
|
||||
QString MediaLocation::notificationText() const {
|
||||
@@ -1049,8 +1313,8 @@ bool MediaWebPage::replyPreviewLoaded() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MediaWebPage::chatListText(DrawInDialog way) const {
|
||||
return notificationText();
|
||||
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
||||
return { .text = notificationText() };
|
||||
}
|
||||
|
||||
QString MediaWebPage::notificationText() const {
|
||||
|
||||
@@ -27,7 +27,9 @@ namespace HistoryView {
|
||||
enum class Context : char;
|
||||
class Element;
|
||||
class Media;
|
||||
enum class DrawInDialog;
|
||||
struct ItemPreview;
|
||||
struct ItemPreviewImage;
|
||||
struct ToPreviewOptions;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Data {
|
||||
@@ -73,7 +75,9 @@ public:
|
||||
|
||||
not_null<HistoryItem*> parent() const;
|
||||
|
||||
using DrawInDialog = HistoryView::DrawInDialog;
|
||||
using ToPreviewOptions = HistoryView::ToPreviewOptions;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;
|
||||
|
||||
@@ -95,7 +99,7 @@ public:
|
||||
virtual bool replyPreviewLoaded() const;
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
virtual QString chatListText(DrawInDialog way) const;
|
||||
virtual ItemPreview toPreview(ToPreviewOptions way) const;
|
||||
virtual QString notificationText() const = 0;
|
||||
virtual QString pinnedTextSubstring() const = 0;
|
||||
virtual TextForMimeData clipboardText() const = 0;
|
||||
@@ -125,6 +129,11 @@ public:
|
||||
not_null<HistoryView::Element*> message,
|
||||
HistoryView::Element *replacing = nullptr);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] ItemPreview toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const;
|
||||
|
||||
private:
|
||||
const not_null<HistoryItem*> _parent;
|
||||
|
||||
@@ -151,7 +160,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -189,7 +198,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -255,7 +264,7 @@ public:
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
Data::CloudImage *location() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -323,7 +332,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
192
Telegram/SourceFiles/data/data_msg_id.h
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_peer_id.h"
|
||||
|
||||
struct MsgId {
|
||||
constexpr MsgId() noexcept = default;
|
||||
constexpr MsgId(int64 value) noexcept : bare(value) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept {
|
||||
return (bare != 0);
|
||||
}
|
||||
[[nodiscard]] constexpr bool operator!() const noexcept {
|
||||
return !bare;
|
||||
}
|
||||
[[nodiscard]] constexpr MsgId operator-() const noexcept {
|
||||
return -bare;
|
||||
}
|
||||
constexpr MsgId operator++() noexcept {
|
||||
return ++bare;
|
||||
}
|
||||
constexpr MsgId operator++(int) noexcept {
|
||||
return bare++;
|
||||
}
|
||||
constexpr MsgId operator--() noexcept {
|
||||
return --bare;
|
||||
}
|
||||
constexpr MsgId operator--(int) noexcept {
|
||||
return bare--;
|
||||
}
|
||||
|
||||
int64 bare = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(MsgId);
|
||||
|
||||
[[nodiscard]] inline constexpr MsgId operator+(MsgId a, MsgId b) noexcept {
|
||||
return MsgId(a.bare + b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr MsgId operator-(MsgId a, MsgId b) noexcept {
|
||||
return MsgId(a.bare - b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare == b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare != b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare < b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare > b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare <= b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare >= b.bare);
|
||||
}
|
||||
|
||||
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
|
||||
constexpr auto EndClientMsgId = MsgId(-(1LL << 57));
|
||||
constexpr auto ServerMaxMsgId = MsgId(1LL << 56);
|
||||
constexpr auto ShowAtUnreadMsgId = MsgId(0);
|
||||
|
||||
constexpr auto SpecialMsgIdShift = EndClientMsgId.bare;
|
||||
constexpr auto ShowAtTheEndMsgId = MsgId(SpecialMsgIdShift + 1);
|
||||
constexpr auto SwitchAtTopMsgId = MsgId(SpecialMsgIdShift + 2);
|
||||
constexpr auto ShowAtProfileMsgId = MsgId(SpecialMsgIdShift + 3);
|
||||
constexpr auto ShowAndStartBotMsgId = MsgId(SpecialMsgIdShift + 4);
|
||||
constexpr auto ShowAtGameShareMsgId = MsgId(SpecialMsgIdShift + 5);
|
||||
constexpr auto ShowForChooseMessagesMsgId = MsgId(SpecialMsgIdShift + 6);
|
||||
|
||||
static_assert(SpecialMsgIdShift + 0xFF < 0);
|
||||
static_assert(-(SpecialMsgIdShift + 0xFF) > ServerMaxMsgId);
|
||||
|
||||
[[nodiscard]] constexpr inline bool IsClientMsgId(MsgId id) noexcept {
|
||||
return (id >= StartClientMsgId && id < EndClientMsgId);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr inline bool IsServerMsgId(MsgId id) noexcept {
|
||||
return (id > 0 && id < ServerMaxMsgId);
|
||||
}
|
||||
|
||||
struct MsgRange {
|
||||
constexpr MsgRange() noexcept = default;
|
||||
constexpr MsgRange(MsgId from, MsgId till) noexcept
|
||||
: from(from)
|
||||
, till(till) {
|
||||
}
|
||||
|
||||
MsgId from = 0;
|
||||
MsgId till = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(
|
||||
MsgRange a,
|
||||
MsgRange b) noexcept {
|
||||
return (a.from == b.from) && (a.till == b.till);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(
|
||||
MsgRange a,
|
||||
MsgRange b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
struct FullMsgId {
|
||||
constexpr FullMsgId() noexcept = default;
|
||||
constexpr FullMsgId(ChannelId channel, MsgId msg) noexcept
|
||||
: channel(channel), msg(msg) {
|
||||
}
|
||||
|
||||
constexpr explicit operator bool() const noexcept {
|
||||
return msg != 0;
|
||||
}
|
||||
constexpr bool operator!() const noexcept {
|
||||
return msg == 0;
|
||||
}
|
||||
|
||||
ChannelId channel = NoChannel;
|
||||
MsgId msg = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
if (a.channel < b.channel) {
|
||||
return true;
|
||||
} else if (a.channel > b.channel) {
|
||||
return false;
|
||||
}
|
||||
return a.msg < b.msg;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return (a.channel == b.channel) && (a.msg == b.msg);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(FullMsgId);
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<MsgId> : private hash<int64> {
|
||||
size_t operator()(MsgId value) const noexcept {
|
||||
return hash<int64>::operator()(value.bare);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
@@ -1004,19 +1004,24 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PeerData::setThemeEmoji(const QString &emoji) {
|
||||
if (_themeEmoji == emoji) {
|
||||
void PeerData::setThemeEmoji(const QString &emoticon) {
|
||||
if (_themeEmoticon == emoticon) {
|
||||
return;
|
||||
}
|
||||
_themeEmoji = emoji;
|
||||
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
|
||||
if (Ui::Emoji::Find(_themeEmoticon) == Ui::Emoji::Find(emoticon)) {
|
||||
_themeEmoticon = emoticon;
|
||||
return;
|
||||
}
|
||||
_themeEmoticon = emoticon;
|
||||
if (!emoticon.isEmpty()
|
||||
&& !owner().cloudThemes().themeForEmoji(emoticon)) {
|
||||
owner().cloudThemes().refreshChatThemes();
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
|
||||
}
|
||||
|
||||
const QString &PeerData::themeEmoji() const {
|
||||
return _themeEmoji;
|
||||
return _themeEmoticon;
|
||||
}
|
||||
|
||||
void PeerData::setIsBlocked(bool is) {
|
||||
|
||||
@@ -459,7 +459,7 @@ public:
|
||||
[[nodiscard]] Data::GroupCall *groupCall() const;
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setThemeEmoji(const QString &emoji);
|
||||
void setThemeEmoji(const QString &emoticon);
|
||||
[[nodiscard]] const QString &themeEmoji() const;
|
||||
|
||||
const PeerId id;
|
||||
@@ -506,7 +506,7 @@ private:
|
||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||
|
||||
QString _about;
|
||||
QString _themeEmoji;
|
||||
QString _themeEmoticon;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ constexpr auto kMessagesPerPage = 50;
|
||||
TimeId date,
|
||||
const QString &text) {
|
||||
return history->makeServiceMessage(
|
||||
history->session().data().nextNonHistoryEntryId(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
MessageFlag::FakeHistoryItem,
|
||||
date,
|
||||
HistoryService::PreparedText{ text });
|
||||
@@ -101,7 +101,7 @@ rpl::producer<MessagesSlice> RepliesList::source(
|
||||
_partLoaded.events(
|
||||
) | rpl::start_with_next(pushDelayed, lifetime);
|
||||
|
||||
_history->session().data().channelDifferenceTooLong(
|
||||
_history->owner().channelDifferenceTooLong(
|
||||
) | rpl::filter([=](not_null<ChannelData*> channel) {
|
||||
if (_history->peer != channel || !_skippedAfter.has_value()) {
|
||||
return false;
|
||||
|
||||
@@ -32,18 +32,18 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
&& (item->date() > base::unixtime::now());
|
||||
}
|
||||
|
||||
MTPMessage PrepareMessage(const MTPMessage &message, MsgId id) {
|
||||
MTPMessage PrepareMessage(const MTPMessage &message) {
|
||||
return message.match([&](const MTPDmessageEmpty &data) {
|
||||
return MTP_messageEmpty(
|
||||
data.vflags(),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
return MTP_messageService(
|
||||
MTP_flags(data.vflags().v
|
||||
| MTPDmessageService::Flag(
|
||||
MTPDmessage::Flag::f_from_scheduled)),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -53,7 +53,7 @@ MTPMessage PrepareMessage(const MTPMessage &message, MsgId id) {
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return MTP_message(
|
||||
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
@@ -192,6 +192,7 @@ void ScheduledMessages::sendNowSimpleMessage(
|
||||
const auto views = 1;
|
||||
const auto forwards = 0;
|
||||
history->addNewMessage(
|
||||
update.vid().v,
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
update.vid(),
|
||||
@@ -269,7 +270,8 @@ void ScheduledMessages::checkEntitiesAndUpdate(const MTPDmessage &data) {
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(_session, data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(data.vreply_markup());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
_session->data().requestItemTextRefresh(existing);
|
||||
|
||||
@@ -454,7 +456,8 @@ HistoryItem *ScheduledMessages::append(
|
||||
_session,
|
||||
data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(data.vreply_markup());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
existing->updateDate(data.vdate().v);
|
||||
history->owner().requestItemTextRefresh(existing);
|
||||
@@ -463,7 +466,8 @@ HistoryItem *ScheduledMessages::append(
|
||||
}
|
||||
|
||||
const auto item = _session->data().addNewMessage(
|
||||
PrepareMessage(message, history->nextNonHistoryEntryId()),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareMessage(message),
|
||||
MessageFlags(), // localFlags
|
||||
NewMessageType::Existing);
|
||||
if (!item || item->history() != history) {
|
||||
@@ -546,7 +550,7 @@ uint64 ScheduledMessages::countListHash(const List &list) const {
|
||||
}) | ranges::views::reverse;
|
||||
for (const auto &item : serverside) {
|
||||
const auto j = list.idByItem.find(item.get());
|
||||
HashUpdate(hash, j->second);
|
||||
HashUpdate(hash, j->second.bare);
|
||||
if (const auto edited = item->Get<HistoryMessageEdited>()) {
|
||||
HashUpdate(hash, edited->date);
|
||||
} else {
|
||||
|
||||
@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_send_action.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_media_rotation.h"
|
||||
@@ -95,7 +96,7 @@ void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
|
||||
return;
|
||||
}
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
for (const auto &row : markup->rows) {
|
||||
for (const auto &row : markup->data.rows) {
|
||||
for (const auto &button : row) {
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
if (button.type == ButtonType::SwitchInline) {
|
||||
@@ -239,7 +240,8 @@ Session::Session(not_null<Main::Session*> session)
|
||||
, _streaming(std::make_unique<Streaming>(this))
|
||||
, _mediaRotation(std::make_unique<MediaRotation>())
|
||||
, _histories(std::make_unique<Histories>(this))
|
||||
, _stickers(std::make_unique<Stickers>(this)) {
|
||||
, _stickers(std::make_unique<Stickers>(this))
|
||||
, _sponsoredMessages(std::make_unique<SponsoredMessages>(this)) {
|
||||
_cache->open(_session->local().cacheKey());
|
||||
_bigFileCache->open(_session->local().cacheBigFileKey());
|
||||
|
||||
@@ -280,6 +282,7 @@ void Session::clear() {
|
||||
|
||||
_histories->unloadAll();
|
||||
_scheduledMessages = nullptr;
|
||||
_sponsoredMessages = nullptr;
|
||||
_dependentMessages.clear();
|
||||
base::take(_messages);
|
||||
base::take(_channelMessages);
|
||||
@@ -1382,6 +1385,10 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto history = item->history();
|
||||
if (history->lastItemDialogsView.dependsOn(item)) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
|
||||
@@ -1838,8 +1845,8 @@ void Session::processMessages(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const auto id = IdFromMessage(message);
|
||||
indices.emplace((uint64(uint32(id)) << 32) | uint64(i), i);
|
||||
const auto id = IdFromMessage(message); // Only 32 bit values here.
|
||||
indices.emplace((uint64(uint32(id.bare)) << 32) | uint64(i), i);
|
||||
}
|
||||
for (const auto &[position, index] : indices) {
|
||||
addNewMessage(
|
||||
@@ -1855,6 +1862,26 @@ void Session::processMessages(
|
||||
processMessages(data.v, type);
|
||||
}
|
||||
|
||||
void Session::processExistingMessages(
|
||||
ChannelData *channel,
|
||||
const MTPmessages_Messages &data) {
|
||||
data.match([&](const MTPDmessages_channelMessages &data) {
|
||||
if (channel) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages!"));
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
|
||||
data.match([&](const MTPDmessages_messagesNotModified&) {
|
||||
LOG(("API Error: received messages.messagesNotModified!"));
|
||||
}, [&](const auto &data) {
|
||||
processUsers(data.vusers());
|
||||
processChats(data.vchats());
|
||||
processMessages(data.vmessages(), NewMessageType::Existing);
|
||||
});
|
||||
}
|
||||
|
||||
const Session::Messages *Session::messagesList(ChannelId channelId) const {
|
||||
if (channelId == NoChannel) {
|
||||
return &_messages;
|
||||
@@ -2166,12 +2193,21 @@ HistoryItem *Session::addNewMessage(
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
return addNewMessage(IdFromMessage(data), data, localFlags, type);
|
||||
}
|
||||
|
||||
HistoryItem *Session::addNewMessage(
|
||||
MsgId id,
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
const auto peerId = PeerFromMessage(data);
|
||||
if (!peerId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto result = history(peerId)->addNewMessage(
|
||||
id,
|
||||
data,
|
||||
localFlags,
|
||||
type);
|
||||
@@ -3948,10 +3984,12 @@ void Session::insertCheckedServiceNotification(
|
||||
| MessageFlag::LocalHistoryEntry;
|
||||
auto sending = TextWithEntities(), left = message;
|
||||
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
|
||||
const auto id = nextLocalMessageId();
|
||||
addNewMessage(
|
||||
id,
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
MTP_int(nextLocalMessageId()),
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
MTPMessageFwdHeader(),
|
||||
|
||||
@@ -51,6 +51,7 @@ class LocationPoint;
|
||||
class WallPaper;
|
||||
class ScheduledMessages;
|
||||
class SendActionManager;
|
||||
class SponsoredMessages;
|
||||
class ChatFilters;
|
||||
class CloudThemes;
|
||||
class Streaming;
|
||||
@@ -109,6 +110,9 @@ public:
|
||||
[[nodiscard]] Stickers &stickers() const {
|
||||
return *_stickers;
|
||||
}
|
||||
[[nodiscard]] SponsoredMessages &sponsoredMessages() const {
|
||||
return *_sponsoredMessages;
|
||||
}
|
||||
[[nodiscard]] MsgId nextNonHistoryEntryId() {
|
||||
return ++_nonHistoryEntryId;
|
||||
}
|
||||
@@ -341,6 +345,9 @@ public:
|
||||
void processMessages(
|
||||
const MTPVector<MTPMessage> &data,
|
||||
NewMessageType type);
|
||||
void processExistingMessages(
|
||||
ChannelData *channel,
|
||||
const MTPmessages_Messages &data);
|
||||
void processMessagesDeleted(
|
||||
ChannelId channelId,
|
||||
const QVector<MTPint> &data);
|
||||
@@ -397,6 +404,11 @@ public:
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
HistoryItem *addNewMessage( // Override message id.
|
||||
MsgId id,
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
|
||||
[[nodiscard]] int unreadBadge() const;
|
||||
[[nodiscard]] bool unreadBadgeMuted() const;
|
||||
@@ -955,6 +967,7 @@ private:
|
||||
std::unique_ptr<MediaRotation> _mediaRotation;
|
||||
std::unique_ptr<Histories> _histories;
|
||||
std::unique_ptr<Stickers> _stickers;
|
||||
std::unique_ptr<SponsoredMessages> _sponsoredMessages;
|
||||
MsgId _nonHistoryEntryId = ServerMaxMsgId;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -366,7 +366,7 @@ std::optional<int> SharedMediaWithLastSlice::indexOf(Value value) const {
|
||||
: QString("-"));
|
||||
if (const auto msgId = std::get_if<FullMsgId>(&value)) {
|
||||
info.push_back("value:" + QString::number(msgId->channel.bare));
|
||||
info.push_back(QString::number(msgId->msg));
|
||||
info.push_back(QString::number(msgId->msg.bare));
|
||||
const auto index = _slice.indexOf(*std::get_if<FullMsgId>(&value));
|
||||
info.push_back("index:" + (index
|
||||
? QString::number(*index)
|
||||
|
||||
196
Telegram/SourceFiles/data/data_sponsored_messages.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_sponsored_messages.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
|
||||
return (received > 0) && (received + kRequestTimeLimit > crl::now());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SponsoredMessages::SponsoredMessages(not_null<Session*> owner)
|
||||
: _session(&owner->session())
|
||||
, _clearTimer([=] { clearOldRequests(); }) {
|
||||
}
|
||||
|
||||
SponsoredMessages::~SponsoredMessages() {
|
||||
for (const auto &request : _requests) {
|
||||
_session->api().request(request.second.requestId).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void SponsoredMessages::clearOldRequests() {
|
||||
const auto now = crl::now();
|
||||
while (true) {
|
||||
const auto i = ranges::find_if(_requests, [&](const auto &value) {
|
||||
const auto &request = value.second;
|
||||
return !request.requestId
|
||||
&& (request.lastReceived + kRequestTimeLimit <= now);
|
||||
});
|
||||
if (i == end(_requests)) {
|
||||
break;
|
||||
}
|
||||
_requests.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool SponsoredMessages::append(not_null<History*> history) {
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return false;
|
||||
}
|
||||
auto &list = it->second;
|
||||
if (list.showedAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto entryIt = ranges::find_if(list.entries, [](const Entry &e) {
|
||||
return e.item == nullptr;
|
||||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
list.showedAll = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto flags = MessageFlags(0)
|
||||
| (history->isChannel() ? MessageFlag::Post : MessageFlags(0))
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::IsSponsored
|
||||
| MessageFlag::LocalHistoryEntry;
|
||||
auto local = history->addNewLocalMessage(
|
||||
_session->data().nextLocalMessageId(),
|
||||
flags,
|
||||
UserId(0),
|
||||
MsgId(0),
|
||||
HistoryItem::NewMessageDate(0),
|
||||
entryIt->sponsored.fromId,
|
||||
QString(),
|
||||
entryIt->sponsored.textWithEntities,
|
||||
MTP_messageMediaEmpty(),
|
||||
HistoryMessageMarkupData());
|
||||
entryIt->item.reset(std::move(local));
|
||||
|
||||
// Since sponsored posts are only created on demand for display,
|
||||
// we can send a request to view immediately.
|
||||
view(entryIt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SponsoredMessages::request(not_null<History*> history) {
|
||||
auto &request = _requests[history];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_GetSponsoredMessages(
|
||||
_session->data().channel(history->channelId())->inputChannel
|
||||
)).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requests.remove(history);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SponsoredMessages::parse(
|
||||
not_null<History*> history,
|
||||
const MTPmessages_sponsoredMessages &list) {
|
||||
auto &request = _requests[history];
|
||||
request.lastReceived = crl::now();
|
||||
request.requestId = 0;
|
||||
if (!_clearTimer.isActive()) {
|
||||
_clearTimer.callOnce(kRequestTimeLimit * 2);
|
||||
}
|
||||
|
||||
list.match([&](const MTPDmessages_sponsoredMessages &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
|
||||
const auto &messages = data.vmessages().v;
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto &list = _data.emplace(history, List()).first->second;
|
||||
list.entries.clear();
|
||||
for (const auto &message : messages) {
|
||||
append(history, list, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SponsoredMessages::append(
|
||||
not_null<History*> history,
|
||||
List &list,
|
||||
const MTPSponsoredMessage &message) {
|
||||
message.match([&](const MTPDsponsoredMessage &data) {
|
||||
const auto randomId = data.vrandom_id().v;
|
||||
auto sharedMessage = SponsoredMessage{
|
||||
.randomId = randomId,
|
||||
.fromId = peerFromMTP(data.vfrom_id()),
|
||||
.textWithEntities = {
|
||||
.text = qs(data.vmessage()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
_session,
|
||||
data.ventities().value_or_empty()),
|
||||
},
|
||||
.history = history,
|
||||
};
|
||||
list.entries.push_back({ nullptr, std::move(sharedMessage) });
|
||||
});
|
||||
}
|
||||
|
||||
void SponsoredMessages::clearItems(not_null<History*> history) {
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return;
|
||||
}
|
||||
auto &list = it->second;
|
||||
for (auto &entry : list.entries) {
|
||||
entry.item.reset();
|
||||
}
|
||||
list.showedAll = false;
|
||||
}
|
||||
|
||||
void SponsoredMessages::view(const std::vector<Entry>::iterator entryIt) {
|
||||
const auto randomId = entryIt->sponsored.randomId;
|
||||
auto &request = _viewRequests[randomId];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto history = entryIt->sponsored.history;
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_ViewSponsoredMessage(
|
||||
_session->data().channel(history->channelId())->inputChannel,
|
||||
MTP_bytes(randomId)
|
||||
)).done([=] {
|
||||
auto &request = _viewRequests[randomId];
|
||||
request.lastReceived = crl::now();
|
||||
request.requestId = 0;
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_viewRequests.remove(randomId);
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
79
Telegram/SourceFiles/data/data_sponsored_messages.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "history/history_item.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
struct SponsoredMessage final {
|
||||
QByteArray randomId;
|
||||
PeerId fromId;
|
||||
TextWithEntities textWithEntities;
|
||||
History *history = nullptr;
|
||||
};
|
||||
|
||||
class SponsoredMessages final {
|
||||
public:
|
||||
using RandomId = QByteArray;
|
||||
explicit SponsoredMessages(not_null<Session*> owner);
|
||||
SponsoredMessages(const SponsoredMessages &other) = delete;
|
||||
SponsoredMessages &operator=(const SponsoredMessages &other) = delete;
|
||||
~SponsoredMessages();
|
||||
|
||||
void request(not_null<History*> history);
|
||||
[[nodiscard]] bool append(not_null<History*> history);
|
||||
void clearItems(not_null<History*> history);
|
||||
|
||||
private:
|
||||
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
|
||||
struct Entry {
|
||||
OwnedItem item;
|
||||
SponsoredMessage sponsored;
|
||||
};
|
||||
struct List {
|
||||
std::vector<Entry> entries;
|
||||
bool showedAll = false;
|
||||
};
|
||||
struct Request {
|
||||
mtpRequestId requestId = 0;
|
||||
crl::time lastReceived = 0;
|
||||
};
|
||||
|
||||
void parse(
|
||||
not_null<History*> history,
|
||||
const MTPmessages_sponsoredMessages &list);
|
||||
void append(
|
||||
not_null<History*> history,
|
||||
List &list,
|
||||
const MTPSponsoredMessage &message);
|
||||
void clearOldRequests();
|
||||
|
||||
void view(const std::vector<Entry>::iterator entryIt);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _clearTimer;
|
||||
base::flat_map<not_null<History*>, List> _data;
|
||||
base::flat_map<not_null<History*>, Request> _requests;
|
||||
base::flat_map<RandomId, Request> _viewRequests;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -80,8 +80,9 @@ Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location) {
|
||||
| (uint32(location.height) & 0xFFFFU);
|
||||
return Storage::Cache::Key{
|
||||
Data::kGeoPointCacheTag | (uint64(zoomscale) << 32) | widthheight,
|
||||
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
|
||||
| uint64(std::round(std::abs(location.lon + 360.) * 1000000))
|
||||
(uint64(base::SafeRound(
|
||||
std::abs(location.lat + 360.) * 1000000)) << 32)
|
||||
| uint64(base::SafeRound(std::abs(location.lon + 360.) * 1000000))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/value_ordering.h"
|
||||
#include "ui/text/text.h" // For QFIXED_MAX
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_msg_id.h"
|
||||
|
||||
class HistoryItem;
|
||||
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
|
||||
@@ -100,80 +101,6 @@ class Folder;
|
||||
|
||||
using FolderId = int32;
|
||||
using FilterId = int32;
|
||||
using MsgId = int32;
|
||||
constexpr auto StartClientMsgId = MsgId(-0x7FFFFFFF);
|
||||
constexpr auto EndClientMsgId = MsgId(-0x40000000);
|
||||
constexpr auto ShowAtTheEndMsgId = MsgId(-0x40000000);
|
||||
constexpr auto SwitchAtTopMsgId = MsgId(-0x3FFFFFFF);
|
||||
constexpr auto ShowAtProfileMsgId = MsgId(-0x3FFFFFFE);
|
||||
constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFFD);
|
||||
constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFFC);
|
||||
constexpr auto ShowForChooseMessagesMsgId = MsgId(-0x3FFFFFFB);
|
||||
constexpr auto ServerMaxMsgId = MsgId(0x3FFFFFFF);
|
||||
constexpr auto ShowAtUnreadMsgId = MsgId(0);
|
||||
constexpr inline bool IsClientMsgId(MsgId id) {
|
||||
return (id >= StartClientMsgId && id < EndClientMsgId);
|
||||
}
|
||||
constexpr inline bool IsServerMsgId(MsgId id) {
|
||||
return (id > 0 && id < ServerMaxMsgId);
|
||||
}
|
||||
|
||||
struct MsgRange {
|
||||
MsgRange() = default;
|
||||
MsgRange(MsgId from, MsgId till) : from(from), till(till) {
|
||||
}
|
||||
|
||||
MsgId from = 0;
|
||||
MsgId till = 0;
|
||||
};
|
||||
inline bool operator==(const MsgRange &a, const MsgRange &b) {
|
||||
return (a.from == b.from) && (a.till == b.till);
|
||||
}
|
||||
inline bool operator!=(const MsgRange &a, const MsgRange &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
struct FullMsgId {
|
||||
constexpr FullMsgId() = default;
|
||||
constexpr FullMsgId(ChannelId channel, MsgId msg)
|
||||
: channel(channel), msg(msg) {
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return msg != 0;
|
||||
}
|
||||
|
||||
|
||||
inline constexpr bool operator<(const FullMsgId &other) const {
|
||||
if (channel < other.channel) {
|
||||
return true;
|
||||
} else if (channel > other.channel) {
|
||||
return false;
|
||||
}
|
||||
return msg < other.msg;
|
||||
}
|
||||
inline constexpr bool operator>(const FullMsgId &other) const {
|
||||
return other < *this;
|
||||
}
|
||||
inline constexpr bool operator<=(const FullMsgId &other) const {
|
||||
return !(other < *this);
|
||||
}
|
||||
inline constexpr bool operator>=(const FullMsgId &other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
inline constexpr bool operator==(const FullMsgId &other) const {
|
||||
return (channel == other.channel) && (msg == other.msg);
|
||||
}
|
||||
inline constexpr bool operator!=(const FullMsgId &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
ChannelId channel = NoChannel;
|
||||
MsgId msg = 0;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(FullMsgId);
|
||||
|
||||
using MessageIdsList = std::vector<FullMsgId>;
|
||||
|
||||
@@ -182,6 +109,10 @@ MTPDmessage::Flags FlagsFromMessage(const MTPmessage &message);
|
||||
MsgId IdFromMessage(const MTPmessage &message);
|
||||
TimeId DateFromMessage(const MTPmessage &message);
|
||||
|
||||
[[nodiscard]] inline MTPint MTP_int(MsgId id) noexcept {
|
||||
return MTP_int(id.bare);
|
||||
}
|
||||
|
||||
class DocumentData;
|
||||
class PhotoData;
|
||||
struct WebPageData;
|
||||
@@ -341,6 +272,12 @@ enum class MessageFlag : uint32 {
|
||||
|
||||
// Fake message for some UI element.
|
||||
FakeHistoryItem = (1U << 27),
|
||||
|
||||
// Contact sign-up message, notification should be skipped for Silent.
|
||||
IsContactSignUp = (1U << 28),
|
||||
|
||||
// In channels.
|
||||
IsSponsored = (1U << 29),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -296,3 +296,9 @@ dialogsScamFont: font(9px semibold);
|
||||
dialogsScamSkip: 4px;
|
||||
dialogsScamRadius: 2px;
|
||||
|
||||
dialogsMiniPreviewTop: 1px;
|
||||
dialogsMiniPreview: 16px;
|
||||
dialogsMiniPreviewRadius: 2px;
|
||||
dialogsMiniPreviewSkip: 2px;
|
||||
dialogsMiniPreviewRight: 3px;
|
||||
dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
|
||||
|
||||
@@ -43,8 +43,7 @@ uint64 PinnedDialogPos(int pinnedIndex) {
|
||||
} // namespace
|
||||
|
||||
Entry::Entry(not_null<Data::Session*> owner, Type type)
|
||||
: lastItemTextCache(st::dialogsTextWidthMin)
|
||||
, _owner(owner)
|
||||
: _owner(owner)
|
||||
, _isFolder(type == Type::Folder) {
|
||||
}
|
||||
|
||||
|
||||
@@ -183,13 +183,10 @@ public:
|
||||
paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size);
|
||||
}
|
||||
|
||||
TimeId chatListTimeId() const {
|
||||
[[nodiscard]] TimeId chatListTimeId() const {
|
||||
return _timeId;
|
||||
}
|
||||
|
||||
mutable const HistoryItem *textCachedFor = nullptr; // cache
|
||||
mutable Ui::Text::String lastItemTextCache;
|
||||
|
||||
protected:
|
||||
void notifyUnreadStateChange(const UnreadState &wasState);
|
||||
auto unreadStateChangeNotifier(bool required) {
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_inner_widget.h"
|
||||
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "history/history.h"
|
||||
@@ -154,24 +154,10 @@ InnerWidget::InnerWidget(
|
||||
dialogRowReplaced(r.old, r.now);
|
||||
}, lifetime());
|
||||
|
||||
session().data().itemRepaintRequest(
|
||||
) | rpl::start_with_next([=](auto item) {
|
||||
const auto history = item->history();
|
||||
if (history->textCachedFor == item) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
if (const auto folder = history->folder()) {
|
||||
if (folder->textCachedFor == item) {
|
||||
folder->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
session().data().sendActionManager().animationUpdated(
|
||||
) | rpl::start_with_next([=](
|
||||
const Data::SendActionManager::AnimationUpdate &update) {
|
||||
using RowPainter = Layout::RowPainter;
|
||||
const auto updateRect = RowPainter::sendActionAnimationRect(
|
||||
const auto updateRect = Ui::RowPainter::sendActionAnimationRect(
|
||||
update.left,
|
||||
update.width,
|
||||
update.height,
|
||||
@@ -241,16 +227,9 @@ InnerWidget::InnerWidget(
|
||||
}, lifetime());
|
||||
|
||||
session().changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::DialogRowRepaint
|
||||
| Data::MessageUpdate::Flag::DialogRowRefresh
|
||||
Data::MessageUpdate::Flag::DialogRowRefresh
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
const auto item = update.item;
|
||||
if (update.flags & Data::MessageUpdate::Flag::DialogRowRefresh) {
|
||||
refreshDialogRow({ item->history(), item->fullId() });
|
||||
}
|
||||
if (update.flags & Data::MessageUpdate::Flag::DialogRowRepaint) {
|
||||
repaintDialogRow({ item->history(), item->fullId() });
|
||||
}
|
||||
refreshDialogRow({ update.item->history(), update.item->fullId() });
|
||||
}, lifetime());
|
||||
|
||||
session().changes().entryUpdates(
|
||||
@@ -445,7 +424,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
const auto isActive = (row->key() == active);
|
||||
const auto isSelected = (row->key() == selected);
|
||||
Layout::RowPainter::paint(
|
||||
Ui::RowPainter::paint(
|
||||
p,
|
||||
row,
|
||||
_filterId,
|
||||
@@ -563,7 +542,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
: (from == (isPressed()
|
||||
? _filteredPressed
|
||||
: _filteredSelected));
|
||||
Layout::RowPainter::paint(
|
||||
Ui::RowPainter::paint(
|
||||
p,
|
||||
_filterResults[from],
|
||||
_filterId,
|
||||
@@ -655,7 +634,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
: (from == (isPressed()
|
||||
? _searchedPressed
|
||||
: _searchedSelected));
|
||||
Layout::RowPainter::paint(
|
||||
Ui::RowPainter::paint(
|
||||
p,
|
||||
result.get(),
|
||||
fullWidth,
|
||||
@@ -697,7 +676,7 @@ void InnerWidget::paintCollapsedRow(
|
||||
|
||||
const auto text = row->folder->chatListName();
|
||||
const auto unread = row->folder->chatListUnreadCount();
|
||||
Layout::PaintCollapsedRow(
|
||||
Ui::PaintCollapsedRow(
|
||||
p,
|
||||
row->row,
|
||||
row->folder,
|
||||
@@ -740,7 +719,7 @@ void InnerWidget::paintPeerSearchResult(
|
||||
QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height);
|
||||
|
||||
// draw chat icon
|
||||
if (auto chatTypeIcon = Layout::ChatTypeIcon(peer, active, selected)) {
|
||||
if (auto chatTypeIcon = Ui::ChatTypeIcon(peer, active, selected)) {
|
||||
chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth);
|
||||
rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip);
|
||||
}
|
||||
@@ -871,7 +850,7 @@ void InnerWidget::paintSearchInPeer(
|
||||
const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, width(), size);
|
||||
};
|
||||
const auto icon = Layout::ChatTypeIcon(peer, false, false);
|
||||
const auto icon = Ui::ChatTypeIcon(peer, false, false);
|
||||
paintSearchInFilter(p, paintUserpic, top, icon, text);
|
||||
}
|
||||
|
||||
@@ -1536,7 +1515,7 @@ void InnerWidget::refreshDialogRow(RowDescriptor row) {
|
||||
if (row.fullId) {
|
||||
for (const auto &result : _searchResults) {
|
||||
if (result->item()->fullId() == row.fullId) {
|
||||
result->invalidateCache();
|
||||
result->itemView().itemInvalidated(result->item());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_list.h"
|
||||
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "data/data_session.h"
|
||||
#include "mainwidget.h"
|
||||
|
||||
|
||||
@@ -285,8 +285,7 @@ void Row::validateListEntryCache() const {
|
||||
|
||||
FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
|
||||
: _searchInChat(searchInChat)
|
||||
, _item(item)
|
||||
, _cache(st::dialogsTextWidthMin) {
|
||||
, _item(item) {
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
|
||||
class History;
|
||||
class HistoryItem;
|
||||
@@ -22,10 +23,12 @@ namespace Ui {
|
||||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Dialogs {
|
||||
namespace Layout {
|
||||
namespace Dialogs::Ui {
|
||||
using namespace ::Ui;
|
||||
class RowPainter;
|
||||
} // namespace Layout
|
||||
} // namespace Dialogs::Ui
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
enum class SortMode;
|
||||
|
||||
@@ -91,25 +94,25 @@ public:
|
||||
}
|
||||
Row(Key key, int pos);
|
||||
|
||||
Key key() const {
|
||||
[[nodiscard]] Key key() const {
|
||||
return _id;
|
||||
}
|
||||
History *history() const {
|
||||
[[nodiscard]] History *history() const {
|
||||
return _id.history();
|
||||
}
|
||||
Data::Folder *folder() const {
|
||||
[[nodiscard]] Data::Folder *folder() const {
|
||||
return _id.folder();
|
||||
}
|
||||
not_null<Entry*> entry() const {
|
||||
[[nodiscard]] not_null<Entry*> entry() const {
|
||||
return _id.entry();
|
||||
}
|
||||
int pos() const {
|
||||
[[nodiscard]] int pos() const {
|
||||
return _pos;
|
||||
}
|
||||
uint64 sortKey(FilterId filterId) const;
|
||||
[[nodiscard]] uint64 sortKey(FilterId filterId) const;
|
||||
|
||||
void validateListEntryCache() const;
|
||||
const Ui::Text::String &listEntryCache() const {
|
||||
[[nodiscard]] const Ui::Text::String &listEntryCache() const {
|
||||
return _listEntryCache;
|
||||
}
|
||||
|
||||
@@ -130,25 +133,22 @@ class FakeRow : public BasicRow {
|
||||
public:
|
||||
FakeRow(Key searchInChat, not_null<HistoryItem*> item);
|
||||
|
||||
Key searchInChat() const {
|
||||
[[nodiscard]] Key searchInChat() const {
|
||||
return _searchInChat;
|
||||
}
|
||||
not_null<HistoryItem*> item() const {
|
||||
[[nodiscard]] not_null<HistoryItem*> item() const {
|
||||
return _item;
|
||||
}
|
||||
|
||||
void invalidateCache() {
|
||||
_cacheFor = nullptr;
|
||||
_cache = Ui::Text::String();
|
||||
[[nodiscard]] Ui::MessageView &itemView() const {
|
||||
return _itemView;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Layout::RowPainter;
|
||||
friend class Ui::RowPainter;
|
||||
|
||||
Key _searchInChat;
|
||||
not_null<HistoryItem*> _item;
|
||||
mutable const HistoryItem *_cacheFor = nullptr;
|
||||
mutable Ui::Text::String _cache;
|
||||
mutable Ui::MessageView _itemView;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -205,7 +205,11 @@ Widget::Widget(
|
||||
) | rpl::to_empty);
|
||||
|
||||
connect(_inner, SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int)));
|
||||
connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int)));
|
||||
connect(_inner, &InnerWidget::mustScrollTo, [=](int top, int bottom) {
|
||||
if (_scroll) {
|
||||
_scroll->scrollToY(top, bottom);
|
||||
}
|
||||
});
|
||||
connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int)));
|
||||
connect(_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages()));
|
||||
connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString)));
|
||||
@@ -234,8 +238,14 @@ Widget::Widget(
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
|
||||
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll()));
|
||||
_scroll->geometryChanged(
|
||||
) | rpl::start_with_next(crl::guard(_inner, [=] {
|
||||
_inner->onParentGeometryChanged();
|
||||
}), lifetime());
|
||||
_scroll->scrolls(
|
||||
) | rpl::start_with_next([=] {
|
||||
onListScroll();
|
||||
}, lifetime());
|
||||
|
||||
session().data().chatsListChanges(
|
||||
) | rpl::filter([=](Data::Folder *folder) {
|
||||
|
||||
@@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/sender.h"
|
||||
#include "api/api_single_message_search.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace MTP {
|
||||
class Error;
|
||||
} // namespace MTP
|
||||
|
||||
@@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
|
||||
#include "data/data_abstract_structure.h"
|
||||
#include "data/data_drafts.h"
|
||||
@@ -31,8 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
||||
namespace Dialogs {
|
||||
namespace Layout {
|
||||
namespace Dialogs::Ui {
|
||||
namespace {
|
||||
|
||||
// Show all dates that are in the last 20 hours in time format.
|
||||
@@ -260,14 +259,14 @@ void paintRow(
|
||||
const auto history = chat.history();
|
||||
|
||||
if (flags & Flag::SavedMessages) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(
|
||||
EmptyUserpic::PaintSavedMessages(
|
||||
p,
|
||||
st::dialogsPadding.x(),
|
||||
st::dialogsPadding.y(),
|
||||
fullWidth,
|
||||
st::dialogsPhotoSize);
|
||||
} else if (flags & Flag::RepliesMessages) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(
|
||||
EmptyUserpic::PaintRepliesMessages(
|
||||
p,
|
||||
st::dialogsPadding.x(),
|
||||
st::dialogsPadding.y(),
|
||||
@@ -344,7 +343,7 @@ void paintRow(
|
||||
history->cloudDraftTextCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
history->topPromotionMessage(),
|
||||
Ui::DialogTextOptions());
|
||||
DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1);
|
||||
@@ -371,7 +370,7 @@ void paintRow(
|
||||
auto draftText = supportMode
|
||||
? textcmdLink(1, Support::ChatOccupiedString(history))
|
||||
: tr::lng_dialogs_text_with_from(tr::now, lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
if (supportMode) {
|
||||
@@ -462,7 +461,7 @@ void paintRow(
|
||||
p.drawTextLeft(rectForName.left(), rectForName.top(), fullWidth, text);
|
||||
} else if (from) {
|
||||
if (!(flags & Flag::SearchResult)) {
|
||||
const auto badgeStyle = Ui::PeerBadgeStyle{
|
||||
const auto badgeStyle = PeerBadgeStyle{
|
||||
(active
|
||||
? &st::dialogsVerifiedIconActive
|
||||
: selected
|
||||
@@ -473,7 +472,7 @@ void paintRow(
|
||||
: selected
|
||||
? &st::dialogsScamFgOver
|
||||
: &st::dialogsScamFg) };
|
||||
const auto badgeWidth = Ui::DrawPeerBadgeGetWidth(
|
||||
const auto badgeWidth = DrawPeerBadgeGetWidth(
|
||||
from,
|
||||
p,
|
||||
rectForName,
|
||||
@@ -601,9 +600,9 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st)
|
||||
if (badgeData->left[index].isNull()) {
|
||||
int imgsize = size * cIntRetinaFactor(), imgsizehalf = sizehalf * cIntRetinaFactor();
|
||||
createCircleMask(badgeData, size);
|
||||
badgeData->left[index] = Ui::PixmapFromImage(
|
||||
badgeData->left[index] = PixmapFromImage(
|
||||
colorizeCircleHalf(badgeData, imgsize, imgsizehalf, 0, bg));
|
||||
badgeData->right[index] = Ui::PixmapFromImage(colorizeCircleHalf(
|
||||
badgeData->right[index] = PixmapFromImage(colorizeCircleHalf(
|
||||
badgeData,
|
||||
imgsize,
|
||||
imgsizehalf,
|
||||
@@ -775,15 +774,14 @@ void RowPainter::paint(
|
||||
: false;
|
||||
if (const auto folder = row->folder()) {
|
||||
PaintListEntryText(p, itemRect, active, selected, row);
|
||||
} else if (!actionWasPainted) {
|
||||
item->drawInDialog(
|
||||
} else if (history && !actionWasPainted) {
|
||||
history->lastItemDialogsView.paint(
|
||||
p,
|
||||
item,
|
||||
itemRect,
|
||||
active,
|
||||
selected,
|
||||
HistoryItem::DrawInDialog::Normal,
|
||||
entry->textCachedFor,
|
||||
entry->lastItemTextCache);
|
||||
{});
|
||||
}
|
||||
};
|
||||
const auto paintCounterCallback = [&] {
|
||||
@@ -846,15 +844,15 @@ void RowPainter::paint(
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
const auto drawInDialogWay = [&] {
|
||||
const auto previewOptions = [&]() -> HistoryView::ToPreviewOptions {
|
||||
if (const auto searchChat = row->searchInChat()) {
|
||||
if (const auto peer = searchChat.peer()) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
return HistoryItem::DrawInDialog::WithoutSender;
|
||||
return { .hideSender = true };
|
||||
}
|
||||
}
|
||||
}
|
||||
return HistoryItem::DrawInDialog::Normal;
|
||||
return {};
|
||||
}();
|
||||
|
||||
const auto unreadCount = displayUnreadInfo
|
||||
@@ -896,14 +894,13 @@ void RowPainter::paint(
|
||||
texttop,
|
||||
availableWidth,
|
||||
st::dialogsTextFont->height);
|
||||
item->drawInDialog(
|
||||
row->itemView().paint(
|
||||
p,
|
||||
item,
|
||||
itemRect,
|
||||
active,
|
||||
selected,
|
||||
drawInDialogWay,
|
||||
row->_cacheFor,
|
||||
row->_cache);
|
||||
previewOptions);
|
||||
};
|
||||
const auto paintCounterCallback = [&] {
|
||||
PaintNarrowCounter(
|
||||
@@ -1015,5 +1012,4 @@ void PaintCollapsedRow(
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace Dialogs
|
||||
} // namespace Dialogs::Ui
|
||||
@@ -7,13 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Dialogs {
|
||||
namespace Ui {
|
||||
} // namespace Ui
|
||||
|
||||
namespace Dialogs {
|
||||
class Row;
|
||||
class FakeRow;
|
||||
class BasicRow;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Layout {
|
||||
namespace Dialogs::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
const style::icon *ChatTypeIcon(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -89,5 +94,4 @@ void paintUnreadCount(
|
||||
|
||||
void clearUnreadBadgesCache();
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace Dialogs
|
||||
} // namespace Dialogs::Ui
|
||||
208
Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <ushort kTag>
|
||||
struct TextWithTagOffset {
|
||||
TextWithTagOffset(QString text) : text(text) {
|
||||
}
|
||||
static TextWithTagOffset FromString(const QString &text) {
|
||||
return { text };
|
||||
}
|
||||
|
||||
QString text;
|
||||
int offset = -1;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Lang {
|
||||
|
||||
template <ushort kTag>
|
||||
struct ReplaceTag<TextWithTagOffset<kTag>> {
|
||||
static TextWithTagOffset<kTag> Call(
|
||||
TextWithTagOffset<kTag> &&original,
|
||||
ushort tag,
|
||||
const TextWithTagOffset<kTag> &replacement);
|
||||
};
|
||||
|
||||
template <ushort kTag>
|
||||
TextWithTagOffset<kTag> ReplaceTag<TextWithTagOffset<kTag>>::Call(
|
||||
TextWithTagOffset<kTag> &&original,
|
||||
ushort tag,
|
||||
const TextWithTagOffset<kTag> &replacement) {
|
||||
const auto replacementPosition = FindTagReplacementPosition(
|
||||
original.text,
|
||||
tag);
|
||||
if (replacementPosition < 0) {
|
||||
return std::move(original);
|
||||
}
|
||||
original.text = ReplaceTag<QString>::Replace(
|
||||
std::move(original.text),
|
||||
replacement.text,
|
||||
replacementPosition);
|
||||
if (tag == kTag) {
|
||||
original.offset = replacementPosition;
|
||||
} else if (original.offset > replacementPosition) {
|
||||
constexpr auto kReplaceCommandLength = 4;
|
||||
original.offset += replacement.text.size() - kReplaceCommandLength;
|
||||
}
|
||||
return std::move(original);
|
||||
}
|
||||
|
||||
} // namespace Lang
|
||||
|
||||
namespace Dialogs::Ui {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
struct MessageView::LoadingContext {
|
||||
std::any context;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
MessageView::MessageView()
|
||||
: _senderCache(st::dialogsTextWidthMin)
|
||||
, _textCache(st::dialogsTextWidthMin) {
|
||||
}
|
||||
|
||||
MessageView::~MessageView() = default;
|
||||
|
||||
void MessageView::itemInvalidated(not_null<const HistoryItem*> item) {
|
||||
if (_textCachedFor == item.get()) {
|
||||
_textCachedFor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageView::dependsOn(not_null<const HistoryItem*> item) const {
|
||||
return (_textCachedFor == item.get());
|
||||
}
|
||||
|
||||
void MessageView::paint(
|
||||
Painter &p,
|
||||
not_null<const HistoryItem*> item,
|
||||
const QRect &geometry,
|
||||
bool active,
|
||||
bool selected,
|
||||
ToPreviewOptions options) const {
|
||||
if (geometry.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (_textCachedFor != item.get()) {
|
||||
options.existing = &_imagesCache;
|
||||
auto preview = item->toPreview(options);
|
||||
if (!preview.images.empty() && preview.imagesInTextPosition > 0) {
|
||||
_senderCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text.mid(0, preview.imagesInTextPosition).trimmed(),
|
||||
DialogTextOptions());
|
||||
preview.text = preview.text.mid(preview.imagesInTextPosition);
|
||||
} else {
|
||||
_senderCache = { st::dialogsTextWidthMin };
|
||||
}
|
||||
_textCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text.trimmed(),
|
||||
DialogTextOptions());
|
||||
_textCachedFor = item;
|
||||
_imagesCache = std::move(preview.images);
|
||||
if (preview.loadingContext.has_value()) {
|
||||
if (!_loadingContext) {
|
||||
_loadingContext = std::make_unique<LoadingContext>();
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
_textCachedFor = nullptr;
|
||||
}, _loadingContext->lifetime);
|
||||
}
|
||||
_loadingContext->context = std::move(preview.loadingContext);
|
||||
} else {
|
||||
_loadingContext = nullptr;
|
||||
}
|
||||
}
|
||||
p.setTextPalette(active
|
||||
? st::dialogsTextPaletteActive
|
||||
: selected
|
||||
? st::dialogsTextPaletteOver
|
||||
: st::dialogsTextPalette);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
p.setPen(active
|
||||
? st::dialogsTextFgActive
|
||||
: selected
|
||||
? st::dialogsTextFgOver
|
||||
: st::dialogsTextFg);
|
||||
const auto guard = gsl::finally([&] {
|
||||
p.restoreTextPalette();
|
||||
});
|
||||
|
||||
auto rect = geometry;
|
||||
if (!_senderCache.isEmpty()) {
|
||||
_senderCache.drawElided(
|
||||
p,
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
rect.width(),
|
||||
rect.height() / st::dialogsTextFont->height);
|
||||
const auto skip = st::dialogsMiniPreviewSkip
|
||||
+ st::dialogsMiniPreviewRight;
|
||||
rect.setLeft(rect.x() + _senderCache.maxWidth() + skip);
|
||||
}
|
||||
for (const auto &image : _imagesCache) {
|
||||
if (rect.width() < st::dialogsMiniPreview) {
|
||||
break;
|
||||
}
|
||||
p.drawImage(
|
||||
rect.x(),
|
||||
rect.y() + st::dialogsMiniPreviewTop,
|
||||
image.data);
|
||||
rect.setLeft(rect.x()
|
||||
+ st::dialogsMiniPreview
|
||||
+ st::dialogsMiniPreviewSkip);
|
||||
}
|
||||
if (!_imagesCache.empty()) {
|
||||
rect.setLeft(rect.x() + st::dialogsMiniPreviewRight);
|
||||
}
|
||||
if (rect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
_textCache.drawElided(
|
||||
p,
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
rect.width(),
|
||||
rect.height() / st::dialogsTextFont->height);
|
||||
}
|
||||
|
||||
HistoryView::ItemPreview PreviewWithSender(
|
||||
HistoryView::ItemPreview &&preview,
|
||||
const QString &sender) {
|
||||
auto textWithOffset = tr::lng_dialogs_text_with_from(
|
||||
tr::now,
|
||||
lt_from_part,
|
||||
sender,
|
||||
lt_message,
|
||||
std::move(preview.text),
|
||||
TextWithTagOffset<lt_from_part>::FromString);
|
||||
preview.text = std::move(textWithOffset.text);
|
||||
preview.imagesInTextPosition = (textWithOffset.offset < 0)
|
||||
? 0
|
||||
: textWithOffset.offset + sender.size();
|
||||
return std::move(preview);
|
||||
}
|
||||
|
||||
} // namespace Dialogs::Ui
|
||||
64
Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
|
||||
class Image;
|
||||
class HistoryItem;
|
||||
enum class ImageRoundRadius;
|
||||
|
||||
namespace Ui {
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
struct ToPreviewOptions;
|
||||
struct ItemPreviewImage;
|
||||
struct ItemPreview;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Dialogs::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
class MessageView final {
|
||||
public:
|
||||
MessageView();
|
||||
~MessageView();
|
||||
|
||||
using ToPreviewOptions = HistoryView::ToPreviewOptions;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
void itemInvalidated(not_null<const HistoryItem*> item);
|
||||
[[nodiscard]] bool dependsOn(not_null<const HistoryItem*> item) const;
|
||||
|
||||
void paint(
|
||||
Painter &p,
|
||||
not_null<const HistoryItem*> item,
|
||||
const QRect &geometry,
|
||||
bool active,
|
||||
bool selected,
|
||||
ToPreviewOptions options) const;
|
||||
|
||||
private:
|
||||
struct LoadingContext;
|
||||
|
||||
mutable const HistoryItem *_textCachedFor = nullptr;
|
||||
mutable Ui::Text::String _senderCache;
|
||||
mutable Ui::Text::String _textCache;
|
||||
mutable std::vector<ItemPreviewImage> _imagesCache;
|
||||
mutable std::unique_ptr<LoadingContext> _loadingContext;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] HistoryView::ItemPreview PreviewWithSender(
|
||||
HistoryView::ItemPreview &&preview,
|
||||
const QString &sender);
|
||||
|
||||
} // namespace Dialogs::Ui
|
||||
@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Editor {
|
||||
|
||||
QImage ImageModified(QImage image, const PhotoModifications &mods) {
|
||||
Expects(!image.isNull());
|
||||
|
||||
if (!mods) {
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ void Scene::cancelDrawing() {
|
||||
_canvas->cancelDrawing();
|
||||
}
|
||||
|
||||
void Scene::addItem(std::shared_ptr<NumberedItem> item) {
|
||||
void Scene::addItem(ItemPtr item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ std::vector<ItemPtr> Scene::items(
|
||||
return copyItems;
|
||||
}
|
||||
|
||||
std::vector<MTPInputDocument> Scene::attachedStickers() const {
|
||||
std::vector<not_null<DocumentData*>> Scene::attachedStickers() const {
|
||||
const auto allItems = items();
|
||||
|
||||
return ranges::views::all(
|
||||
|
||||
@@ -39,7 +39,8 @@ public:
|
||||
[[nodiscard]] rpl::producer<> addsItem() const;
|
||||
[[nodiscard]] rpl::producer<> removesItem() const;
|
||||
|
||||
[[nodiscard]] std::vector<MTPInputDocument> attachedStickers() const;
|
||||
[[nodiscard]] auto attachedStickers() const
|
||||
-> std::vector<not_null<DocumentData*>>;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<float64> lastZ() const;
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ void ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
const auto angle = Normalized((isLeft ? 180 : 0)
|
||||
+ (std::atan2(diff.y(), diff.x()) * 180 / M_PI));
|
||||
setRotation(shift
|
||||
? (std::round(angle / kSnapAngle) * kSnapAngle) // Snap rotation.
|
||||
? (base::SafeRound(angle / kSnapAngle) * kSnapAngle)
|
||||
: angle);
|
||||
} else {
|
||||
QGraphicsItem::mouseMoveEvent(event);
|
||||
|
||||
@@ -95,8 +95,8 @@ void ItemSticker::paint(
|
||||
ItemBase::paint(p, option, w);
|
||||
}
|
||||
|
||||
MTPInputDocument ItemSticker::sticker() const {
|
||||
return _document->mtpInput();
|
||||
not_null<DocumentData*> ItemSticker::sticker() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
int ItemSticker::type() const {
|
||||
|
||||
@@ -30,11 +30,13 @@ public:
|
||||
QPainter *p,
|
||||
const QStyleOptionGraphicsItem *option,
|
||||
QWidget *widget) override;
|
||||
MTPInputDocument sticker() const;
|
||||
[[nodiscard]] not_null<DocumentData*> sticker() const;
|
||||
int type() const override;
|
||||
|
||||
protected:
|
||||
void performFlip() override;
|
||||
std::shared_ptr<ItemBase> duplicate(ItemBase::Data data) const override;
|
||||
|
||||
private:
|
||||
const not_null<DocumentData*> _document;
|
||||
const std::shared_ptr<::Data::DocumentMedia> _mediaView;
|
||||
|
||||
@@ -56,14 +56,11 @@ TimeId ExtractSentDate(const MTPMessage &message) {
|
||||
});
|
||||
}
|
||||
|
||||
MTPMessage PrepareLogMessage(
|
||||
const MTPMessage &message,
|
||||
MsgId newId,
|
||||
TimeId newDate) {
|
||||
MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) {
|
||||
return message.match([&](const MTPDmessageEmpty &data) {
|
||||
return MTP_messageEmpty(
|
||||
data.vflags(),
|
||||
MTP_int(newId),
|
||||
data.vid(),
|
||||
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
const auto removeFlags = MTPDmessageService::Flag::f_out
|
||||
@@ -72,7 +69,7 @@ MTPMessage PrepareLogMessage(
|
||||
| MTPDmessageService::Flag::f_ttl_period;
|
||||
return MTP_messageService(
|
||||
MTP_flags(data.vflags().v & ~removeFlags),
|
||||
MTP_int(newId),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
MTPMessageReplyHeader(),
|
||||
@@ -93,7 +90,7 @@ MTPMessage PrepareLogMessage(
|
||||
| MTPDmessage::Flag::f_ttl_period;
|
||||
return MTP_message(
|
||||
MTP_flags(data.vflags().v & ~removeFlags),
|
||||
MTP_int(newId),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
@@ -559,7 +556,7 @@ void GenerateItems(
|
||||
QString(),
|
||||
std::move(text),
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
bodyGroupedId);
|
||||
};
|
||||
|
||||
@@ -669,10 +666,8 @@ void GenerateItems(
|
||||
auto detachExistingItem = false;
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vmessage(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
@@ -698,10 +693,8 @@ void GenerateItems(
|
||||
auto oldValue = ExtractEditedText(session, action.vprev_message());
|
||||
auto detachExistingItem = false;
|
||||
auto body = history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vnew_message(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vnew_message(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem);
|
||||
if (oldValue.text.isEmpty()) {
|
||||
@@ -724,10 +717,8 @@ void GenerateItems(
|
||||
auto detachExistingItem = false;
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vmessage(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
@@ -821,10 +812,8 @@ void GenerateItems(
|
||||
auto detachExistingItem = false;
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareLogMessage(action.vmessage(), date),
|
||||
MessageFlag::AdminLogEntry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
|
||||