Merge branch 'AyuGram:dev' into dev
|
@ -1339,6 +1339,8 @@ PRIVATE
|
||||||
media/view/media_view_playback_controls.h
|
media/view/media_view_playback_controls.h
|
||||||
media/view/media_view_playback_progress.cpp
|
media/view/media_view_playback_progress.cpp
|
||||||
media/view/media_view_playback_progress.h
|
media/view/media_view_playback_progress.h
|
||||||
|
media/view/media_view_playback_sponsored.cpp
|
||||||
|
media/view/media_view_playback_sponsored.h
|
||||||
media/system_media_controls_manager.h
|
media/system_media_controls_manager.h
|
||||||
media/system_media_controls_manager.cpp
|
media/system_media_controls_manager.cpp
|
||||||
menu/menu_antispam_validator.cpp
|
menu/menu_antispam_validator.cpp
|
||||||
|
@ -2071,7 +2073,7 @@ if (MSVC)
|
||||||
/DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
|
/DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
|
||||||
/DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
|
/DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
|
||||||
/DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
|
/DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
|
||||||
/DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
|
# /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
|
||||||
/DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
|
/DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
|
||||||
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
|
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
|
||||||
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
|
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
|
||||||
|
|
BIN
Telegram/Resources/animations/no_chats.tgs
Normal file
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 899 B After Width: | Height: | Size: 899 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -1,10 +1,10 @@
|
||||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_1067_20)">
|
<g clip-path="url(#clip0_1362_52)">
|
||||||
<path d="M7.58588 9.73341C8.52939 10.6769 9.41338 12.269 10.085 13.6905C10.5357 14.6445 12.0164 14.6078 12.3503 13.6068L15.8086 3.23922C16.1618 2.1804 15.1541 1.17304 14.0954 1.52654L3.74092 4.98387C2.73902 5.3184 2.70382 6.80252 3.65913 7.2532C5.0706 7.91907 6.64716 8.79469 7.58588 9.73341Z" fill="white"/>
|
<path d="M6.92088 9.06526C7.86439 10.0088 8.74838 11.6009 9.41997 13.0223C9.87073 13.9764 11.3514 13.9397 11.6853 12.9387L15.1436 2.57106C15.4968 1.51225 14.4891 0.504885 13.4304 0.858384L3.07592 4.31571C2.07402 4.65025 2.03882 6.13437 2.99412 6.58505C4.4056 7.25092 5.98216 8.12654 6.92088 9.06526Z" fill="white"/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_1067_20">
|
<clipPath id="clip0_1362_52">
|
||||||
<rect width="16" height="16.0037" fill="white" transform="translate(0.665001 0.668152)"/>
|
<rect width="16" height="16.0037" fill="white"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 566 B |
|
@ -434,6 +434,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_dlg_new_channel_name" = "Channel name";
|
"lng_dlg_new_channel_name" = "Channel name";
|
||||||
"lng_dlg_new_bot_name" = "Bot name";
|
"lng_dlg_new_bot_name" = "Bot name";
|
||||||
"lng_no_chats" = "Your chats will be here";
|
"lng_no_chats" = "Your chats will be here";
|
||||||
|
"lng_no_conversations" = "You have no\nconversations yet.";
|
||||||
|
"lng_no_conversations_button" = "New Message";
|
||||||
|
"lng_no_conversations_subtitle" = "Your contacts on Telegram";
|
||||||
"lng_no_chats_filter" = "No chats currently belong to this folder.";
|
"lng_no_chats_filter" = "No chats currently belong to this folder.";
|
||||||
"lng_no_saved_sublists" = "You can save messages from other chats here.";
|
"lng_no_saved_sublists" = "You can save messages from other chats here.";
|
||||||
"lng_contacts_loading" = "Loading...";
|
"lng_contacts_loading" = "Loading...";
|
||||||
|
@ -4260,6 +4263,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_context_to_msg" = "Go To Message";
|
"lng_context_to_msg" = "Go To Message";
|
||||||
"lng_context_reply_msg" = "Reply";
|
"lng_context_reply_msg" = "Reply";
|
||||||
"lng_context_quote_and_reply" = "Quote & Reply";
|
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||||
|
"lng_context_reply_to_task" = "Reply to Task";
|
||||||
"lng_context_edit_msg" = "Edit";
|
"lng_context_edit_msg" = "Edit";
|
||||||
"lng_context_add_factcheck" = "Add Fact Check";
|
"lng_context_add_factcheck" = "Add Fact Check";
|
||||||
"lng_context_edit_factcheck" = "Edit Fact Check";
|
"lng_context_edit_factcheck" = "Edit Fact Check";
|
||||||
|
@ -4450,6 +4454,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
||||||
"lng_preview_reply_to" = "Reply to {name}";
|
"lng_preview_reply_to" = "Reply to {name}";
|
||||||
"lng_preview_reply_to_quote" = "Reply to quote from {name}";
|
"lng_preview_reply_to_quote" = "Reply to quote from {name}";
|
||||||
|
"lng_preview_reply_to_task" = "Reply to task from {title}";
|
||||||
|
|
||||||
"lng_suggest_bar_title" = "Suggest a Post Below";
|
"lng_suggest_bar_title" = "Suggest a Post Below";
|
||||||
"lng_suggest_bar_text" = "Click to offer a price for publishing.";
|
"lng_suggest_bar_text" = "Click to offer a price for publishing.";
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
<file alias="topics_tabs.tgs">../../animations/edit_peers/topics_tabs.tgs</file>
|
<file alias="topics_tabs.tgs">../../animations/edit_peers/topics_tabs.tgs</file>
|
||||||
<file alias="topics_list.tgs">../../animations/edit_peers/topics_list.tgs</file>
|
<file alias="topics_list.tgs">../../animations/edit_peers/topics_list.tgs</file>
|
||||||
<file alias="direct_messages.tgs">../../animations/edit_peers/direct_messages.tgs</file>
|
<file alias="direct_messages.tgs">../../animations/edit_peers/direct_messages.tgs</file>
|
||||||
|
<file alias="no_chats.tgs">../../animations/no_chats.tgs</file>
|
||||||
|
|
||||||
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="5.16.3.0" />
|
Version="5.16.4.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 5,16,3,0
|
FILEVERSION 5,16,4,0
|
||||||
PRODUCTVERSION 5,16,3,0
|
PRODUCTVERSION 5,16,4,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop"
|
VALUE "FileDescription", "AyuGram Desktop"
|
||||||
VALUE "FileVersion", "5.16.3.0"
|
VALUE "FileVersion", "5.16.4.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "5.16.3.0"
|
VALUE "ProductVersion", "5.16.4.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 5,16,3,0
|
FILEVERSION 5,16,4,0
|
||||||
PRODUCTVERSION 5,16,3,0
|
PRODUCTVERSION 5,16,4,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||||
VALUE "FileVersion", "5.16.3.0"
|
VALUE "FileVersion", "5.16.4.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "5.16.3.0"
|
VALUE "ProductVersion", "5.16.4.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -47,10 +47,10 @@ bool UnreadThings::trackReactions(Data::Thread *thread) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto &settings = AyuSettings::getInstance();
|
const auto &settings = AyuSettings::getInstance();
|
||||||
if (peer->isChannel() && !peer->isMegagroup() && !settings.hideChannelReactions) {
|
if (peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (peer->isMegagroup() && !settings.hideGroupReactions) {
|
if (peer->isMegagroup() && !settings.showGroupReactions) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return peer->isUser() || peer->isChat() || peer->isMegagroup();
|
return peer->isUser() || peer->isChat() || peer->isMegagroup();
|
||||||
|
|
|
@ -231,8 +231,8 @@ AyuGramSettings::AyuGramSettings() {
|
||||||
|
|
||||||
disableNotificationsDelay = false;
|
disableNotificationsDelay = false;
|
||||||
localPremium = false;
|
localPremium = false;
|
||||||
hideChannelReactions = true;
|
showChannelReactions = true;
|
||||||
hideGroupReactions = true;
|
showGroupReactions = true;
|
||||||
|
|
||||||
// ~ Customization
|
// ~ Customization
|
||||||
appIcon =
|
appIcon =
|
||||||
|
@ -420,11 +420,11 @@ void set_localPremium(bool val) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_hideChannelReactions(bool val) {
|
void set_hideChannelReactions(bool val) {
|
||||||
settings->hideChannelReactions = val;
|
settings->showChannelReactions = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_hideGroupReactions(bool val) {
|
void set_hideGroupReactions(bool val) {
|
||||||
settings->hideGroupReactions = val;
|
settings->showGroupReactions = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_appIcon(const QString &val) {
|
void set_appIcon(const QString &val) {
|
||||||
|
|
|
@ -76,8 +76,8 @@ public:
|
||||||
|
|
||||||
bool disableNotificationsDelay;
|
bool disableNotificationsDelay;
|
||||||
bool localPremium;
|
bool localPremium;
|
||||||
bool hideChannelReactions;
|
bool showChannelReactions;
|
||||||
bool hideGroupReactions;
|
bool showGroupReactions;
|
||||||
|
|
||||||
QString appIcon;
|
QString appIcon;
|
||||||
bool simpleQuotesAndReplies;
|
bool simpleQuotesAndReplies;
|
||||||
|
@ -259,8 +259,8 @@ inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nloh
|
||||||
NLOHMANN_JSON_TO(showGhostToggleInTray)
|
NLOHMANN_JSON_TO(showGhostToggleInTray)
|
||||||
NLOHMANN_JSON_TO(showStreamerToggleInTray)
|
NLOHMANN_JSON_TO(showStreamerToggleInTray)
|
||||||
NLOHMANN_JSON_TO(monoFont)
|
NLOHMANN_JSON_TO(monoFont)
|
||||||
NLOHMANN_JSON_TO(hideChannelReactions)
|
NLOHMANN_JSON_TO(showChannelReactions)
|
||||||
NLOHMANN_JSON_TO(hideGroupReactions)
|
NLOHMANN_JSON_TO(showGroupReactions)
|
||||||
NLOHMANN_JSON_TO(hideNotificationCounters)
|
NLOHMANN_JSON_TO(hideNotificationCounters)
|
||||||
NLOHMANN_JSON_TO(hideNotificationBadge)
|
NLOHMANN_JSON_TO(hideNotificationBadge)
|
||||||
NLOHMANN_JSON_TO(hideAllChatsFolder)
|
NLOHMANN_JSON_TO(hideAllChatsFolder)
|
||||||
|
@ -324,8 +324,8 @@ inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nl
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInTray)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGhostToggleInTray)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(showStreamerToggleInTray)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(showStreamerToggleInTray)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(monoFont)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(monoFont)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideChannelReactions)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(showChannelReactions)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideGroupReactions)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(showGroupReactions)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationCounters)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationCounters)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationBadge)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideNotificationBadge)
|
||||||
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideAllChatsFolder)
|
NLOHMANN_JSON_FROM_WITH_DEFAULT(hideAllChatsFolder)
|
||||||
|
|
|
@ -99,8 +99,11 @@ std::pair<QString, QString> stateName(const PeerId &id) {
|
||||||
|
|
||||||
void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) {
|
void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) {
|
||||||
state = st;
|
state = st;
|
||||||
|
auto peerCopy = *peer;
|
||||||
session.changes().peerUpdated(session.data().peer(*peer), Data::PeerUpdate::Flag::Rights);
|
crl::on_main([&, peerCopy]
|
||||||
|
{
|
||||||
|
session.changes().peerUpdated(session.data().peer(peerCopy), Data::PeerUpdate::Flag::Rights);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
|
static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
|
||||||
|
@ -111,6 +114,10 @@ static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
|
||||||
{
|
{
|
||||||
groupMedia.emplace_back(media);
|
groupMedia.emplace_back(media);
|
||||||
auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media));
|
auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media));
|
||||||
|
if (prepared.path.isEmpty()) {
|
||||||
|
// otherwise will fail assertion in PrepareDetails
|
||||||
|
return prepared;
|
||||||
|
}
|
||||||
Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, PhotoSideLimit());
|
Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, PhotoSideLimit());
|
||||||
return prepared;
|
return prepared;
|
||||||
};
|
};
|
||||||
|
@ -120,7 +127,9 @@ static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
|
||||||
const auto groupId = startItem->groupId();
|
const auto groupId = startItem->groupId();
|
||||||
|
|
||||||
Ui::PreparedList list;
|
Ui::PreparedList list;
|
||||||
list.files.emplace_back(prepare(media));
|
if (auto prepared = prepare(media); !prepared.path.isEmpty()) {
|
||||||
|
list.files.emplace_back(std::move(prepared));
|
||||||
|
}
|
||||||
|
|
||||||
if (!groupId.value) {
|
if (!groupId.value) {
|
||||||
return list;
|
return list;
|
||||||
|
@ -132,7 +141,9 @@ static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (const auto nextMedia = nextItem->media()) {
|
if (const auto nextMedia = nextItem->media()) {
|
||||||
list.files.emplace_back(prepare(nextMedia));
|
if (auto prepared = prepare(nextMedia); !prepared.path.isEmpty()) {
|
||||||
|
list.files.emplace_back(std::move(prepared));
|
||||||
|
}
|
||||||
i = k;
|
i = k;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,7 +307,10 @@ void forwardMessages(
|
||||||
const auto history = action.history;
|
const auto history = action.history;
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
|
|
||||||
history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {});
|
crl::on_main([&]
|
||||||
|
{
|
||||||
|
history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {});
|
||||||
|
});
|
||||||
|
|
||||||
std::shared_ptr<ForwardState> state;
|
std::shared_ptr<ForwardState> state;
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
//
|
//
|
||||||
// Copyright @Radolyn, 2025
|
// Copyright @Radolyn, 2025
|
||||||
#include "ayu_sync.h"
|
#include "ayu_sync.h"
|
||||||
#include "apiwrap.h"
|
|
||||||
#include "api/api_sending.h"
|
#include "api/api_sending.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "ayu/utils/telegram_helpers.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
|
@ -127,19 +128,22 @@ void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_
|
||||||
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
||||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||||
|
|
||||||
data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media()));
|
crl::on_main([&]
|
||||||
|
|
||||||
rpl::single() | rpl::then(
|
|
||||||
session->downloaderTaskFinished()
|
|
||||||
) | rpl::filter([&]
|
|
||||||
{
|
{
|
||||||
return data->status == FileDownloadFailed || fileSize(item) == data->size;
|
data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media()));
|
||||||
}) | rpl::start_with_next([&]() mutable
|
|
||||||
{
|
rpl::single() | rpl::then(
|
||||||
latch->countDown();
|
session->downloaderTaskFinished()
|
||||||
base::take(lifetime)->destroy();
|
) | rpl::filter([&]
|
||||||
},
|
{
|
||||||
*lifetime);
|
return data->status == FileDownloadFailed || fileSize(item) == data->size;
|
||||||
|
}) | rpl::start_with_next([&]() mutable
|
||||||
|
{
|
||||||
|
latch->countDown();
|
||||||
|
base::take(lifetime)->destroy();
|
||||||
|
},
|
||||||
|
*lifetime);
|
||||||
|
});
|
||||||
|
|
||||||
latch->await(std::chrono::minutes(5));
|
latch->await(std::chrono::minutes(5));
|
||||||
}
|
}
|
||||||
|
@ -207,19 +211,21 @@ void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<Ph
|
||||||
if (finalCheck()) {
|
if (finalCheck()) {
|
||||||
saveToFiles();
|
saveToFiles();
|
||||||
} else {
|
} else {
|
||||||
session->downloaderTaskFinished() | rpl::filter([&]
|
crl::on_main([&]
|
||||||
{
|
{
|
||||||
return finalCheck();
|
session->downloaderTaskFinished() | rpl::filter([&]
|
||||||
}) | rpl::start_with_next([&]() mutable
|
{
|
||||||
{
|
return finalCheck();
|
||||||
saveToFiles();
|
}) | rpl::start_with_next([&]() mutable
|
||||||
latch->countDown();
|
{
|
||||||
base::take(lifetime)->destroy();
|
saveToFiles();
|
||||||
},
|
latch->countDown();
|
||||||
*lifetime);
|
base::take(lifetime)->destroy();
|
||||||
|
},
|
||||||
|
*lifetime);
|
||||||
|
});
|
||||||
|
latch->await(std::chrono::minutes(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
latch->await(std::chrono::minutes(5));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message) {
|
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message) {
|
||||||
|
@ -240,17 +246,19 @@ void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &act
|
||||||
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
auto latch = std::make_shared<TimedCountDownLatch>(1);
|
||||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||||
|
|
||||||
|
crl::on_main([&]
|
||||||
session->data().itemIdChanged()
|
{
|
||||||
| rpl::filter([&](const Data::Session::IdChange &update)
|
session->data().itemIdChanged()
|
||||||
{
|
| rpl::filter([&](const Data::Session::IdChange &update)
|
||||||
return action.history->peer->id == update.newId.peer;
|
{
|
||||||
}) | rpl::start_with_next([&]
|
return action.history->peer->id == update.newId.peer;
|
||||||
{
|
}) | rpl::start_with_next([&]
|
||||||
latch->countDown();
|
{
|
||||||
base::take(lifetime)->destroy();
|
latch->countDown();
|
||||||
},
|
base::take(lifetime)->destroy();
|
||||||
*lifetime);
|
},
|
||||||
|
*lifetime);
|
||||||
|
});
|
||||||
|
|
||||||
latch->await(std::chrono::minutes(2));
|
latch->await(std::chrono::minutes(2));
|
||||||
}
|
}
|
||||||
|
@ -260,9 +268,6 @@ void sendDocumentSync(not_null<Main::Session*> session,
|
||||||
SendMediaType type,
|
SendMediaType type,
|
||||||
TextWithTags &&caption,
|
TextWithTags &&caption,
|
||||||
const Api::SendAction &action) {
|
const Api::SendAction &action) {
|
||||||
const auto size = group.list.files.size();
|
|
||||||
auto latch = std::make_shared<TimedCountDownLatch>(size);
|
|
||||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
|
||||||
|
|
||||||
auto groupId = std::make_shared<SendingAlbum>();
|
auto groupId = std::make_shared<SendingAlbum>();
|
||||||
groupId->groupId = base::RandomValue<uint64>();
|
groupId->groupId = base::RandomValue<uint64>();
|
||||||
|
@ -272,27 +277,7 @@ void sendDocumentSync(not_null<Main::Session*> session,
|
||||||
session->api().sendFiles(std::move(lst), type, std::move(caption), groupId, action);
|
session->api().sendFiles(std::move(lst), type, std::move(caption), groupId, action);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
waitForMsgSync(session, action);
|
||||||
// probably need to handle
|
|
||||||
// session->uploader().photoFailed()
|
|
||||||
// and
|
|
||||||
// session->uploader().documentFailed()
|
|
||||||
// too
|
|
||||||
|
|
||||||
rpl::merge(
|
|
||||||
session->uploader().documentReady(),
|
|
||||||
session->uploader().photoReady()
|
|
||||||
) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto)
|
|
||||||
{
|
|
||||||
return docOrPhoto.fullId.peer == action.history->peer->id;
|
|
||||||
}) | rpl::start_with_next([&]
|
|
||||||
{
|
|
||||||
latch->countDown();
|
|
||||||
},
|
|
||||||
*lifetime);
|
|
||||||
|
|
||||||
latch->await(std::chrono::minutes(5 * size));
|
|
||||||
base::take(lifetime)->destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendStickerSync(not_null<Main::Session*> session,
|
void sendStickerSync(not_null<Main::Session*> session,
|
||||||
|
|
|
@ -209,8 +209,7 @@ bool MessageShotDelegate::elementHideReply(not_null<const HistoryView::Element*>
|
||||||
|
|
||||||
HistoryView::ElementChatMode MessageShotDelegate::elementChatMode() {
|
HistoryView::ElementChatMode MessageShotDelegate::elementChatMode() {
|
||||||
using Mode = HistoryView::ElementChatMode;
|
using Mode = HistoryView::ElementChatMode;
|
||||||
// Mode::Wide;
|
return Mode::Wide;
|
||||||
return Mode::Default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage removeEmptySpaceAround(const QImage &original) {
|
QImage removeEmptySpaceAround(const QImage &original) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
** This file is an amalgamation of many separate C source files from SQLite
|
** This file is an amalgamation of many separate C source files from SQLite
|
||||||
** version 3.50.0. By combining all the individual C code files into this
|
** version 3.50.2. By combining all the individual C code files into this
|
||||||
** single large file, the entire code can be compiled as a single translation
|
** single large file, the entire code can be compiled as a single translation
|
||||||
** unit. This allows many compilers to do optimizations that would not be
|
** unit. This allows many compilers to do optimizations that would not be
|
||||||
** possible if the files were compiled separately. Performance improvements
|
** possible if the files were compiled separately. Performance improvements
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
** separate file. This file contains only code for the core SQLite library.
|
** separate file. This file contains only code for the core SQLite library.
|
||||||
**
|
**
|
||||||
** The content in this amalgamation comes from Fossil check-in
|
** The content in this amalgamation comes from Fossil check-in
|
||||||
** dfc790f998f450d9c35e3ba1c8c89c17466c with changes in files:
|
** 2af157d77fb1304a74176eaee7fbc7c7e932 with changes in files:
|
||||||
**
|
**
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
|
@ -465,9 +465,9 @@ extern "C" {
|
||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.50.0"
|
#define SQLITE_VERSION "3.50.2"
|
||||||
#define SQLITE_VERSION_NUMBER 3050000
|
#define SQLITE_VERSION_NUMBER 3050002
|
||||||
#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742"
|
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
|
@ -4398,7 +4398,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
|
||||||
**
|
**
|
||||||
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
||||||
** database filename D with corresponding journal file J and WAL file W and
|
** database filename D with corresponding journal file J and WAL file W and
|
||||||
** with N URI parameters key/values pairs in the array P. The result from
|
** an array P of N URI Key/Value pairs. The result from
|
||||||
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
||||||
** is safe to pass to routines like:
|
** is safe to pass to routines like:
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -5079,7 +5079,7 @@ typedef struct sqlite3_context sqlite3_context;
|
||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
**
|
**
|
||||||
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
||||||
** literals may be replaced by a [parameter] that matches one of following
|
** literals may be replaced by a [parameter] that matches one of the following
|
||||||
** templates:
|
** templates:
|
||||||
**
|
**
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -5124,7 +5124,7 @@ typedef struct sqlite3_context sqlite3_context;
|
||||||
**
|
**
|
||||||
** [[byte-order determination rules]] ^The byte-order of
|
** [[byte-order determination rules]] ^The byte-order of
|
||||||
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
||||||
** found in first character, which is removed, or in the absence of a BOM
|
** found in the first character, which is removed, or in the absence of a BOM
|
||||||
** the byte order is the native byte order of the host
|
** the byte order is the native byte order of the host
|
||||||
** machine for sqlite3_bind_text16() or the byte order specified in
|
** machine for sqlite3_bind_text16() or the byte order specified in
|
||||||
** the 6th parameter for sqlite3_bind_text64().)^
|
** the 6th parameter for sqlite3_bind_text64().)^
|
||||||
|
@ -5144,7 +5144,7 @@ typedef struct sqlite3_context sqlite3_context;
|
||||||
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
||||||
** that parameter must be the byte offset
|
** that parameter must be the byte offset
|
||||||
** where the NUL terminator would occur assuming the string were NUL
|
** where the NUL terminator would occur assuming the string were NUL
|
||||||
** terminated. If any NUL characters occurs at byte offsets less than
|
** terminated. If any NUL characters occur at byte offsets less than
|
||||||
** the value of the fourth parameter then the resulting string value will
|
** the value of the fourth parameter then the resulting string value will
|
||||||
** contain embedded NULs. The result of expressions involving strings
|
** contain embedded NULs. The result of expressions involving strings
|
||||||
** with embedded NULs is undefined.
|
** with embedded NULs is undefined.
|
||||||
|
@ -5356,7 +5356,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
|
||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
**
|
**
|
||||||
** ^These routines provide a means to determine the database, table, and
|
** ^These routines provide a means to determine the database, table, and
|
||||||
** table column that is the origin of a particular result column in
|
** table column that is the origin of a particular result column in a
|
||||||
** [SELECT] statement.
|
** [SELECT] statement.
|
||||||
** ^The name of the database or table or column can be returned as
|
** ^The name of the database or table or column can be returned as
|
||||||
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
||||||
|
@ -5925,8 +5925,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||||
**
|
**
|
||||||
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
||||||
** all application-defined SQL functions that do not need to be
|
** all application-defined SQL functions that do not need to be
|
||||||
** used inside of triggers, view, CHECK constraints, or other elements of
|
** used inside of triggers, views, CHECK constraints, or other elements of
|
||||||
** the database schema. This flags is especially recommended for SQL
|
** the database schema. This flag is especially recommended for SQL
|
||||||
** functions that have side effects or reveal internal application state.
|
** functions that have side effects or reveal internal application state.
|
||||||
** Without this flag, an attacker might be able to modify the schema of
|
** Without this flag, an attacker might be able to modify the schema of
|
||||||
** a database file to include invocations of the function with parameters
|
** a database file to include invocations of the function with parameters
|
||||||
|
@ -5957,7 +5957,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||||
** [user-defined window functions|available here].
|
** [user-defined window functions|available here].
|
||||||
**
|
**
|
||||||
** ^(If the final parameter to sqlite3_create_function_v2() or
|
** ^(If the final parameter to sqlite3_create_function_v2() or
|
||||||
** sqlite3_create_window_function() is not NULL, then it is destructor for
|
** sqlite3_create_window_function() is not NULL, then it is the destructor for
|
||||||
** the application data pointer. The destructor is invoked when the function
|
** the application data pointer. The destructor is invoked when the function
|
||||||
** is deleted, either by being overloaded or when the database connection
|
** is deleted, either by being overloaded or when the database connection
|
||||||
** closes.)^ ^The destructor is also invoked if the call to
|
** closes.)^ ^The destructor is also invoked if the call to
|
||||||
|
@ -6357,7 +6357,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
|
||||||
** METHOD: sqlite3_value
|
** METHOD: sqlite3_value
|
||||||
**
|
**
|
||||||
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
||||||
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
|
** object V and returns a pointer to that copy. ^The [sqlite3_value] returned
|
||||||
** is a [protected sqlite3_value] object even if the input is not.
|
** is a [protected sqlite3_value] object even if the input is not.
|
||||||
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
||||||
** memory allocation fails. ^If V is a [pointer value], then the result
|
** memory allocation fails. ^If V is a [pointer value], then the result
|
||||||
|
@ -6395,7 +6395,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
|
||||||
** allocation error occurs.
|
** allocation error occurs.
|
||||||
**
|
**
|
||||||
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
||||||
** determined by the N parameter on first successful call. Changing the
|
** determined by the N parameter on the first successful call. Changing the
|
||||||
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
||||||
** the same aggregate function instance will not resize the memory
|
** the same aggregate function instance will not resize the memory
|
||||||
** allocation.)^ Within the xFinal callback, it is customary to set
|
** allocation.)^ Within the xFinal callback, it is customary to set
|
||||||
|
@ -6557,7 +6557,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
|
||||||
**
|
**
|
||||||
** Security Warning: These interfaces should not be exposed in scripting
|
** Security Warning: These interfaces should not be exposed in scripting
|
||||||
** languages or in other circumstances where it might be possible for an
|
** languages or in other circumstances where it might be possible for an
|
||||||
** an attacker to invoke them. Any agent that can invoke these interfaces
|
** attacker to invoke them. Any agent that can invoke these interfaces
|
||||||
** can probably also take control of the process.
|
** can probably also take control of the process.
|
||||||
**
|
**
|
||||||
** Database connection client data is only available for SQLite
|
** Database connection client data is only available for SQLite
|
||||||
|
@ -6671,7 +6671,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
||||||
** pointed to by the 2nd parameter are taken as the application-defined
|
** pointed to by the 2nd parameter are taken as the application-defined
|
||||||
** function result. If the 3rd parameter is non-negative, then it
|
** function result. If the 3rd parameter is non-negative, then it
|
||||||
** must be the byte offset into the string where the NUL terminator would
|
** must be the byte offset into the string where the NUL terminator would
|
||||||
** appear if the string where NUL terminated. If any NUL characters occur
|
** appear if the string were NUL terminated. If any NUL characters occur
|
||||||
** in the string at a byte offset that is less than the value of the 3rd
|
** in the string at a byte offset that is less than the value of the 3rd
|
||||||
** parameter, then the resulting string will contain embedded NULs and the
|
** parameter, then the resulting string will contain embedded NULs and the
|
||||||
** result of expressions operating on strings with embedded NULs is undefined.
|
** result of expressions operating on strings with embedded NULs is undefined.
|
||||||
|
@ -6729,7 +6729,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
||||||
** string and preferably a string literal. The sqlite3_result_pointer()
|
** string and preferably a string literal. The sqlite3_result_pointer()
|
||||||
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
||||||
**
|
**
|
||||||
** If these routines are called from within the different thread
|
** If these routines are called from within a different thread
|
||||||
** than the one containing the application-defined function that received
|
** than the one containing the application-defined function that received
|
||||||
** the [sqlite3_context] pointer, the results are undefined.
|
** the [sqlite3_context] pointer, the results are undefined.
|
||||||
*/
|
*/
|
||||||
|
@ -7135,7 +7135,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
||||||
** for the N-th database on database connection D, or a NULL pointer of N is
|
** for the N-th database on database connection D, or a NULL pointer if N is
|
||||||
** out of range. An N value of 0 means the main database file. An N of 1 is
|
** out of range. An N value of 0 means the main database file. An N of 1 is
|
||||||
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
||||||
** databases.
|
** databases.
|
||||||
|
@ -7230,7 +7230,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
||||||
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
||||||
** in a read transaction. Content has been read from the database file
|
** in a read transaction. Content has been read from the database file
|
||||||
** but nothing in the database file has changed. The transaction state
|
** but nothing in the database file has changed. The transaction state
|
||||||
** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
||||||
** no other conflicting concurrent write transactions. The transaction
|
** no other conflicting concurrent write transactions. The transaction
|
||||||
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
||||||
** [COMMIT].</dd>
|
** [COMMIT].</dd>
|
||||||
|
@ -7239,7 +7239,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
||||||
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
||||||
** in a write transaction. Content has been written to the database file
|
** in a write transaction. Content has been written to the database file
|
||||||
** but has not yet committed. The transaction state will change to
|
** but has not yet committed. The transaction state will change to
|
||||||
** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_TXN_NONE 0
|
#define SQLITE_TXN_NONE 0
|
||||||
#define SQLITE_TXN_READ 1
|
#define SQLITE_TXN_READ 1
|
||||||
|
@ -7520,7 +7520,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
||||||
** CAPI3REF: Impose A Limit On Heap Size
|
** CAPI3REF: Impose A Limit On Heap Size
|
||||||
**
|
**
|
||||||
** These interfaces impose limits on the amount of heap memory that will be
|
** These interfaces impose limits on the amount of heap memory that will be
|
||||||
** by all database connections within a single process.
|
** used by all database connections within a single process.
|
||||||
**
|
**
|
||||||
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
||||||
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
||||||
|
@ -7578,7 +7578,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
||||||
** </ul>)^
|
** </ul>)^
|
||||||
**
|
**
|
||||||
** The circumstances under which SQLite will enforce the heap limits may
|
** The circumstances under which SQLite will enforce the heap limits may
|
||||||
** changes in future releases of SQLite.
|
** change in future releases of SQLite.
|
||||||
*/
|
*/
|
||||||
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
||||||
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
||||||
|
@ -7693,8 +7693,8 @@ SQLITE_API int sqlite3_table_column_metadata(
|
||||||
** ^The entry point is zProc.
|
** ^The entry point is zProc.
|
||||||
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
||||||
** entry point name on its own. It first tries "sqlite3_extension_init".
|
** entry point name on its own. It first tries "sqlite3_extension_init".
|
||||||
** If that does not work, it constructs a name "sqlite3_X_init" where the
|
** If that does not work, it constructs a name "sqlite3_X_init" where
|
||||||
** X is consists of the lower-case equivalent of all ASCII alphabetic
|
** X consists of the lower-case equivalent of all ASCII alphabetic
|
||||||
** characters in the filename from the last "/" to the first following
|
** characters in the filename from the last "/" to the first following
|
||||||
** "." and omitting any initial "lib".)^
|
** "." and omitting any initial "lib".)^
|
||||||
** ^The sqlite3_load_extension() interface returns
|
** ^The sqlite3_load_extension() interface returns
|
||||||
|
@ -7765,7 +7765,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
|
||||||
** ^(Even though the function prototype shows that xEntryPoint() takes
|
** ^(Even though the function prototype shows that xEntryPoint() takes
|
||||||
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
||||||
** arguments and expects an integer result as if the signature of the
|
** arguments and expects an integer result as if the signature of the
|
||||||
** entry point where as follows:
|
** entry point were as follows:
|
||||||
**
|
**
|
||||||
** <blockquote><pre>
|
** <blockquote><pre>
|
||||||
** int xEntryPoint(
|
** int xEntryPoint(
|
||||||
|
@ -7929,7 +7929,7 @@ struct sqlite3_module {
|
||||||
** virtual table and might not be checked again by the byte code.)^ ^(The
|
** virtual table and might not be checked again by the byte code.)^ ^(The
|
||||||
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
||||||
** is left in its default setting of false, the constraint will always be
|
** is left in its default setting of false, the constraint will always be
|
||||||
** checked separately in byte code. If the omit flag is change to true, then
|
** checked separately in byte code. If the omit flag is changed to true, then
|
||||||
** the constraint may or may not be checked in byte code. In other words,
|
** the constraint may or may not be checked in byte code. In other words,
|
||||||
** when the omit flag is true there is no guarantee that the constraint will
|
** when the omit flag is true there is no guarantee that the constraint will
|
||||||
** not be checked again using byte code.)^
|
** not be checked again using byte code.)^
|
||||||
|
@ -7955,7 +7955,7 @@ struct sqlite3_module {
|
||||||
** The xBestIndex method may optionally populate the idxFlags field with a
|
** The xBestIndex method may optionally populate the idxFlags field with a
|
||||||
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
||||||
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
||||||
** output to show the idxNum has hex instead of as decimal. Another flag is
|
** output to show the idxNum as hex instead of as decimal. Another flag is
|
||||||
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
||||||
** return at most one row.
|
** return at most one row.
|
||||||
**
|
**
|
||||||
|
@ -8096,7 +8096,7 @@ struct sqlite3_index_info {
|
||||||
** the implementation of the [virtual table module]. ^The fourth
|
** the implementation of the [virtual table module]. ^The fourth
|
||||||
** parameter is an arbitrary client data pointer that is passed through
|
** parameter is an arbitrary client data pointer that is passed through
|
||||||
** into the [xCreate] and [xConnect] methods of the virtual table module
|
** into the [xCreate] and [xConnect] methods of the virtual table module
|
||||||
** when a new virtual table is be being created or reinitialized.
|
** when a new virtual table is being created or reinitialized.
|
||||||
**
|
**
|
||||||
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
||||||
** is a pointer to a destructor for the pClientData. ^SQLite will
|
** is a pointer to a destructor for the pClientData. ^SQLite will
|
||||||
|
@ -8261,7 +8261,7 @@ typedef struct sqlite3_blob sqlite3_blob;
|
||||||
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
||||||
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
||||||
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
||||||
** on *ppBlob after this function it returns.
|
** on *ppBlob after this function returns.
|
||||||
**
|
**
|
||||||
** This function fails with SQLITE_ERROR if any of the following are true:
|
** This function fails with SQLITE_ERROR if any of the following are true:
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -8381,7 +8381,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
|
||||||
**
|
**
|
||||||
** ^Returns the size in bytes of the BLOB accessible via the
|
** ^Returns the size in bytes of the BLOB accessible via the
|
||||||
** successfully opened [BLOB handle] in its only argument. ^The
|
** successfully opened [BLOB handle] in its only argument. ^The
|
||||||
** incremental blob I/O routines can only read or overwriting existing
|
** incremental blob I/O routines can only read or overwrite existing
|
||||||
** blob content; they cannot change the size of a blob.
|
** blob content; they cannot change the size of a blob.
|
||||||
**
|
**
|
||||||
** This routine only works on a [BLOB handle] which has been created
|
** This routine only works on a [BLOB handle] which has been created
|
||||||
|
@ -8531,7 +8531,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
|
||||||
** ^The sqlite3_mutex_alloc() routine allocates a new
|
** ^The sqlite3_mutex_alloc() routine allocates a new
|
||||||
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
||||||
** routine returns NULL if it is unable to allocate the requested
|
** routine returns NULL if it is unable to allocate the requested
|
||||||
** mutex. The argument to sqlite3_mutex_alloc() must one of these
|
** mutex. The argument to sqlite3_mutex_alloc() must be one of these
|
||||||
** integer constants:
|
** integer constants:
|
||||||
**
|
**
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -8764,7 +8764,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
|
||||||
** CAPI3REF: Retrieve the mutex for a database connection
|
** CAPI3REF: Retrieve the mutex for a database connection
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^This interface returns a pointer the [sqlite3_mutex] object that
|
** ^This interface returns a pointer to the [sqlite3_mutex] object that
|
||||||
** serializes access to the [database connection] given in the argument
|
** serializes access to the [database connection] given in the argument
|
||||||
** when the [threading mode] is Serialized.
|
** when the [threading mode] is Serialized.
|
||||||
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
||||||
|
@ -8887,7 +8887,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
||||||
** CAPI3REF: SQL Keyword Checking
|
** CAPI3REF: SQL Keyword Checking
|
||||||
**
|
**
|
||||||
** These routines provide access to the set of SQL language keywords
|
** These routines provide access to the set of SQL language keywords
|
||||||
** recognized by SQLite. Applications can uses these routines to determine
|
** recognized by SQLite. Applications can use these routines to determine
|
||||||
** whether or not a specific identifier needs to be escaped (for example,
|
** whether or not a specific identifier needs to be escaped (for example,
|
||||||
** by enclosing in double-quotes) so as not to confuse the parser.
|
** by enclosing in double-quotes) so as not to confuse the parser.
|
||||||
**
|
**
|
||||||
|
@ -9055,7 +9055,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*);
|
||||||
** content of the dynamic string under construction in X. The value
|
** content of the dynamic string under construction in X. The value
|
||||||
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
||||||
** and might be freed or altered by any subsequent method on the same
|
** and might be freed or altered by any subsequent method on the same
|
||||||
** [sqlite3_str] object. Applications must not used the pointer returned
|
** [sqlite3_str] object. Applications must not use the pointer returned by
|
||||||
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
||||||
** object. ^Applications may change the content of the string returned
|
** object. ^Applications may change the content of the string returned
|
||||||
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
||||||
|
@ -9141,7 +9141,7 @@ SQLITE_API int sqlite3_status64(
|
||||||
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
||||||
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
||||||
** returned value includes allocations that overflowed because they
|
** returned value includes allocations that overflowed because they
|
||||||
** where too large (they were larger than the "sz" parameter to
|
** were too large (they were larger than the "sz" parameter to
|
||||||
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
||||||
** no space was left in the page cache.</dd>)^
|
** no space was left in the page cache.</dd>)^
|
||||||
**
|
**
|
||||||
|
@ -9225,28 +9225,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
||||||
** <dd>This parameter returns the number of malloc attempts that were
|
** <dd>This parameter returns the number of malloc attempts that were
|
||||||
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
||||||
** <dd>This parameter returns the number malloc attempts that might have
|
** <dd>This parameter returns the number of malloc attempts that might have
|
||||||
** been satisfied using lookaside memory but failed due to the amount of
|
** been satisfied using lookaside memory but failed due to the amount of
|
||||||
** memory requested being larger than the lookaside slot size.
|
** memory requested being larger than the lookaside slot size.
|
||||||
** Only the high-water value is meaningful;
|
** Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
||||||
** <dd>This parameter returns the number malloc attempts that might have
|
** <dd>This parameter returns the number of malloc attempts that might have
|
||||||
** been satisfied using lookaside memory but failed due to all lookaside
|
** been satisfied using lookaside memory but failed due to all lookaside
|
||||||
** memory already being in use.
|
** memory already being in use.
|
||||||
** Only the high-water value is meaningful;
|
** Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
** memory used by all pager caches associated with the database connection.)^
|
** memory used by all pager caches associated with the database connection.)^
|
||||||
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
||||||
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
||||||
|
@ -9255,10 +9256,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** memory used by that pager cache is divided evenly between the attached
|
** memory used by that pager cache is divided evenly between the attached
|
||||||
** connections.)^ In other words, if none of the pager caches associated
|
** connections.)^ In other words, if none of the pager caches associated
|
||||||
** with the database connection are shared, this request returns the same
|
** with the database connection are shared, this request returns the same
|
||||||
** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are
|
** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are
|
||||||
** shared, the value returned by this call will be smaller than that returned
|
** shared, the value returned by this call will be smaller than that returned
|
||||||
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
||||||
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
|
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
|
@ -9268,6 +9269,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** schema memory is shared with other database connections due to
|
** schema memory is shared with other database connections due to
|
||||||
** [shared cache mode] being enabled.
|
** [shared cache mode] being enabled.
|
||||||
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
||||||
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
|
@ -9304,7 +9306,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** been written to disk in the middle of a transaction due to the page
|
** been written to disk in the middle of a transaction due to the page
|
||||||
** cache overflowing. Transactions are more efficient if they are written
|
** cache overflowing. Transactions are more efficient if they are written
|
||||||
** to disk all at once. When pages spill mid-transaction, that introduces
|
** to disk all at once. When pages spill mid-transaction, that introduces
|
||||||
** additional overhead. This parameter can be used help identify
|
** additional overhead. This parameter can be used to help identify
|
||||||
** inefficiencies that can be resolved by increasing the cache size.
|
** inefficiencies that can be resolved by increasing the cache size.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
|
@ -9784,7 +9786,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
||||||
** external process or via a database connection other than the one being
|
** external process or via a database connection other than the one being
|
||||||
** used by the backup operation, then the backup will be automatically
|
** used by the backup operation, then the backup will be automatically
|
||||||
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
||||||
** database is modified by the using the same database connection as is used
|
** database is modified by using the same database connection as is used
|
||||||
** by the backup operation, then the backup database is automatically
|
** by the backup operation, then the backup database is automatically
|
||||||
** updated at the same time.
|
** updated at the same time.
|
||||||
**
|
**
|
||||||
|
@ -9801,7 +9803,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
||||||
** and may not be used following a call to sqlite3_backup_finish().
|
** and may not be used following a call to sqlite3_backup_finish().
|
||||||
**
|
**
|
||||||
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
||||||
** sqlite3_backup_step() errors occurred, regardless or whether or not
|
** sqlite3_backup_step() errors occurred, regardless of whether or not
|
||||||
** sqlite3_backup_step() completed.
|
** sqlite3_backup_step() completed.
|
||||||
** ^If an out-of-memory condition or IO error occurred during any prior
|
** ^If an out-of-memory condition or IO error occurred during any prior
|
||||||
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
||||||
|
@ -10871,7 +10873,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^If a write-transaction is open on [database connection] D when the
|
** ^If a write-transaction is open on [database connection] D when the
|
||||||
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
|
** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty
|
||||||
** pages in the pager-cache that are not currently in use are written out
|
** pages in the pager-cache that are not currently in use are written out
|
||||||
** to disk. A dirty page may be in use if a database cursor created by an
|
** to disk. A dirty page may be in use if a database cursor created by an
|
||||||
** active SQL statement is reading from it, or if it is page 1 of a database
|
** active SQL statement is reading from it, or if it is page 1 of a database
|
||||||
|
@ -15442,8 +15444,8 @@ typedef INT16_TYPE LogEst;
|
||||||
** assuming n is a signed integer type. UMXV(n) is similar for unsigned
|
** assuming n is a signed integer type. UMXV(n) is similar for unsigned
|
||||||
** integer types.
|
** integer types.
|
||||||
*/
|
*/
|
||||||
#define SMXV(n) ((((i64)1)<<(sizeof(n)-1))-1)
|
#define SMXV(n) ((((i64)1)<<(sizeof(n)*8-1))-1)
|
||||||
#define UMXV(n) ((((i64)1)<<(sizeof(n)))-1)
|
#define UMXV(n) ((((i64)1)<<(sizeof(n)*8))-1)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Round up a number to the next larger multiple of 8. This is used
|
** Round up a number to the next larger multiple of 8. This is used
|
||||||
|
@ -18703,6 +18705,7 @@ struct CollSeq {
|
||||||
#define SQLITE_AFF_INTEGER 0x44 /* 'D' */
|
#define SQLITE_AFF_INTEGER 0x44 /* 'D' */
|
||||||
#define SQLITE_AFF_REAL 0x45 /* 'E' */
|
#define SQLITE_AFF_REAL 0x45 /* 'E' */
|
||||||
#define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */
|
#define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */
|
||||||
|
#define SQLITE_AFF_DEFER 0x58 /* 'X' - defer computation until later */
|
||||||
|
|
||||||
#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC)
|
#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC)
|
||||||
|
|
||||||
|
@ -19253,7 +19256,7 @@ struct AggInfo {
|
||||||
** from source tables rather than from accumulators */
|
** from source tables rather than from accumulators */
|
||||||
u8 useSortingIdx; /* In direct mode, reference the sorting index rather
|
u8 useSortingIdx; /* In direct mode, reference the sorting index rather
|
||||||
** than the source table */
|
** than the source table */
|
||||||
u16 nSortingColumn; /* Number of columns in the sorting index */
|
u32 nSortingColumn; /* Number of columns in the sorting index */
|
||||||
int sortingIdx; /* Cursor number of the sorting index */
|
int sortingIdx; /* Cursor number of the sorting index */
|
||||||
int sortingIdxPTab; /* Cursor number of pseudo-table */
|
int sortingIdxPTab; /* Cursor number of pseudo-table */
|
||||||
int iFirstReg; /* First register in range for aCol[] and aFunc[] */
|
int iFirstReg; /* First register in range for aCol[] and aFunc[] */
|
||||||
|
@ -19262,8 +19265,8 @@ struct AggInfo {
|
||||||
Table *pTab; /* Source table */
|
Table *pTab; /* Source table */
|
||||||
Expr *pCExpr; /* The original expression */
|
Expr *pCExpr; /* The original expression */
|
||||||
int iTable; /* Cursor number of the source table */
|
int iTable; /* Cursor number of the source table */
|
||||||
i16 iColumn; /* Column number within the source table */
|
int iColumn; /* Column number within the source table */
|
||||||
i16 iSorterColumn; /* Column number in the sorting index */
|
int iSorterColumn; /* Column number in the sorting index */
|
||||||
} *aCol;
|
} *aCol;
|
||||||
int nColumn; /* Number of used entries in aCol[] */
|
int nColumn; /* Number of used entries in aCol[] */
|
||||||
int nAccumulator; /* Number of columns that show through to the output.
|
int nAccumulator; /* Number of columns that show through to the output.
|
||||||
|
@ -43874,21 +43877,20 @@ static int unixShmLock(
|
||||||
/* Check that, if this to be a blocking lock, no locks that occur later
|
/* Check that, if this to be a blocking lock, no locks that occur later
|
||||||
** in the following list than the lock being obtained are already held:
|
** in the following list than the lock being obtained are already held:
|
||||||
**
|
**
|
||||||
** 1. Checkpointer lock (ofst==1).
|
** 1. Recovery lock (ofst==2).
|
||||||
** 2. Write lock (ofst==0).
|
** 2. Checkpointer lock (ofst==1).
|
||||||
** 3. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK).
|
** 3. Write lock (ofst==0).
|
||||||
|
** 4. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK).
|
||||||
**
|
**
|
||||||
** In other words, if this is a blocking lock, none of the locks that
|
** In other words, if this is a blocking lock, none of the locks that
|
||||||
** occur later in the above list than the lock being obtained may be
|
** occur later in the above list than the lock being obtained may be
|
||||||
** held.
|
** held.
|
||||||
**
|
|
||||||
** It is not permitted to block on the RECOVER lock.
|
|
||||||
*/
|
*/
|
||||||
#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) && defined(SQLITE_DEBUG)
|
#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) && defined(SQLITE_DEBUG)
|
||||||
{
|
{
|
||||||
u16 lockMask = (p->exclMask|p->sharedMask);
|
u16 lockMask = (p->exclMask|p->sharedMask);
|
||||||
assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
|
assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
|
||||||
(ofst!=2) /* not RECOVER */
|
(ofst!=2 || lockMask==0)
|
||||||
&& (ofst!=1 || lockMask==0 || lockMask==2)
|
&& (ofst!=1 || lockMask==0 || lockMask==2)
|
||||||
&& (ofst!=0 || lockMask<3)
|
&& (ofst!=0 || lockMask<3)
|
||||||
&& (ofst<3 || lockMask<(1<<ofst))
|
&& (ofst<3 || lockMask<(1<<ofst))
|
||||||
|
@ -49849,7 +49851,11 @@ static int winHandleLockTimeout(
|
||||||
if( res==WAIT_OBJECT_0 ){
|
if( res==WAIT_OBJECT_0 ){
|
||||||
ret = TRUE;
|
ret = TRUE;
|
||||||
}else if( res==WAIT_TIMEOUT ){
|
}else if( res==WAIT_TIMEOUT ){
|
||||||
|
#if SQLITE_ENABLE_SETLK_TIMEOUT==1
|
||||||
rc = SQLITE_BUSY_TIMEOUT;
|
rc = SQLITE_BUSY_TIMEOUT;
|
||||||
|
#else
|
||||||
|
rc = SQLITE_BUSY;
|
||||||
|
#endif
|
||||||
}else{
|
}else{
|
||||||
/* Some other error has occurred */
|
/* Some other error has occurred */
|
||||||
rc = SQLITE_IOERR_LOCK;
|
rc = SQLITE_IOERR_LOCK;
|
||||||
|
@ -51660,21 +51666,20 @@ static int winShmLock(
|
||||||
/* Check that, if this to be a blocking lock, no locks that occur later
|
/* Check that, if this to be a blocking lock, no locks that occur later
|
||||||
** in the following list than the lock being obtained are already held:
|
** in the following list than the lock being obtained are already held:
|
||||||
**
|
**
|
||||||
** 1. Checkpointer lock (ofst==1).
|
** 1. Recovery lock (ofst==2).
|
||||||
** 2. Write lock (ofst==0).
|
** 2. Checkpointer lock (ofst==1).
|
||||||
** 3. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK).
|
** 3. Write lock (ofst==0).
|
||||||
|
** 4. Read locks (ofst>=3 && ofst<SQLITE_SHM_NLOCK).
|
||||||
**
|
**
|
||||||
** In other words, if this is a blocking lock, none of the locks that
|
** In other words, if this is a blocking lock, none of the locks that
|
||||||
** occur later in the above list than the lock being obtained may be
|
** occur later in the above list than the lock being obtained may be
|
||||||
** held.
|
** held.
|
||||||
**
|
|
||||||
** It is not permitted to block on the RECOVER lock.
|
|
||||||
*/
|
*/
|
||||||
#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) && defined(SQLITE_DEBUG)
|
#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) && defined(SQLITE_DEBUG)
|
||||||
{
|
{
|
||||||
u16 lockMask = (p->exclMask|p->sharedMask);
|
u16 lockMask = (p->exclMask|p->sharedMask);
|
||||||
assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
|
assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || (
|
||||||
(ofst!=2) /* not RECOVER */
|
(ofst!=2 || lockMask==0)
|
||||||
&& (ofst!=1 || lockMask==0 || lockMask==2)
|
&& (ofst!=1 || lockMask==0 || lockMask==2)
|
||||||
&& (ofst!=0 || lockMask<3)
|
&& (ofst!=0 || lockMask<3)
|
||||||
&& (ofst<3 || lockMask<(1<<ofst))
|
&& (ofst<3 || lockMask<(1<<ofst))
|
||||||
|
@ -54963,7 +54968,9 @@ bitvec_set_rehash:
|
||||||
}else{
|
}else{
|
||||||
memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash));
|
memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash));
|
||||||
memset(p->u.apSub, 0, sizeof(p->u.apSub));
|
memset(p->u.apSub, 0, sizeof(p->u.apSub));
|
||||||
p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR;
|
p->iDivisor = p->iSize/BITVEC_NPTR;
|
||||||
|
if( (p->iSize%BITVEC_NPTR)!=0 ) p->iDivisor++;
|
||||||
|
if( p->iDivisor<BITVEC_NBIT ) p->iDivisor = BITVEC_NBIT;
|
||||||
rc = sqlite3BitvecSet(p, i);
|
rc = sqlite3BitvecSet(p, i);
|
||||||
for(j=0; j<BITVEC_NINT; j++){
|
for(j=0; j<BITVEC_NINT; j++){
|
||||||
if( aiValues[j] ) rc |= sqlite3BitvecSet(p, aiValues[j]);
|
if( aiValues[j] ) rc |= sqlite3BitvecSet(p, aiValues[j]);
|
||||||
|
@ -58750,6 +58757,9 @@ struct Pager {
|
||||||
Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */
|
Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */
|
||||||
char *zWal; /* File name for write-ahead log */
|
char *zWal; /* File name for write-ahead log */
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
||||||
|
sqlite3 *dbWal;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -65631,6 +65641,11 @@ static int pagerOpenWal(Pager *pPager){
|
||||||
pPager->fd, pPager->zWal, pPager->exclusiveMode,
|
pPager->fd, pPager->zWal, pPager->exclusiveMode,
|
||||||
pPager->journalSizeLimit, &pPager->pWal
|
pPager->journalSizeLimit, &pPager->pWal
|
||||||
);
|
);
|
||||||
|
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
sqlite3WalDb(pPager->pWal, pPager->dbWal);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
pagerFixMaplimit(pPager);
|
pagerFixMaplimit(pPager);
|
||||||
|
|
||||||
|
@ -65750,6 +65765,7 @@ SQLITE_PRIVATE int sqlite3PagerWalWriteLock(Pager *pPager, int bLock){
|
||||||
** blocking locks are required.
|
** blocking locks are required.
|
||||||
*/
|
*/
|
||||||
SQLITE_PRIVATE void sqlite3PagerWalDb(Pager *pPager, sqlite3 *db){
|
SQLITE_PRIVATE void sqlite3PagerWalDb(Pager *pPager, sqlite3 *db){
|
||||||
|
pPager->dbWal = db;
|
||||||
if( pagerUseWal(pPager) ){
|
if( pagerUseWal(pPager) ){
|
||||||
sqlite3WalDb(pPager->pWal, db);
|
sqlite3WalDb(pPager->pWal, db);
|
||||||
}
|
}
|
||||||
|
@ -68923,7 +68939,6 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
|
||||||
rc = walIndexReadHdr(pWal, pChanged);
|
rc = walIndexReadHdr(pWal, pChanged);
|
||||||
}
|
}
|
||||||
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
||||||
walDisableBlocking(pWal);
|
|
||||||
if( rc==SQLITE_BUSY_TIMEOUT ){
|
if( rc==SQLITE_BUSY_TIMEOUT ){
|
||||||
rc = SQLITE_BUSY;
|
rc = SQLITE_BUSY;
|
||||||
*pCnt |= WAL_RETRY_BLOCKED_MASK;
|
*pCnt |= WAL_RETRY_BLOCKED_MASK;
|
||||||
|
@ -68938,6 +68953,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
|
||||||
** WAL_RETRY this routine will be called again and will probably be
|
** WAL_RETRY this routine will be called again and will probably be
|
||||||
** right on the second iteration.
|
** right on the second iteration.
|
||||||
*/
|
*/
|
||||||
|
(void)walEnableBlocking(pWal);
|
||||||
if( pWal->apWiData[0]==0 ){
|
if( pWal->apWiData[0]==0 ){
|
||||||
/* This branch is taken when the xShmMap() method returns SQLITE_BUSY.
|
/* This branch is taken when the xShmMap() method returns SQLITE_BUSY.
|
||||||
** We assume this is a transient condition, so return WAL_RETRY. The
|
** We assume this is a transient condition, so return WAL_RETRY. The
|
||||||
|
@ -68954,6 +68970,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
|
||||||
rc = SQLITE_BUSY_RECOVERY;
|
rc = SQLITE_BUSY_RECOVERY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
walDisableBlocking(pWal);
|
||||||
if( rc!=SQLITE_OK ){
|
if( rc!=SQLITE_OK ){
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -69641,6 +69658,7 @@ SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *p
|
||||||
if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal);
|
if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal);
|
||||||
}
|
}
|
||||||
SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; )
|
SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; )
|
||||||
|
pWal->iReCksum = 0;
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -69688,6 +69706,9 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
|
||||||
walCleanupHash(pWal);
|
walCleanupHash(pWal);
|
||||||
}
|
}
|
||||||
SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; )
|
SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; )
|
||||||
|
if( pWal->iReCksum>pWal->hdr.mxFrame ){
|
||||||
|
pWal->iReCksum = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
|
@ -75228,6 +75249,13 @@ static SQLITE_NOINLINE int btreeBeginTrans(
|
||||||
(void)sqlite3PagerWalWriteLock(pPager, 0);
|
(void)sqlite3PagerWalWriteLock(pPager, 0);
|
||||||
unlockBtreeIfUnused(pBt);
|
unlockBtreeIfUnused(pBt);
|
||||||
}
|
}
|
||||||
|
#if defined(SQLITE_ENABLE_SETLK_TIMEOUT)
|
||||||
|
if( rc==SQLITE_BUSY_TIMEOUT ){
|
||||||
|
/* If a blocking lock timed out, break out of the loop here so that
|
||||||
|
** the busy-handler is not invoked. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
|
}while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
|
||||||
btreeInvokeBusyHandler(pBt) );
|
btreeInvokeBusyHandler(pBt) );
|
||||||
sqlite3PagerWalDb(pPager, 0);
|
sqlite3PagerWalDb(pPager, 0);
|
||||||
|
@ -105039,7 +105067,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
|
||||||
assert( pCsr->eCurType==CURTYPE_SORTER );
|
assert( pCsr->eCurType==CURTYPE_SORTER );
|
||||||
assert( sizeof(KeyInfo) + UMXV(pCsr->pKeyInfo->nKeyField)*sizeof(CollSeq*)
|
assert( sizeof(KeyInfo) + UMXV(pCsr->pKeyInfo->nKeyField)*sizeof(CollSeq*)
|
||||||
< 0x7fffffff );
|
< 0x7fffffff );
|
||||||
szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nKeyField+1);
|
szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nKeyField);
|
||||||
sz = SZ_VDBESORTER(nWorker+1);
|
sz = SZ_VDBESORTER(nWorker+1);
|
||||||
|
|
||||||
pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo);
|
pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo);
|
||||||
|
@ -110389,7 +110417,9 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){
|
||||||
pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr
|
pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if( op==TK_VECTOR ){
|
if( op==TK_VECTOR
|
||||||
|
|| (op==TK_FUNCTION && pExpr->affExpr==SQLITE_AFF_DEFER)
|
||||||
|
){
|
||||||
assert( ExprUseXList(pExpr) );
|
assert( ExprUseXList(pExpr) );
|
||||||
return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr);
|
return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr);
|
||||||
}
|
}
|
||||||
|
@ -110582,7 +110612,9 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){
|
||||||
p = p->pLeft;
|
p = p->pLeft;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if( op==TK_VECTOR ){
|
if( op==TK_VECTOR
|
||||||
|
|| (op==TK_FUNCTION && p->affExpr==SQLITE_AFF_DEFER)
|
||||||
|
){
|
||||||
assert( ExprUseXList(p) );
|
assert( ExprUseXList(p) );
|
||||||
p = p->x.pList->a[0].pExpr;
|
p = p->x.pList->a[0].pExpr;
|
||||||
continue;
|
continue;
|
||||||
|
@ -117322,7 +117354,9 @@ static void findOrCreateAggInfoColumn(
|
||||||
){
|
){
|
||||||
struct AggInfo_col *pCol;
|
struct AggInfo_col *pCol;
|
||||||
int k;
|
int k;
|
||||||
|
int mxTerm = pParse->db->aLimit[SQLITE_LIMIT_COLUMN];
|
||||||
|
|
||||||
|
assert( mxTerm <= SMXV(i16) );
|
||||||
assert( pAggInfo->iFirstReg==0 );
|
assert( pAggInfo->iFirstReg==0 );
|
||||||
pCol = pAggInfo->aCol;
|
pCol = pAggInfo->aCol;
|
||||||
for(k=0; k<pAggInfo->nColumn; k++, pCol++){
|
for(k=0; k<pAggInfo->nColumn; k++, pCol++){
|
||||||
|
@ -117340,6 +117374,10 @@ static void findOrCreateAggInfoColumn(
|
||||||
assert( pParse->db->mallocFailed );
|
assert( pParse->db->mallocFailed );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if( k>mxTerm ){
|
||||||
|
sqlite3ErrorMsg(pParse, "more than %d aggregate terms", mxTerm);
|
||||||
|
k = mxTerm;
|
||||||
|
}
|
||||||
pCol = &pAggInfo->aCol[k];
|
pCol = &pAggInfo->aCol[k];
|
||||||
assert( ExprUseYTab(pExpr) );
|
assert( ExprUseYTab(pExpr) );
|
||||||
pCol->pTab = pExpr->y.pTab;
|
pCol->pTab = pExpr->y.pTab;
|
||||||
|
@ -117373,6 +117411,7 @@ fix_up_expr:
|
||||||
if( pExpr->op==TK_COLUMN ){
|
if( pExpr->op==TK_COLUMN ){
|
||||||
pExpr->op = TK_AGG_COLUMN;
|
pExpr->op = TK_AGG_COLUMN;
|
||||||
}
|
}
|
||||||
|
assert( k <= SMXV(pExpr->iAgg) );
|
||||||
pExpr->iAgg = (i16)k;
|
pExpr->iAgg = (i16)k;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117457,13 +117496,19 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
|
||||||
** function that is already in the pAggInfo structure
|
** function that is already in the pAggInfo structure
|
||||||
*/
|
*/
|
||||||
struct AggInfo_func *pItem = pAggInfo->aFunc;
|
struct AggInfo_func *pItem = pAggInfo->aFunc;
|
||||||
|
int mxTerm = pParse->db->aLimit[SQLITE_LIMIT_COLUMN];
|
||||||
|
assert( mxTerm <= SMXV(i16) );
|
||||||
for(i=0; i<pAggInfo->nFunc; i++, pItem++){
|
for(i=0; i<pAggInfo->nFunc; i++, pItem++){
|
||||||
if( NEVER(pItem->pFExpr==pExpr) ) break;
|
if( NEVER(pItem->pFExpr==pExpr) ) break;
|
||||||
if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){
|
if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if( i>=pAggInfo->nFunc ){
|
if( i>mxTerm ){
|
||||||
|
sqlite3ErrorMsg(pParse, "more than %d aggregate terms", mxTerm);
|
||||||
|
i = mxTerm;
|
||||||
|
assert( i<pAggInfo->nFunc );
|
||||||
|
}else if( i>=pAggInfo->nFunc ){
|
||||||
/* pExpr is original. Make a new entry in pAggInfo->aFunc[]
|
/* pExpr is original. Make a new entry in pAggInfo->aFunc[]
|
||||||
*/
|
*/
|
||||||
u8 enc = ENC(pParse->db);
|
u8 enc = ENC(pParse->db);
|
||||||
|
@ -117517,6 +117562,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
|
||||||
*/
|
*/
|
||||||
assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
|
assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
|
||||||
ExprSetVVAProperty(pExpr, EP_NoReduce);
|
ExprSetVVAProperty(pExpr, EP_NoReduce);
|
||||||
|
assert( i <= SMXV(pExpr->iAgg) );
|
||||||
pExpr->iAgg = (i16)i;
|
pExpr->iAgg = (i16)i;
|
||||||
pExpr->pAggInfo = pAggInfo;
|
pExpr->pAggInfo = pAggInfo;
|
||||||
return WRC_Prune;
|
return WRC_Prune;
|
||||||
|
@ -131975,7 +132021,7 @@ static void concatFuncCore(
|
||||||
int nSep,
|
int nSep,
|
||||||
const char *zSep
|
const char *zSep
|
||||||
){
|
){
|
||||||
i64 j, k, n = 0;
|
i64 j, n = 0;
|
||||||
int i;
|
int i;
|
||||||
char *z;
|
char *z;
|
||||||
for(i=0; i<argc; i++){
|
for(i=0; i<argc; i++){
|
||||||
|
@ -131989,8 +132035,8 @@ static void concatFuncCore(
|
||||||
}
|
}
|
||||||
j = 0;
|
j = 0;
|
||||||
for(i=0; i<argc; i++){
|
for(i=0; i<argc; i++){
|
||||||
k = sqlite3_value_bytes(argv[i]);
|
if( sqlite3_value_type(argv[i])!=SQLITE_NULL ){
|
||||||
if( k>0 ){
|
int k = sqlite3_value_bytes(argv[i]);
|
||||||
const char *v = (const char*)sqlite3_value_text(argv[i]);
|
const char *v = (const char*)sqlite3_value_text(argv[i]);
|
||||||
if( v!=0 ){
|
if( v!=0 ){
|
||||||
if( j>0 && nSep>0 ){
|
if( j>0 && nSep>0 ){
|
||||||
|
@ -145364,7 +145410,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){
|
||||||
}
|
}
|
||||||
pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol);
|
pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol);
|
||||||
sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol);
|
sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol);
|
||||||
if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){
|
if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 && pParse->nErr==0 ){
|
||||||
/* This branch runs if the query contains one or more RIGHT or FULL
|
/* This branch runs if the query contains one or more RIGHT or FULL
|
||||||
** JOINs. If only a single table on the left side of this join
|
** JOINs. If only a single table on the left side of this join
|
||||||
** contains the zName column, then this branch is a no-op.
|
** contains the zName column, then this branch is a no-op.
|
||||||
|
@ -145380,6 +145426,8 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){
|
||||||
*/
|
*/
|
||||||
ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */
|
ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */
|
||||||
static const Token tkCoalesce = { "coalesce", 8 };
|
static const Token tkCoalesce = { "coalesce", 8 };
|
||||||
|
assert( pE1!=0 );
|
||||||
|
ExprSetProperty(pE1, EP_CanBeNull);
|
||||||
while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol,
|
while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol,
|
||||||
pRight->fg.isSynthUsing)!=0 ){
|
pRight->fg.isSynthUsing)!=0 ){
|
||||||
if( pSrc->a[iLeft].fg.isUsing==0
|
if( pSrc->a[iLeft].fg.isUsing==0
|
||||||
|
@ -145396,7 +145444,13 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){
|
||||||
if( pFuncArgs ){
|
if( pFuncArgs ){
|
||||||
pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1);
|
pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1);
|
||||||
pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0);
|
pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0);
|
||||||
|
if( pE1 ){
|
||||||
|
pE1->affExpr = SQLITE_AFF_DEFER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}else if( (pSrc->a[i+1].fg.jointype & JT_LEFT)!=0 && pParse->nErr==0 ){
|
||||||
|
assert( pE1!=0 );
|
||||||
|
ExprSetProperty(pE1, EP_CanBeNull);
|
||||||
}
|
}
|
||||||
pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol);
|
pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol);
|
||||||
sqlite3SrcItemColumnUsed(pRight, iRightCol);
|
sqlite3SrcItemColumnUsed(pRight, iRightCol);
|
||||||
|
@ -149004,9 +149058,9 @@ static int compoundHasDifferentAffinities(Select *p){
|
||||||
** from 2015-02-09.)
|
** from 2015-02-09.)
|
||||||
**
|
**
|
||||||
** (3) If the subquery is the right operand of a LEFT JOIN then
|
** (3) If the subquery is the right operand of a LEFT JOIN then
|
||||||
** (3a) the subquery may not be a join and
|
** (3a) the subquery may not be a join
|
||||||
** (3b) the FROM clause of the subquery may not contain a virtual
|
** (**) Was (3b): "the FROM clause of the subquery may not contain
|
||||||
** table and
|
** a virtual table"
|
||||||
** (**) Was: "The outer query may not have a GROUP BY." This case
|
** (**) Was: "The outer query may not have a GROUP BY." This case
|
||||||
** is now managed correctly
|
** is now managed correctly
|
||||||
** (3d) the outer query may not be DISTINCT.
|
** (3d) the outer query may not be DISTINCT.
|
||||||
|
@ -149222,7 +149276,7 @@ static int flattenSubquery(
|
||||||
*/
|
*/
|
||||||
if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){
|
if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){
|
||||||
if( pSubSrc->nSrc>1 /* (3a) */
|
if( pSubSrc->nSrc>1 /* (3a) */
|
||||||
|| IsVirtual(pSubSrc->a[0].pSTab) /* (3b) */
|
/**** || IsVirtual(pSubSrc->a[0].pSTab) (3b)-omitted */
|
||||||
|| (p->selFlags & SF_Distinct)!=0 /* (3d) */
|
|| (p->selFlags & SF_Distinct)!=0 /* (3d) */
|
||||||
|| (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */
|
|| (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */
|
||||||
){
|
){
|
||||||
|
@ -161722,12 +161776,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
|
||||||
if( pLevel->iLeftJoin==0 ){
|
if( pLevel->iLeftJoin==0 ){
|
||||||
/* If a partial index is driving the loop, try to eliminate WHERE clause
|
/* If a partial index is driving the loop, try to eliminate WHERE clause
|
||||||
** terms from the query that must be true due to the WHERE clause of
|
** terms from the query that must be true due to the WHERE clause of
|
||||||
** the partial index.
|
** the partial index. This optimization does not work on an outer join,
|
||||||
|
** as shown by:
|
||||||
**
|
**
|
||||||
** 2019-11-02 ticket 623eff57e76d45f6: This optimization does not work
|
** 2019-11-02 ticket 623eff57e76d45f6 (LEFT JOIN)
|
||||||
** for a LEFT JOIN.
|
** 2025-05-29 forum post 7dee41d32506c4ae (RIGHT JOIN)
|
||||||
*/
|
*/
|
||||||
if( pIdx->pPartIdxWhere ){
|
if( pIdx->pPartIdxWhere && pLevel->pRJ==0 ){
|
||||||
whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC);
|
whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
@ -163400,30 +163455,42 @@ static void exprAnalyzeOrTerm(
|
||||||
** 1. The SQLITE_Transitive optimization must be enabled
|
** 1. The SQLITE_Transitive optimization must be enabled
|
||||||
** 2. Must be either an == or an IS operator
|
** 2. Must be either an == or an IS operator
|
||||||
** 3. Not originating in the ON clause of an OUTER JOIN
|
** 3. Not originating in the ON clause of an OUTER JOIN
|
||||||
** 4. The affinities of A and B must be compatible
|
** 4. The operator is not IS or else the query does not contain RIGHT JOIN
|
||||||
** 5a. Both operands use the same collating sequence OR
|
** 5. The affinities of A and B must be compatible
|
||||||
** 5b. The overall collating sequence is BINARY
|
** 6a. Both operands use the same collating sequence OR
|
||||||
|
** 6b. The overall collating sequence is BINARY
|
||||||
** If this routine returns TRUE, that means that the RHS can be substituted
|
** If this routine returns TRUE, that means that the RHS can be substituted
|
||||||
** for the LHS anyplace else in the WHERE clause where the LHS column occurs.
|
** for the LHS anyplace else in the WHERE clause where the LHS column occurs.
|
||||||
** This is an optimization. No harm comes from returning 0. But if 1 is
|
** This is an optimization. No harm comes from returning 0. But if 1 is
|
||||||
** returned when it should not be, then incorrect answers might result.
|
** returned when it should not be, then incorrect answers might result.
|
||||||
*/
|
*/
|
||||||
static int termIsEquivalence(Parse *pParse, Expr *pExpr){
|
static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){
|
||||||
char aff1, aff2;
|
char aff1, aff2;
|
||||||
CollSeq *pColl;
|
CollSeq *pColl;
|
||||||
if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0;
|
if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; /* (1) */
|
||||||
if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0;
|
if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; /* (2) */
|
||||||
if( ExprHasProperty(pExpr, EP_OuterON) ) return 0;
|
if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* (3) */
|
||||||
|
assert( pSrc!=0 );
|
||||||
|
if( pExpr->op==TK_IS
|
||||||
|
&& pSrc->nSrc
|
||||||
|
&& (pSrc->a[0].fg.jointype & JT_LTORJ)!=0
|
||||||
|
){
|
||||||
|
return 0; /* (4) */
|
||||||
|
}
|
||||||
aff1 = sqlite3ExprAffinity(pExpr->pLeft);
|
aff1 = sqlite3ExprAffinity(pExpr->pLeft);
|
||||||
aff2 = sqlite3ExprAffinity(pExpr->pRight);
|
aff2 = sqlite3ExprAffinity(pExpr->pRight);
|
||||||
if( aff1!=aff2
|
if( aff1!=aff2
|
||||||
&& (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2))
|
&& (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2))
|
||||||
){
|
){
|
||||||
return 0;
|
return 0; /* (5) */
|
||||||
}
|
}
|
||||||
pColl = sqlite3ExprCompareCollSeq(pParse, pExpr);
|
pColl = sqlite3ExprCompareCollSeq(pParse, pExpr);
|
||||||
if( sqlite3IsBinary(pColl) ) return 1;
|
if( !sqlite3IsBinary(pColl)
|
||||||
return sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight);
|
&& !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight)
|
||||||
|
){
|
||||||
|
return 0; /* (6) */
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -163688,8 +163755,8 @@ static void exprAnalyze(
|
||||||
if( op==TK_IS ) pNew->wtFlags |= TERM_IS;
|
if( op==TK_IS ) pNew->wtFlags |= TERM_IS;
|
||||||
pTerm = &pWC->a[idxTerm];
|
pTerm = &pWC->a[idxTerm];
|
||||||
pTerm->wtFlags |= TERM_COPIED;
|
pTerm->wtFlags |= TERM_COPIED;
|
||||||
|
assert( pWInfo->pTabList!=0 );
|
||||||
if( termIsEquivalence(pParse, pDup) ){
|
if( termIsEquivalence(pParse, pDup, pWInfo->pTabList) ){
|
||||||
pTerm->eOperator |= WO_EQUIV;
|
pTerm->eOperator |= WO_EQUIV;
|
||||||
eExtraOp = WO_EQUIV;
|
eExtraOp = WO_EQUIV;
|
||||||
}
|
}
|
||||||
|
@ -184391,6 +184458,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3 *db, int ms, int flags){
|
||||||
#endif
|
#endif
|
||||||
if( ms<-1 ) return SQLITE_RANGE;
|
if( ms<-1 ) return SQLITE_RANGE;
|
||||||
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
||||||
|
sqlite3_mutex_enter(db->mutex);
|
||||||
db->setlkTimeout = ms;
|
db->setlkTimeout = ms;
|
||||||
db->setlkFlags = flags;
|
db->setlkFlags = flags;
|
||||||
sqlite3BtreeEnterAll(db);
|
sqlite3BtreeEnterAll(db);
|
||||||
|
@ -184402,6 +184470,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3 *db, int ms, int flags){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sqlite3BtreeLeaveAll(db);
|
sqlite3BtreeLeaveAll(db);
|
||||||
|
sqlite3_mutex_leave(db->mutex);
|
||||||
#endif
|
#endif
|
||||||
#if !defined(SQLITE_ENABLE_API_ARMOR) && !defined(SQLITE_ENABLE_SETLK_TIMEOUT)
|
#if !defined(SQLITE_ENABLE_API_ARMOR) && !defined(SQLITE_ENABLE_SETLK_TIMEOUT)
|
||||||
UNUSED_PARAMETER(db);
|
UNUSED_PARAMETER(db);
|
||||||
|
@ -209021,8 +209090,10 @@ static int jsonBlobChangePayloadSize(
|
||||||
nExtra = 1;
|
nExtra = 1;
|
||||||
}else if( szType==13 ){
|
}else if( szType==13 ){
|
||||||
nExtra = 2;
|
nExtra = 2;
|
||||||
}else{
|
}else if( szType==14 ){
|
||||||
nExtra = 4;
|
nExtra = 4;
|
||||||
|
}else{
|
||||||
|
nExtra = 8;
|
||||||
}
|
}
|
||||||
if( szPayload<=11 ){
|
if( szPayload<=11 ){
|
||||||
nNeeded = 0;
|
nNeeded = 0;
|
||||||
|
@ -213407,6 +213478,8 @@ SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){
|
||||||
#endif
|
#endif
|
||||||
SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
|
SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
|
||||||
|
|
||||||
|
/* #include <stddef.h> */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** If building separately, we will need some setup that is normally
|
** If building separately, we will need some setup that is normally
|
||||||
** found in sqliteInt.h
|
** found in sqliteInt.h
|
||||||
|
@ -235449,6 +235522,7 @@ SQLITE_EXTENSION_INIT1
|
||||||
|
|
||||||
/* #include <string.h> */
|
/* #include <string.h> */
|
||||||
/* #include <assert.h> */
|
/* #include <assert.h> */
|
||||||
|
/* #include <stddef.h> */
|
||||||
|
|
||||||
#ifndef SQLITE_AMALGAMATION
|
#ifndef SQLITE_AMALGAMATION
|
||||||
|
|
||||||
|
@ -257192,7 +257266,7 @@ static void fts5SourceIdFunc(
|
||||||
){
|
){
|
||||||
assert( nArg==0 );
|
assert( nArg==0 );
|
||||||
UNUSED_PARAM2(nArg, apUnused);
|
UNUSED_PARAM2(nArg, apUnused);
|
||||||
sqlite3_result_text(pCtx, "fts5: 2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742", -1, SQLITE_TRANSIENT);
|
sqlite3_result_text(pCtx, "fts5: 2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079", -1, SQLITE_TRANSIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -258007,6 +258081,7 @@ static int fts5StorageDeleteFromIndex(
|
||||||
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
|
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
|
||||||
if( pConfig->abUnindexed[iCol-1]==0 ){
|
if( pConfig->abUnindexed[iCol-1]==0 ){
|
||||||
sqlite3_value *pVal = 0;
|
sqlite3_value *pVal = 0;
|
||||||
|
sqlite3_value *pFree = 0;
|
||||||
const char *pText = 0;
|
const char *pText = 0;
|
||||||
int nText = 0;
|
int nText = 0;
|
||||||
const char *pLoc = 0;
|
const char *pLoc = 0;
|
||||||
|
@ -258023,11 +258098,22 @@ static int fts5StorageDeleteFromIndex(
|
||||||
if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
|
if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
|
||||||
rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
|
rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
|
||||||
}else{
|
}else{
|
||||||
pText = (const char*)sqlite3_value_text(pVal);
|
if( sqlite3_value_type(pVal)!=SQLITE_TEXT ){
|
||||||
nText = sqlite3_value_bytes(pVal);
|
/* Make a copy of the value to work with. This is because the call
|
||||||
if( pConfig->bLocale && pSeek ){
|
** to sqlite3_value_text() below forces the type of the value to
|
||||||
pLoc = (const char*)sqlite3_column_text(pSeek, iCol + pConfig->nCol);
|
** SQLITE_TEXT, and we may need to use it again later. */
|
||||||
nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol);
|
pFree = pVal = sqlite3_value_dup(pVal);
|
||||||
|
if( pVal==0 ){
|
||||||
|
rc = SQLITE_NOMEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
pText = (const char*)sqlite3_value_text(pVal);
|
||||||
|
nText = sqlite3_value_bytes(pVal);
|
||||||
|
if( pConfig->bLocale && pSeek ){
|
||||||
|
pLoc = (const char*)sqlite3_column_text(pSeek, iCol+pConfig->nCol);
|
||||||
|
nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258043,6 +258129,7 @@ static int fts5StorageDeleteFromIndex(
|
||||||
}
|
}
|
||||||
sqlite3Fts5ClearLocale(pConfig);
|
sqlite3Fts5ClearLocale(pConfig);
|
||||||
}
|
}
|
||||||
|
sqlite3_value_free(pFree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if( rc==SQLITE_OK && p->nTotalRow<1 ){
|
if( rc==SQLITE_OK && p->nTotalRow<1 ){
|
||||||
|
|
|
@ -146,9 +146,9 @@ extern "C" {
|
||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.50.0"
|
#define SQLITE_VERSION "3.50.2"
|
||||||
#define SQLITE_VERSION_NUMBER 3050000
|
#define SQLITE_VERSION_NUMBER 3050002
|
||||||
#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742"
|
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
|
@ -4079,7 +4079,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
|
||||||
**
|
**
|
||||||
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
||||||
** database filename D with corresponding journal file J and WAL file W and
|
** database filename D with corresponding journal file J and WAL file W and
|
||||||
** with N URI parameters key/values pairs in the array P. The result from
|
** an array P of N URI Key/Value pairs. The result from
|
||||||
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
||||||
** is safe to pass to routines like:
|
** is safe to pass to routines like:
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -4760,7 +4760,7 @@ typedef struct sqlite3_context sqlite3_context;
|
||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
**
|
**
|
||||||
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
||||||
** literals may be replaced by a [parameter] that matches one of following
|
** literals may be replaced by a [parameter] that matches one of the following
|
||||||
** templates:
|
** templates:
|
||||||
**
|
**
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -4805,7 +4805,7 @@ typedef struct sqlite3_context sqlite3_context;
|
||||||
**
|
**
|
||||||
** [[byte-order determination rules]] ^The byte-order of
|
** [[byte-order determination rules]] ^The byte-order of
|
||||||
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
||||||
** found in first character, which is removed, or in the absence of a BOM
|
** found in the first character, which is removed, or in the absence of a BOM
|
||||||
** the byte order is the native byte order of the host
|
** the byte order is the native byte order of the host
|
||||||
** machine for sqlite3_bind_text16() or the byte order specified in
|
** machine for sqlite3_bind_text16() or the byte order specified in
|
||||||
** the 6th parameter for sqlite3_bind_text64().)^
|
** the 6th parameter for sqlite3_bind_text64().)^
|
||||||
|
@ -4825,7 +4825,7 @@ typedef struct sqlite3_context sqlite3_context;
|
||||||
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
||||||
** that parameter must be the byte offset
|
** that parameter must be the byte offset
|
||||||
** where the NUL terminator would occur assuming the string were NUL
|
** where the NUL terminator would occur assuming the string were NUL
|
||||||
** terminated. If any NUL characters occurs at byte offsets less than
|
** terminated. If any NUL characters occur at byte offsets less than
|
||||||
** the value of the fourth parameter then the resulting string value will
|
** the value of the fourth parameter then the resulting string value will
|
||||||
** contain embedded NULs. The result of expressions involving strings
|
** contain embedded NULs. The result of expressions involving strings
|
||||||
** with embedded NULs is undefined.
|
** with embedded NULs is undefined.
|
||||||
|
@ -5037,7 +5037,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
|
||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
**
|
**
|
||||||
** ^These routines provide a means to determine the database, table, and
|
** ^These routines provide a means to determine the database, table, and
|
||||||
** table column that is the origin of a particular result column in
|
** table column that is the origin of a particular result column in a
|
||||||
** [SELECT] statement.
|
** [SELECT] statement.
|
||||||
** ^The name of the database or table or column can be returned as
|
** ^The name of the database or table or column can be returned as
|
||||||
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
||||||
|
@ -5606,8 +5606,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||||
**
|
**
|
||||||
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
||||||
** all application-defined SQL functions that do not need to be
|
** all application-defined SQL functions that do not need to be
|
||||||
** used inside of triggers, view, CHECK constraints, or other elements of
|
** used inside of triggers, views, CHECK constraints, or other elements of
|
||||||
** the database schema. This flags is especially recommended for SQL
|
** the database schema. This flag is especially recommended for SQL
|
||||||
** functions that have side effects or reveal internal application state.
|
** functions that have side effects or reveal internal application state.
|
||||||
** Without this flag, an attacker might be able to modify the schema of
|
** Without this flag, an attacker might be able to modify the schema of
|
||||||
** a database file to include invocations of the function with parameters
|
** a database file to include invocations of the function with parameters
|
||||||
|
@ -5638,7 +5638,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||||
** [user-defined window functions|available here].
|
** [user-defined window functions|available here].
|
||||||
**
|
**
|
||||||
** ^(If the final parameter to sqlite3_create_function_v2() or
|
** ^(If the final parameter to sqlite3_create_function_v2() or
|
||||||
** sqlite3_create_window_function() is not NULL, then it is destructor for
|
** sqlite3_create_window_function() is not NULL, then it is the destructor for
|
||||||
** the application data pointer. The destructor is invoked when the function
|
** the application data pointer. The destructor is invoked when the function
|
||||||
** is deleted, either by being overloaded or when the database connection
|
** is deleted, either by being overloaded or when the database connection
|
||||||
** closes.)^ ^The destructor is also invoked if the call to
|
** closes.)^ ^The destructor is also invoked if the call to
|
||||||
|
@ -6038,7 +6038,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
|
||||||
** METHOD: sqlite3_value
|
** METHOD: sqlite3_value
|
||||||
**
|
**
|
||||||
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
||||||
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
|
** object V and returns a pointer to that copy. ^The [sqlite3_value] returned
|
||||||
** is a [protected sqlite3_value] object even if the input is not.
|
** is a [protected sqlite3_value] object even if the input is not.
|
||||||
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
||||||
** memory allocation fails. ^If V is a [pointer value], then the result
|
** memory allocation fails. ^If V is a [pointer value], then the result
|
||||||
|
@ -6076,7 +6076,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
|
||||||
** allocation error occurs.
|
** allocation error occurs.
|
||||||
**
|
**
|
||||||
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
||||||
** determined by the N parameter on first successful call. Changing the
|
** determined by the N parameter on the first successful call. Changing the
|
||||||
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
||||||
** the same aggregate function instance will not resize the memory
|
** the same aggregate function instance will not resize the memory
|
||||||
** allocation.)^ Within the xFinal callback, it is customary to set
|
** allocation.)^ Within the xFinal callback, it is customary to set
|
||||||
|
@ -6238,7 +6238,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
|
||||||
**
|
**
|
||||||
** Security Warning: These interfaces should not be exposed in scripting
|
** Security Warning: These interfaces should not be exposed in scripting
|
||||||
** languages or in other circumstances where it might be possible for an
|
** languages or in other circumstances where it might be possible for an
|
||||||
** an attacker to invoke them. Any agent that can invoke these interfaces
|
** attacker to invoke them. Any agent that can invoke these interfaces
|
||||||
** can probably also take control of the process.
|
** can probably also take control of the process.
|
||||||
**
|
**
|
||||||
** Database connection client data is only available for SQLite
|
** Database connection client data is only available for SQLite
|
||||||
|
@ -6352,7 +6352,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
||||||
** pointed to by the 2nd parameter are taken as the application-defined
|
** pointed to by the 2nd parameter are taken as the application-defined
|
||||||
** function result. If the 3rd parameter is non-negative, then it
|
** function result. If the 3rd parameter is non-negative, then it
|
||||||
** must be the byte offset into the string where the NUL terminator would
|
** must be the byte offset into the string where the NUL terminator would
|
||||||
** appear if the string where NUL terminated. If any NUL characters occur
|
** appear if the string were NUL terminated. If any NUL characters occur
|
||||||
** in the string at a byte offset that is less than the value of the 3rd
|
** in the string at a byte offset that is less than the value of the 3rd
|
||||||
** parameter, then the resulting string will contain embedded NULs and the
|
** parameter, then the resulting string will contain embedded NULs and the
|
||||||
** result of expressions operating on strings with embedded NULs is undefined.
|
** result of expressions operating on strings with embedded NULs is undefined.
|
||||||
|
@ -6410,7 +6410,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
||||||
** string and preferably a string literal. The sqlite3_result_pointer()
|
** string and preferably a string literal. The sqlite3_result_pointer()
|
||||||
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
||||||
**
|
**
|
||||||
** If these routines are called from within the different thread
|
** If these routines are called from within a different thread
|
||||||
** than the one containing the application-defined function that received
|
** than the one containing the application-defined function that received
|
||||||
** the [sqlite3_context] pointer, the results are undefined.
|
** the [sqlite3_context] pointer, the results are undefined.
|
||||||
*/
|
*/
|
||||||
|
@ -6816,7 +6816,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
||||||
** for the N-th database on database connection D, or a NULL pointer of N is
|
** for the N-th database on database connection D, or a NULL pointer if N is
|
||||||
** out of range. An N value of 0 means the main database file. An N of 1 is
|
** out of range. An N value of 0 means the main database file. An N of 1 is
|
||||||
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
||||||
** databases.
|
** databases.
|
||||||
|
@ -6911,7 +6911,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
||||||
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
||||||
** in a read transaction. Content has been read from the database file
|
** in a read transaction. Content has been read from the database file
|
||||||
** but nothing in the database file has changed. The transaction state
|
** but nothing in the database file has changed. The transaction state
|
||||||
** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
||||||
** no other conflicting concurrent write transactions. The transaction
|
** no other conflicting concurrent write transactions. The transaction
|
||||||
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
||||||
** [COMMIT].</dd>
|
** [COMMIT].</dd>
|
||||||
|
@ -6920,7 +6920,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
||||||
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
||||||
** in a write transaction. Content has been written to the database file
|
** in a write transaction. Content has been written to the database file
|
||||||
** but has not yet committed. The transaction state will change to
|
** but has not yet committed. The transaction state will change to
|
||||||
** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_TXN_NONE 0
|
#define SQLITE_TXN_NONE 0
|
||||||
#define SQLITE_TXN_READ 1
|
#define SQLITE_TXN_READ 1
|
||||||
|
@ -7201,7 +7201,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
||||||
** CAPI3REF: Impose A Limit On Heap Size
|
** CAPI3REF: Impose A Limit On Heap Size
|
||||||
**
|
**
|
||||||
** These interfaces impose limits on the amount of heap memory that will be
|
** These interfaces impose limits on the amount of heap memory that will be
|
||||||
** by all database connections within a single process.
|
** used by all database connections within a single process.
|
||||||
**
|
**
|
||||||
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
||||||
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
||||||
|
@ -7259,7 +7259,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
||||||
** </ul>)^
|
** </ul>)^
|
||||||
**
|
**
|
||||||
** The circumstances under which SQLite will enforce the heap limits may
|
** The circumstances under which SQLite will enforce the heap limits may
|
||||||
** changes in future releases of SQLite.
|
** change in future releases of SQLite.
|
||||||
*/
|
*/
|
||||||
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
||||||
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
||||||
|
@ -7374,8 +7374,8 @@ SQLITE_API int sqlite3_table_column_metadata(
|
||||||
** ^The entry point is zProc.
|
** ^The entry point is zProc.
|
||||||
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
||||||
** entry point name on its own. It first tries "sqlite3_extension_init".
|
** entry point name on its own. It first tries "sqlite3_extension_init".
|
||||||
** If that does not work, it constructs a name "sqlite3_X_init" where the
|
** If that does not work, it constructs a name "sqlite3_X_init" where
|
||||||
** X is consists of the lower-case equivalent of all ASCII alphabetic
|
** X consists of the lower-case equivalent of all ASCII alphabetic
|
||||||
** characters in the filename from the last "/" to the first following
|
** characters in the filename from the last "/" to the first following
|
||||||
** "." and omitting any initial "lib".)^
|
** "." and omitting any initial "lib".)^
|
||||||
** ^The sqlite3_load_extension() interface returns
|
** ^The sqlite3_load_extension() interface returns
|
||||||
|
@ -7446,7 +7446,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
|
||||||
** ^(Even though the function prototype shows that xEntryPoint() takes
|
** ^(Even though the function prototype shows that xEntryPoint() takes
|
||||||
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
||||||
** arguments and expects an integer result as if the signature of the
|
** arguments and expects an integer result as if the signature of the
|
||||||
** entry point where as follows:
|
** entry point were as follows:
|
||||||
**
|
**
|
||||||
** <blockquote><pre>
|
** <blockquote><pre>
|
||||||
** int xEntryPoint(
|
** int xEntryPoint(
|
||||||
|
@ -7610,7 +7610,7 @@ struct sqlite3_module {
|
||||||
** virtual table and might not be checked again by the byte code.)^ ^(The
|
** virtual table and might not be checked again by the byte code.)^ ^(The
|
||||||
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
||||||
** is left in its default setting of false, the constraint will always be
|
** is left in its default setting of false, the constraint will always be
|
||||||
** checked separately in byte code. If the omit flag is change to true, then
|
** checked separately in byte code. If the omit flag is changed to true, then
|
||||||
** the constraint may or may not be checked in byte code. In other words,
|
** the constraint may or may not be checked in byte code. In other words,
|
||||||
** when the omit flag is true there is no guarantee that the constraint will
|
** when the omit flag is true there is no guarantee that the constraint will
|
||||||
** not be checked again using byte code.)^
|
** not be checked again using byte code.)^
|
||||||
|
@ -7636,7 +7636,7 @@ struct sqlite3_module {
|
||||||
** The xBestIndex method may optionally populate the idxFlags field with a
|
** The xBestIndex method may optionally populate the idxFlags field with a
|
||||||
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
||||||
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
||||||
** output to show the idxNum has hex instead of as decimal. Another flag is
|
** output to show the idxNum as hex instead of as decimal. Another flag is
|
||||||
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
||||||
** return at most one row.
|
** return at most one row.
|
||||||
**
|
**
|
||||||
|
@ -7777,7 +7777,7 @@ struct sqlite3_index_info {
|
||||||
** the implementation of the [virtual table module]. ^The fourth
|
** the implementation of the [virtual table module]. ^The fourth
|
||||||
** parameter is an arbitrary client data pointer that is passed through
|
** parameter is an arbitrary client data pointer that is passed through
|
||||||
** into the [xCreate] and [xConnect] methods of the virtual table module
|
** into the [xCreate] and [xConnect] methods of the virtual table module
|
||||||
** when a new virtual table is be being created or reinitialized.
|
** when a new virtual table is being created or reinitialized.
|
||||||
**
|
**
|
||||||
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
||||||
** is a pointer to a destructor for the pClientData. ^SQLite will
|
** is a pointer to a destructor for the pClientData. ^SQLite will
|
||||||
|
@ -7942,7 +7942,7 @@ typedef struct sqlite3_blob sqlite3_blob;
|
||||||
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
||||||
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
||||||
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
||||||
** on *ppBlob after this function it returns.
|
** on *ppBlob after this function returns.
|
||||||
**
|
**
|
||||||
** This function fails with SQLITE_ERROR if any of the following are true:
|
** This function fails with SQLITE_ERROR if any of the following are true:
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -8062,7 +8062,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
|
||||||
**
|
**
|
||||||
** ^Returns the size in bytes of the BLOB accessible via the
|
** ^Returns the size in bytes of the BLOB accessible via the
|
||||||
** successfully opened [BLOB handle] in its only argument. ^The
|
** successfully opened [BLOB handle] in its only argument. ^The
|
||||||
** incremental blob I/O routines can only read or overwriting existing
|
** incremental blob I/O routines can only read or overwrite existing
|
||||||
** blob content; they cannot change the size of a blob.
|
** blob content; they cannot change the size of a blob.
|
||||||
**
|
**
|
||||||
** This routine only works on a [BLOB handle] which has been created
|
** This routine only works on a [BLOB handle] which has been created
|
||||||
|
@ -8212,7 +8212,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
|
||||||
** ^The sqlite3_mutex_alloc() routine allocates a new
|
** ^The sqlite3_mutex_alloc() routine allocates a new
|
||||||
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
||||||
** routine returns NULL if it is unable to allocate the requested
|
** routine returns NULL if it is unable to allocate the requested
|
||||||
** mutex. The argument to sqlite3_mutex_alloc() must one of these
|
** mutex. The argument to sqlite3_mutex_alloc() must be one of these
|
||||||
** integer constants:
|
** integer constants:
|
||||||
**
|
**
|
||||||
** <ul>
|
** <ul>
|
||||||
|
@ -8445,7 +8445,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
|
||||||
** CAPI3REF: Retrieve the mutex for a database connection
|
** CAPI3REF: Retrieve the mutex for a database connection
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^This interface returns a pointer the [sqlite3_mutex] object that
|
** ^This interface returns a pointer to the [sqlite3_mutex] object that
|
||||||
** serializes access to the [database connection] given in the argument
|
** serializes access to the [database connection] given in the argument
|
||||||
** when the [threading mode] is Serialized.
|
** when the [threading mode] is Serialized.
|
||||||
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
||||||
|
@ -8568,7 +8568,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
||||||
** CAPI3REF: SQL Keyword Checking
|
** CAPI3REF: SQL Keyword Checking
|
||||||
**
|
**
|
||||||
** These routines provide access to the set of SQL language keywords
|
** These routines provide access to the set of SQL language keywords
|
||||||
** recognized by SQLite. Applications can uses these routines to determine
|
** recognized by SQLite. Applications can use these routines to determine
|
||||||
** whether or not a specific identifier needs to be escaped (for example,
|
** whether or not a specific identifier needs to be escaped (for example,
|
||||||
** by enclosing in double-quotes) so as not to confuse the parser.
|
** by enclosing in double-quotes) so as not to confuse the parser.
|
||||||
**
|
**
|
||||||
|
@ -8736,7 +8736,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*);
|
||||||
** content of the dynamic string under construction in X. The value
|
** content of the dynamic string under construction in X. The value
|
||||||
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
||||||
** and might be freed or altered by any subsequent method on the same
|
** and might be freed or altered by any subsequent method on the same
|
||||||
** [sqlite3_str] object. Applications must not used the pointer returned
|
** [sqlite3_str] object. Applications must not use the pointer returned by
|
||||||
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
||||||
** object. ^Applications may change the content of the string returned
|
** object. ^Applications may change the content of the string returned
|
||||||
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
||||||
|
@ -8822,7 +8822,7 @@ SQLITE_API int sqlite3_status64(
|
||||||
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
||||||
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
||||||
** returned value includes allocations that overflowed because they
|
** returned value includes allocations that overflowed because they
|
||||||
** where too large (they were larger than the "sz" parameter to
|
** were too large (they were larger than the "sz" parameter to
|
||||||
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
||||||
** no space was left in the page cache.</dd>)^
|
** no space was left in the page cache.</dd>)^
|
||||||
**
|
**
|
||||||
|
@ -8906,28 +8906,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
||||||
** <dd>This parameter returns the number of malloc attempts that were
|
** <dd>This parameter returns the number of malloc attempts that were
|
||||||
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
||||||
** <dd>This parameter returns the number malloc attempts that might have
|
** <dd>This parameter returns the number of malloc attempts that might have
|
||||||
** been satisfied using lookaside memory but failed due to the amount of
|
** been satisfied using lookaside memory but failed due to the amount of
|
||||||
** memory requested being larger than the lookaside slot size.
|
** memory requested being larger than the lookaside slot size.
|
||||||
** Only the high-water value is meaningful;
|
** Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
||||||
** <dd>This parameter returns the number malloc attempts that might have
|
** <dd>This parameter returns the number of malloc attempts that might have
|
||||||
** been satisfied using lookaside memory but failed due to all lookaside
|
** been satisfied using lookaside memory but failed due to all lookaside
|
||||||
** memory already being in use.
|
** memory already being in use.
|
||||||
** Only the high-water value is meaningful;
|
** Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
** memory used by all pager caches associated with the database connection.)^
|
** memory used by all pager caches associated with the database connection.)^
|
||||||
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
||||||
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
||||||
|
@ -8936,10 +8937,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** memory used by that pager cache is divided evenly between the attached
|
** memory used by that pager cache is divided evenly between the attached
|
||||||
** connections.)^ In other words, if none of the pager caches associated
|
** connections.)^ In other words, if none of the pager caches associated
|
||||||
** with the database connection are shared, this request returns the same
|
** with the database connection are shared, this request returns the same
|
||||||
** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are
|
** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are
|
||||||
** shared, the value returned by this call will be smaller than that returned
|
** shared, the value returned by this call will be smaller than that returned
|
||||||
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
||||||
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
|
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
|
@ -8949,6 +8950,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** schema memory is shared with other database connections due to
|
** schema memory is shared with other database connections due to
|
||||||
** [shared cache mode] being enabled.
|
** [shared cache mode] being enabled.
|
||||||
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
||||||
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
|
@ -8985,7 +8987,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||||
** been written to disk in the middle of a transaction due to the page
|
** been written to disk in the middle of a transaction due to the page
|
||||||
** cache overflowing. Transactions are more efficient if they are written
|
** cache overflowing. Transactions are more efficient if they are written
|
||||||
** to disk all at once. When pages spill mid-transaction, that introduces
|
** to disk all at once. When pages spill mid-transaction, that introduces
|
||||||
** additional overhead. This parameter can be used help identify
|
** additional overhead. This parameter can be used to help identify
|
||||||
** inefficiencies that can be resolved by increasing the cache size.
|
** inefficiencies that can be resolved by increasing the cache size.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
|
@ -9465,7 +9467,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
||||||
** external process or via a database connection other than the one being
|
** external process or via a database connection other than the one being
|
||||||
** used by the backup operation, then the backup will be automatically
|
** used by the backup operation, then the backup will be automatically
|
||||||
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
||||||
** database is modified by the using the same database connection as is used
|
** database is modified by using the same database connection as is used
|
||||||
** by the backup operation, then the backup database is automatically
|
** by the backup operation, then the backup database is automatically
|
||||||
** updated at the same time.
|
** updated at the same time.
|
||||||
**
|
**
|
||||||
|
@ -9482,7 +9484,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
||||||
** and may not be used following a call to sqlite3_backup_finish().
|
** and may not be used following a call to sqlite3_backup_finish().
|
||||||
**
|
**
|
||||||
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
||||||
** sqlite3_backup_step() errors occurred, regardless or whether or not
|
** sqlite3_backup_step() errors occurred, regardless of whether or not
|
||||||
** sqlite3_backup_step() completed.
|
** sqlite3_backup_step() completed.
|
||||||
** ^If an out-of-memory condition or IO error occurred during any prior
|
** ^If an out-of-memory condition or IO error occurred during any prior
|
||||||
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
||||||
|
@ -10552,7 +10554,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^If a write-transaction is open on [database connection] D when the
|
** ^If a write-transaction is open on [database connection] D when the
|
||||||
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
|
** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty
|
||||||
** pages in the pager-cache that are not currently in use are written out
|
** pages in the pager-cache that are not currently in use are written out
|
||||||
** to disk. A dirty page may be in use if a database cursor created by an
|
** to disk. A dirty page may be in use if a database cursor created by an
|
||||||
** active SQL statement is reading from it, or if it is page 1 of a database
|
** active SQL statement is reading from it, or if it is page 1 of a database
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "api/api_chat_participants.h"
|
#include "api/api_chat_participants.h"
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
|
#include "ayu/utils/ayu_mapper.h"
|
||||||
#include "ayu/ui/message_history/history_inner.h"
|
#include "ayu/ui/message_history/history_inner.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
@ -118,7 +119,10 @@ void GenerateItems(
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto text = QString::fromStdString(message.text);
|
const auto text = QString::fromStdString(message.text);
|
||||||
addSimpleTextMessage(Ui::Text::WithEntities(text));
|
auto textAndEntities = Ui::Text::WithEntities(text);
|
||||||
|
const auto entities = AyuMapper::deserializeTextWithEntities(message.textEntities);
|
||||||
|
textAndEntities.entities = Api::EntitiesFromMTP(&history->session(), entities.v);
|
||||||
|
addSimpleTextMessage(std::move(textAndEntities));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace MessageHistory
|
} // namespace MessageHistory
|
||||||
|
|
|
@ -855,12 +855,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
|
||||||
tr::ayu_HideChannelReactions(),
|
tr::ayu_HideChannelReactions(),
|
||||||
st::settingsButtonNoIcon
|
st::settingsButtonNoIcon
|
||||||
)->toggleOn(
|
)->toggleOn(
|
||||||
rpl::single(!settings->hideChannelReactions)
|
rpl::single(!settings->showChannelReactions)
|
||||||
)->toggledValue(
|
)->toggledValue(
|
||||||
) | rpl::filter(
|
) | rpl::filter(
|
||||||
[=](bool enabled)
|
[=](bool enabled)
|
||||||
{
|
{
|
||||||
return (!enabled != settings->hideChannelReactions);
|
return (!enabled != settings->showChannelReactions);
|
||||||
}) | start_with_next(
|
}) | start_with_next(
|
||||||
[=](bool enabled)
|
[=](bool enabled)
|
||||||
{
|
{
|
||||||
|
@ -874,12 +874,12 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
|
||||||
tr::ayu_HideGroupReactions(),
|
tr::ayu_HideGroupReactions(),
|
||||||
st::settingsButtonNoIcon
|
st::settingsButtonNoIcon
|
||||||
)->toggleOn(
|
)->toggleOn(
|
||||||
rpl::single(!settings->hideGroupReactions)
|
rpl::single(!settings->showGroupReactions)
|
||||||
)->toggledValue(
|
)->toggledValue(
|
||||||
) | rpl::filter(
|
) | rpl::filter(
|
||||||
[=](bool enabled)
|
[=](bool enabled)
|
||||||
{
|
{
|
||||||
return (!enabled != settings->hideGroupReactions);
|
return (!enabled != settings->showGroupReactions);
|
||||||
}) | start_with_next(
|
}) | start_with_next(
|
||||||
[=](bool enabled)
|
[=](bool enabled)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,9 +6,14 @@
|
||||||
// Copyright @Radolyn, 2025
|
// Copyright @Radolyn, 2025
|
||||||
#include "ayu_mapper.h"
|
#include "ayu_mapper.h"
|
||||||
|
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "mtproto/connection_abstract.h"
|
||||||
|
#include "mtproto/details/mtproto_dump_to_text.h"
|
||||||
|
|
||||||
namespace AyuMapper {
|
namespace AyuMapper {
|
||||||
|
|
||||||
|
@ -39,17 +44,56 @@ constexpr auto kMessageFlagHasTTL = 0x02000000;
|
||||||
constexpr auto kMessageFlagInvertMedia = 0x08000000;
|
constexpr auto kMessageFlagInvertMedia = 0x08000000;
|
||||||
constexpr auto kMessageFlagHasSavedPeer = 0x10000000;
|
constexpr auto kMessageFlagHasSavedPeer = 0x10000000;
|
||||||
|
|
||||||
|
template<typename MTPObject>
|
||||||
|
std::vector<char> serializeObject(MTPObject object) {
|
||||||
|
mtpBuffer buffer;
|
||||||
|
object.write(buffer);
|
||||||
|
|
||||||
|
const auto from = reinterpret_cast<char*>(buffer.data());
|
||||||
|
const auto end = from + buffer.size() * sizeof(mtpPrime);
|
||||||
|
|
||||||
|
std::vector<char> entities(from, end);
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename MTPObject>
|
||||||
|
MTPObject deserializeObject(std::vector<char> serialized) {
|
||||||
|
gsl::span<char> span(serialized.data(), serialized.size());
|
||||||
|
|
||||||
|
auto from = reinterpret_cast<const mtpPrime*>(span.data());
|
||||||
|
const auto end = from + span.size() / sizeof(mtpPrime);
|
||||||
|
|
||||||
|
MTPObject data;
|
||||||
|
if (!data.read(from, end)) {
|
||||||
|
LOG(("AyuMapper: Failed to deserialize object"));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<std::string, std::vector<char>> serializeTextWithEntities(not_null<HistoryItem*> item) {
|
std::pair<std::string, std::vector<char>> serializeTextWithEntities(not_null<HistoryItem*> item) {
|
||||||
if (item->emptyText()) {
|
if (item->emptyText()) {
|
||||||
return std::make_pair("", std::vector<char>());
|
return std::make_pair("", std::vector<char>());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto textWithEntities = item->originalText();
|
auto textWithEntities = item->originalText();
|
||||||
std::vector<char> entities; // todo: implement writing to buffer
|
|
||||||
|
|
||||||
|
std::vector<char> entities;
|
||||||
|
if (!textWithEntities.entities.empty()) {
|
||||||
|
const auto mtpEntities = Api::EntitiesToMTP(
|
||||||
|
&item->history()->session(),
|
||||||
|
textWithEntities.entities,
|
||||||
|
Api::ConvertOption::WithLocal);
|
||||||
|
|
||||||
|
entities = serializeObject(mtpEntities);
|
||||||
|
}
|
||||||
|
|
||||||
return std::make_pair(textWithEntities.text.toStdString(), entities);
|
return std::make_pair(textWithEntities.text.toStdString(), entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MTPVector<MTPMessageEntity> deserializeTextWithEntities(std::vector<char> serialized) {
|
||||||
|
return deserializeObject<MTPVector<MTPMessageEntity>>(serialized);
|
||||||
|
}
|
||||||
|
|
||||||
int mapItemFlagsToMTPFlags(not_null<HistoryItem*> item) {
|
int mapItemFlagsToMTPFlags(not_null<HistoryItem*> item) {
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,14 @@
|
||||||
|
|
||||||
namespace AyuMapper {
|
namespace AyuMapper {
|
||||||
|
|
||||||
|
template<typename MTPObject>
|
||||||
|
[[nodiscard]] MTPObject deserializeObject(std::vector<char> serialized);
|
||||||
|
|
||||||
|
template<typename MTPObject>
|
||||||
|
[[nodiscard]] std::vector<char> serializeObject(MTPObject object);
|
||||||
|
|
||||||
std::pair<std::string, std::vector<char>> serializeTextWithEntities(not_null<HistoryItem*> item);
|
std::pair<std::string, std::vector<char>> serializeTextWithEntities(not_null<HistoryItem*> item);
|
||||||
|
[[nodiscard]] MTPVector<MTPMessageEntity> deserializeTextWithEntities(std::vector<char> serialized);
|
||||||
int mapItemFlagsToMTPFlags(not_null<HistoryItem*> item);
|
int mapItemFlagsToMTPFlags(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
} // namespace AyuMapper
|
} // namespace AyuMapper
|
||||||
|
|
|
@ -14,19 +14,21 @@
|
||||||
|
|
||||||
std::unordered_set<ID> default_developers = {
|
std::unordered_set<ID> default_developers = {
|
||||||
963080346, 1282540315, 1374434073, 168769611,
|
963080346, 1282540315, 1374434073, 168769611,
|
||||||
1773117711, 5330087923, 666154369, 139303278,
|
1773117711, 5330087923, 139303278, 1752394339,
|
||||||
668557709, 1348136086, 6288255532, 7453676178,
|
668557709, 1348136086, 6288255532, 7453676178,
|
||||||
|
880708503, 2135966128, 7818249287,
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
778327202, 238292700, 1795176335, 6247153446,
|
778327202, 238292700, 1795176335, 6247153446,
|
||||||
1752394339, 7745305003, 1183312839, 497855299,
|
1183312839, 497855299
|
||||||
623054735
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_set<ID> default_channels = {
|
std::unordered_set<ID> default_channels = {
|
||||||
1233768168, 1524581881, 1571726392, 1632728092,
|
1233768168, 1524581881, 1571726392, 1632728092,
|
||||||
1172503281, 1877362358, 1905581924, 1794457129,
|
1172503281, 1877362358, 1905581924, 1794457129,
|
||||||
1434550607, 1947958814, 1815864846, 2130395384,
|
1434550607, 1947958814, 1815864846, 2130395384,
|
||||||
1976430343, 1754537498, 1725670701,
|
1976430343, 1754537498, 1725670701, 2401498637,
|
||||||
|
2685666919, 2562664432, 2564770112, 2331068091,
|
||||||
|
1559501352, 2641258043
|
||||||
};
|
};
|
||||||
|
|
||||||
void RCManager::start() {
|
void RCManager::start() {
|
||||||
|
|
|
@ -320,6 +320,22 @@ QString formatDateTime(const QDateTime &date) {
|
||||||
return datePart + getLocalizedAt() + timePart;
|
return datePart + getLocalizedAt() + timePart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString formatMessageTime(const QTime &time) {
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
|
||||||
|
const auto format =
|
||||||
|
settings.showMessageSeconds
|
||||||
|
? (QLocale().timeFormat(QLocale::ShortFormat).contains("AP")
|
||||||
|
? "h:mm:ss AP"
|
||||||
|
: "HH:mm:ss")
|
||||||
|
: QLocale().timeFormat(QLocale::ShortFormat);
|
||||||
|
|
||||||
|
return QLocale().toString(
|
||||||
|
time,
|
||||||
|
format
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int getMediaSizeBytes(not_null<HistoryItem*> message) {
|
int getMediaSizeBytes(not_null<HistoryItem*> message) {
|
||||||
if (!message->media()) {
|
if (!message->media()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -37,6 +37,7 @@ void readHistory(not_null<HistoryItem*> message);
|
||||||
|
|
||||||
QString formatTTL(int time);
|
QString formatTTL(int time);
|
||||||
QString formatDateTime(const QDateTime &date);
|
QString formatDateTime(const QDateTime &date);
|
||||||
|
QString formatMessageTime(const QTime &time);
|
||||||
|
|
||||||
QString getDCName(int dc);
|
QString getDCName(int dc);
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,13 @@ void AddProxyFromClipboard(
|
||||||
QGuiApplication::clipboard()->text());
|
QGuiApplication::clipboard()->text());
|
||||||
const auto isSingle = maybeUrls.size() == 1;
|
const auto isSingle = maybeUrls.size() == 1;
|
||||||
|
|
||||||
|
enum class Result {
|
||||||
|
Success,
|
||||||
|
Failed,
|
||||||
|
Unsupported,
|
||||||
|
Invalid,
|
||||||
|
};
|
||||||
|
|
||||||
const auto proceedUrl = [=](const auto &local) {
|
const auto proceedUrl = [=](const auto &local) {
|
||||||
const auto command = base::StringViewMid(
|
const auto command = base::StringViewMid(
|
||||||
local,
|
local,
|
||||||
|
@ -146,6 +153,11 @@ void AddProxyFromClipboard(
|
||||||
match->captured(1),
|
match->captured(1),
|
||||||
qthelp::UrlParamNameTransform::ToLower);
|
qthelp::UrlParamNameTransform::ToLower);
|
||||||
const auto proxy = ProxyDataFromFields(type, fields);
|
const auto proxy = ProxyDataFromFields(type, fields);
|
||||||
|
if (!proxy) {
|
||||||
|
return (proxy.status() == ProxyData::Status::Unsupported)
|
||||||
|
? Result::Unsupported
|
||||||
|
: Result::Invalid;
|
||||||
|
}
|
||||||
const auto contains = controller->contains(proxy);
|
const auto contains = controller->contains(proxy);
|
||||||
const auto toast = (contains
|
const auto toast = (contains
|
||||||
? tr::lng_proxy_add_from_clipboard_existing_toast
|
? tr::lng_proxy_add_from_clipboard_existing_toast
|
||||||
|
@ -158,19 +170,29 @@ void AddProxyFromClipboard(
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return Result::Success;
|
||||||
}
|
}
|
||||||
return false;
|
return Result::Failed;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto success = false;
|
auto success = Result::Failed;
|
||||||
for (const auto &maybeUrl : maybeUrls) {
|
for (const auto &maybeUrl : maybeUrls) {
|
||||||
success |= proceedUrl(Core::TryConvertUrlToLocal(maybeUrl));
|
const auto result = proceedUrl(Core::TryConvertUrlToLocal(maybeUrl));
|
||||||
|
if (success != Result::Success) {
|
||||||
|
success = result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (success != Result::Success) {
|
||||||
show->showToast(
|
if (success == Result::Failed) {
|
||||||
tr::lng_proxy_add_from_clipboard_failed_toast(tr::now));
|
show->showToast(
|
||||||
|
tr::lng_proxy_add_from_clipboard_failed_toast(tr::now));
|
||||||
|
} else {
|
||||||
|
show->showBox(Ui::MakeInformBox(
|
||||||
|
(success == Result::Unsupported
|
||||||
|
? tr::lng_proxy_unsupported(tr::now)
|
||||||
|
: tr::lng_proxy_invalid(tr::now))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -361,9 +361,14 @@ void CreateModerateMessagesBox(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (allCanBan) {
|
if (allCanBan) {
|
||||||
auto ownedWrap = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
const auto peer = items.front()->history()->peer;
|
||||||
inner,
|
auto ownedWrap = peer->isMonoforum()
|
||||||
object_ptr<Ui::VerticalLayout>(inner));
|
? nullptr
|
||||||
|
: object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
inner,
|
||||||
|
object_ptr<Ui::VerticalLayout>(inner));
|
||||||
|
auto computeRestrictions = Fn<ChatRestrictions()>();
|
||||||
|
const auto wrap = ownedWrap.data();
|
||||||
|
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
|
@ -371,7 +376,9 @@ void CreateModerateMessagesBox(
|
||||||
object_ptr<Ui::Checkbox>(
|
object_ptr<Ui::Checkbox>(
|
||||||
box,
|
box,
|
||||||
rpl::conditional(
|
rpl::conditional(
|
||||||
ownedWrap->toggledValue(),
|
(ownedWrap
|
||||||
|
? ownedWrap->toggledValue()
|
||||||
|
: rpl::single(false) | rpl::type_erased()),
|
||||||
tr::lng_restrict_user(
|
tr::lng_restrict_user(
|
||||||
lt_count,
|
lt_count,
|
||||||
rpl::single(participants.size()) | tr::to_count()),
|
rpl::single(participants.size()) | tr::to_count()),
|
||||||
|
@ -390,136 +397,141 @@ void CreateModerateMessagesBox(
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
|
|
||||||
const auto wrap = inner->add(std::move(ownedWrap));
|
if (ownedWrap) {
|
||||||
const auto container = wrap->entity();
|
inner->add(std::move(ownedWrap));
|
||||||
wrap->toggle(false, anim::type::instant);
|
|
||||||
|
|
||||||
const auto session = &participants.front()->session();
|
const auto container = wrap->entity();
|
||||||
const auto emojiMargin = QMargins(
|
wrap->toggle(false, anim::type::instant);
|
||||||
-st::moderateBoxExpandInnerSkip,
|
|
||||||
-st::moderateBoxExpandInnerSkip / 2,
|
|
||||||
0,
|
|
||||||
0);
|
|
||||||
const auto emojiUp = Ui::Text::SingleCustomEmoji(
|
|
||||||
session->data().customEmojiManager().registerInternalEmoji(
|
|
||||||
st::moderateBoxExpandIcon,
|
|
||||||
emojiMargin,
|
|
||||||
false));
|
|
||||||
const auto emojiDown = Ui::Text::SingleCustomEmoji(
|
|
||||||
session->data().customEmojiManager().registerInternalEmoji(
|
|
||||||
st::moderateBoxExpandIconDown,
|
|
||||||
emojiMargin,
|
|
||||||
false));
|
|
||||||
|
|
||||||
auto label = object_ptr<Ui::FlatLabel>(
|
const auto session = &participants.front()->session();
|
||||||
inner,
|
const auto emojiMargin = QMargins(
|
||||||
QString(),
|
-st::moderateBoxExpandInnerSkip,
|
||||||
st::moderateBoxDividerLabel);
|
-st::moderateBoxExpandInnerSkip / 2,
|
||||||
const auto raw = label.data();
|
0,
|
||||||
|
0);
|
||||||
|
const auto emojiUp = Ui::Text::SingleCustomEmoji(
|
||||||
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
|
st::moderateBoxExpandIcon,
|
||||||
|
emojiMargin,
|
||||||
|
false));
|
||||||
|
const auto emojiDown = Ui::Text::SingleCustomEmoji(
|
||||||
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
|
st::moderateBoxExpandIconDown,
|
||||||
|
emojiMargin,
|
||||||
|
false));
|
||||||
|
|
||||||
auto &lifetime = wrap->lifetime();
|
auto label = object_ptr<Ui::FlatLabel>(
|
||||||
const auto scrollLifetime = lifetime.make_state<rpl::lifetime>();
|
inner,
|
||||||
label->setClickHandlerFilter([=](
|
QString(),
|
||||||
const ClickHandlerPtr &handler,
|
st::moderateBoxDividerLabel);
|
||||||
Qt::MouseButton button) {
|
const auto raw = label.data();
|
||||||
if (button != Qt::LeftButton) {
|
|
||||||
return false;
|
auto &lifetime = wrap->lifetime();
|
||||||
}
|
const auto scrollLifetime = lifetime.make_state<rpl::lifetime>();
|
||||||
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
label->setClickHandlerFilter([=](
|
||||||
{
|
const ClickHandlerPtr &handler,
|
||||||
inner->heightValue() | rpl::start_with_next([=] {
|
Qt::MouseButton button) {
|
||||||
if (!wrap->animating()) {
|
if (button != Qt::LeftButton) {
|
||||||
scrollLifetime->destroy();
|
return false;
|
||||||
Ui::PostponeCall(crl::guard(box, [=] {
|
}
|
||||||
|
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
||||||
|
{
|
||||||
|
inner->heightValue() | rpl::start_with_next([=] {
|
||||||
|
if (!wrap->animating()) {
|
||||||
|
scrollLifetime->destroy();
|
||||||
|
Ui::PostponeCall(crl::guard(box, [=] {
|
||||||
|
box->scrollToY(std::numeric_limits<int>::max());
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
box->scrollToY(std::numeric_limits<int>::max());
|
box->scrollToY(std::numeric_limits<int>::max());
|
||||||
}));
|
}
|
||||||
} else {
|
}, *scrollLifetime);
|
||||||
box->scrollToY(std::numeric_limits<int>::max());
|
}
|
||||||
}
|
return true;
|
||||||
}, *scrollLifetime);
|
});
|
||||||
}
|
wrap->toggledValue(
|
||||||
return true;
|
) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) {
|
||||||
});
|
return ((toggled && isSingle)
|
||||||
wrap->toggledValue(
|
? tr::lng_restrict_user_part
|
||||||
) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) {
|
: (toggled && !isSingle)
|
||||||
return ((toggled && isSingle)
|
? tr::lng_restrict_users_part
|
||||||
? tr::lng_restrict_user_part
|
: isSingle
|
||||||
: (toggled && !isSingle)
|
? tr::lng_restrict_user_full
|
||||||
? tr::lng_restrict_users_part
|
: tr::lng_restrict_users_full)(
|
||||||
: isSingle
|
lt_emoji,
|
||||||
? tr::lng_restrict_user_full
|
rpl::single(toggled ? emojiUp : emojiDown),
|
||||||
: tr::lng_restrict_users_full)(
|
Ui::Text::WithEntities);
|
||||||
lt_emoji,
|
}) | rpl::flatten_latest(
|
||||||
rpl::single(toggled ? emojiUp : emojiDown),
|
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||||
Ui::Text::WithEntities);
|
raw->setMarkedText(
|
||||||
}) | rpl::flatten_latest(
|
Ui::Text::Link(text, u"internal:"_q),
|
||||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
Core::TextContext({ .session = session }));
|
||||||
raw->setMarkedText(
|
}, label->lifetime());
|
||||||
Ui::Text::Link(text, u"internal:"_q),
|
|
||||||
Core::TextContext({ .session = session }));
|
|
||||||
}, label->lifetime());
|
|
||||||
|
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
inner->add(object_ptr<Ui::DividerLabel>(
|
inner->add(object_ptr<Ui::DividerLabel>(
|
||||||
inner,
|
inner,
|
||||||
std::move(label),
|
std::move(label),
|
||||||
st::defaultBoxDividerLabelPadding,
|
st::defaultBoxDividerLabelPadding,
|
||||||
RectPart::Top | RectPart::Bottom));
|
RectPart::Top | RectPart::Bottom));
|
||||||
|
|
||||||
using Flag = ChatRestriction;
|
using Flag = ChatRestriction;
|
||||||
using Flags = ChatRestrictions;
|
using Flags = ChatRestrictions;
|
||||||
const auto peer = items.front()->history()->peer;
|
const auto chat = peer->asChat();
|
||||||
const auto chat = peer->asChat();
|
const auto channel = peer->asChannel();
|
||||||
const auto channel = peer->asChannel();
|
const auto defaultRestrictions = chat
|
||||||
const auto defaultRestrictions = chat
|
? chat->defaultRestrictions()
|
||||||
? chat->defaultRestrictions()
|
: channel->defaultRestrictions();
|
||||||
: channel->defaultRestrictions();
|
const auto prepareFlags = FixDependentRestrictions(
|
||||||
const auto prepareFlags = FixDependentRestrictions(
|
defaultRestrictions
|
||||||
defaultRestrictions
|
| ((channel && channel->isPublic())
|
||||||
| ((channel && channel->isPublic())
|
? (Flag::ChangeInfo | Flag::PinMessages)
|
||||||
? (Flag::ChangeInfo | Flag::PinMessages)
|
: Flags(0)));
|
||||||
: Flags(0)));
|
const auto disabledMessages = [&] {
|
||||||
const auto disabledMessages = [&] {
|
auto result = base::flat_map<Flags, QString>();
|
||||||
auto result = base::flat_map<Flags, QString>();
|
{
|
||||||
{
|
const auto disabled = FixDependentRestrictions(
|
||||||
const auto disabled = FixDependentRestrictions(
|
defaultRestrictions
|
||||||
defaultRestrictions
|
| ((channel && channel->isPublic())
|
||||||
| ((channel && channel->isPublic())
|
? (Flag::ChangeInfo | Flag::PinMessages)
|
||||||
? (Flag::ChangeInfo | Flag::PinMessages)
|
: Flags(0)));
|
||||||
: Flags(0)));
|
result.emplace(
|
||||||
result.emplace(
|
disabled,
|
||||||
disabled,
|
tr::lng_rights_restriction_for_all(tr::now));
|
||||||
tr::lng_rights_restriction_for_all(tr::now));
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
}();
|
||||||
}();
|
|
||||||
|
|
||||||
Ui::AddSubsectionTitle(
|
Ui::AddSubsectionTitle(
|
||||||
inner,
|
inner,
|
||||||
rpl::conditional(
|
rpl::conditional(
|
||||||
rpl::single(isSingle),
|
rpl::single(isSingle),
|
||||||
tr::lng_restrict_users_part_single_header(),
|
tr::lng_restrict_users_part_single_header(),
|
||||||
tr::lng_restrict_users_part_header(
|
tr::lng_restrict_users_part_header(
|
||||||
lt_count,
|
lt_count,
|
||||||
rpl::single(participants.size()) | tr::to_count())));
|
rpl::single(participants.size()) | tr::to_count())));
|
||||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||||
box,
|
box,
|
||||||
prepareFlags,
|
prepareFlags,
|
||||||
disabledMessages,
|
disabledMessages,
|
||||||
{ .isForum = peer->isForum() });
|
{ .isForum = peer->isForum() });
|
||||||
std::move(changes) | rpl::start_with_next([=] {
|
computeRestrictions = getRestrictions;
|
||||||
ban->setChecked(true);
|
std::move(changes) | rpl::start_with_next([=] {
|
||||||
}, ban->lifetime());
|
ban->setChecked(true);
|
||||||
Ui::AddSkip(container);
|
}, ban->lifetime());
|
||||||
Ui::AddDivider(container);
|
Ui::AddSkip(container);
|
||||||
Ui::AddSkip(container);
|
Ui::AddDivider(container);
|
||||||
container->add(std::move(checkboxes));
|
Ui::AddSkip(container);
|
||||||
|
container->add(std::move(checkboxes));
|
||||||
|
}
|
||||||
|
|
||||||
// Handle confirmation manually.
|
// Handle confirmation manually.
|
||||||
confirms->events() | rpl::start_with_next([=] {
|
confirms->events() | rpl::start_with_next([=] {
|
||||||
if (ban->checked() && controller->collectRequests) {
|
if (ban->checked() && controller->collectRequests) {
|
||||||
const auto kick = !wrap->toggled();
|
const auto kick = !wrap || !wrap->toggled();
|
||||||
const auto restrictions = getRestrictions();
|
const auto restrictions = computeRestrictions
|
||||||
|
? computeRestrictions()
|
||||||
|
: ChatRestrictions();
|
||||||
const auto request = [=](
|
const auto request = [=](
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<ChannelData*> channel) {
|
not_null<ChannelData*> channel) {
|
||||||
|
@ -532,10 +544,15 @@ void CreateModerateMessagesBox(
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr);
|
nullptr);
|
||||||
} else {
|
} else {
|
||||||
channel->session().api().chatParticipants().kick(
|
const auto block = channel->isMonoforum()
|
||||||
channel,
|
? channel->monoforumBroadcast()
|
||||||
peer,
|
: channel.get();
|
||||||
{ channel->restrictions(), 0 });
|
if (block) {
|
||||||
|
block->session().api().chatParticipants().kick(
|
||||||
|
block,
|
||||||
|
peer,
|
||||||
|
{ block->restrictions(), 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sequentiallyRequest(request, controller->collectRequests());
|
sequentiallyRequest(request, controller->collectRequests());
|
||||||
|
|
|
@ -127,6 +127,7 @@ constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
|
||||||
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
|
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
|
||||||
constexpr auto kResaleGiftsPerPage = 50;
|
constexpr auto kResaleGiftsPerPage = 50;
|
||||||
constexpr auto kFiltersCount = 4;
|
constexpr auto kFiltersCount = 4;
|
||||||
|
constexpr auto kResellPriceCacheLifetime = 60 * crl::time(1000);
|
||||||
|
|
||||||
using namespace HistoryView;
|
using namespace HistoryView;
|
||||||
using namespace Info::PeerGifts;
|
using namespace Info::PeerGifts;
|
||||||
|
@ -220,6 +221,33 @@ struct GiftDetails {
|
||||||
bool byStars = false;
|
bool byStars = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SessionResalePrices {
|
||||||
|
explicit SessionResalePrices(not_null<Main::Session*> session)
|
||||||
|
: api(std::make_unique<Api::PremiumGiftCodeOptions>(session->user())) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Api::PremiumGiftCodeOptions> api;
|
||||||
|
base::flat_map<QString, int> prices;
|
||||||
|
std::vector<Fn<void()>> waiting;
|
||||||
|
rpl::lifetime requestLifetime;
|
||||||
|
crl::time lastReceived = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<SessionResalePrices*> ResalePrices(
|
||||||
|
not_null<Main::Session*> session) {
|
||||||
|
static auto result = base::flat_map<
|
||||||
|
not_null<Main::Session*>,
|
||||||
|
std::unique_ptr<SessionResalePrices>>();
|
||||||
|
if (const auto i = result.find(session); i != end(result)) {
|
||||||
|
return i->second.get();
|
||||||
|
}
|
||||||
|
const auto i = result.emplace(
|
||||||
|
session,
|
||||||
|
std::make_unique<SessionResalePrices>(session)).first;
|
||||||
|
session->lifetime().add([session] { result.remove(session); });
|
||||||
|
return i->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
class PeerRow final : public PeerListRow {
|
class PeerRow final : public PeerListRow {
|
||||||
public:
|
public:
|
||||||
using PeerListRow::PeerListRow;
|
using PeerListRow::PeerListRow;
|
||||||
|
@ -4381,6 +4409,55 @@ void ShowUniqueGiftWearBox(
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PreloadUniqueGiftResellPrices(not_null<Main::Session*> session) {
|
||||||
|
const auto entry = ResalePrices(session);
|
||||||
|
const auto now = crl::now();
|
||||||
|
const auto makeRequest = entry->prices.empty()
|
||||||
|
|| (now - entry->lastReceived >= kResellPriceCacheLifetime);
|
||||||
|
if (!makeRequest || entry->requestLifetime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto finish = [=] {
|
||||||
|
entry->requestLifetime.destroy();
|
||||||
|
entry->lastReceived = crl::now();
|
||||||
|
for (const auto &callback : base::take(entry->waiting)) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
entry->requestLifetime = entry->api->requestStarGifts(
|
||||||
|
) | rpl::start_with_error_done(finish, [=] {
|
||||||
|
const auto &gifts = entry->api->starGifts();
|
||||||
|
entry->prices.reserve(gifts.size());
|
||||||
|
for (auto &gift : gifts) {
|
||||||
|
if (!gift.resellTitle.isEmpty() && gift.starsResellMin > 0) {
|
||||||
|
entry->prices[gift.resellTitle] = gift.starsResellMin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvokeWithUniqueGiftResellPrice(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const QString &title,
|
||||||
|
Fn<void(int)> callback) {
|
||||||
|
PreloadUniqueGiftResellPrices(session);
|
||||||
|
|
||||||
|
const auto finish = [=] {
|
||||||
|
const auto entry = ResalePrices(session);
|
||||||
|
Assert(entry->lastReceived != 0);
|
||||||
|
|
||||||
|
const auto i = entry->prices.find(title);
|
||||||
|
callback((i != end(entry->prices)) ? i->second : 0);
|
||||||
|
};
|
||||||
|
const auto entry = ResalePrices(session);
|
||||||
|
if (entry->lastReceived) {
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
entry->waiting.push_back(finish);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateGiftSellPrice(
|
void UpdateGiftSellPrice(
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
std::shared_ptr<Data::UniqueGift> unique,
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
@ -4422,6 +4499,132 @@ void UpdateGiftSellPrice(
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UniqueGiftSellBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
Data::SavedStarGiftId savedId,
|
||||||
|
int price,
|
||||||
|
Settings::GiftWearBoxStyleOverride st) {
|
||||||
|
box->setTitle(tr::lng_gift_sell_title());
|
||||||
|
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
|
||||||
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
|
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
|
||||||
|
box->closeBox();
|
||||||
|
});
|
||||||
|
const auto priceNow = unique->starsForResale;
|
||||||
|
const auto name = Data::UniqueGiftName(*unique);
|
||||||
|
const auto slug = unique->slug;
|
||||||
|
|
||||||
|
const auto session = &show->session();
|
||||||
|
AddSubsectionTitle(
|
||||||
|
box->verticalLayout(),
|
||||||
|
tr::lng_gift_sell_placeholder(),
|
||||||
|
(st::boxRowPadding - QMargins(
|
||||||
|
st::defaultSubsectionTitlePadding.left(),
|
||||||
|
0,
|
||||||
|
st::defaultSubsectionTitlePadding.right(),
|
||||||
|
st::defaultSubsectionTitlePadding.bottom())));
|
||||||
|
const auto &appConfig = session->appConfig();
|
||||||
|
const auto limit = appConfig.giftResalePriceMax();
|
||||||
|
const auto minimal = appConfig.giftResalePriceMin();
|
||||||
|
const auto thousandths = appConfig.giftResaleReceiveThousandths();
|
||||||
|
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
box,
|
||||||
|
st::editTagField.heightMin));
|
||||||
|
auto owned = object_ptr<Ui::NumberInput>(
|
||||||
|
wrap,
|
||||||
|
st::editTagField,
|
||||||
|
rpl::single(QString()),
|
||||||
|
QString::number(priceNow ? priceNow : price ? price : minimal),
|
||||||
|
limit);
|
||||||
|
const auto field = owned.data();
|
||||||
|
wrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
|
field->move(0, 0);
|
||||||
|
field->resize(width, field->height());
|
||||||
|
wrap->resize(width, field->height());
|
||||||
|
}, wrap->lifetime());
|
||||||
|
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||||
|
auto p = QPainter(field);
|
||||||
|
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
||||||
|
}, field->lifetime());
|
||||||
|
field->selectAll();
|
||||||
|
box->setFocusCallback([=] {
|
||||||
|
field->setFocusFast();
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto errors = box->lifetime().make_state<
|
||||||
|
rpl::event_stream<>
|
||||||
|
>();
|
||||||
|
auto goods = rpl::merge(
|
||||||
|
rpl::single(rpl::empty) | rpl::map_to(true),
|
||||||
|
base::qt_signal_producer(
|
||||||
|
field,
|
||||||
|
&Ui::NumberInput::changed
|
||||||
|
) | rpl::map_to(true),
|
||||||
|
errors->events() | rpl::map_to(false)
|
||||||
|
) | rpl::start_spawning(box->lifetime());
|
||||||
|
auto text = rpl::duplicate(goods) | rpl::map([=](bool good) {
|
||||||
|
const auto value = field->getLastText().toInt();
|
||||||
|
const auto receive = (int64(value) * thousandths) / 1000;
|
||||||
|
return !good
|
||||||
|
? tr::lng_gift_sell_min_price(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
minimal,
|
||||||
|
Ui::Text::RichLangValue)
|
||||||
|
: (value >= minimal)
|
||||||
|
? tr::lng_gift_sell_amount(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
receive,
|
||||||
|
Ui::Text::RichLangValue)
|
||||||
|
: tr::lng_gift_sell_about(
|
||||||
|
tr::now,
|
||||||
|
lt_percent,
|
||||||
|
TextWithEntities{ u"%1%"_q.arg(thousandths / 10.) },
|
||||||
|
Ui::Text::RichLangValue);
|
||||||
|
});
|
||||||
|
const auto details = box->addRow(object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
std::move(text) | rpl::after_next([=] {
|
||||||
|
box->verticalLayout()->resizeToWidth(box->width());
|
||||||
|
}),
|
||||||
|
st::boxLabel));
|
||||||
|
Ui::AddSkip(box->verticalLayout());
|
||||||
|
|
||||||
|
rpl::duplicate(goods) | rpl::start_with_next([=](bool good) {
|
||||||
|
details->setTextColorOverride(
|
||||||
|
good ? st::windowSubTextFg->c : st::boxTextFgError->c);
|
||||||
|
}, details->lifetime());
|
||||||
|
|
||||||
|
QObject::connect(field, &NumberInput::submitted, [=] {
|
||||||
|
const auto count = field->getLastText().toInt();
|
||||||
|
if (count < minimal) {
|
||||||
|
field->showError();
|
||||||
|
errors->fire({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
box->closeBox();
|
||||||
|
UpdateGiftSellPrice(show, unique, savedId, count);
|
||||||
|
});
|
||||||
|
const auto button = box->addButton(priceNow
|
||||||
|
? tr::lng_gift_sell_update()
|
||||||
|
: tr::lng_gift_sell_put(), [=] { field->submitted({}); });
|
||||||
|
rpl::combine(
|
||||||
|
box->widthValue(),
|
||||||
|
button->widthValue()
|
||||||
|
) | rpl::start_with_next([=](int outer, int inner) {
|
||||||
|
const auto padding = st::giftBox.buttonPadding;
|
||||||
|
const auto wanted = outer - padding.left() - padding.right();
|
||||||
|
if (inner != wanted) {
|
||||||
|
button->resizeToWidth(wanted);
|
||||||
|
button->moveToLeft(padding.left(), padding.top());
|
||||||
|
}
|
||||||
|
}, box->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
void ShowUniqueGiftSellBox(
|
void ShowUniqueGiftSellBox(
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
std::shared_ptr<Data::UniqueGift> unique,
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
@ -4430,125 +4633,11 @@ void ShowUniqueGiftSellBox(
|
||||||
if (ShowResaleGiftLater(show, unique)) {
|
if (ShowResaleGiftLater(show, unique)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
const auto session = &show->session();
|
||||||
box->setTitle(tr::lng_gift_sell_title());
|
const auto &title = unique->title;
|
||||||
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
|
InvokeWithUniqueGiftResellPrice(session, title, [=](int price) {
|
||||||
box->setWidth(st::boxWideWidth);
|
show->show(Box(UniqueGiftSellBox, show, unique, savedId, price, st));
|
||||||
|
});
|
||||||
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
|
|
||||||
box->closeBox();
|
|
||||||
});
|
|
||||||
const auto priceNow = unique->starsForResale;
|
|
||||||
const auto name = Data::UniqueGiftName(*unique);
|
|
||||||
const auto slug = unique->slug;
|
|
||||||
|
|
||||||
const auto session = &show->session();
|
|
||||||
AddSubsectionTitle(
|
|
||||||
box->verticalLayout(),
|
|
||||||
tr::lng_gift_sell_placeholder(),
|
|
||||||
(st::boxRowPadding - QMargins(
|
|
||||||
st::defaultSubsectionTitlePadding.left(),
|
|
||||||
0,
|
|
||||||
st::defaultSubsectionTitlePadding.right(),
|
|
||||||
st::defaultSubsectionTitlePadding.bottom())));
|
|
||||||
const auto &appConfig = session->appConfig();
|
|
||||||
const auto limit = appConfig.giftResalePriceMax();
|
|
||||||
const auto minimal = appConfig.giftResalePriceMin();
|
|
||||||
const auto thousandths = appConfig.giftResaleReceiveThousandths();
|
|
||||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
|
||||||
box,
|
|
||||||
st::editTagField.heightMin));
|
|
||||||
auto owned = object_ptr<Ui::NumberInput>(
|
|
||||||
wrap,
|
|
||||||
st::editTagField,
|
|
||||||
rpl::single(QString()),
|
|
||||||
QString::number(priceNow ? priceNow : minimal),
|
|
||||||
limit);
|
|
||||||
const auto field = owned.data();
|
|
||||||
wrap->widthValue() | rpl::start_with_next([=](int width) {
|
|
||||||
field->move(0, 0);
|
|
||||||
field->resize(width, field->height());
|
|
||||||
wrap->resize(width, field->height());
|
|
||||||
}, wrap->lifetime());
|
|
||||||
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
|
||||||
auto p = QPainter(field);
|
|
||||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
|
||||||
}, field->lifetime());
|
|
||||||
field->selectAll();
|
|
||||||
box->setFocusCallback([=] {
|
|
||||||
field->setFocusFast();
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto errors = box->lifetime().make_state<
|
|
||||||
rpl::event_stream<>
|
|
||||||
>();
|
|
||||||
auto goods = rpl::merge(
|
|
||||||
rpl::single(rpl::empty) | rpl::map_to(true),
|
|
||||||
base::qt_signal_producer(
|
|
||||||
field,
|
|
||||||
&Ui::NumberInput::changed
|
|
||||||
) | rpl::map_to(true),
|
|
||||||
errors->events() | rpl::map_to(false)
|
|
||||||
) | rpl::start_spawning(box->lifetime());
|
|
||||||
auto text = rpl::duplicate(goods) | rpl::map([=](bool good) {
|
|
||||||
const auto value = field->getLastText().toInt();
|
|
||||||
const auto receive = (int64(value) * thousandths) / 1000;
|
|
||||||
return !good
|
|
||||||
? tr::lng_gift_sell_min_price(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
minimal,
|
|
||||||
Ui::Text::RichLangValue)
|
|
||||||
: (value >= minimal)
|
|
||||||
? tr::lng_gift_sell_amount(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
receive,
|
|
||||||
Ui::Text::RichLangValue)
|
|
||||||
: tr::lng_gift_sell_about(
|
|
||||||
tr::now,
|
|
||||||
lt_percent,
|
|
||||||
TextWithEntities{ u"%1%"_q.arg(thousandths / 10.) },
|
|
||||||
Ui::Text::RichLangValue);
|
|
||||||
});
|
|
||||||
const auto details = box->addRow(object_ptr<Ui::FlatLabel>(
|
|
||||||
box,
|
|
||||||
std::move(text) | rpl::after_next([=] {
|
|
||||||
box->verticalLayout()->resizeToWidth(box->width());
|
|
||||||
}),
|
|
||||||
st::boxLabel));
|
|
||||||
Ui::AddSkip(box->verticalLayout());
|
|
||||||
|
|
||||||
rpl::duplicate(goods) | rpl::start_with_next([=](bool good) {
|
|
||||||
details->setTextColorOverride(
|
|
||||||
good ? st::windowSubTextFg->c : st::boxTextFgError->c);
|
|
||||||
}, details->lifetime());
|
|
||||||
|
|
||||||
QObject::connect(field, &NumberInput::submitted, [=] {
|
|
||||||
const auto count = field->getLastText().toInt();
|
|
||||||
if (count < minimal) {
|
|
||||||
field->showError();
|
|
||||||
errors->fire({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
box->closeBox();
|
|
||||||
UpdateGiftSellPrice(show, unique, savedId, count);
|
|
||||||
});
|
|
||||||
const auto button = box->addButton(priceNow
|
|
||||||
? tr::lng_gift_sell_update()
|
|
||||||
: tr::lng_gift_sell_put(), [=] { field->submitted({}); });
|
|
||||||
rpl::combine(
|
|
||||||
box->widthValue(),
|
|
||||||
button->widthValue()
|
|
||||||
) | rpl::start_with_next([=](int outer, int inner) {
|
|
||||||
const auto padding = st::giftBox.buttonPadding;
|
|
||||||
const auto wanted = outer - padding.left() - padding.right();
|
|
||||||
if (inner != wanted) {
|
|
||||||
button->resizeToWidth(wanted);
|
|
||||||
button->moveToLeft(padding.left(), padding.top());
|
|
||||||
}
|
|
||||||
}, box->lifetime());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiftReleasedByHandler(not_null<PeerData*> peer) {
|
void GiftReleasedByHandler(not_null<PeerData*> peer) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ class SavedStarGiftId;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
|
class Session;
|
||||||
class SessionShow;
|
class SessionShow;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
|
@ -71,6 +72,8 @@ void ShowUniqueGiftWearBox(
|
||||||
const Data::UniqueGift &gift,
|
const Data::UniqueGift &gift,
|
||||||
Settings::GiftWearBoxStyleOverride st);
|
Settings::GiftWearBoxStyleOverride st);
|
||||||
|
|
||||||
|
void PreloadUniqueGiftResellPrices(not_null<Main::Session*> session);
|
||||||
|
|
||||||
void UpdateGiftSellPrice(
|
void UpdateGiftSellPrice(
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
std::shared_ptr<Data::UniqueGift> unique,
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
|
|
@ -768,12 +768,14 @@ void StickerSetBox::updateButtons() {
|
||||||
&st::menuIconManage);
|
&st::menuIconManage);
|
||||||
});
|
});
|
||||||
}();
|
}();
|
||||||
const auto addPackOwner = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
|
const auto addPackIdActions = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
|
||||||
{
|
{
|
||||||
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
|
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
const auto weak = Ui::MakeWeak(this);
|
const auto weak = Ui::MakeWeak(this);
|
||||||
const auto session = _session;
|
const auto session = _session;
|
||||||
const auto innerId = _inner->setId() >> 32;
|
const auto setId = _inner->setId();
|
||||||
|
const auto innerId = setId >> 32;
|
||||||
|
|
||||||
(*menu)->addAction(
|
(*menu)->addAction(
|
||||||
tr::ayu_MessageDetailsPackOwnerPC(tr::now),
|
tr::ayu_MessageDetailsPackOwnerPC(tr::now),
|
||||||
|
@ -816,6 +818,26 @@ void StickerSetBox::updateButtons() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
&st::menuIconProfile);
|
&st::menuIconProfile);
|
||||||
|
|
||||||
|
if (settings.showPeerId != 0) {
|
||||||
|
(*menu)->addAction(
|
||||||
|
tr::ayu_ContextCopyID(tr::now),
|
||||||
|
[weak, session, setId]
|
||||||
|
{
|
||||||
|
if (!weak) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto strongInner = weak.data();
|
||||||
|
if (!strongInner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QGuiApplication::clipboard()->setText(QString::number(setId));
|
||||||
|
strongInner->showToast(tr::ayu_IDCopiedToast(tr::now));
|
||||||
|
},
|
||||||
|
&st::menuIconCopy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (_inner->notInstalled()) {
|
if (_inner->notInstalled()) {
|
||||||
|
@ -865,7 +887,7 @@ void StickerSetBox::updateButtons() {
|
||||||
: tr::lng_stickers_share_pack)(tr::now),
|
: tr::lng_stickers_share_pack)(tr::now),
|
||||||
[=] { share(); closeBox(); },
|
[=] { share(); closeBox(); },
|
||||||
&st::menuIconShare);
|
&st::menuIconShare);
|
||||||
addPackOwner(menu);
|
addPackIdActions(menu);
|
||||||
(*menu)->popup(QCursor::pos());
|
(*menu)->popup(QCursor::pos());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -918,7 +940,7 @@ void StickerSetBox::updateButtons() {
|
||||||
archive,
|
archive,
|
||||||
&st::menuIconArchive);
|
&st::menuIconArchive);
|
||||||
}
|
}
|
||||||
addPackOwner(menu);
|
addPackIdActions(menu);
|
||||||
(*menu)->popup(QCursor::pos());
|
(*menu)->popup(QCursor::pos());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -1486,6 +1508,16 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
QGuiApplication::clipboard()->setMimeData(data.release());
|
QGuiApplication::clipboard()->setMimeData(data.release());
|
||||||
}
|
}
|
||||||
}, &st::menuIconCopy);
|
}, &st::menuIconCopy);
|
||||||
|
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
if (settings.showPeerId != 0) {
|
||||||
|
_menu->addAction(tr::ayu_ContextCopyID(tr::now),
|
||||||
|
[=]
|
||||||
|
{
|
||||||
|
QGuiApplication::clipboard()->setText(QString::number(_pack[index]->id));
|
||||||
|
},
|
||||||
|
&st::menuIconCopy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (details.type != SendMenu::Type::Disabled) {
|
} else if (details.type != SendMenu::Type::Disabled) {
|
||||||
const auto document = _pack[index];
|
const auto document = _pack[index];
|
||||||
|
|
|
@ -425,8 +425,6 @@ void TransferGift(
|
||||||
Data::SavedStarGiftId savedId,
|
Data::SavedStarGiftId savedId,
|
||||||
Fn<void(Payments::CheckoutResult)> done,
|
Fn<void(Payments::CheckoutResult)> done,
|
||||||
bool skipPaymentForm = false) {
|
bool skipPaymentForm = false) {
|
||||||
Expects(to->isUser());
|
|
||||||
|
|
||||||
const auto session = &window->session();
|
const auto session = &window->session();
|
||||||
const auto weak = base::make_weak(window);
|
const auto weak = base::make_weak(window);
|
||||||
auto formDone = [=](
|
auto formDone = [=](
|
||||||
|
|
|
@ -17,6 +17,7 @@ constexpr auto kSendReactionEmojiProperty = 0x04;
|
||||||
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
||||||
constexpr auto kDocumentFilenameTooltipProperty = 0x06;
|
constexpr auto kDocumentFilenameTooltipProperty = 0x06;
|
||||||
constexpr auto kPhoneNumberLinkProperty = 0x07;
|
constexpr auto kPhoneNumberLinkProperty = 0x07;
|
||||||
|
constexpr auto kTodoListItemIdProperty = 0x08;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class Show;
|
class Show;
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||||
constexpr auto AppFile = "AyuGram"_cs;
|
constexpr auto AppFile = "AyuGram"_cs;
|
||||||
constexpr auto AppVersion = 5016003;
|
constexpr auto AppVersion = 5016004;
|
||||||
constexpr auto AppVersionStr = "5.16.3";
|
constexpr auto AppVersionStr = "5.16.4";
|
||||||
constexpr auto AppBetaVersion = false;
|
constexpr auto AppBetaVersion = false;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -32,8 +32,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Data {
|
namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMs = crl::time(1000);
|
||||||
constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
|
constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
|
||||||
|
|
||||||
|
const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01));
|
||||||
|
|
||||||
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
|
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
|
||||||
return (received > 0) && (received + kRequestTimeLimit > crl::now());
|
return (received > 0) && (received + kRequestTimeLimit > crl::now());
|
||||||
}
|
}
|
||||||
|
@ -77,17 +80,21 @@ void SponsoredMessages::clear() {
|
||||||
|
|
||||||
void SponsoredMessages::clearOldRequests() {
|
void SponsoredMessages::clearOldRequests() {
|
||||||
const auto now = crl::now();
|
const auto now = crl::now();
|
||||||
while (true) {
|
const auto clear = [&](auto &requests) {
|
||||||
const auto i = ranges::find_if(_requests, [&](const auto &value) {
|
while (true) {
|
||||||
const auto &request = value.second;
|
const auto i = ranges::find_if(requests, [&](const auto &value) {
|
||||||
return !request.requestId
|
const auto &request = value.second;
|
||||||
&& (request.lastReceived + kRequestTimeLimit <= now);
|
return !request.requestId
|
||||||
});
|
&& (request.lastReceived + kRequestTimeLimit <= now);
|
||||||
if (i == end(_requests)) {
|
});
|
||||||
break;
|
if (i == end(requests)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
requests.erase(i);
|
||||||
}
|
}
|
||||||
_requests.erase(i);
|
};
|
||||||
}
|
clear(_requests);
|
||||||
|
clear(_requestsForVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||||
|
@ -241,6 +248,16 @@ bool SponsoredMessages::canHaveFor(not_null<History*> history) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SponsoredMessages::canHaveFor(not_null<HistoryItem*> item) const {
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
if (settings.disableAds) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item->history()->peer->isBroadcast()
|
||||||
|
&& item->isRegular();
|
||||||
|
}
|
||||||
|
|
||||||
bool SponsoredMessages::isTopBarFor(not_null<History*> history) const {
|
bool SponsoredMessages::isTopBarFor(not_null<History*> history) const {
|
||||||
const auto &settings = AyuSettings::getInstance();
|
const auto &settings = AyuSettings::getInstance();
|
||||||
if (settings.disableAds) {
|
if (settings.disableAds) {
|
||||||
|
@ -291,6 +308,78 @@ void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SponsoredMessages::requestForVideo(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
Fn<void(SponsoredForVideo)> done) {
|
||||||
|
Expects(done != nullptr);
|
||||||
|
|
||||||
|
if (!canHaveFor(item)) {
|
||||||
|
done({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto peer = item->history()->peer;
|
||||||
|
auto &request = _requestsForVideo[peer];
|
||||||
|
if (TooEarlyForRequest(request.lastReceived)) {
|
||||||
|
auto prepared = prepareForVideo(peer);
|
||||||
|
if (prepared.list.empty()
|
||||||
|
|| prepared.state.itemIndex < prepared.list.size()
|
||||||
|
|| prepared.state.leftTillShow > 0) {
|
||||||
|
done(std::move(prepared));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.callbacks.push_back(std::move(done));
|
||||||
|
if (request.requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto it = _dataForVideo.find(peer);
|
||||||
|
if (it != end(_dataForVideo)) {
|
||||||
|
auto &list = it->second;
|
||||||
|
// Don't rebuild currently displayed messages.
|
||||||
|
const auto proj = [](const Entry &e) {
|
||||||
|
return e.item != nullptr;
|
||||||
|
};
|
||||||
|
if (ranges::any_of(list.entries, proj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto finish = [=] {
|
||||||
|
const auto i = _requestsForVideo.find(peer);
|
||||||
|
if (i != end(_requestsForVideo)) {
|
||||||
|
for (const auto &callback : base::take(i->second.callbacks)) {
|
||||||
|
callback(prepareForVideo(peer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using Flag = MTPmessages_GetSponsoredMessages::Flag;
|
||||||
|
request.requestId = _session->api().request(
|
||||||
|
MTPmessages_GetSponsoredMessages(
|
||||||
|
MTP_flags(Flag::f_msg_id),
|
||||||
|
peer->input,
|
||||||
|
MTP_int(item->id.bare))
|
||||||
|
).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||||
|
parseForVideo(peer, result);
|
||||||
|
finish();
|
||||||
|
}).fail([=] {
|
||||||
|
_requestsForVideo.remove(peer);
|
||||||
|
finish();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SponsoredMessages::updateForVideo(
|
||||||
|
FullMsgId itemId,
|
||||||
|
SponsoredForVideoState state) {
|
||||||
|
if (state.initial()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto i = _dataForVideo.find(_session->data().peer(itemId.peer));
|
||||||
|
if (i != end(_dataForVideo)) {
|
||||||
|
i->second.state = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SponsoredMessages::parse(
|
void SponsoredMessages::parse(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
const MTPmessages_sponsoredMessages &list) {
|
const MTPmessages_sponsoredMessages &list) {
|
||||||
|
@ -306,12 +395,9 @@ void SponsoredMessages::parse(
|
||||||
_session->data().processChats(data.vchats());
|
_session->data().processChats(data.vchats());
|
||||||
|
|
||||||
const auto &messages = data.vmessages().v;
|
const auto &messages = data.vmessages().v;
|
||||||
auto &list = _data.emplace(history, List()).first->second;
|
auto &list = _data.emplace(history).first->second;
|
||||||
list.entries.clear();
|
list.entries.clear();
|
||||||
list.received = crl::now();
|
list.received = crl::now();
|
||||||
for (const auto &message : messages) {
|
|
||||||
append(history, list, message);
|
|
||||||
}
|
|
||||||
if (const auto postsBetween = data.vposts_between()) {
|
if (const auto postsBetween = data.vposts_between()) {
|
||||||
list.postsBetween = postsBetween->v;
|
list.postsBetween = postsBetween->v;
|
||||||
list.state = State::InjectToMiddle;
|
list.state = State::InjectToMiddle;
|
||||||
|
@ -320,10 +406,66 @@ void SponsoredMessages::parse(
|
||||||
? State::AppendToEnd
|
? State::AppendToEnd
|
||||||
: State::AppendToTopBar;
|
: State::AppendToTopBar;
|
||||||
}
|
}
|
||||||
|
for (const auto &message : messages) {
|
||||||
|
append([=] {
|
||||||
|
return &_data[history].entries;
|
||||||
|
}, history, message);
|
||||||
|
}
|
||||||
}, [](const MTPDmessages_sponsoredMessagesEmpty &) {
|
}, [](const MTPDmessages_sponsoredMessagesEmpty &) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SponsoredMessages::parseForVideo(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPmessages_sponsoredMessages &list) {
|
||||||
|
auto &request = _requestsForVideo[peer];
|
||||||
|
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 history = _session->data().history(peer);
|
||||||
|
const auto &messages = data.vmessages().v;
|
||||||
|
auto &list = _dataForVideo.emplace(peer).first->second;
|
||||||
|
list.entries.clear();
|
||||||
|
list.received = crl::now();
|
||||||
|
list.startDelay = data.vstart_delay().value_or_empty() * kMs;
|
||||||
|
list.betweenDelay = data.vbetween_delay().value_or_empty() * kMs;
|
||||||
|
for (const auto &message : messages) {
|
||||||
|
append([=] {
|
||||||
|
return &_dataForVideo[peer].entries;
|
||||||
|
}, history, message);
|
||||||
|
}
|
||||||
|
}, [](const MTPDmessages_sponsoredMessagesEmpty &) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SponsoredForVideo SponsoredMessages::prepareForVideo(
|
||||||
|
not_null<PeerData*> peer) {
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
if (settings.disableAds) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto i = _dataForVideo.find(peer);
|
||||||
|
if (i == end(_dataForVideo) || i->second.entries.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return SponsoredForVideo{
|
||||||
|
.list = i->second.entries | ranges::views::transform(
|
||||||
|
&Entry::sponsored
|
||||||
|
) | ranges::to_vector,
|
||||||
|
.startDelay = i->second.startDelay,
|
||||||
|
.betweenDelay = i->second.betweenDelay,
|
||||||
|
.state = i->second.state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
FullMsgId SponsoredMessages::fillTopBar(
|
FullMsgId SponsoredMessages::fillTopBar(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
not_null<Ui::RpWidget*> widget) {
|
not_null<Ui::RpWidget*> widget) {
|
||||||
|
@ -373,8 +515,8 @@ rpl::producer<> SponsoredMessages::itemRemoved(const FullMsgId &fullId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SponsoredMessages::append(
|
void SponsoredMessages::append(
|
||||||
|
Fn<not_null<std::vector<Entry>*>()> entries,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
List &list,
|
|
||||||
const MTPSponsoredMessage &message) {
|
const MTPSponsoredMessage &message) {
|
||||||
const auto &data = message.data();
|
const auto &data = message.data();
|
||||||
const auto randomId = data.vrandom_id().v;
|
const auto randomId = data.vrandom_id().v;
|
||||||
|
@ -385,14 +527,14 @@ void SponsoredMessages::append(
|
||||||
data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) {
|
data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) {
|
||||||
if (const auto tlPhoto = media.vphoto()) {
|
if (const auto tlPhoto = media.vphoto()) {
|
||||||
tlPhoto->match([&](const MTPDphoto &data) {
|
tlPhoto->match([&](const MTPDphoto &data) {
|
||||||
mediaPhoto = history->owner().processPhoto(data);
|
mediaPhoto = _session->data().processPhoto(data);
|
||||||
}, [](const MTPDphotoEmpty &) {
|
}, [](const MTPDphotoEmpty &) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [&](const MTPDmessageMediaDocument &media) {
|
}, [&](const MTPDmessageMediaDocument &media) {
|
||||||
if (const auto tlDocument = media.vdocument()) {
|
if (const auto tlDocument = media.vdocument()) {
|
||||||
tlDocument->match([&](const MTPDdocument &data) {
|
tlDocument->match([&](const MTPDdocument &data) {
|
||||||
const auto d = history->owner().processDocument(
|
const auto d = _session->data().processDocument(
|
||||||
data,
|
data,
|
||||||
media.valt_documents());
|
media.valt_documents());
|
||||||
if (d->isVideoFile()
|
if (d->isVideoFile()
|
||||||
|
@ -413,7 +555,7 @@ void SponsoredMessages::append(
|
||||||
.link = qs(data.vurl()),
|
.link = qs(data.vurl()),
|
||||||
.buttonText = qs(data.vbutton_text()),
|
.buttonText = qs(data.vbutton_text()),
|
||||||
.photoId = data.vphoto()
|
.photoId = data.vphoto()
|
||||||
? history->session().data().processPhoto(*data.vphoto())->id
|
? _session->data().processPhoto(*data.vphoto())->id
|
||||||
: PhotoId(0),
|
: PhotoId(0),
|
||||||
.mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0),
|
.mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0),
|
||||||
.mediaDocumentId = (mediaDocument ? mediaDocument->id : 0),
|
.mediaDocumentId = (mediaDocument ? mediaDocument->id : 0),
|
||||||
|
@ -449,25 +591,24 @@ void SponsoredMessages::append(
|
||||||
.link = from.link,
|
.link = from.link,
|
||||||
.sponsorInfo = std::move(sponsorInfo),
|
.sponsorInfo = std::move(sponsorInfo),
|
||||||
.additionalInfo = std::move(additionalInfo),
|
.additionalInfo = std::move(additionalInfo),
|
||||||
|
.durationMin = data.vmin_display_duration().value_or_empty() * kMs,
|
||||||
|
.durationMax = data.vmax_display_duration().value_or_empty() * kMs,
|
||||||
};
|
};
|
||||||
list.entries.push_back({
|
const auto itemId = FullMsgId(
|
||||||
.sponsored = std::move(sharedMessage),
|
|
||||||
});
|
|
||||||
auto &entry = list.entries.back();
|
|
||||||
const auto itemId = entry.itemFullId = FullMsgId(
|
|
||||||
history->peer->id,
|
history->peer->id,
|
||||||
_session->data().nextLocalMessageId());
|
_session->data().nextLocalMessageId());
|
||||||
|
const auto list = entries();
|
||||||
|
list->push_back({
|
||||||
|
.itemFullId = itemId,
|
||||||
|
.sponsored = std::move(sharedMessage),
|
||||||
|
});
|
||||||
|
auto &entry = list->back();
|
||||||
const auto fileOrigin = FileOrigin(); // No way to refresh in ads.
|
const auto fileOrigin = FileOrigin(); // No way to refresh in ads.
|
||||||
|
|
||||||
static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01));
|
|
||||||
const auto preloaded = [=] {
|
const auto preloaded = [=] {
|
||||||
const auto i = _data.find(history);
|
const auto list = entries();
|
||||||
if (i == end(_data)) {
|
const auto j = ranges::find(*list, itemId, &Entry::itemFullId);
|
||||||
return;
|
if (j == end(*list)) {
|
||||||
}
|
|
||||||
auto &entries = i->second.entries;
|
|
||||||
const auto j = ranges::find(entries, itemId, &Entry::itemFullId);
|
|
||||||
if (j == end(entries)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto &entry = *j;
|
auto &entry = *j;
|
||||||
|
@ -565,7 +706,11 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
|
||||||
if (!entryPtr) {
|
if (!entryPtr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto &data = entryPtr->sponsored;
|
return lookupDetails(entryPtr->sponsored);
|
||||||
|
}
|
||||||
|
|
||||||
|
SponsoredMessages::Details SponsoredMessages::lookupDetails(
|
||||||
|
const SponsoredMessage &data) const {
|
||||||
return {
|
return {
|
||||||
.info = Prepare(data),
|
.info = Prepare(data),
|
||||||
.link = data.link,
|
.link = data.link,
|
||||||
|
|
|
@ -67,10 +67,12 @@ struct SponsoredMessage {
|
||||||
QByteArray randomId;
|
QByteArray randomId;
|
||||||
SponsoredFrom from;
|
SponsoredFrom from;
|
||||||
TextWithEntities textWithEntities;
|
TextWithEntities textWithEntities;
|
||||||
History *history = nullptr;
|
not_null<History*> history;
|
||||||
QString link;
|
QString link;
|
||||||
TextWithEntities sponsorInfo;
|
TextWithEntities sponsorInfo;
|
||||||
TextWithEntities additionalInfo;
|
TextWithEntities additionalInfo;
|
||||||
|
crl::time durationMin = 0;
|
||||||
|
crl::time durationMax = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SponsoredMessageDetails {
|
struct SponsoredMessageDetails {
|
||||||
|
@ -92,6 +94,23 @@ struct SponsoredReportAction {
|
||||||
Fn<void(Data::SponsoredReportResult)>)> callback;
|
Fn<void(Data::SponsoredReportResult)>)> callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SponsoredForVideoState {
|
||||||
|
int itemIndex = 0;
|
||||||
|
crl::time leftTillShow = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] bool initial() const {
|
||||||
|
return !itemIndex && !leftTillShow;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SponsoredForVideo {
|
||||||
|
std::vector<SponsoredMessage> list;
|
||||||
|
crl::time startDelay = 0;
|
||||||
|
crl::time betweenDelay = 0;
|
||||||
|
|
||||||
|
SponsoredForVideoState state;
|
||||||
|
};
|
||||||
|
|
||||||
class SponsoredMessages final {
|
class SponsoredMessages final {
|
||||||
public:
|
public:
|
||||||
enum class AppendResult {
|
enum class AppendResult {
|
||||||
|
@ -111,10 +130,18 @@ public:
|
||||||
~SponsoredMessages();
|
~SponsoredMessages();
|
||||||
|
|
||||||
[[nodiscard]] bool canHaveFor(not_null<History*> history) const;
|
[[nodiscard]] bool canHaveFor(not_null<History*> history) const;
|
||||||
|
[[nodiscard]] bool canHaveFor(not_null<HistoryItem*> item) const;
|
||||||
[[nodiscard]] bool isTopBarFor(not_null<History*> history) const;
|
[[nodiscard]] bool isTopBarFor(not_null<History*> history) const;
|
||||||
void request(not_null<History*> history, Fn<void()> done);
|
void request(not_null<History*> history, Fn<void()> done);
|
||||||
|
void requestForVideo(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
Fn<void(SponsoredForVideo)> done);
|
||||||
|
void updateForVideo(
|
||||||
|
FullMsgId itemId,
|
||||||
|
SponsoredForVideoState state);
|
||||||
void clearItems(not_null<History*> history);
|
void clearItems(not_null<History*> history);
|
||||||
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
|
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
|
||||||
|
[[nodiscard]] Details lookupDetails(const SponsoredMessage &data) const;
|
||||||
[[nodiscard]] Details lookupDetails(
|
[[nodiscard]] Details lookupDetails(
|
||||||
const Api::SponsoredSearchResult &data) const;
|
const Api::SponsoredSearchResult &data) const;
|
||||||
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
|
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
|
||||||
|
@ -166,18 +193,35 @@ private:
|
||||||
int postsBetween = 0;
|
int postsBetween = 0;
|
||||||
State state = State::None;
|
State state = State::None;
|
||||||
};
|
};
|
||||||
|
struct ListForVideo {
|
||||||
|
std::vector<Entry> entries;
|
||||||
|
crl::time received = 0;
|
||||||
|
crl::time startDelay = 0;
|
||||||
|
crl::time betweenDelay = 0;
|
||||||
|
SponsoredForVideoState state;
|
||||||
|
};
|
||||||
struct Request {
|
struct Request {
|
||||||
mtpRequestId requestId = 0;
|
mtpRequestId requestId = 0;
|
||||||
crl::time lastReceived = 0;
|
crl::time lastReceived = 0;
|
||||||
};
|
};
|
||||||
|
struct RequestForVideo {
|
||||||
|
std::vector<Fn<void(SponsoredForVideo)>> callbacks;
|
||||||
|
mtpRequestId requestId = 0;
|
||||||
|
crl::time lastReceived = 0;
|
||||||
|
};
|
||||||
|
|
||||||
void parse(
|
void parse(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
const MTPmessages_sponsoredMessages &list);
|
const MTPmessages_sponsoredMessages &list);
|
||||||
|
void parseForVideo(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const MTPmessages_sponsoredMessages &list);
|
||||||
void append(
|
void append(
|
||||||
|
Fn<not_null<std::vector<Entry>*>()> entries,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
List &list,
|
|
||||||
const MTPSponsoredMessage &message);
|
const MTPSponsoredMessage &message);
|
||||||
|
[[nodiscard]] SponsoredForVideo prepareForVideo(
|
||||||
|
not_null<PeerData*> peer);
|
||||||
void clearOldRequests();
|
void clearOldRequests();
|
||||||
|
|
||||||
const Entry *find(const FullMsgId &fullId) const;
|
const Entry *find(const FullMsgId &fullId) const;
|
||||||
|
@ -189,6 +233,9 @@ private:
|
||||||
base::flat_map<not_null<History*>, Request> _requests;
|
base::flat_map<not_null<History*>, Request> _requests;
|
||||||
base::flat_map<RandomId, Request> _viewRequests;
|
base::flat_map<RandomId, Request> _viewRequests;
|
||||||
|
|
||||||
|
base::flat_map<not_null<PeerData*>, ListForVideo> _dataForVideo;
|
||||||
|
base::flat_map<not_null<PeerData*>, RequestForVideo> _requestsForVideo;
|
||||||
|
|
||||||
rpl::event_stream<FullMsgId> _itemRemoved;
|
rpl::event_stream<FullMsgId> _itemRemoved;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
|
@ -72,7 +72,7 @@ namespace {
|
||||||
| (data.is_send_audios() ? Flag::SendMusic : Flag())
|
| (data.is_send_audios() ? Flag::SendMusic : Flag())
|
||||||
| (data.is_send_voices() ? Flag::SendVoiceMessages : Flag())
|
| (data.is_send_voices() ? Flag::SendVoiceMessages : Flag())
|
||||||
| (data.is_send_docs() ? Flag::SendFiles : Flag())
|
| (data.is_send_docs() ? Flag::SendFiles : Flag())
|
||||||
| (data.is_send_messages() ? Flag::SendOther : Flag())
|
| (data.is_send_plain() ? Flag::SendOther : Flag())
|
||||||
| (data.is_embed_links() ? Flag::EmbedLinks : Flag())
|
| (data.is_embed_links() ? Flag::EmbedLinks : Flag())
|
||||||
| (data.is_change_info() ? Flag::ChangeInfo : Flag())
|
| (data.is_change_info() ? Flag::ChangeInfo : Flag())
|
||||||
| (data.is_invite_users() ? Flag::AddParticipants : Flag())
|
| (data.is_invite_users() ? Flag::AddParticipants : Flag())
|
||||||
|
@ -142,7 +142,7 @@ MTPChatBannedRights RestrictionsToMTP(ChatRestrictionsInfo info) {
|
||||||
| ((flags & R::SendMusic) ? Flag::f_send_audios : Flag())
|
| ((flags & R::SendMusic) ? Flag::f_send_audios : Flag())
|
||||||
| ((flags & R::SendVoiceMessages) ? Flag::f_send_voices : Flag())
|
| ((flags & R::SendVoiceMessages) ? Flag::f_send_voices : Flag())
|
||||||
| ((flags & R::SendFiles) ? Flag::f_send_docs : Flag())
|
| ((flags & R::SendFiles) ? Flag::f_send_docs : Flag())
|
||||||
| ((flags & R::SendOther) ? Flag::f_send_messages : Flag())
|
| ((flags & R::SendOther) ? Flag::f_send_plain : Flag())
|
||||||
| ((flags & R::EmbedLinks) ? Flag::f_embed_links : Flag())
|
| ((flags & R::EmbedLinks) ? Flag::f_embed_links : Flag())
|
||||||
| ((flags & R::ChangeInfo) ? Flag::f_change_info : Flag())
|
| ((flags & R::ChangeInfo) ? Flag::f_change_info : Flag())
|
||||||
| ((flags & R::AddParticipants) ? Flag::f_invite_users : Flag())
|
| ((flags & R::AddParticipants) ? Flag::f_invite_users : Flag())
|
||||||
|
|
|
@ -92,7 +92,8 @@ MTPInputReplyTo ReplyToForMTP(
|
||||||
: Flag())
|
: Flag())
|
||||||
| (quoteEntities.v.isEmpty()
|
| (quoteEntities.v.isEmpty()
|
||||||
? Flag()
|
? Flag()
|
||||||
: Flag::f_quote_entities)),
|
: Flag::f_quote_entities)
|
||||||
|
| (replyTo.todoItemId ? Flag::f_todo_item_id : Flag())),
|
||||||
MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0),
|
MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0),
|
||||||
MTP_int(replyTo.topicRootId),
|
MTP_int(replyTo.topicRootId),
|
||||||
(external
|
(external
|
||||||
|
@ -103,7 +104,8 @@ MTPInputReplyTo ReplyToForMTP(
|
||||||
MTP_int(replyTo.quoteOffset),
|
MTP_int(replyTo.quoteOffset),
|
||||||
(replyToMonoforumPeerId
|
(replyToMonoforumPeerId
|
||||||
? history->owner().peer(replyToMonoforumPeerId)->input
|
? history->owner().peer(replyToMonoforumPeerId)->input
|
||||||
: MTPInputPeer()));
|
: MTPInputPeer()),
|
||||||
|
MTP_int(replyTo.todoItemId));
|
||||||
} else if (history->peer->amMonoforumAdmin()
|
} else if (history->peer->amMonoforumAdmin()
|
||||||
&& replyTo.monoforumPeerId) {
|
&& replyTo.monoforumPeerId) {
|
||||||
const auto replyToMonoforumPeer = replyTo.monoforumPeerId
|
const auto replyToMonoforumPeer = replyTo.monoforumPeerId
|
||||||
|
|
|
@ -172,6 +172,19 @@ inline QDebug operator<<(QDebug debug, const FullMsgId &fullMsgId) {
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(FullMsgId);
|
Q_DECLARE_METATYPE(FullMsgId);
|
||||||
|
|
||||||
|
struct MessageHighlightId {
|
||||||
|
TextWithEntities quote;
|
||||||
|
int quoteOffset = 0;
|
||||||
|
int todoItemId = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return quote.empty() && !todoItemId;
|
||||||
|
}
|
||||||
|
[[nodiscard]] friend inline bool operator==(
|
||||||
|
const MessageHighlightId &a,
|
||||||
|
const MessageHighlightId &b) = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct FullReplyTo {
|
struct FullReplyTo {
|
||||||
FullMsgId messageId;
|
FullMsgId messageId;
|
||||||
TextWithEntities quote;
|
TextWithEntities quote;
|
||||||
|
@ -179,7 +192,11 @@ struct FullReplyTo {
|
||||||
MsgId topicRootId = 0;
|
MsgId topicRootId = 0;
|
||||||
PeerId monoforumPeerId = 0;
|
PeerId monoforumPeerId = 0;
|
||||||
int quoteOffset = 0;
|
int quoteOffset = 0;
|
||||||
|
int todoItemId = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] MessageHighlightId highlight() const {
|
||||||
|
return { quote, quoteOffset, todoItemId };
|
||||||
|
}
|
||||||
[[nodiscard]] bool replying() const {
|
[[nodiscard]] bool replying() const {
|
||||||
return messageId || (storyId && storyId.peer);
|
return messageId || (storyId && storyId.peer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,17 +96,6 @@ Thread *SavedMessages::activeSubsectionThread() const {
|
||||||
return _activeSubsectionSublist;
|
return _activeSubsectionSublist;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialogs::UnreadState SavedMessages::unreadStateWithParentMuted() const {
|
|
||||||
auto result = _chatsList.unreadState();
|
|
||||||
if (_owningHistory->muted()) {
|
|
||||||
result.chatsMuted = result.chats;
|
|
||||||
result.marksMuted = result.marks;
|
|
||||||
result.messagesMuted = result.messages;
|
|
||||||
result.reactionsMuted = result.reactions;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
SavedMessages::~SavedMessages() {
|
SavedMessages::~SavedMessages() {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
@ -458,6 +447,9 @@ void SavedMessages::applySublistDeleted(not_null<PeerData*> sublistPeer) {
|
||||||
if (ranges::contains(_lastSublists, not_null(raw))) {
|
if (ranges::contains(_lastSublists, not_null(raw))) {
|
||||||
reorderLastSublists();
|
reorderLastSublists();
|
||||||
}
|
}
|
||||||
|
if (_activeSubsectionSublist == raw) {
|
||||||
|
_activeSubsectionSublist = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
_sublistDestroyed.fire(raw);
|
_sublistDestroyed.fire(raw);
|
||||||
session().changes().sublistUpdated(
|
session().changes().sublistUpdated(
|
||||||
|
|
|
@ -84,8 +84,6 @@ public:
|
||||||
void saveActiveSubsectionThread(not_null<Thread*> thread);
|
void saveActiveSubsectionThread(not_null<Thread*> thread);
|
||||||
Thread *activeSubsectionThread() const;
|
Thread *activeSubsectionThread() const;
|
||||||
|
|
||||||
[[nodiscard]] Dialogs::UnreadState unreadStateWithParentMuted() const;
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::lifetime &lifetime();
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -217,7 +217,28 @@ void SavedSublist::applyItemRemoved(MsgId id) {
|
||||||
if (const auto chatListItem = _chatListMessage.value_or(nullptr)) {
|
if (const auto chatListItem = _chatListMessage.value_or(nullptr)) {
|
||||||
if (chatListItem->id == id) {
|
if (chatListItem->id == id) {
|
||||||
_chatListMessage = std::nullopt;
|
_chatListMessage = std::nullopt;
|
||||||
requestChatListMessage();
|
crl::on_main(this, [=] {
|
||||||
|
// We didn't yet update _list here.
|
||||||
|
if (_chatListMessage.has_value()) {
|
||||||
|
return;
|
||||||
|
} else if (_skippedAfter == 0) {
|
||||||
|
if (!_list.empty()) {
|
||||||
|
applyMaybeLast(owner().message(
|
||||||
|
owningHistory()->peer,
|
||||||
|
_list.front()));
|
||||||
|
return;
|
||||||
|
} else if (_skippedBefore == 0) {
|
||||||
|
setLastServerMessage(nullptr);
|
||||||
|
updateChatListExistence();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_parent->parentChat()) {
|
||||||
|
requestChatListMessage();
|
||||||
|
} else {
|
||||||
|
loadAround(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1110,6 +1131,10 @@ void SavedSublist::loadAround(MsgId id) {
|
||||||
_list.clear();
|
_list.clear();
|
||||||
if (processMessagesIsEmpty(result)) {
|
if (processMessagesIsEmpty(result)) {
|
||||||
_fullCount = _skippedBefore = _skippedAfter = 0;
|
_fullCount = _skippedBefore = _skippedAfter = 0;
|
||||||
|
if (!_parent->parentChat() && !_chatListMessage) {
|
||||||
|
setLastServerMessage(nullptr);
|
||||||
|
updateChatListExistence();
|
||||||
|
}
|
||||||
} else if (id) {
|
} else if (id) {
|
||||||
Assert(!_list.empty());
|
Assert(!_list.empty());
|
||||||
if (_list.front() <= id) {
|
if (_list.front() <= id) {
|
||||||
|
@ -1117,6 +1142,11 @@ void SavedSublist::loadAround(MsgId id) {
|
||||||
} else if (_list.back() >= id) {
|
} else if (_list.back() >= id) {
|
||||||
_skippedBefore = 0;
|
_skippedBefore = 0;
|
||||||
}
|
}
|
||||||
|
} else if (!_parent->parentChat() && !_chatListMessage) {
|
||||||
|
Assert(!_list.empty());
|
||||||
|
applyMaybeLast(owner().message(
|
||||||
|
owningHistory()->peer,
|
||||||
|
_list.front()));
|
||||||
}
|
}
|
||||||
checkReadTillEnd();
|
checkReadTillEnd();
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "data/components/top_peers.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
@ -425,10 +426,7 @@ void Stories::parseAndApply(const MTPPeerStories &stories) {
|
||||||
};
|
};
|
||||||
if (result.peer->isSelf()
|
if (result.peer->isSelf()
|
||||||
|| (result.peer->isChannel() && result.peer->asChannel()->amIn())
|
|| (result.peer->isChannel() && result.peer->asChannel()->amIn())
|
||||||
|| (result.peer->isUser()
|
|| result.peer->isUser()) {
|
||||||
&& (result.peer->asUser()->isBot()
|
|
||||||
|| result.peer->asUser()->isContact()))
|
|
||||||
|| result.peer->isServiceUser()) {
|
|
||||||
const auto hidden = result.peer->hasStoriesHidden();
|
const auto hidden = result.peer->hasStoriesHidden();
|
||||||
using List = StorySourcesList;
|
using List = StorySourcesList;
|
||||||
add(hidden ? List::Hidden : List::NotHidden);
|
add(hidden ? List::Hidden : List::NotHidden);
|
||||||
|
@ -1197,7 +1195,11 @@ void Stories::toggleHidden(
|
||||||
bool hidden,
|
bool hidden,
|
||||||
std::shared_ptr<Ui::Show> show) {
|
std::shared_ptr<Ui::Show> show) {
|
||||||
const auto peer = _owner->peer(peerId);
|
const auto peer = _owner->peer(peerId);
|
||||||
const auto justRemove = peer->isServiceUser() && hidden;
|
const auto byHints = peer->isUser()
|
||||||
|
&& !peer->asUser()->isBot()
|
||||||
|
&& !peer->asUser()->isContact()
|
||||||
|
&& !peer->asUser()->isServiceUser();
|
||||||
|
const auto justRemove = (byHints || peer->isServiceUser()) && hidden;
|
||||||
if (peer->hasStoriesHidden() != hidden) {
|
if (peer->hasStoriesHidden() != hidden) {
|
||||||
if (!justRemove) {
|
if (!justRemove) {
|
||||||
peer->setStoriesHidden(hidden);
|
peer->setStoriesHidden(hidden);
|
||||||
|
@ -1206,6 +1208,9 @@ void Stories::toggleHidden(
|
||||||
peer->input,
|
peer->input,
|
||||||
MTP_bool(hidden)
|
MTP_bool(hidden)
|
||||||
)).send();
|
)).send();
|
||||||
|
if (byHints) {
|
||||||
|
peer->session().topPeers().remove(peer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto name = peer->shortName();
|
const auto name = peer->shortName();
|
||||||
|
|
|
@ -244,6 +244,17 @@ dialogsEmptyLabel: FlatLabel(defaultFlatLabel) {
|
||||||
align: align(top);
|
align: align(top);
|
||||||
textFg: windowSubTextFg;
|
textFg: windowSubTextFg;
|
||||||
}
|
}
|
||||||
|
dialogEmptyButton: RoundButton(defaultActiveButton) {
|
||||||
|
}
|
||||||
|
dialogEmptyButtonSkip: 12px;
|
||||||
|
dialogEmptyButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(boxFontSize semibold);
|
||||||
|
}
|
||||||
|
minWidth: 32px;
|
||||||
|
align: align(top);
|
||||||
|
textFg: windowFg;
|
||||||
|
}
|
||||||
|
|
||||||
dialogsMenuToggle: IconButton {
|
dialogsMenuToggle: IconButton {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/dynamic_thumbnails.h"
|
#include "ui/dynamic_thumbnails.h"
|
||||||
|
#include "ui/vertical_list.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/rect.h"
|
#include "ui/rect.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
|
@ -58,8 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/options.h"
|
#include "base/options.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "lottie/lottie_icon.h"
|
#include "lottie/lottie_icon.h"
|
||||||
#include "mainwindow.h"
|
#include "settings/settings_common.h"
|
||||||
#include "mainwidget.h"
|
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -80,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_chat_filters.h"
|
#include "api/api_chat_filters.h"
|
||||||
#include "base/qt/qt_common_adapters.h"
|
#include "base/qt/qt_common_adapters.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_chat.h" // popupMenuExpandedSeparator
|
#include "styles/style_chat.h" // popupMenuExpandedSeparator
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_color_indices.h"
|
#include "styles/style_color_indices.h"
|
||||||
|
@ -3081,6 +3082,11 @@ void InnerWidget::clearSelection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::fillSupportSearchMenu(not_null<Ui::PopupMenu*> menu) {
|
void InnerWidget::fillSupportSearchMenu(not_null<Ui::PopupMenu*> menu) {
|
||||||
|
const auto globalSearch = (_searchState.tab == ChatSearchTab::MyMessages)
|
||||||
|
|| (_searchState.tab == ChatSearchTab::PublicPosts);
|
||||||
|
if (!globalSearch && _searchState.inChat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto all = session().settings().supportAllSearchResults();
|
const auto all = session().settings().supportAllSearchResults();
|
||||||
const auto text = all ? "Only one from chat" : "Show all messages";
|
const auto text = all ? "Only one from chat" : "Show all messages";
|
||||||
menu->addAction(text, [=] {
|
menu->addAction(text, [=] {
|
||||||
|
@ -3091,9 +3097,11 @@ void InnerWidget::fillSupportSearchMenu(not_null<Ui::PopupMenu*> menu) {
|
||||||
|
|
||||||
void InnerWidget::fillArchiveSearchMenu(not_null<Ui::PopupMenu*> menu) {
|
void InnerWidget::fillArchiveSearchMenu(not_null<Ui::PopupMenu*> menu) {
|
||||||
const auto folder = session().data().folderLoaded(Data::Folder::kId);
|
const auto folder = session().data().folderLoaded(Data::Folder::kId);
|
||||||
|
const auto globalSearch = (_searchState.tab == ChatSearchTab::MyMessages)
|
||||||
|
|| (_searchState.tab == ChatSearchTab::PublicPosts);
|
||||||
if (!folder
|
if (!folder
|
||||||
|| !folder->chatsList()->fullSize().current()
|
|| !folder->chatsList()->fullSize().current()
|
||||||
|| _searchState.inChat) {
|
|| (!globalSearch && _searchState.inChat)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto skip = session().settings().skipArchiveInSearch();
|
const auto skip = session().settings().skipArchiveInSearch();
|
||||||
|
@ -3263,16 +3271,13 @@ void InnerWidget::showSponsoredMenu(int peerSearchIndex, QPoint globalPos) {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
Menu::FillSponsored(
|
Menu::FillSponsored(
|
||||||
this,
|
|
||||||
Ui::Menu::CreateAddActionCallback(_menu),
|
Ui::Menu::CreateAddActionCallback(_menu),
|
||||||
_controller->uiShow(),
|
_controller->uiShow(),
|
||||||
Menu::SponsoredPhrases::Search,
|
Menu::SponsoredPhrases::Search,
|
||||||
session().sponsoredMessages().lookupDetails(entry->sponsored->data),
|
session().sponsoredMessages().lookupDetails(entry->sponsored->data),
|
||||||
session().sponsoredMessages().createReportCallback(
|
session().sponsoredMessages().createReportCallback(
|
||||||
entry->sponsored->data.randomId,
|
entry->sponsored->data.randomId,
|
||||||
remove),
|
remove));
|
||||||
false,
|
|
||||||
false);
|
|
||||||
QObject::connect(_menu.get(), &QObject::destroyed, [=] {
|
QObject::connect(_menu.get(), &QObject::destroyed, [=] {
|
||||||
if (_peerSearchMenu >= 0
|
if (_peerSearchMenu >= 0
|
||||||
&& _peerSearchMenu < _peerSearchResults.size()) {
|
&& _peerSearchMenu < _peerSearchResults.size()) {
|
||||||
|
@ -3811,7 +3816,7 @@ void InnerWidget::itemRemoved(not_null<const HistoryItem*> item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InnerWidget::uniqueSearchResults() const {
|
bool InnerWidget::uniqueSearchResults() const {
|
||||||
return _controller->uniqueChatsInSearchResults();
|
return _controller->uniqueChatsInSearchResults(_searchState);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InnerWidget::hasHistoryInResults(not_null<History*> history) const {
|
bool InnerWidget::hasHistoryInResults(not_null<History*> history) const {
|
||||||
|
@ -3869,7 +3874,8 @@ void InnerWidget::searchReceived(
|
||||||
? _searchState.inChat
|
? _searchState.inChat
|
||||||
: Key(_openedForum->history());
|
: Key(_openedForum->history());
|
||||||
if (inject
|
if (inject
|
||||||
&& (!_searchState.inChat
|
&& (globalSearch
|
||||||
|
|| !_searchState.inChat
|
||||||
|| inject->history() == _searchState.inChat.history())) {
|
|| inject->history() == _searchState.inChat.history())) {
|
||||||
Assert(_searchResults.empty());
|
Assert(_searchResults.empty());
|
||||||
Assert(!toPreview);
|
Assert(!toPreview);
|
||||||
|
@ -4082,9 +4088,18 @@ void InnerWidget::refreshEmpty() {
|
||||||
if (state == EmptyState::None) {
|
if (state == EmptyState::None) {
|
||||||
_emptyState = state;
|
_emptyState = state;
|
||||||
_empty.destroy();
|
_empty.destroy();
|
||||||
|
_emptyList.destroy();
|
||||||
|
_emptyButton.destroy();
|
||||||
return;
|
return;
|
||||||
} else if (_emptyState == state) {
|
} else if (_emptyState == state) {
|
||||||
_empty->setVisible(_state == WidgetState::Default);
|
_empty->setVisible(_state == WidgetState::Default);
|
||||||
|
if (_emptyList) {
|
||||||
|
_emptyList->setVisible(_state == WidgetState::Default);
|
||||||
|
_empty->setVisible(!_emptyList->isVisible());
|
||||||
|
}
|
||||||
|
if (_emptyButton) {
|
||||||
|
_emptyButton->setVisible(_state == WidgetState::Default);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_emptyState = state;
|
_emptyState = state;
|
||||||
|
@ -4115,7 +4130,6 @@ void InnerWidget::refreshEmpty() {
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
_empty.create(this, std::move(full), st::dialogsEmptyLabel);
|
_empty.create(this, std::move(full), st::dialogsEmptyLabel);
|
||||||
resizeEmpty();
|
|
||||||
_empty->overrideLinkClickHandler([=] {
|
_empty->overrideLinkClickHandler([=] {
|
||||||
if (_emptyState == EmptyState::NoContacts) {
|
if (_emptyState == EmptyState::NoContacts) {
|
||||||
_controller->showAddContact();
|
_controller->showAddContact();
|
||||||
|
@ -4127,6 +4141,58 @@ void InnerWidget::refreshEmpty() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_empty->setVisible(_state == WidgetState::Default);
|
_empty->setVisible(_state == WidgetState::Default);
|
||||||
|
|
||||||
|
if (state == EmptyState::NoContacts) {
|
||||||
|
const auto isListVisible = _state == WidgetState::Default;
|
||||||
|
_emptyList.create(this);
|
||||||
|
_emptyList->setVisible(isListVisible);
|
||||||
|
|
||||||
|
auto icon = ::Settings::CreateLottieIcon(
|
||||||
|
_emptyList,
|
||||||
|
{
|
||||||
|
.name = u"no_chats"_q,
|
||||||
|
.sizeOverride = Size(st::changePhoneIconSize),
|
||||||
|
});
|
||||||
|
_emptyList->add(
|
||||||
|
object_ptr<Ui::CenterWrap<>>(_emptyList, std::move(icon.widget)));
|
||||||
|
Ui::AddSkip(_emptyList);
|
||||||
|
_emptyList->add(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
_emptyList,
|
||||||
|
tr::lng_no_conversations(),
|
||||||
|
st::dialogEmptyButtonLabel));
|
||||||
|
if (_state == WidgetState::Default) {
|
||||||
|
icon.animate(anim::repeat::once);
|
||||||
|
}
|
||||||
|
_emptyButton.create(
|
||||||
|
this,
|
||||||
|
tr::lng_no_conversations_button(),
|
||||||
|
st::dialogEmptyButton);
|
||||||
|
_emptyButton->setTextTransform(
|
||||||
|
Ui::RoundButton::TextTransform::NoTransform);
|
||||||
|
_emptyButton->setVisible(isListVisible);
|
||||||
|
_emptyButton->setClickedCallback([=, window = _controller] {
|
||||||
|
window->show(PrepareContactsBox(window));
|
||||||
|
});
|
||||||
|
geometryValue() | rpl::start_with_next([=](const QRect &r) {
|
||||||
|
const auto top = r.height()
|
||||||
|
- _emptyButton->height()
|
||||||
|
- st::dialogEmptyButtonSkip;
|
||||||
|
_emptyButton->moveToLeft(st::dialogEmptyButtonSkip, top);
|
||||||
|
}, _emptyButton->lifetime());
|
||||||
|
geometryValue() | rpl::start_with_next([=](const QRect &r) {
|
||||||
|
const auto bottom = _emptyButton
|
||||||
|
? (_emptyButton->height() + st::dialogEmptyButtonSkip)
|
||||||
|
: 0;
|
||||||
|
_emptyList->moveToLeft(
|
||||||
|
0,
|
||||||
|
((r.height() - bottom) - _emptyList->height()) / 2);
|
||||||
|
}, _emptyList->lifetime());
|
||||||
|
|
||||||
|
_empty->setVisible(!_emptyList->isVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::resizeEmpty() {
|
void InnerWidget::resizeEmpty() {
|
||||||
|
@ -4135,6 +4201,13 @@ void InnerWidget::resizeEmpty() {
|
||||||
_empty->resizeToWidth(width() - 2 * skip);
|
_empty->resizeToWidth(width() - 2 * skip);
|
||||||
_empty->move(skip, (st::dialogsEmptyHeight - _empty->height()) / 2);
|
_empty->move(skip, (st::dialogsEmptyHeight - _empty->height()) / 2);
|
||||||
}
|
}
|
||||||
|
if (_emptyList) {
|
||||||
|
_emptyList->resizeToWidth(width());
|
||||||
|
}
|
||||||
|
if (_emptyButton) {
|
||||||
|
const auto skip = st::dialogEmptyButtonSkip;
|
||||||
|
_emptyButton->resizeToWidth(width() - 2 * skip);
|
||||||
|
}
|
||||||
if (_searchEmpty) {
|
if (_searchEmpty) {
|
||||||
_searchEmpty->resizeToWidth(width());
|
_searchEmpty->resizeToWidth(width());
|
||||||
_searchEmpty->move(0, searchedOffset());
|
_searchEmpty->move(0, searchedOffset());
|
||||||
|
|
|
@ -43,6 +43,8 @@ namespace Ui {
|
||||||
class IconButton;
|
class IconButton;
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
class FlatLabel;
|
class FlatLabel;
|
||||||
|
class VerticalLayout;
|
||||||
|
class RoundButton;
|
||||||
struct ScrollToRequest;
|
struct ScrollToRequest;
|
||||||
namespace Controls {
|
namespace Controls {
|
||||||
enum class QuickDialogAction;
|
enum class QuickDialogAction;
|
||||||
|
@ -619,6 +621,8 @@ private:
|
||||||
object_ptr<SearchEmpty> _searchEmpty = { nullptr };
|
object_ptr<SearchEmpty> _searchEmpty = { nullptr };
|
||||||
SearchState _searchEmptyState;
|
SearchState _searchEmptyState;
|
||||||
object_ptr<Ui::FlatLabel> _empty = { nullptr };
|
object_ptr<Ui::FlatLabel> _empty = { nullptr };
|
||||||
|
object_ptr<Ui::VerticalLayout> _emptyList = { nullptr };
|
||||||
|
object_ptr<Ui::RoundButton> _emptyButton = { nullptr };
|
||||||
|
|
||||||
Ui::DraggingScrollManager _draggingScroll;
|
Ui::DraggingScrollManager _draggingScroll;
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,10 @@ void MainList::unreadStateChanged(
|
||||||
const auto notify = !useClouded || wasState.known;
|
const auto notify = !useClouded || wasState.known;
|
||||||
const auto notifier = unreadStateChangeNotifier(notify);
|
const auto notifier = unreadStateChangeNotifier(notify);
|
||||||
_unreadState += nowState - wasState;
|
_unreadState += nowState - wasState;
|
||||||
|
if (_unreadState.chatsMuted > _unreadState.chats
|
||||||
|
|| _unreadState.messagesMuted > _unreadState.messages) {
|
||||||
|
[[maybe_unused]] int a = 0;
|
||||||
|
}
|
||||||
if (updateCloudUnread) {
|
if (updateCloudUnread) {
|
||||||
// Assert(nowState.known);
|
// Assert(nowState.known);
|
||||||
_cloudUnreadState += nowState - wasState;
|
_cloudUnreadState += nowState - wasState;
|
||||||
|
@ -145,6 +149,10 @@ void MainList::unreadEntryChanged(
|
||||||
} else {
|
} else {
|
||||||
_unreadState -= state;
|
_unreadState -= state;
|
||||||
}
|
}
|
||||||
|
if (_unreadState.chatsMuted > _unreadState.chats
|
||||||
|
|| _unreadState.messagesMuted > _unreadState.messages) {
|
||||||
|
[[maybe_unused]] int a = 0;
|
||||||
|
}
|
||||||
if (updateCloudUnread) {
|
if (updateCloudUnread) {
|
||||||
if (added) {
|
if (added) {
|
||||||
_cloudUnreadState += state;
|
_cloudUnreadState += state;
|
||||||
|
|
|
@ -903,10 +903,7 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||||
} else if (const auto topic = row.key.topic()) {
|
} else if (const auto topic = row.key.topic()) {
|
||||||
auto params = Window::SectionShow(
|
auto params = Window::SectionShow(
|
||||||
Window::SectionShow::Way::ClearStack);
|
Window::SectionShow::Way::ClearStack);
|
||||||
params.highlightPart.text = _searchState.query;
|
params.highlight = Window::SearchHighlightId(_searchState.query);
|
||||||
if (!params.highlightPart.empty()) {
|
|
||||||
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
|
|
||||||
}
|
|
||||||
if (row.newWindow) {
|
if (row.newWindow) {
|
||||||
controller()->showInNewWindow(
|
controller()->showInNewWindow(
|
||||||
Window::SeparateId(topic),
|
Window::SeparateId(topic),
|
||||||
|
@ -973,15 +970,12 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||||
return;
|
return;
|
||||||
} else if (history) {
|
} else if (history) {
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
|
const auto showAtMsgId = controller()->uniqueChatsInSearchResults(
|
||||||
? ShowAtUnreadMsgId
|
_searchState
|
||||||
: row.message.fullId.msg;
|
) ? ShowAtUnreadMsgId : row.message.fullId.msg;
|
||||||
auto params = Window::SectionShow(
|
auto params = Window::SectionShow(
|
||||||
Window::SectionShow::Way::ClearStack);
|
Window::SectionShow::Way::ClearStack);
|
||||||
params.highlightPart.text = _searchState.query;
|
params.highlight = Window::SearchHighlightId(_searchState.query);
|
||||||
if (!params.highlightPart.empty()) {
|
|
||||||
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
|
|
||||||
}
|
|
||||||
if (row.newWindow) {
|
if (row.newWindow) {
|
||||||
controller()->showInNewWindow(peer, showAtMsgId);
|
controller()->showInNewWindow(peer, showAtMsgId);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1167,8 +1167,15 @@ Chat ParseChat(const MTPChat &data) {
|
||||||
result.colorIndex = (color && color->data().vcolor())
|
result.colorIndex = (color && color->data().vcolor())
|
||||||
? color->data().vcolor()->v
|
? color->data().vcolor()->v
|
||||||
: PeerColorIndex(result.bareId);
|
: PeerColorIndex(result.bareId);
|
||||||
|
result.isMonoforum = data.is_monoforum();
|
||||||
result.isBroadcast = data.is_broadcast();
|
result.isBroadcast = data.is_broadcast();
|
||||||
result.isSupergroup = data.is_megagroup();
|
result.isSupergroup = data.is_megagroup();
|
||||||
|
result.hasMonoforumAdminRights = data.is_broadcast()
|
||||||
|
&& (data.is_creator()
|
||||||
|
|| (data.vadmin_rights()
|
||||||
|
&& data.vadmin_rights()->data().is_manage_direct_messages()));
|
||||||
|
result.monoforumLinkId
|
||||||
|
= data.vlinked_monoforum_id().value_or_empty();
|
||||||
result.title = ParseString(data.vtitle());
|
result.title = ParseString(data.vtitle());
|
||||||
if (const auto username = data.vusername()) {
|
if (const auto username = data.vusername()) {
|
||||||
result.username = ParseString(*username);
|
result.username = ParseString(*username);
|
||||||
|
@ -1188,15 +1195,6 @@ Chat ParseChat(const MTPChat &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<PeerId, Chat> ParseChatsList(const MTPVector<MTPChat> &data) {
|
|
||||||
auto result = std::map<PeerId, Chat>();
|
|
||||||
for (const auto &chat : data.v) {
|
|
||||||
auto parsed = ParseChat(chat);
|
|
||||||
result.emplace(parsed.id(), std::move(parsed));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utf8String ContactInfo::name() const {
|
Utf8String ContactInfo::name() const {
|
||||||
return firstName.isEmpty()
|
return firstName.isEmpty()
|
||||||
? (lastName.isEmpty()
|
? (lastName.isEmpty()
|
||||||
|
@ -1273,6 +1271,20 @@ std::map<PeerId, Peer> ParsePeersLists(
|
||||||
auto parsed = ParseChat(chat);
|
auto parsed = ParseChat(chat);
|
||||||
result.emplace(parsed.id(), Peer{ std::move(parsed) });
|
result.emplace(parsed.id(), Peer{ std::move(parsed) });
|
||||||
}
|
}
|
||||||
|
for (auto &[peerId, parsed] : result) {
|
||||||
|
if (const auto chat = std::get_if<Chat>(&parsed.data)) {
|
||||||
|
if (chat->isMonoforum) {
|
||||||
|
const auto i = result.find(
|
||||||
|
PeerId(ChannelId(chat->monoforumLinkId)));
|
||||||
|
if (i != end(result)) {
|
||||||
|
chat->isMonoforumAdmin
|
||||||
|
= i->second.chat()->hasMonoforumAdminRights;
|
||||||
|
chat->isMonoforumOfPublicBroadcast
|
||||||
|
= !i->second.chat()->username.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2191,7 +2203,13 @@ const DialogInfo *DialogsInfo::item(int index) const {
|
||||||
|
|
||||||
DialogInfo::Type DialogTypeFromChat(const Chat &chat) {
|
DialogInfo::Type DialogTypeFromChat(const Chat &chat) {
|
||||||
using Type = DialogInfo::Type;
|
using Type = DialogInfo::Type;
|
||||||
return chat.username.isEmpty()
|
return (chat.isMonoforum && !chat.isMonoforumAdmin)
|
||||||
|
? Type::Personal
|
||||||
|
: (chat.isMonoforumAdmin && chat.isMonoforumOfPublicBroadcast)
|
||||||
|
? Type::PublicSupergroup
|
||||||
|
: chat.isMonoforumAdmin
|
||||||
|
? Type::PrivateSupergroup
|
||||||
|
: chat.username.isEmpty()
|
||||||
? (chat.isBroadcast
|
? (chat.isBroadcast
|
||||||
? Type::PrivateChannel
|
? Type::PrivateChannel
|
||||||
: chat.isSupergroup
|
: chat.isSupergroup
|
||||||
|
@ -2252,6 +2270,11 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
|
||||||
info.migratedToChannelId = peer.chat()
|
info.migratedToChannelId = peer.chat()
|
||||||
? peer.chat()->migratedToChannelId
|
? peer.chat()->migratedToChannelId
|
||||||
: 0;
|
: 0;
|
||||||
|
info.isMonoforum = peer.chat()
|
||||||
|
&& peer.chat()->isMonoforum;
|
||||||
|
info.monoforumBroadcastInput = peer.chat()
|
||||||
|
? peer.chat()->monoforumBroadcastInput
|
||||||
|
: MTPInputPeer(MTP_inputPeerEmpty());
|
||||||
}
|
}
|
||||||
info.topMessageId = fields.vtop_message().v;
|
info.topMessageId = fields.vtop_message().v;
|
||||||
const auto messageIt = messages.find(MessageId{
|
const auto messageIt = messages.find(MessageId{
|
||||||
|
@ -2290,6 +2313,10 @@ DialogInfo DialogInfoFromChat(const Chat &data) {
|
||||||
result.topMessageId = 0;
|
result.topMessageId = 0;
|
||||||
result.type = DialogTypeFromChat(data);
|
result.type = DialogTypeFromChat(data);
|
||||||
result.migratedToChannelId = data.migratedToChannelId;
|
result.migratedToChannelId = data.migratedToChannelId;
|
||||||
|
result.isMonoforum = data.isMonoforum;
|
||||||
|
if (data.isMonoforumAdmin) {
|
||||||
|
result.monoforumBroadcastInput = data.monoforumBroadcastInput;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2424,7 +2451,8 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
|
||||||
}
|
}
|
||||||
Unexpected("Type in ApiWrap::onlyMyMessages.");
|
Unexpected("Type in ApiWrap::onlyMyMessages.");
|
||||||
}();
|
}();
|
||||||
dialog.onlyMyMessages = ((settings.fullChats & setting) != setting);
|
dialog.onlyMyMessages = (dialog.type != DialogType::Personal)
|
||||||
|
&& ((settings.fullChats & setting) != setting);
|
||||||
|
|
||||||
ranges::sort(dialog.splits);
|
ranges::sort(dialog.splits);
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,14 +319,19 @@ struct Chat {
|
||||||
Utf8String title;
|
Utf8String title;
|
||||||
Utf8String username;
|
Utf8String username;
|
||||||
uint8 colorIndex = 0;
|
uint8 colorIndex = 0;
|
||||||
|
bool isMonoforum = false;
|
||||||
bool isBroadcast = false;
|
bool isBroadcast = false;
|
||||||
bool isSupergroup = false;
|
bool isSupergroup = false;
|
||||||
|
bool isMonoforumAdmin = false;
|
||||||
|
bool hasMonoforumAdminRights = false;
|
||||||
|
bool isMonoforumOfPublicBroadcast = false;
|
||||||
|
BareId monoforumLinkId = 0;
|
||||||
|
|
||||||
MTPInputPeer input = MTP_inputPeerEmpty();
|
MTPInputPeer input = MTP_inputPeerEmpty();
|
||||||
|
MTPInputPeer monoforumBroadcastInput = MTP_inputPeerEmpty();
|
||||||
};
|
};
|
||||||
|
|
||||||
Chat ParseChat(const MTPChat &data);
|
Chat ParseChat(const MTPChat &data);
|
||||||
std::map<PeerId, Chat> ParseChatsList(const MTPVector<MTPChat> &data);
|
|
||||||
|
|
||||||
struct Peer {
|
struct Peer {
|
||||||
PeerId id() const;
|
PeerId id() const;
|
||||||
|
@ -952,12 +957,15 @@ struct DialogInfo {
|
||||||
MTPInputPeer migratedFromInput = MTP_inputPeerEmpty();
|
MTPInputPeer migratedFromInput = MTP_inputPeerEmpty();
|
||||||
ChannelId migratedToChannelId = 0;
|
ChannelId migratedToChannelId = 0;
|
||||||
|
|
||||||
|
MTPInputPeer monoforumBroadcastInput = MTP_inputPeerEmpty();
|
||||||
|
|
||||||
// User messages splits which contained that dialog.
|
// User messages splits which contained that dialog.
|
||||||
std::vector<int> splits;
|
std::vector<int> splits;
|
||||||
|
|
||||||
// Filled after the whole dialogs list is accumulated.
|
// Filled after the whole dialogs list is accumulated.
|
||||||
bool onlyMyMessages = false;
|
bool onlyMyMessages = false;
|
||||||
bool isLeftChannel = false;
|
bool isLeftChannel = false;
|
||||||
|
bool isMonoforum = false;
|
||||||
QString relativePath;
|
QString relativePath;
|
||||||
|
|
||||||
// Filled when requesting dialog messages.
|
// Filled when requesting dialog messages.
|
||||||
|
|
|
@ -1370,7 +1370,7 @@ void ApiWrap::appendSinglePeerDialogs(Data::DialogsInfo &&info) {
|
||||||
if (isSupergroupType(info.type) && !migratedRequestId) {
|
if (isSupergroupType(info.type) && !migratedRequestId) {
|
||||||
migratedRequestId = requestSinglePeerMigrated(info);
|
migratedRequestId = requestSinglePeerMigrated(info);
|
||||||
continue;
|
continue;
|
||||||
} else if (isChannelType(info.type)) {
|
} else if (isChannelType(info.type) || info.isMonoforum) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (auto i = last; i != 0; --i) {
|
for (auto i = last; i != 0; --i) {
|
||||||
|
@ -1642,6 +1642,9 @@ void ApiWrap::requestChatMessages(
|
||||||
const auto realPeerInput = (splitIndex >= 0)
|
const auto realPeerInput = (splitIndex >= 0)
|
||||||
? _chatProcess->info.input
|
? _chatProcess->info.input
|
||||||
: _chatProcess->info.migratedFromInput;
|
: _chatProcess->info.migratedFromInput;
|
||||||
|
const auto outgoingInput = _chatProcess->info.isMonoforum
|
||||||
|
? _chatProcess->info.monoforumBroadcastInput
|
||||||
|
: MTP_inputPeerSelf();
|
||||||
const auto realSplitIndex = (splitIndex >= 0)
|
const auto realSplitIndex = (splitIndex >= 0)
|
||||||
? splitIndex
|
? splitIndex
|
||||||
: (splitsCount + splitIndex);
|
: (splitsCount + splitIndex);
|
||||||
|
@ -1650,7 +1653,7 @@ void ApiWrap::requestChatMessages(
|
||||||
MTP_flags(MTPmessages_Search::Flag::f_from_id),
|
MTP_flags(MTPmessages_Search::Flag::f_from_id),
|
||||||
realPeerInput,
|
realPeerInput,
|
||||||
MTP_string(), // query
|
MTP_string(), // query
|
||||||
MTP_inputPeerSelf(),
|
outgoingInput,
|
||||||
MTPInputPeer(), // saved_peer_id
|
MTPInputPeer(), // saved_peer_id
|
||||||
MTPVector<MTPReaction>(), // saved_reaction
|
MTPVector<MTPReaction>(), // saved_reaction
|
||||||
MTPint(), // top_msg_id
|
MTPint(), // top_msg_id
|
||||||
|
|
|
@ -111,7 +111,8 @@ std::optional<MTPMessageReplyHeader> PrepareLogReply(
|
||||||
MTP_int(topId),
|
MTP_int(topId),
|
||||||
MTPstring(), // quote_text
|
MTPstring(), // quote_text
|
||||||
MTPVector<MTPMessageEntity>(), // quote_entities
|
MTPVector<MTPMessageEntity>(), // quote_entities
|
||||||
MTPint()); // quote_offset
|
MTPint(), // quote_offset
|
||||||
|
MTPint()); // todo_item_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -2380,7 +2380,7 @@ Dialogs::UnreadState History::chatListUnreadState() const {
|
||||||
return AdjustedForumUnreadState(forum->topicsList()->unreadState());
|
return AdjustedForumUnreadState(forum->topicsList()->unreadState());
|
||||||
} else if (const auto monoforum = peer->monoforum()) {
|
} else if (const auto monoforum = peer->monoforum()) {
|
||||||
return AdjustedForumUnreadState(
|
return AdjustedForumUnreadState(
|
||||||
monoforum->unreadStateWithParentMuted());
|
withMyMuted(monoforum->chatsList()->unreadState()));;
|
||||||
}
|
}
|
||||||
return computeUnreadState();
|
return computeUnreadState();
|
||||||
}
|
}
|
||||||
|
@ -2395,7 +2395,7 @@ Dialogs::BadgesState History::chatListBadgesState() const {
|
||||||
} else if (const auto monoforum = peer->monoforum()) {
|
} else if (const auto monoforum = peer->monoforum()) {
|
||||||
return adjustBadgesStateByFolder(
|
return adjustBadgesStateByFolder(
|
||||||
Dialogs::BadgesForUnread(
|
Dialogs::BadgesForUnread(
|
||||||
monoforum->unreadStateWithParentMuted(),
|
withMyMuted(monoforum->chatsList()->unreadState()),
|
||||||
Dialogs::CountInBadge::Chats,
|
Dialogs::CountInBadge::Chats,
|
||||||
Dialogs::IncludeInBadge::All));
|
Dialogs::IncludeInBadge::All));
|
||||||
}
|
}
|
||||||
|
@ -2429,8 +2429,8 @@ Dialogs::UnreadState History::computeUnreadState() const {
|
||||||
result.mentions = unreadMentions().has() ? 1 : 0;
|
result.mentions = unreadMentions().has() ? 1 : 0;
|
||||||
const auto peer = this->peer.get();
|
const auto peer = this->peer.get();
|
||||||
const auto &settings = AyuSettings::getInstance();
|
const auto &settings = AyuSettings::getInstance();
|
||||||
const auto hideReactions = (peer->isChannel() && !peer->isMegagroup() && !settings.hideChannelReactions)
|
const auto hideReactions = (peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions)
|
||||||
|| (peer->isMegagroup() && !settings.hideGroupReactions);
|
|| (peer->isMegagroup() && !settings.showGroupReactions);
|
||||||
result.reactions = hideReactions ? 0 : (unreadReactions().has() ? 1 : 0);
|
result.reactions = hideReactions ? 0 : (unreadReactions().has() ? 1 : 0);
|
||||||
result.messagesMuted = muted ? result.messages : 0;
|
result.messagesMuted = muted ? result.messages : 0;
|
||||||
result.chatsMuted = muted ? result.chats : 0;
|
result.chatsMuted = muted ? result.chats : 0;
|
||||||
|
@ -2440,6 +2440,16 @@ Dialogs::UnreadState History::computeUnreadState() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dialogs::UnreadState History::withMyMuted(Dialogs::UnreadState state) const {
|
||||||
|
if (muted()) {
|
||||||
|
state.chatsMuted = state.chats;
|
||||||
|
state.marksMuted = state.marks;
|
||||||
|
state.messagesMuted = state.messages;
|
||||||
|
state.reactionsMuted = state.reactions;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
void History::allowChatListMessageResolve() {
|
void History::allowChatListMessageResolve() {
|
||||||
if (_flags & Flag::ResolveChatListMessage) {
|
if (_flags & Flag::ResolveChatListMessage) {
|
||||||
return;
|
return;
|
||||||
|
@ -3368,7 +3378,8 @@ bool History::isForum() const {
|
||||||
void History::monoforumChanged(Data::SavedMessages *old) {
|
void History::monoforumChanged(Data::SavedMessages *old) {
|
||||||
if (inChatList()) {
|
if (inChatList()) {
|
||||||
notifyUnreadStateChange(old
|
notifyUnreadStateChange(old
|
||||||
? AdjustedForumUnreadState(old->chatsList()->unreadState())
|
? AdjustedForumUnreadState(
|
||||||
|
withMyMuted(old->chatsList()->unreadState()))
|
||||||
: computeUnreadState());
|
: computeUnreadState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3378,9 +3389,9 @@ void History::monoforumChanged(Data::SavedMessages *old) {
|
||||||
monoforum->chatsList()->unreadStateChanges(
|
monoforum->chatsList()->unreadStateChanges(
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return (_flags & Flag::IsMonoforumAdmin) && inChatList();
|
return (_flags & Flag::IsMonoforumAdmin) && inChatList();
|
||||||
}) | rpl::map(
|
}) | rpl::map([=](const Dialogs::UnreadState &was) {
|
||||||
AdjustedForumUnreadState
|
return AdjustedForumUnreadState(withMyMuted(was));
|
||||||
) | rpl::start_with_next([=](const Dialogs::UnreadState &old) {
|
}) | rpl::start_with_next([=](const Dialogs::UnreadState &old) {
|
||||||
notifyUnreadStateChange(old);
|
notifyUnreadStateChange(old);
|
||||||
}, monoforum->lifetime());
|
}, monoforum->lifetime());
|
||||||
|
|
||||||
|
|
|
@ -602,6 +602,8 @@ private:
|
||||||
[[nodiscard]] Dialogs::BadgesState adjustBadgesStateByFolder(
|
[[nodiscard]] Dialogs::BadgesState adjustBadgesStateByFolder(
|
||||||
Dialogs::BadgesState state) const;
|
Dialogs::BadgesState state) const;
|
||||||
[[nodiscard]] Dialogs::UnreadState computeUnreadState() const;
|
[[nodiscard]] Dialogs::UnreadState computeUnreadState() const;
|
||||||
|
[[nodiscard]] Dialogs::UnreadState withMyMuted(
|
||||||
|
Dialogs::UnreadState state) const;
|
||||||
void setFolderPointer(Data::Folder *folder);
|
void setFolderPointer(Data::Folder *folder);
|
||||||
|
|
||||||
void hasUnreadMentionChanged(bool has) override;
|
void hasUnreadMentionChanged(bool has) override;
|
||||||
|
|
|
@ -674,10 +674,10 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
||||||
: still)->fullId();
|
: still)->fullId();
|
||||||
_widget->replyToMessage({
|
_widget->replyToMessage({
|
||||||
.messageId = replyToItemId,
|
.messageId = replyToItemId,
|
||||||
.quote = selected.text,
|
.quote = selected.highlight.quote,
|
||||||
.quoteOffset = selected.offset,
|
.quoteOffset = selected.highlight.quoteOffset,
|
||||||
});
|
});
|
||||||
if (!selected.text.empty()) {
|
if (!selected.highlight.quote.empty()) {
|
||||||
_widget->clearSelected();
|
_widget->clearSelected();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1221,8 +1221,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
if (markingAsViewed && item->hasUnwatchedEffect()) {
|
if (markingAsViewed && item->hasUnwatchedEffect()) {
|
||||||
const auto peer = item->history()->peer;
|
const auto peer = item->history()->peer;
|
||||||
const auto &settings = AyuSettings::getInstance();
|
const auto &settings = AyuSettings::getInstance();
|
||||||
const auto hide = (!settings.hideChannelReactions && peer->isChannel() && !peer->isMegagroup()) ||
|
const auto hide = (!settings.showChannelReactions && peer->isChannel() && !peer->isMegagroup()) ||
|
||||||
(!settings.hideGroupReactions && peer->isMegagroup());
|
(!settings.showGroupReactions && peer->isMegagroup());
|
||||||
if (!hide) {
|
if (!hide) {
|
||||||
startEffects.emplace(view);
|
startEffects.emplace(view);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2409,6 +2409,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
const auto linkUserpicPeerId = (link && _dragStateUserpic)
|
const auto linkUserpicPeerId = (link && _dragStateUserpic)
|
||||||
? link->property(kPeerLinkPeerIdProperty).toULongLong()
|
? link->property(kPeerLinkPeerIdProperty).toULongLong()
|
||||||
: 0;
|
: 0;
|
||||||
|
const auto todoListTaskId = link
|
||||||
|
? link->property(kTodoListItemIdProperty).toInt()
|
||||||
|
: 0;
|
||||||
const auto session = &this->session();
|
const auto session = &this->session();
|
||||||
_whoReactedMenuLifetime.destroy();
|
_whoReactedMenuLifetime.destroy();
|
||||||
if (!clickedReaction.empty()
|
if (!clickedReaction.empty()
|
||||||
|
@ -2777,20 +2780,21 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
const auto selected = selectedQuote(item);
|
const auto selected = selectedQuote(item);
|
||||||
auto text = (selected
|
auto text = (selected
|
||||||
? tr::lng_context_quote_and_reply
|
? tr::lng_context_quote_and_reply
|
||||||
|
: todoListTaskId
|
||||||
|
? tr::lng_context_reply_to_task
|
||||||
: tr::lng_context_reply_msg)(
|
: tr::lng_context_reply_msg)(
|
||||||
tr::now,
|
tr::now,
|
||||||
Ui::Text::FixAmpersandInAction);
|
Ui::Text::FixAmpersandInAction);
|
||||||
const auto replyToItem = selected.item ? selected.item : item;
|
const auto replyToItem = selected.item ? selected.item : item;
|
||||||
const auto itemId = replyToItem->fullId();
|
const auto itemId = replyToItem->fullId();
|
||||||
const auto quote = selected.text;
|
|
||||||
const auto quoteOffset = selected.offset;
|
|
||||||
_menu->addAction(std::move(text), [=] {
|
_menu->addAction(std::move(text), [=] {
|
||||||
_widget->replyToMessage({
|
_widget->replyToMessage({
|
||||||
.messageId = itemId,
|
.messageId = itemId,
|
||||||
.quote = quote,
|
.quote = selected.highlight.quote,
|
||||||
.quoteOffset = quoteOffset,
|
.quoteOffset = selected.highlight.quoteOffset,
|
||||||
|
.todoItemId = todoListTaskId,
|
||||||
});
|
});
|
||||||
if (!quote.empty()) {
|
if (!selected.highlight.quote.empty()) {
|
||||||
_widget->clearSelected();
|
_widget->clearSelected();
|
||||||
}
|
}
|
||||||
}, &st::menuIconReply);
|
}, &st::menuIconReply);
|
||||||
|
@ -2809,7 +2813,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
Window::PeerMenuAddTodoListTasks(_controller, item);
|
Window::PeerMenuAddTodoListTasks(_controller, item);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
&st::menuIconCreateTodoList);
|
&st::menuIconAdd);
|
||||||
};
|
};
|
||||||
const auto lnkPhoto = link
|
const auto lnkPhoto = link
|
||||||
? reinterpret_cast<PhotoData*>(
|
? reinterpret_cast<PhotoData*>(
|
||||||
|
@ -2955,11 +2959,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
: nullptr;
|
: nullptr;
|
||||||
if (sponsored) {
|
if (sponsored) {
|
||||||
Menu::FillSponsored(
|
Menu::FillSponsored(
|
||||||
this,
|
|
||||||
Ui::Menu::CreateAddActionCallback(_menu),
|
Ui::Menu::CreateAddActionCallback(_menu),
|
||||||
controller->uiShow(),
|
controller->uiShow(),
|
||||||
sponsored->fullId(),
|
sponsored->fullId());
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
if (isUponSelected > 0) {
|
if (isUponSelected > 0) {
|
||||||
addReplyAction(item);
|
addReplyAction(item);
|
||||||
|
|
|
@ -964,12 +964,26 @@ void HistoryItem::updateServiceDependent(bool force) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dependent->lnk) {
|
if (!dependent->lnk) {
|
||||||
|
auto todoItemId = 0;
|
||||||
|
if (const auto done = Get<HistoryServiceTodoCompletions>()) {
|
||||||
|
const auto &items = !done->completed.empty()
|
||||||
|
? done->completed
|
||||||
|
: done->incompleted;
|
||||||
|
if (items.size() == 1) {
|
||||||
|
todoItemId = items.front();
|
||||||
|
}
|
||||||
|
} else if (const auto append = Get<HistoryServiceTodoAppendTasks>()) {
|
||||||
|
if (append->list.size() == 1) {
|
||||||
|
todoItemId = append->list.front().id;
|
||||||
|
}
|
||||||
|
}
|
||||||
dependent->lnk = JumpToMessageClickHandler(
|
dependent->lnk = JumpToMessageClickHandler(
|
||||||
(dependent->peerId
|
(dependent->peerId
|
||||||
? _history->owner().peer(dependent->peerId)
|
? _history->owner().peer(dependent->peerId)
|
||||||
: _history->peer),
|
: _history->peer),
|
||||||
dependent->msgId,
|
dependent->msgId,
|
||||||
fullId());
|
fullId(),
|
||||||
|
{ .todoItemId = todoItemId });
|
||||||
}
|
}
|
||||||
auto gotDependencyItem = false;
|
auto gotDependencyItem = false;
|
||||||
if (!dependent->msg) {
|
if (!dependent->msg) {
|
||||||
|
@ -1316,14 +1330,8 @@ void HistoryItem::setCommentsItemId(FullMsgId id) {
|
||||||
void HistoryItem::setServiceText(PreparedServiceText &&prepared) {
|
void HistoryItem::setServiceText(PreparedServiceText &&prepared) {
|
||||||
auto text = std::move(prepared.text);
|
auto text = std::move(prepared.text);
|
||||||
|
|
||||||
const auto &settings = AyuSettings::getInstance();
|
|
||||||
if (date() > 0) {
|
if (date() > 0) {
|
||||||
const auto timeString = QString(" (%1)").arg(QLocale().toString(
|
const auto timeString = QString(" (%1)").arg(formatMessageTime(base::unixtime::parse(_date).time()));
|
||||||
base::unixtime::parse(_date),
|
|
||||||
settings.showMessageSeconds
|
|
||||||
? QLocale::system().timeFormat(QLocale::LongFormat).remove(" t")
|
|
||||||
: QLocale::system().timeFormat(QLocale::ShortFormat)
|
|
||||||
));
|
|
||||||
if (!text.text.isEmpty() && !text.text.contains(timeString)) {
|
if (!text.text.isEmpty() && !text.text.contains(timeString)) {
|
||||||
text = text.append(timeString);
|
text = text.append(timeString);
|
||||||
}
|
}
|
||||||
|
@ -1858,7 +1866,10 @@ bool HistoryItem::isAyuNoForwards() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryItem::canLookupMessageAuthor() const {
|
bool HistoryItem::canLookupMessageAuthor() const {
|
||||||
return isRegular() && _history->amMonoforumAdmin() && _from->isChannel();
|
return isRegular()
|
||||||
|
&& !isService()
|
||||||
|
&& _history->amMonoforumAdmin()
|
||||||
|
&& _from->isChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryItem::skipNotification() const {
|
bool HistoryItem::skipNotification() const {
|
||||||
|
@ -4392,6 +4403,7 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) {
|
||||||
: replyTo.monoforumPeerId
|
: replyTo.monoforumPeerId
|
||||||
? replyTo.monoforumPeerId
|
? replyTo.monoforumPeerId
|
||||||
: PeerId();
|
: PeerId();
|
||||||
|
config.reply.todoItemId = replyTo.todoItemId;
|
||||||
const auto replyToTop = replyTo.topicRootId
|
const auto replyToTop = replyTo.topicRootId
|
||||||
? replyTo.topicRootId
|
? replyTo.topicRootId
|
||||||
: LookupReplyToTop(_history, to);
|
: LookupReplyToTop(_history, to);
|
||||||
|
|
|
@ -390,6 +390,7 @@ ReplyFields ReplyFieldsFromMTP(
|
||||||
= data.vreply_to_top_id().value_or(result.messageId.bare);
|
= data.vreply_to_top_id().value_or(result.messageId.bare);
|
||||||
result.topicPost = data.is_forum_topic() ? 1 : 0;
|
result.topicPost = data.is_forum_topic() ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
result.todoItemId = data.vtodo_item_id().value_or_empty();
|
||||||
if (const auto header = data.vreply_from()) {
|
if (const auto header = data.vreply_from()) {
|
||||||
const auto &data = header->data();
|
const auto &data = header->data();
|
||||||
result.externalPostAuthor
|
result.externalPostAuthor
|
||||||
|
@ -704,7 +705,8 @@ auto ReplyMarkupClickHandler::getUrlButton() const
|
||||||
-> const HistoryMessageMarkupButton* {
|
-> const HistoryMessageMarkupButton* {
|
||||||
if (const auto button = getButton()) {
|
if (const auto button = getButton()) {
|
||||||
using Type = HistoryMessageMarkupButton::Type;
|
using Type = HistoryMessageMarkupButton::Type;
|
||||||
if (button->type == Type::Url || button->type == Type::Auth || button->type == Type::Callback) {
|
if (button->type == Type::Url || button->type == Type::Auth || button->type == Type::Callback ||
|
||||||
|
button->type == Type::WebView || button->type == Type::SimpleWebView) {
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,6 +277,7 @@ struct ReplyFields {
|
||||||
MsgId messageId = 0;
|
MsgId messageId = 0;
|
||||||
MsgId topMessageId = 0;
|
MsgId topMessageId = 0;
|
||||||
StoryId storyId = 0;
|
StoryId storyId = 0;
|
||||||
|
int todoItemId = 0;
|
||||||
uint32 quoteOffset : 30 = 0;
|
uint32 quoteOffset : 30 = 0;
|
||||||
uint32 manualQuote : 1 = 0;
|
uint32 manualQuote : 1 = 0;
|
||||||
uint32 topicPost : 1 = 0;
|
uint32 topicPost : 1 = 0;
|
||||||
|
|
|
@ -722,22 +722,19 @@ bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
|
||||||
ClickHandlerPtr JumpToMessageClickHandler(
|
ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
FullMsgId returnToId,
|
FullMsgId returnToId,
|
||||||
TextWithEntities highlightPart,
|
MessageHighlightId highlight) {
|
||||||
int highlightPartOffsetHint) {
|
|
||||||
return JumpToMessageClickHandler(
|
return JumpToMessageClickHandler(
|
||||||
item->history()->peer,
|
item->history()->peer,
|
||||||
item->id,
|
item->id,
|
||||||
returnToId,
|
returnToId,
|
||||||
std::move(highlightPart),
|
std::move(highlight));
|
||||||
highlightPartOffsetHint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr JumpToMessageClickHandler(
|
ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId msgId,
|
MsgId msgId,
|
||||||
FullMsgId returnToId,
|
FullMsgId returnToId,
|
||||||
TextWithEntities highlightPart,
|
MessageHighlightId highlight) {
|
||||||
int highlightPartOffsetHint) {
|
|
||||||
return std::make_shared<LambdaClickHandler>([=] {
|
return std::make_shared<LambdaClickHandler>([=] {
|
||||||
const auto separate = Core::App().separateWindowFor(peer);
|
const auto separate = Core::App().separateWindowFor(peer);
|
||||||
const auto controller = separate
|
const auto controller = separate
|
||||||
|
@ -747,8 +744,7 @@ ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
auto params = Window::SectionShow{
|
auto params = Window::SectionShow{
|
||||||
Window::SectionShow::Way::Forward
|
Window::SectionShow::Way::Forward
|
||||||
};
|
};
|
||||||
params.highlightPart = highlightPart;
|
params.highlight = highlight;
|
||||||
params.highlightPartOffsetHint = highlightPartOffsetHint;
|
|
||||||
params.origin = Window::SectionShow::OriginMessage{
|
params.origin = Window::SectionShow::OriginMessage{
|
||||||
returnToId
|
returnToId
|
||||||
};
|
};
|
||||||
|
@ -910,7 +906,8 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
|
||||||
| Flag::f_quote_offset))
|
| Flag::f_quote_offset))
|
||||||
| (quoteEntities.v.empty()
|
| (quoteEntities.v.empty()
|
||||||
? Flag()
|
? Flag()
|
||||||
: Flag::f_quote_entities)),
|
: Flag::f_quote_entities)
|
||||||
|
| (replyTo.todoItemId ? Flag::f_todo_item_id : Flag())),
|
||||||
MTP_int(replyTo.messageId.msg),
|
MTP_int(replyTo.messageId.msg),
|
||||||
peerToMTP(externalPeerId),
|
peerToMTP(externalPeerId),
|
||||||
MTPMessageFwdHeader(), // reply_from
|
MTPMessageFwdHeader(), // reply_from
|
||||||
|
@ -918,7 +915,8 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
|
||||||
MTP_int(replyToTop),
|
MTP_int(replyToTop),
|
||||||
MTP_string(replyTo.quote.text),
|
MTP_string(replyTo.quote.text),
|
||||||
quoteEntities,
|
quoteEntities,
|
||||||
MTP_int(replyTo.quoteOffset));
|
MTP_int(replyTo.quoteOffset),
|
||||||
|
MTP_int(replyTo.todoItemId));
|
||||||
}
|
}
|
||||||
return MTPMessageReplyHeader();
|
return MTPMessageReplyHeader();
|
||||||
}
|
}
|
||||||
|
@ -1173,8 +1171,8 @@ void CheckReactionNotificationSchedule(
|
||||||
}
|
}
|
||||||
const auto peer = item->history()->peer;
|
const auto peer = item->history()->peer;
|
||||||
const auto &settings = AyuSettings::getInstance();
|
const auto &settings = AyuSettings::getInstance();
|
||||||
if ((peer->isChannel() && !peer->isMegagroup() && !settings.hideChannelReactions)
|
if ((peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions)
|
||||||
|| (peer->isMegagroup() && !settings.hideGroupReactions)) {
|
|| (peer->isMegagroup() && !settings.showGroupReactions)) {
|
||||||
item->markEffectWatched();
|
item->markEffectWatched();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,13 +229,11 @@ private:
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId msgId,
|
MsgId msgId,
|
||||||
FullMsgId returnToId = FullMsgId(),
|
FullMsgId returnToId = FullMsgId(),
|
||||||
TextWithEntities highlightPart = {},
|
MessageHighlightId highlight = {});
|
||||||
int highlightPartOffsetHint = 0);
|
|
||||||
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
FullMsgId returnToId = FullMsgId(),
|
FullMsgId returnToId = FullMsgId(),
|
||||||
TextWithEntities highlightPart = {},
|
MessageHighlightId highlight = {});
|
||||||
int highlightPartOffsetHint = 0);
|
|
||||||
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
|
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
|
||||||
not_null<Data::Story*> story);
|
not_null<Data::Story*> story);
|
||||||
ClickHandlerPtr JumpToStoryClickHandler(
|
ClickHandlerPtr JumpToStoryClickHandler(
|
||||||
|
|
|
@ -65,6 +65,7 @@ Ui::ChatPaintHighlight ElementHighlighter::state(
|
||||||
if (item->fullId() == _highlighted.itemId) {
|
if (item->fullId() == _highlighted.itemId) {
|
||||||
auto result = _animation.state();
|
auto result = _animation.state();
|
||||||
result.range = _highlighted.part;
|
result.range = _highlighted.part;
|
||||||
|
result.todoItemId = _highlighted.todoListId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
@ -82,19 +83,27 @@ ElementHighlighter::Highlight ElementHighlighter::computeHighlight(
|
||||||
const auto i = ranges::find(group->items, item);
|
const auto i = ranges::find(group->items, item);
|
||||||
if (i != end(group->items)) {
|
if (i != end(group->items)) {
|
||||||
const auto index = int(i - begin(group->items));
|
const auto index = int(i - begin(group->items));
|
||||||
if (quote.text.empty()) {
|
if (quote.highlight.empty()) {
|
||||||
return { leaderId, AddGroupItemSelection({}, index) };
|
return { leaderId, AddGroupItemSelection({}, index) };
|
||||||
} else if (const auto leaderView = _viewForItem(leader)) {
|
} else if (const auto leaderView = _viewForItem(leader)) {
|
||||||
return { leaderId, leaderView->selectionFromQuote(quote) };
|
return {
|
||||||
|
leaderId,
|
||||||
|
leaderView->selectionFromQuote(quote),
|
||||||
|
quote.highlight.todoItemId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { leaderId };
|
return { leaderId, {}, quote.highlight.todoItemId };
|
||||||
} else if (quote.text.empty()) {
|
} else if (quote.highlight.quote.empty()) {
|
||||||
return { item->fullId() };
|
return { item->fullId(), {}, quote.highlight.todoItemId };
|
||||||
} else if (const auto view = _viewForItem(item)) {
|
} else if (const auto view = _viewForItem(item)) {
|
||||||
return { item->fullId(), view->selectionFromQuote(quote) };
|
return {
|
||||||
|
item->fullId(),
|
||||||
|
view->selectionFromQuote(quote),
|
||||||
|
quote.highlight.todoItemId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { item->fullId() };
|
return { item->fullId(), {}, quote.highlight.todoItemId };
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::highlight(Highlight data) {
|
void ElementHighlighter::highlight(Highlight data) {
|
||||||
|
@ -108,7 +117,7 @@ void ElementHighlighter::highlight(Highlight data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_highlighted = data;
|
_highlighted = data;
|
||||||
_animation.start(!data.part.empty()
|
_animation.start((!data.part.empty() || data.todoListId)
|
||||||
&& !IsSubGroupSelection(data.part));
|
&& !IsSubGroupSelection(data.part));
|
||||||
|
|
||||||
repaintHighlightedItem(view);
|
repaintHighlightedItem(view);
|
||||||
|
|
|
@ -65,6 +65,7 @@ private:
|
||||||
struct Highlight {
|
struct Highlight {
|
||||||
FullMsgId itemId;
|
FullMsgId itemId;
|
||||||
TextSelection part;
|
TextSelection part;
|
||||||
|
int todoListId = 0;
|
||||||
|
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return itemId.operator bool();
|
return itemId.operator bool();
|
||||||
|
|
|
@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_todo_list.h"
|
||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
|
@ -1575,12 +1576,21 @@ int HistoryWidget::itemTopForHighlight(
|
||||||
const auto heightLeft = (visibleAreaHeight - viewHeight);
|
const auto heightLeft = (visibleAreaHeight - viewHeight);
|
||||||
if (heightLeft >= 0) {
|
if (heightLeft >= 0) {
|
||||||
return std::max(itemTop - (heightLeft / 2), 0);
|
return std::max(itemTop - (heightLeft / 2), 0);
|
||||||
} else if (const auto sel = itemHighlight(item).range
|
} else if (const auto highlight = itemHighlight(item)
|
||||||
; !sel.empty() && !IsSubGroupSelection(sel)) {
|
; (!highlight.range.empty() || highlight.todoItemId)
|
||||||
|
&& !IsSubGroupSelection(highlight.range)) {
|
||||||
|
const auto sel = highlight.range;
|
||||||
const auto single = st::messageTextStyle.font->height;
|
const auto single = st::messageTextStyle.font->height;
|
||||||
const auto begin = HistoryView::FindViewY(view, sel.from) - single;
|
const auto todoy = sel.empty()
|
||||||
const auto end = HistoryView::FindViewY(view, sel.to, begin + single)
|
? HistoryView::FindViewTaskY(view, highlight.todoItemId)
|
||||||
+ 2 * single;
|
: 0;
|
||||||
|
const auto begin = sel.empty()
|
||||||
|
? (todoy - 4 * single)
|
||||||
|
: HistoryView::FindViewY(view, sel.from) - single;
|
||||||
|
const auto end = sel.empty()
|
||||||
|
? (todoy + 4 * single)
|
||||||
|
: (HistoryView::FindViewY(view, sel.to, begin + single)
|
||||||
|
+ 2 * single);
|
||||||
auto result = itemTop;
|
auto result = itemTop;
|
||||||
if (end > visibleAreaHeight) {
|
if (end > visibleAreaHeight) {
|
||||||
result = std::max(result, itemTop + end - visibleAreaHeight);
|
result = std::max(result, itemTop + end - visibleAreaHeight);
|
||||||
|
@ -5797,8 +5807,7 @@ void HistoryWidget::switchToSearch(QString query) {
|
||||||
const auto item = activation.item;
|
const auto item = activation.item;
|
||||||
auto params = ::Window::SectionShow(
|
auto params = ::Window::SectionShow(
|
||||||
::Window::SectionShow::Way::ClearStack);
|
::Window::SectionShow::Way::ClearStack);
|
||||||
params.highlightPart = { activation.query };
|
params.highlight = Window::SearchHighlightId(activation.query);
|
||||||
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
|
|
||||||
controller()->showPeerHistory(
|
controller()->showPeerHistory(
|
||||||
item->history()->peer->id,
|
item->history()->peer->id,
|
||||||
params,
|
params,
|
||||||
|
@ -6907,8 +6916,7 @@ int HistoryWidget::countInitialScrollTop() {
|
||||||
|
|
||||||
enqueueMessageHighlight({
|
enqueueMessageHighlight({
|
||||||
item,
|
item,
|
||||||
base::take(_showAtMsgParams.highlightPart),
|
base::take(_showAtMsgParams.highlight),
|
||||||
base::take(_showAtMsgParams.highlightPartOffsetHint),
|
|
||||||
});
|
});
|
||||||
const auto result = itemTopForHighlight(view);
|
const auto result = itemTopForHighlight(view);
|
||||||
createUnreadBarIfBelowVisibleArea(result);
|
createUnreadBarIfBelowVisibleArea(result);
|
||||||
|
@ -7670,12 +7678,7 @@ void HistoryWidget::editDraftOptions() {
|
||||||
|
|
||||||
void HistoryWidget::jumpToReply(FullReplyTo to) {
|
void HistoryWidget::jumpToReply(FullReplyTo to) {
|
||||||
if (const auto item = session().data().message(to.messageId)) {
|
if (const auto item = session().data().message(to.messageId)) {
|
||||||
JumpToMessageClickHandler(
|
JumpToMessageClickHandler(item, {}, to.highlight())->onClick({});
|
||||||
item,
|
|
||||||
{},
|
|
||||||
to.quote,
|
|
||||||
to.quoteOffset
|
|
||||||
)->onClick({});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8718,7 +8721,7 @@ void HistoryWidget::clearFieldText(
|
||||||
void HistoryWidget::replyToMessage(FullReplyTo id) {
|
void HistoryWidget::replyToMessage(FullReplyTo id) {
|
||||||
if (const auto item = session().data().message(id.messageId)) {
|
if (const auto item = session().data().message(id.messageId)) {
|
||||||
if (CanSendReply(item) && !base::IsCtrlPressed()) {
|
if (CanSendReply(item) && !base::IsCtrlPressed()) {
|
||||||
replyToMessage(item, id.quote, id.quoteOffset);
|
replyToMessage(item, id);
|
||||||
} else if (item->allowsForward()) {
|
} else if (item->allowsForward()) {
|
||||||
const auto show = controller()->uiShow();
|
const auto show = controller()->uiShow();
|
||||||
HistoryView::Controls::ShowReplyToChatBox(show, id);
|
HistoryView::Controls::ShowReplyToChatBox(show, id);
|
||||||
|
@ -8731,16 +8734,12 @@ void HistoryWidget::replyToMessage(FullReplyTo id) {
|
||||||
|
|
||||||
void HistoryWidget::replyToMessage(
|
void HistoryWidget::replyToMessage(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
TextWithEntities quote,
|
FullReplyTo fields) {
|
||||||
int quoteOffset) {
|
|
||||||
if (isJoinChannel()) {
|
if (isJoinChannel()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_processingReplyTo = {
|
fields.messageId = item->fullId();
|
||||||
.messageId = item->fullId(),
|
_processingReplyTo = fields;
|
||||||
.quote = quote,
|
|
||||||
.quoteOffset = quoteOffset,
|
|
||||||
};
|
|
||||||
_processingReplyItem = item;
|
_processingReplyItem = item;
|
||||||
processReply();
|
processReply();
|
||||||
}
|
}
|
||||||
|
@ -9429,11 +9428,24 @@ void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
|
||||||
.session = &session(),
|
.session = &session(),
|
||||||
.repaint = [=] { updateField(); },
|
.repaint = [=] { updateField(); },
|
||||||
});
|
});
|
||||||
|
const auto text = [&] {
|
||||||
|
const auto media = _replyTo.todoItemId ? item->media() : nullptr;
|
||||||
|
if (const auto todolist = media ? media->todolist() : nullptr) {
|
||||||
|
const auto i = ranges::find(
|
||||||
|
todolist->items,
|
||||||
|
_replyTo.todoItemId,
|
||||||
|
&TodoListItem::id);
|
||||||
|
if (i != end(todolist->items)) {
|
||||||
|
return i->text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (_editMsgId || _replyTo.quote.empty())
|
||||||
|
? item->inReplyText()
|
||||||
|
: _replyTo.quote;
|
||||||
|
}();
|
||||||
_replyEditMsgText.setMarkedText(
|
_replyEditMsgText.setMarkedText(
|
||||||
st::defaultTextStyle,
|
st::defaultTextStyle,
|
||||||
((_editMsgId || _replyTo.quote.empty())
|
text,
|
||||||
? item->inReplyText()
|
|
||||||
: _replyTo.quote),
|
|
||||||
Ui::DialogTextOptions(),
|
Ui::DialogTextOptions(),
|
||||||
context);
|
context);
|
||||||
if (fieldOrDisabledShown() || isRecording()) {
|
if (fieldOrDisabledShown() || isRecording()) {
|
||||||
|
@ -9519,10 +9531,9 @@ void HistoryWidget::updateReplyToName() {
|
||||||
.customEmojiLoopLimit = 1,
|
.customEmojiLoopLimit = 1,
|
||||||
});
|
});
|
||||||
const auto to = _replyEditMsg ? _replyEditMsg : _kbReplyTo;
|
const auto to = _replyEditMsg ? _replyEditMsg : _kbReplyTo;
|
||||||
const auto replyToQuote = _replyTo && !_replyTo.quote.empty();
|
|
||||||
_replyToName.setMarkedText(
|
_replyToName.setMarkedText(
|
||||||
st::fwdTextStyle,
|
st::fwdTextStyle,
|
||||||
HistoryView::Reply::ComposePreviewName(_history, to, replyToQuote),
|
HistoryView::Reply::ComposePreviewName(_history, to, _replyTo),
|
||||||
Ui::NameTextOptions(),
|
Ui::NameTextOptions(),
|
||||||
context);
|
context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,8 +205,7 @@ public:
|
||||||
void replyToMessage(FullReplyTo id);
|
void replyToMessage(FullReplyTo id);
|
||||||
void replyToMessage(
|
void replyToMessage(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
TextWithEntities quote = {},
|
FullReplyTo fields = {});
|
||||||
int quoteOffset = 0);
|
|
||||||
void editMessage(
|
void editMessage(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const TextSelection &selection);
|
const TextSelection &selection);
|
||||||
|
|
|
@ -92,6 +92,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_menu_icons.h"
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
|
// AyuGram includes
|
||||||
|
#include "ayu/ayu_settings.h"
|
||||||
|
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -119,6 +123,13 @@ using SetHistoryArgs = ComposeControls::SetHistoryArgs;
|
||||||
using VoiceRecordBar = Controls::VoiceRecordBar;
|
using VoiceRecordBar = Controls::VoiceRecordBar;
|
||||||
using ForwardPanel = Controls::ForwardPanel;
|
using ForwardPanel = Controls::ForwardPanel;
|
||||||
|
|
||||||
|
#define SWITCH_BUTTON(button, show_v) \
|
||||||
|
if (show_v) { \
|
||||||
|
(button)->show(); \
|
||||||
|
} else { \
|
||||||
|
(button)->hide(); \
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const ChatHelpers::PauseReason kDefaultPanelsLevel
|
const ChatHelpers::PauseReason kDefaultPanelsLevel
|
||||||
|
@ -492,10 +503,9 @@ void FieldHeader::setShownMessage(HistoryItem *item) {
|
||||||
.customEmojiLoopLimit = 1,
|
.customEmojiLoopLimit = 1,
|
||||||
});
|
});
|
||||||
const auto replyTo = _replyTo.current();
|
const auto replyTo = _replyTo.current();
|
||||||
const auto quote = replyTo && !replyTo.quote.empty();
|
|
||||||
_shownMessageName.setMarkedText(
|
_shownMessageName.setMarkedText(
|
||||||
st::fwdTextStyle,
|
st::fwdTextStyle,
|
||||||
HistoryView::Reply::ComposePreviewName(_history, item, quote),
|
HistoryView::Reply::ComposePreviewName(_history, item, replyTo),
|
||||||
Ui::NameTextOptions(),
|
Ui::NameTextOptions(),
|
||||||
context);
|
context);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1595,6 +1605,14 @@ void ComposeControls::init() {
|
||||||
updateAttachBotsMenu();
|
updateAttachBotsMenu();
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
|
AyuSettings::get_historyUpdateReactive() | rpl::start_with_next([=]
|
||||||
|
{
|
||||||
|
updateSendButtonType();
|
||||||
|
updateControlsVisibility();
|
||||||
|
updateControlsGeometry(_wrap->size());
|
||||||
|
orderControls();
|
||||||
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
orderControls();
|
orderControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1604,6 +1622,11 @@ void ComposeControls::orderControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ComposeControls::showRecordButton() const {
|
bool ComposeControls::showRecordButton() const {
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
if (!settings.showMicrophoneButtonInMessageField) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (_recordAvailability != Webrtc::RecordAvailability::None)
|
return (_recordAvailability != Webrtc::RecordAvailability::None)
|
||||||
&& !_voiceRecordBar->isListenState()
|
&& !_voiceRecordBar->isListenState()
|
||||||
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
||||||
|
@ -2686,17 +2709,19 @@ void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
// (_attachToggle|_replaceMedia) (_sendAs) -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel
|
// (_attachToggle|_replaceMedia) (_sendAs) -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel
|
||||||
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_botCommandStart) _tabbedSelectorToggle _send
|
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_botCommandStart) _tabbedSelectorToggle _send
|
||||||
|
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
|
||||||
const auto fieldWidth = size.width()
|
const auto fieldWidth = size.width()
|
||||||
- _attachToggle->width()
|
- (settings.showAttachButtonInMessageField ? _attachToggle->width() : 0)
|
||||||
- (_sendAs ? _sendAs->width() : 0)
|
- (_sendAs ? _sendAs->width() : 0)
|
||||||
- st::historySendRight
|
- st::historySendRight
|
||||||
- _send->width()
|
- _send->width()
|
||||||
- _tabbedSelectorToggle->width()
|
- (settings.showEmojiButtonInMessageField ? _tabbedSelectorToggle->width() : 0)
|
||||||
- (_likeShown ? _like->width() : 0)
|
- (_likeShown ? _like->width() : 0)
|
||||||
- (_botCommandShown ? _botCommandStart->width() : 0)
|
- (_botCommandShown && settings.showCommandsButtonInMessageField ? _botCommandStart->width() : 0)
|
||||||
- (_silent ? _silent->width() : 0)
|
- (_silent ? _silent->width() : 0)
|
||||||
- (_scheduled ? _scheduled->width() : 0)
|
- (_scheduled ? _scheduled->width() : 0)
|
||||||
- (_ttlInfo ? _ttlInfo->width() : 0);
|
- (_ttlInfo && settings.showAutoDeleteButtonInMessageField ? _ttlInfo->width() : 0);
|
||||||
{
|
{
|
||||||
const auto oldFieldHeight = _field->height();
|
const auto oldFieldHeight = _field->height();
|
||||||
_field->resizeToWidth(fieldWidth);
|
_field->resizeToWidth(fieldWidth);
|
||||||
|
@ -2713,8 +2738,10 @@ void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
if (_replaceMedia) {
|
if (_replaceMedia) {
|
||||||
_replaceMedia->moveToLeft(left, buttonsTop);
|
_replaceMedia->moveToLeft(left, buttonsTop);
|
||||||
}
|
}
|
||||||
_attachToggle->moveToLeft(left, buttonsTop);
|
if (settings.showAttachButtonInMessageField) {
|
||||||
left += _attachToggle->width();
|
_attachToggle->moveToLeft(left, buttonsTop);
|
||||||
|
left += _attachToggle->width();
|
||||||
|
}
|
||||||
if (_sendAs) {
|
if (_sendAs) {
|
||||||
_sendAs->moveToLeft(left, buttonsTop);
|
_sendAs->moveToLeft(left, buttonsTop);
|
||||||
left += _sendAs->width();
|
left += _sendAs->width();
|
||||||
|
@ -2731,8 +2758,10 @@ void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
auto right = st::historySendRight;
|
auto right = st::historySendRight;
|
||||||
_send->moveToRight(right, buttonsTop);
|
_send->moveToRight(right, buttonsTop);
|
||||||
right += _send->width();
|
right += _send->width();
|
||||||
_tabbedSelectorToggle->moveToRight(right, buttonsTop);
|
if (settings.showEmojiButtonInMessageField) {
|
||||||
right += _tabbedSelectorToggle->width();
|
_tabbedSelectorToggle->moveToRight(right, buttonsTop);
|
||||||
|
right += _tabbedSelectorToggle->width();
|
||||||
|
}
|
||||||
if (_like) {
|
if (_like) {
|
||||||
using Type = Controls::WriteRestrictionType;
|
using Type = Controls::WriteRestrictionType;
|
||||||
if (_writeRestriction.current().type == Type::PremiumRequired) {
|
if (_writeRestriction.current().type == Type::PremiumRequired) {
|
||||||
|
@ -2746,7 +2775,7 @@ void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
}
|
}
|
||||||
if (_botCommandStart) {
|
if (_botCommandStart) {
|
||||||
_botCommandStart->moveToRight(right, buttonsTop);
|
_botCommandStart->moveToRight(right, buttonsTop);
|
||||||
if (_botCommandShown) {
|
if (_botCommandShown && settings.showCommandsButtonInMessageField) {
|
||||||
right += _botCommandStart->width();
|
right += _botCommandStart->width();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2758,7 +2787,7 @@ void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
_scheduled->moveToRight(right, buttonsTop);
|
_scheduled->moveToRight(right, buttonsTop);
|
||||||
right += _scheduled->width();
|
right += _scheduled->width();
|
||||||
}
|
}
|
||||||
if (_ttlInfo) {
|
if (_ttlInfo && settings.showAutoDeleteButtonInMessageField) {
|
||||||
_ttlInfo->move(size.width() - right - _ttlInfo->width(), buttonsTop);
|
_ttlInfo->move(size.width() - right - _ttlInfo->width(), buttonsTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2769,14 +2798,16 @@ void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::updateControlsVisibility() {
|
void ComposeControls::updateControlsVisibility() {
|
||||||
|
const auto &settings = AyuSettings::getInstance();
|
||||||
|
|
||||||
if (_botCommandStart) {
|
if (_botCommandStart) {
|
||||||
_botCommandStart->setVisible(_botCommandShown);
|
SWITCH_BUTTON(_botCommandStart, _botCommandShown && settings.showCommandsButtonInMessageField);
|
||||||
}
|
}
|
||||||
if (_like) {
|
if (_like) {
|
||||||
_like->setVisible(_likeShown);
|
_like->setVisible(_likeShown);
|
||||||
}
|
}
|
||||||
if (_ttlInfo) {
|
if (_ttlInfo) {
|
||||||
_ttlInfo->show();
|
SWITCH_BUTTON(_ttlInfo, settings.showAutoDeleteButtonInMessageField);
|
||||||
}
|
}
|
||||||
if (_sendAs) {
|
if (_sendAs) {
|
||||||
_sendAs->show();
|
_sendAs->show();
|
||||||
|
@ -2785,11 +2816,12 @@ void ComposeControls::updateControlsVisibility() {
|
||||||
_replaceMedia->show();
|
_replaceMedia->show();
|
||||||
_attachToggle->hide();
|
_attachToggle->hide();
|
||||||
} else {
|
} else {
|
||||||
_attachToggle->show();
|
SWITCH_BUTTON(_attachToggle, settings.showAttachButtonInMessageField);
|
||||||
}
|
}
|
||||||
if (_scheduled) {
|
if (_scheduled) {
|
||||||
_scheduled->setVisible(!isEditingMessage());
|
_scheduled->setVisible(!isEditingMessage());
|
||||||
}
|
}
|
||||||
|
SWITCH_BUTTON(_tabbedSelectorToggle, settings.showEmojiButtonInMessageField);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ComposeControls::updateLikeShown() {
|
bool ComposeControls::updateLikeShown() {
|
||||||
|
|
|
@ -718,8 +718,7 @@ void DraftOptionsBox(
|
||||||
state->link = args.usedLink;
|
state->link = args.usedLink;
|
||||||
state->quote = SelectedQuote{
|
state->quote = SelectedQuote{
|
||||||
replyItem,
|
replyItem,
|
||||||
draft.reply.quote,
|
{ draft.reply.quote, draft.reply.quoteOffset },
|
||||||
draft.reply.quoteOffset,
|
|
||||||
};
|
};
|
||||||
state->forward = std::move(args.forward);
|
state->forward = std::move(args.forward);
|
||||||
state->webpage = draft.webpage;
|
state->webpage = draft.webpage;
|
||||||
|
@ -783,7 +782,7 @@ void DraftOptionsBox(
|
||||||
box->setTitle(hasLink
|
box->setTitle(hasLink
|
||||||
? tr::lng_link_options_header()
|
? tr::lng_link_options_header()
|
||||||
: hasReply
|
: hasReply
|
||||||
? (state->quote.current().text.empty()
|
? (state->quote.current().highlight.quote.empty()
|
||||||
? tr::lng_reply_options_header()
|
? tr::lng_reply_options_header()
|
||||||
: tr::lng_reply_options_quote())
|
: tr::lng_reply_options_quote())
|
||||||
: (forwardCount == 1)
|
: (forwardCount == 1)
|
||||||
|
@ -807,10 +806,12 @@ void DraftOptionsBox(
|
||||||
auto result = draft.reply;
|
auto result = draft.reply;
|
||||||
if (const auto current = state->quote.current()) {
|
if (const auto current = state->quote.current()) {
|
||||||
result.messageId = current.item->fullId();
|
result.messageId = current.item->fullId();
|
||||||
result.quote = current.text;
|
result.quote = current.highlight.quote;
|
||||||
result.quoteOffset = current.offset;
|
result.quoteOffset = current.highlight.quoteOffset;
|
||||||
|
// result.todoItemId = current.highlight.todoItemId;
|
||||||
} else {
|
} else {
|
||||||
result.quote = {};
|
result.quote = {};
|
||||||
|
// result.todoItemId = 0;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -1112,7 +1113,7 @@ void DraftOptionsBox(
|
||||||
state->quote.value(),
|
state->quote.value(),
|
||||||
state->shown.value()
|
state->shown.value()
|
||||||
) | rpl::map([=](const SelectedQuote "e, Section shown) {
|
) | rpl::map([=](const SelectedQuote "e, Section shown) {
|
||||||
return (quote.text.empty() || shown != Section::Reply)
|
return (quote.highlight.quote.empty() || shown != Section::Reply)
|
||||||
? tr::lng_settings_save()
|
? tr::lng_settings_save()
|
||||||
: tr::lng_reply_quote_selected();
|
: tr::lng_reply_quote_selected();
|
||||||
}) | rpl::flatten_latest();
|
}) | rpl::flatten_latest();
|
||||||
|
|
|
@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
// AyuGram includes
|
// AyuGram includes
|
||||||
#include "ayu/ayu_settings.h"
|
#include "ayu/ayu_settings.h"
|
||||||
#include "ayu/features/messageshot/message_shot.h"
|
#include "ayu/features/messageshot/message_shot.h"
|
||||||
|
#include "ayu/utils/telegram_helpers.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
#include "styles/style_ayu_icons.h"
|
#include "styles/style_ayu_icons.h"
|
||||||
|
|
||||||
|
@ -428,12 +429,7 @@ void BottomInfo::layoutDateText() {
|
||||||
: QString();
|
: QString();
|
||||||
const auto author = _data.author;
|
const auto author = _data.author;
|
||||||
const auto prefix = !author.isEmpty() ? u", "_q : QString();
|
const auto prefix = !author.isEmpty() ? u", "_q : QString();
|
||||||
const auto date = edited + QLocale().toString(
|
const auto date = edited + formatMessageTime(_data.date.time());
|
||||||
_data.date.time(),
|
|
||||||
settings.showMessageSeconds
|
|
||||||
? QLocale::system().timeFormat(QLocale::LongFormat).remove(" t")
|
|
||||||
: QLocale::system().timeFormat(QLocale::ShortFormat)
|
|
||||||
);
|
|
||||||
const auto afterAuthor = prefix + date;
|
const auto afterAuthor = prefix + date;
|
||||||
const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor);
|
const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor);
|
||||||
const auto authorWidth = st::msgDateFont->width(author);
|
const auto authorWidth = st::msgDateFont->width(author);
|
||||||
|
@ -494,12 +490,9 @@ void BottomInfo::layoutDateText() {
|
||||||
const auto author = _data.author;
|
const auto author = _data.author;
|
||||||
const auto prefix = !author.isEmpty() ? (_data.flags & Data::Flag::Edited ? u" "_q : u", "_q) : QString();
|
const auto prefix = !author.isEmpty() ? (_data.flags & Data::Flag::Edited ? u" "_q : u", "_q) : QString();
|
||||||
|
|
||||||
const auto date = TextWithEntities{}.append(edited).append(QLocale().toString(
|
const auto date = TextWithEntities{}
|
||||||
_data.date.time(),
|
.append(edited)
|
||||||
settings.showMessageSeconds
|
.append(formatMessageTime(_data.date.time()));
|
||||||
? QLocale::system().timeFormat(QLocale::LongFormat).remove(" t")
|
|
||||||
: QLocale::system().timeFormat(QLocale::ShortFormat)
|
|
||||||
));
|
|
||||||
|
|
||||||
const auto afterAuthor = TextWithEntities{}.append(prefix).append(date);
|
const auto afterAuthor = TextWithEntities{}.append(prefix).append(date);
|
||||||
const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor.text);
|
const auto afterAuthorWidth = st::msgDateFont->width(afterAuthor.text);
|
||||||
|
|
|
@ -123,12 +123,10 @@ rpl::producer<Ui::MessageBarContent> RootViewContent(
|
||||||
ChatMemento::ChatMemento(
|
ChatMemento::ChatMemento(
|
||||||
ChatViewId id,
|
ChatViewId id,
|
||||||
MsgId highlightId,
|
MsgId highlightId,
|
||||||
const TextWithEntities &highlightPart,
|
MessageHighlightId highlight)
|
||||||
int highlightPartOffsetHint)
|
|
||||||
: _id(id)
|
: _id(id)
|
||||||
, _highlightPart(highlightPart)
|
, _highlightId(highlightId)
|
||||||
, _highlightPartOffsetHint(highlightPartOffsetHint)
|
, _highlight(std::move(highlight)) {
|
||||||
, _highlightId(highlightId) {
|
|
||||||
if (highlightId || _id.sublist) {
|
if (highlightId || _id.sublist) {
|
||||||
_list.setAroundPosition({
|
_list.setAroundPosition({
|
||||||
.fullId = FullMsgId(_id.history->peer->id, highlightId),
|
.fullId = FullMsgId(_id.history->peer->id, highlightId),
|
||||||
|
@ -884,12 +882,7 @@ void ChatWidget::setupComposeControls() {
|
||||||
_composeControls->jumpToItemRequests(
|
_composeControls->jumpToItemRequests(
|
||||||
) | rpl::start_with_next([=](FullReplyTo to) {
|
) | rpl::start_with_next([=](FullReplyTo to) {
|
||||||
if (const auto item = session().data().message(to.messageId)) {
|
if (const auto item = session().data().message(to.messageId)) {
|
||||||
JumpToMessageClickHandler(
|
JumpToMessageClickHandler(item, {}, to.highlight())->onClick({});
|
||||||
item,
|
|
||||||
{},
|
|
||||||
to.quote,
|
|
||||||
to.quoteOffset
|
|
||||||
)->onClick({});
|
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
@ -1047,8 +1040,9 @@ void ChatWidget::setupSwipeReplyAndBack() {
|
||||||
: still)->fullId();
|
: still)->fullId();
|
||||||
_inner->replyToMessageRequestNotify({
|
_inner->replyToMessageRequestNotify({
|
||||||
.messageId = replyToItemId,
|
.messageId = replyToItemId,
|
||||||
.quote = selected.text,
|
.quote = selected.highlight.quote,
|
||||||
.quoteOffset = selected.offset,
|
.quoteOffset = selected.highlight.quoteOffset,
|
||||||
|
.todoItemId = selected.highlight.todoItemId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
|
@ -2648,8 +2642,7 @@ void ChatWidget::restoreState(not_null<ChatMemento*> memento) {
|
||||||
auto params = Window::SectionShow(
|
auto params = Window::SectionShow(
|
||||||
Window::SectionShow::Way::Forward,
|
Window::SectionShow::Way::Forward,
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
params.highlightPart = memento->highlightPart();
|
params.highlight = memento->highlight();
|
||||||
params.highlightPartOffsetHint = memento->highlightPartOffsetHint();
|
|
||||||
showAtPosition(Data::MessagePosition{
|
showAtPosition(Data::MessagePosition{
|
||||||
.fullId = FullMsgId(_peer->id, highlight),
|
.fullId = FullMsgId(_peer->id, highlight),
|
||||||
.date = TimeId(0),
|
.date = TimeId(0),
|
||||||
|
@ -3452,8 +3445,7 @@ bool ChatWidget::searchInChatEmbedded(
|
||||||
const auto item = activation.item;
|
const auto item = activation.item;
|
||||||
auto params = ::Window::SectionShow(
|
auto params = ::Window::SectionShow(
|
||||||
::Window::SectionShow::Way::ClearStack);
|
::Window::SectionShow::Way::ClearStack);
|
||||||
params.highlightPart = { activation.query };
|
params.highlight = Window::SearchHighlightId(activation.query);
|
||||||
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
|
|
||||||
controller()->showPeerHistory(
|
controller()->showPeerHistory(
|
||||||
item->history()->peer->id,
|
item->history()->peer->id,
|
||||||
params,
|
params,
|
||||||
|
|
|
@ -461,8 +461,7 @@ public:
|
||||||
explicit ChatMemento(
|
explicit ChatMemento(
|
||||||
ChatViewId id,
|
ChatViewId id,
|
||||||
MsgId highlightId = 0,
|
MsgId highlightId = 0,
|
||||||
const TextWithEntities &highlightPart = {},
|
MessageHighlightId highlight = {});
|
||||||
int highlightPartOffsetHint = 0);
|
|
||||||
|
|
||||||
struct Comments {
|
struct Comments {
|
||||||
};
|
};
|
||||||
|
@ -511,20 +510,16 @@ public:
|
||||||
[[nodiscard]] MsgId highlightId() const {
|
[[nodiscard]] MsgId highlightId() const {
|
||||||
return _highlightId;
|
return _highlightId;
|
||||||
}
|
}
|
||||||
[[nodiscard]] const TextWithEntities &highlightPart() const {
|
[[nodiscard]] const MessageHighlightId &highlight() const {
|
||||||
return _highlightPart;
|
return _highlight;
|
||||||
}
|
|
||||||
[[nodiscard]] int highlightPartOffsetHint() const {
|
|
||||||
return _highlightPartOffsetHint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupTopicViewer();
|
void setupTopicViewer();
|
||||||
|
|
||||||
ChatViewId _id;
|
ChatViewId _id;
|
||||||
const TextWithEntities _highlightPart;
|
|
||||||
const int _highlightPartOffsetHint = 0;
|
|
||||||
const MsgId _highlightId = 0;
|
const MsgId _highlightId = 0;
|
||||||
|
const MessageHighlightId _highlight;
|
||||||
ListMemento _list;
|
ListMemento _list;
|
||||||
std::shared_ptr<Data::RepliesList> _replies;
|
std::shared_ptr<Data::RepliesList> _replies;
|
||||||
QVector<FullMsgId> _replyReturns;
|
QVector<FullMsgId> _replyReturns;
|
||||||
|
|
|
@ -644,8 +644,13 @@ bool AddReplyToMessageAction(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto todoListTaskId = request.link
|
||||||
|
? request.link->property(kTodoListItemIdProperty).toInt()
|
||||||
|
: 0;
|
||||||
const auto "e = request.quote;
|
const auto "e = request.quote;
|
||||||
auto text = (quote.text.empty()
|
auto text = (todoListTaskId
|
||||||
|
? tr::lng_context_reply_to_task
|
||||||
|
: quote.highlight.quote.empty()
|
||||||
? tr::lng_context_reply_msg
|
? tr::lng_context_reply_msg
|
||||||
: tr::lng_context_quote_and_reply)(
|
: tr::lng_context_quote_and_reply)(
|
||||||
tr::now,
|
tr::now,
|
||||||
|
@ -653,8 +658,9 @@ bool AddReplyToMessageAction(
|
||||||
menu->addAction(std::move(text), [=, itemId = item->fullId()] {
|
menu->addAction(std::move(text), [=, itemId = item->fullId()] {
|
||||||
list->replyToMessageRequestNotify({
|
list->replyToMessageRequestNotify({
|
||||||
.messageId = itemId,
|
.messageId = itemId,
|
||||||
.quote = quote.text,
|
.quote = quote.highlight.quote,
|
||||||
.quoteOffset = quote.offset,
|
.quoteOffset = quote.highlight.quoteOffset,
|
||||||
|
.todoItemId = todoListTaskId,
|
||||||
}, base::IsCtrlPressed());
|
}, base::IsCtrlPressed());
|
||||||
}, &st::menuIconReply);
|
}, &st::menuIconReply);
|
||||||
return true;
|
return true;
|
||||||
|
@ -680,7 +686,7 @@ bool AddTodoListAction(
|
||||||
if (const auto item = controller->session().data().message(itemId)) {
|
if (const auto item = controller->session().data().message(itemId)) {
|
||||||
Window::PeerMenuAddTodoListTasks(controller, item);
|
Window::PeerMenuAddTodoListTasks(controller, item);
|
||||||
}
|
}
|
||||||
}, &st::menuIconCreateTodoList);
|
}, &st::menuIconAdd);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_saved_sublist.h"
|
#include "data/data_saved_sublist.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_todo_list.h"
|
||||||
#include "data/data_forum.h"
|
#include "data/data_forum.h"
|
||||||
#include "data/data_forum_topic.h"
|
#include "data/data_forum_topic.h"
|
||||||
#include "data/data_message_reactions.h"
|
#include "data/data_message_reactions.h"
|
||||||
|
@ -1360,9 +1361,18 @@ void Element::validateText() {
|
||||||
|
|
||||||
if (const auto done = item->Get<HistoryServiceTodoCompletions>()) {
|
if (const auto done = item->Get<HistoryServiceTodoCompletions>()) {
|
||||||
if (!done->completed.empty() && !done->incompleted.empty()) {
|
if (!done->completed.empty() && !done->incompleted.empty()) {
|
||||||
|
const auto todoItemId = (done->incompleted.size() == 1)
|
||||||
|
? done->incompleted.front()
|
||||||
|
: 0;
|
||||||
setServicePreMessage(
|
setServicePreMessage(
|
||||||
item->composeTodoIncompleted(done),
|
item->composeTodoIncompleted(done),
|
||||||
done->lnk);
|
JumpToMessageClickHandler(
|
||||||
|
(done->peerId
|
||||||
|
? history()->owner().peer(done->peerId)
|
||||||
|
: history()->peer),
|
||||||
|
done->msgId,
|
||||||
|
item->fullId(),
|
||||||
|
{ .todoItemId = todoItemId }));
|
||||||
} else {
|
} else {
|
||||||
setServicePreMessage({});
|
setServicePreMessage({});
|
||||||
}
|
}
|
||||||
|
@ -2205,7 +2215,7 @@ SelectedQuote Element::FindSelectedQuote(
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { item, result, modified.from, overflown };
|
return { item, { result, modified.from }, overflown };
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Element::FindSelectionFromQuote(
|
TextSelection Element::FindSelectionFromQuote(
|
||||||
|
@ -2213,17 +2223,18 @@ TextSelection Element::FindSelectionFromQuote(
|
||||||
const SelectedQuote "e) {
|
const SelectedQuote "e) {
|
||||||
Expects(quote.item != nullptr);
|
Expects(quote.item != nullptr);
|
||||||
|
|
||||||
if (quote.text.empty()) {
|
const auto &rich = quote.highlight.quote;
|
||||||
|
if (rich.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto &original = quote.item->originalText();
|
const auto &original = quote.item->originalText();
|
||||||
if (quote.offset == kSearchQueryOffsetHint) {
|
if (quote.highlight.quoteOffset == kSearchQueryOffsetHint) {
|
||||||
return ApplyModificationsFrom(
|
return ApplyModificationsFrom(
|
||||||
FindSearchQueryHighlight(original.text, quote.text.text),
|
FindSearchQueryHighlight(original.text, rich.text),
|
||||||
text);
|
text);
|
||||||
}
|
}
|
||||||
const auto length = int(original.text.size());
|
const auto length = int(original.text.size());
|
||||||
const auto qlength = int(quote.text.text.size());
|
const auto qlength = int(rich.text.size());
|
||||||
const auto checkAt = [&](int offset) {
|
const auto checkAt = [&](int offset) {
|
||||||
return TextSelection{
|
return TextSelection{
|
||||||
uint16(offset),
|
uint16(offset),
|
||||||
|
@ -2234,7 +2245,7 @@ TextSelection Element::FindSelectionFromQuote(
|
||||||
if (offset > length - qlength) {
|
if (offset > length - qlength) {
|
||||||
return TextSelection();
|
return TextSelection();
|
||||||
}
|
}
|
||||||
const auto i = original.text.indexOf(quote.text.text, offset);
|
const auto i = original.text.indexOf(rich.text, offset);
|
||||||
return (i >= 0) ? checkAt(i) : TextSelection();
|
return (i >= 0) ? checkAt(i) : TextSelection();
|
||||||
};
|
};
|
||||||
const auto findOneBefore = [&](int offset) {
|
const auto findOneBefore = [&](int offset) {
|
||||||
|
@ -2243,7 +2254,7 @@ TextSelection Element::FindSelectionFromQuote(
|
||||||
}
|
}
|
||||||
const auto end = std::min(offset + qlength - 1, length);
|
const auto end = std::min(offset + qlength - 1, length);
|
||||||
const auto from = end - length - 1;
|
const auto from = end - length - 1;
|
||||||
const auto i = original.text.lastIndexOf(quote.text.text, from);
|
const auto i = original.text.lastIndexOf(rich.text, from);
|
||||||
return (i >= 0) ? checkAt(i) : TextSelection();
|
return (i >= 0) ? checkAt(i) : TextSelection();
|
||||||
};
|
};
|
||||||
const auto findAfter = [&](int offset) {
|
const auto findAfter = [&](int offset) {
|
||||||
|
@ -2281,7 +2292,7 @@ TextSelection Element::FindSelectionFromQuote(
|
||||||
? before
|
? before
|
||||||
: after;
|
: after;
|
||||||
};
|
};
|
||||||
auto result = findTwoWays(quote.offset);
|
auto result = findTwoWays(quote.highlight.quoteOffset);
|
||||||
if (result.empty()) {
|
if (result.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -2468,6 +2479,70 @@ int FindViewY(not_null<Element*> view, uint16 symbol, int yfrom) {
|
||||||
return origin.y() + (yfrom + ytill) / 2;
|
return origin.y() + (yfrom + ytill) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int FindViewTaskY(not_null<Element*> view, int taskId, int yfrom) {
|
||||||
|
auto request = HistoryView::StateRequest();
|
||||||
|
request.flags = Ui::Text::StateRequest::Flag::LookupLink;
|
||||||
|
const auto single = st::messageTextStyle.font->height;
|
||||||
|
const auto inner = view->innerGeometry();
|
||||||
|
const auto origin = inner.topLeft();
|
||||||
|
const auto top = 0;
|
||||||
|
const auto bottom = view->height();
|
||||||
|
if (origin.y() < top
|
||||||
|
|| origin.y() + inner.height() > bottom
|
||||||
|
|| inner.height() <= 0) {
|
||||||
|
return yfrom;
|
||||||
|
}
|
||||||
|
const auto media = view->data()->media();
|
||||||
|
const auto todolist = media ? media->todolist() : nullptr;
|
||||||
|
if (!todolist) {
|
||||||
|
return yfrom;
|
||||||
|
}
|
||||||
|
const auto &items = todolist->items;
|
||||||
|
const auto indexOf = [&](int id) -> int {
|
||||||
|
return ranges::find(items, id, &TodoListItem::id) - begin(items);
|
||||||
|
};
|
||||||
|
const auto index = indexOf(taskId);
|
||||||
|
const auto count = int(items.size());
|
||||||
|
if (index == count) {
|
||||||
|
return yfrom;
|
||||||
|
}
|
||||||
|
yfrom = std::max(yfrom - origin.y(), 0);
|
||||||
|
auto ytill = inner.height() - 1;
|
||||||
|
const auto middle = (yfrom + ytill) / 2;
|
||||||
|
const auto fory = [&](int y) {
|
||||||
|
const auto state = view->textState(origin + QPoint(0, y), request);
|
||||||
|
const auto &link = state.link;
|
||||||
|
const auto id = link
|
||||||
|
? link->property(kTodoListItemIdProperty).toInt()
|
||||||
|
: -1;
|
||||||
|
const auto index = (id >= 0) ? indexOf(id) : int(items.size());
|
||||||
|
return (index < count) ? index : (y < middle) ? -1 : count;
|
||||||
|
};
|
||||||
|
auto indexfrom = fory(yfrom);
|
||||||
|
auto indextill = fory(ytill);
|
||||||
|
if ((yfrom >= ytill) || (indexfrom >= index)) {
|
||||||
|
return origin.y() + yfrom;
|
||||||
|
} else if (indextill <= index) {
|
||||||
|
return origin.y() + ytill;
|
||||||
|
}
|
||||||
|
while (ytill - yfrom >= 2 * single) {
|
||||||
|
const auto middle = (yfrom + ytill) / 2;
|
||||||
|
const auto found = fory(middle);
|
||||||
|
if (found == index
|
||||||
|
|| indexfrom > found
|
||||||
|
|| indextill < found) {
|
||||||
|
return origin.y() + middle;
|
||||||
|
} else if (found < index) {
|
||||||
|
yfrom = middle;
|
||||||
|
indexfrom = found;
|
||||||
|
} else {
|
||||||
|
ytill = middle;
|
||||||
|
indextill = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return origin.y() + (yfrom + ytill) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
Window::SessionController *ExtractController(const ClickContext &context) {
|
Window::SessionController *ExtractController(const ClickContext &context) {
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
|
|
|
@ -357,12 +357,11 @@ struct TopicButton {
|
||||||
|
|
||||||
struct SelectedQuote {
|
struct SelectedQuote {
|
||||||
HistoryItem *item = nullptr;
|
HistoryItem *item = nullptr;
|
||||||
TextWithEntities text;
|
MessageHighlightId highlight;
|
||||||
int offset = 0;
|
|
||||||
bool overflown = false;
|
bool overflown = false;
|
||||||
|
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return item && !text.empty();
|
return item && !highlight.quote.empty();
|
||||||
}
|
}
|
||||||
friend inline bool operator==(SelectedQuote, SelectedQuote) = default;
|
friend inline bool operator==(SelectedQuote, SelectedQuote) = default;
|
||||||
};
|
};
|
||||||
|
@ -748,6 +747,11 @@ private:
|
||||||
uint16 symbol,
|
uint16 symbol,
|
||||||
int yfrom = 0);
|
int yfrom = 0);
|
||||||
|
|
||||||
|
[[nodiscard]] int FindViewTaskY(
|
||||||
|
not_null<Element*> view,
|
||||||
|
int taskId,
|
||||||
|
int yfrom = 0);
|
||||||
|
|
||||||
[[nodiscard]] Window::SessionController *ExtractController(
|
[[nodiscard]] Window::SessionController *ExtractController(
|
||||||
const ClickContext &context);
|
const ClickContext &context);
|
||||||
|
|
||||||
|
|
|
@ -720,12 +720,21 @@ std::optional<int> ListWidget::scrollTopForView(
|
||||||
const auto heightLeft = (available - height);
|
const auto heightLeft = (available - height);
|
||||||
if (heightLeft >= 0) {
|
if (heightLeft >= 0) {
|
||||||
return std::max(top - (heightLeft / 2), 0);
|
return std::max(top - (heightLeft / 2), 0);
|
||||||
} else if (const auto sel = _highlighter.state(view->data()).range
|
} else if (const auto highlight = _highlighter.state(view->data())
|
||||||
; !sel.empty() && !IsSubGroupSelection(sel)) {
|
; (!highlight.range.empty() || highlight.todoItemId)
|
||||||
|
&& !IsSubGroupSelection(highlight.range)) {
|
||||||
|
const auto sel = highlight.range;
|
||||||
const auto single = st::messageTextStyle.font->height;
|
const auto single = st::messageTextStyle.font->height;
|
||||||
const auto begin = HistoryView::FindViewY(view, sel.from) - single;
|
const auto todoy = sel.empty()
|
||||||
const auto end = HistoryView::FindViewY(view, sel.to, begin + single)
|
? HistoryView::FindViewTaskY(view, highlight.todoItemId)
|
||||||
+ 2 * single;
|
: 0;
|
||||||
|
const auto begin = sel.empty()
|
||||||
|
? (todoy - 4 * single)
|
||||||
|
: HistoryView::FindViewY(view, sel.from) - single;
|
||||||
|
const auto end = sel.empty()
|
||||||
|
? (todoy + 4 * single)
|
||||||
|
: (HistoryView::FindViewY(view, sel.to, begin + single)
|
||||||
|
+ 2 * single);
|
||||||
auto result = top;
|
auto result = top;
|
||||||
if (end > available) {
|
if (end > available) {
|
||||||
result = std::max(result, top + end - available);
|
result = std::max(result, top + end - available);
|
||||||
|
@ -822,10 +831,9 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const {
|
||||||
|
|
||||||
void ListWidget::highlightMessage(
|
void ListWidget::highlightMessage(
|
||||||
FullMsgId itemId,
|
FullMsgId itemId,
|
||||||
const TextWithEntities &part,
|
const MessageHighlightId &highlight) {
|
||||||
int partOffsetHint) {
|
|
||||||
if (const auto view = viewForItem(itemId)) {
|
if (const auto view = viewForItem(itemId)) {
|
||||||
_highlighter.highlight({ view->data(), part, partOffsetHint });
|
_highlighter.highlight({ view->data(), highlight });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,11 +911,8 @@ bool ListWidget::showAtPositionNow(
|
||||||
}
|
}
|
||||||
if (position != Data::MaxMessagePosition
|
if (position != Data::MaxMessagePosition
|
||||||
&& position != Data::UnreadMessagePosition) {
|
&& position != Data::UnreadMessagePosition) {
|
||||||
const auto hasHighlight = !params.highlightPart.empty();
|
const auto hasHighlight = !params.highlight.empty();
|
||||||
highlightMessage(
|
highlightMessage(position.fullId, params.highlight);
|
||||||
position.fullId,
|
|
||||||
params.highlightPart,
|
|
||||||
params.highlightPartOffsetHint);
|
|
||||||
if (hasHighlight) {
|
if (hasHighlight) {
|
||||||
// We may want to scroll to a different part of the message.
|
// We may want to scroll to a different part of the message.
|
||||||
scrollTop = scrollTopForPosition(position);
|
scrollTop = scrollTopForPosition(position);
|
||||||
|
|
|
@ -314,8 +314,7 @@ public:
|
||||||
bool isBelowPosition(Data::MessagePosition position) const;
|
bool isBelowPosition(Data::MessagePosition position) const;
|
||||||
void highlightMessage(
|
void highlightMessage(
|
||||||
FullMsgId itemId,
|
FullMsgId itemId,
|
||||||
const TextWithEntities &part,
|
const MessageHighlightId &highlight);
|
||||||
int partOffsetHint);
|
|
||||||
|
|
||||||
void showAtPosition(
|
void showAtPosition(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
|
|
|
@ -490,6 +490,8 @@ void Message::initPaidInformation() {
|
||||||
refreshSuggestedInfo(item, suggest, replyData);
|
refreshSuggestedInfo(item, suggest, replyData);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
} else if (!item->history()->peer->isUser()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
const auto mine = PaidInformation{
|
const auto mine = PaidInformation{
|
||||||
|
@ -3348,7 +3350,7 @@ TextSelection Message::selectionFromQuote(
|
||||||
const SelectedQuote "e) const {
|
const SelectedQuote "e) const {
|
||||||
Expects(quote.item != nullptr);
|
Expects(quote.item != nullptr);
|
||||||
|
|
||||||
if (quote.text.empty()) {
|
if (quote.highlight.quote.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto item = quote.item;
|
const auto item = quote.item;
|
||||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_story.h"
|
#include "data/data_story.h"
|
||||||
|
#include "data/data_todo_list.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "history/view/history_view_item_preview.h"
|
#include "history/view/history_view_item_preview.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
@ -42,6 +43,85 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kNonExpandedLinesLimit = 5;
|
constexpr auto kNonExpandedLinesLimit = 5;
|
||||||
|
|
||||||
|
[[nodiscard]] QImage MakeTaskImage() {
|
||||||
|
const auto diameter = st::normalFont->ascent;
|
||||||
|
const auto line = st::historyPollRadio.thickness;
|
||||||
|
const auto size = 2 * line + diameter;
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
auto result = QImage(
|
||||||
|
QSize(size, size) * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.fill(Qt::transparent);
|
||||||
|
result.setDevicePixelRatio(ratio);
|
||||||
|
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
|
p.setOpacity(st::historyPollRadioOpacity);
|
||||||
|
|
||||||
|
const auto rect = QRectF(line, line, diameter, diameter).marginsRemoved(
|
||||||
|
QMarginsF(line / 2., line / 2., line / 2., line / 2.));
|
||||||
|
auto pen = QPen(QColor(255, 255, 255));
|
||||||
|
pen.setWidth(line);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.drawEllipse(rect);
|
||||||
|
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage MakeTaskDoneImage() {
|
||||||
|
const auto white = QColor(255, 255, 255);
|
||||||
|
const auto black = QColor(0, 0, 0);
|
||||||
|
|
||||||
|
const auto diameter = st::normalFont->ascent;
|
||||||
|
const auto line = st::historyPollRadio.thickness;
|
||||||
|
const auto size = 2 * line + diameter;
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
auto result = QImage(
|
||||||
|
QSize(size, size) * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.fill(black);
|
||||||
|
result.setDevicePixelRatio(ratio);
|
||||||
|
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
|
const auto rect = QRectF(line, line, diameter, diameter).marginsRemoved(
|
||||||
|
QMarginsF(line / 2., line / 2., line / 2., line / 2.));
|
||||||
|
auto pen = QPen(white);
|
||||||
|
pen.setWidth(line);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.setBrush(white);
|
||||||
|
p.drawEllipse(rect);
|
||||||
|
const auto &icon = st::historyPollInChoiceRight;
|
||||||
|
icon.paint(
|
||||||
|
p,
|
||||||
|
line + (diameter - icon.width()) / 2,
|
||||||
|
line + (diameter - icon.height()) / 2,
|
||||||
|
size,
|
||||||
|
black);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
return style::colorizeImage(result, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities TaskDoneIcon(
|
||||||
|
not_null<Main::Session*> session) {
|
||||||
|
return Ui::Text::SingleCustomEmoji(
|
||||||
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
|
MakeTaskDoneImage(),
|
||||||
|
QMargins(0, st::lineWidth, st::lineWidth, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities TaskIcon(not_null<Main::Session*> session) {
|
||||||
|
return Ui::Text::SingleCustomEmoji(
|
||||||
|
session->data().customEmojiManager().registerInternalEmoji(
|
||||||
|
MakeTaskImage(),
|
||||||
|
QMargins(0, st::lineWidth, st::lineWidth, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ValidateBackgroundEmoji(
|
void ValidateBackgroundEmoji(
|
||||||
|
@ -197,6 +277,22 @@ void Reply::update(
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
const auto &fields = data->fields();
|
const auto &fields = data->fields();
|
||||||
const auto message = data->resolvedMessage.get();
|
const auto message = data->resolvedMessage.get();
|
||||||
|
const auto messageMedia = (message && fields.todoItemId)
|
||||||
|
? message->media()
|
||||||
|
: nullptr;
|
||||||
|
const auto messageTodoList = messageMedia
|
||||||
|
? messageMedia->todolist()
|
||||||
|
: nullptr;
|
||||||
|
const auto taskIndex = messageTodoList
|
||||||
|
? int(ranges::find(
|
||||||
|
messageTodoList->items,
|
||||||
|
fields.todoItemId,
|
||||||
|
&TodoListItem::id) - begin(messageTodoList->items))
|
||||||
|
: -1;
|
||||||
|
const auto task = (taskIndex >= 0
|
||||||
|
&& taskIndex < messageTodoList->items.size())
|
||||||
|
? &messageTodoList->items[taskIndex]
|
||||||
|
: nullptr;
|
||||||
const auto story = data->resolvedStory.get();
|
const auto story = data->resolvedStory.get();
|
||||||
const auto externalMedia = fields.externalMedia.get();
|
const auto externalMedia = fields.externalMedia.get();
|
||||||
if (!_externalSender) {
|
if (!_externalSender) {
|
||||||
|
@ -214,7 +310,6 @@ void Reply::update(
|
||||||
_hiddenSenderColorIndexPlusOne = (!_colorPeer && message)
|
_hiddenSenderColorIndexPlusOne = (!_colorPeer && message)
|
||||||
? (message->originalHiddenSenderInfo()->colorIndex + 1)
|
? (message->originalHiddenSenderInfo()->colorIndex + 1)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const auto hasPreview = (story && story->hasReplyPreview())
|
const auto hasPreview = (story && story->hasReplyPreview())
|
||||||
|| (message
|
|| (message
|
||||||
&& message->media()
|
&& message->media()
|
||||||
|
@ -229,8 +324,13 @@ void Reply::update(
|
||||||
&& !fields.quote.empty();
|
&& !fields.quote.empty();
|
||||||
_hasQuoteIcon = hasQuoteIcon ? 1 : 0;
|
_hasQuoteIcon = hasQuoteIcon ? 1 : 0;
|
||||||
|
|
||||||
|
const auto session = &view->history()->session();
|
||||||
const auto text = (!_displaying && data->unavailable())
|
const auto text = (!_displaying && data->unavailable())
|
||||||
? TextWithEntities()
|
? TextWithEntities()
|
||||||
|
: task
|
||||||
|
? Ui::Text::Colorized(task->completionDate
|
||||||
|
? TaskDoneIcon(session)
|
||||||
|
: TaskIcon(session)).append(task->text)
|
||||||
: (message && (fields.quote.empty() || !fields.manualQuote))
|
: (message && (fields.quote.empty() || !fields.manualQuote))
|
||||||
? message->inReplyText()
|
? message->inReplyText()
|
||||||
: !fields.quote.empty()
|
: !fields.quote.empty()
|
||||||
|
@ -288,10 +388,11 @@ void Reply::setLinkFrom(
|
||||||
const auto &fields = data->fields();
|
const auto &fields = data->fields();
|
||||||
const auto externalChannelId = peerToChannel(fields.externalPeerId);
|
const auto externalChannelId = peerToChannel(fields.externalPeerId);
|
||||||
const auto messageId = fields.messageId;
|
const auto messageId = fields.messageId;
|
||||||
const auto quote = fields.manualQuote
|
const auto highlight = MessageHighlightId{
|
||||||
? fields.quote
|
.quote = fields.manualQuote ? fields.quote : TextWithEntities(),
|
||||||
: TextWithEntities();
|
.quoteOffset = int(fields.quoteOffset),
|
||||||
const auto quoteOffset = fields.quoteOffset;
|
.todoItemId = fields.todoItemId,
|
||||||
|
};
|
||||||
const auto returnToId = view->data()->fullId();
|
const auto returnToId = view->data()->fullId();
|
||||||
const auto externalLink = [=](ClickContext context) {
|
const auto externalLink = [=](ClickContext context) {
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
|
@ -314,8 +415,7 @@ void Reply::setLinkFrom(
|
||||||
channel,
|
channel,
|
||||||
messageId,
|
messageId,
|
||||||
returnToId,
|
returnToId,
|
||||||
quote,
|
highlight
|
||||||
quoteOffset
|
|
||||||
)->onClick(context);
|
)->onClick(context);
|
||||||
} else {
|
} else {
|
||||||
controller->showPeerInfo(channel);
|
controller->showPeerInfo(channel);
|
||||||
|
@ -336,7 +436,7 @@ void Reply::setLinkFrom(
|
||||||
const auto message = data->resolvedMessage.get();
|
const auto message = data->resolvedMessage.get();
|
||||||
const auto story = data->resolvedStory.get();
|
const auto story = data->resolvedStory.get();
|
||||||
_link = message
|
_link = message
|
||||||
? JumpToMessageClickHandler(message, returnToId, quote, quoteOffset)
|
? JumpToMessageClickHandler(message, returnToId, highlight)
|
||||||
: story
|
: story
|
||||||
? JumpToStoryClickHandler(story)
|
? JumpToStoryClickHandler(story)
|
||||||
: (data->external()
|
: (data->external()
|
||||||
|
@ -873,18 +973,28 @@ TextWithEntities Reply::ForwardEmoji(not_null<Data::Session*> owner) {
|
||||||
TextWithEntities Reply::ComposePreviewName(
|
TextWithEntities Reply::ComposePreviewName(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
not_null<HistoryItem*> to,
|
not_null<HistoryItem*> to,
|
||||||
bool quote) {
|
const FullReplyTo &replyTo) {
|
||||||
const auto sender = [&] {
|
const auto sender = [&] {
|
||||||
if (const auto from = to->displayFrom()) {
|
if (const auto from = to->displayFrom()) {
|
||||||
return not_null(from);
|
return not_null(from);
|
||||||
}
|
}
|
||||||
return to->author();
|
return to->author();
|
||||||
}();
|
}();
|
||||||
|
if (const auto media = replyTo.todoItemId ? to->media() : nullptr) {
|
||||||
|
if (const auto todolist = media->todolist()) {
|
||||||
|
return tr::lng_preview_reply_to_task(
|
||||||
|
tr::now,
|
||||||
|
lt_title,
|
||||||
|
todolist->title,
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto toPeer = to->history()->peer;
|
const auto toPeer = to->history()->peer;
|
||||||
const auto displayAsExternal = (to->history() != history);
|
const auto displayAsExternal = (to->history() != history);
|
||||||
const auto groupNameAdded = displayAsExternal
|
const auto groupNameAdded = displayAsExternal
|
||||||
&& (toPeer != sender)
|
&& (toPeer != sender)
|
||||||
&& (toPeer->isChat() || toPeer->isMegagroup());
|
&& (toPeer->isChat() || toPeer->isMegagroup());
|
||||||
|
const auto quote = replyTo && !replyTo.quote.empty();
|
||||||
const auto shorten = groupNameAdded || quote;
|
const auto shorten = groupNameAdded || quote;
|
||||||
|
|
||||||
auto nameFull = TextWithEntities();
|
auto nameFull = TextWithEntities();
|
||||||
|
|
|
@ -110,7 +110,7 @@ public:
|
||||||
[[nodiscard]] static TextWithEntities ComposePreviewName(
|
[[nodiscard]] static TextWithEntities ComposePreviewName(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
not_null<HistoryItem*> to,
|
not_null<HistoryItem*> to,
|
||||||
bool quote);
|
const FullReplyTo &replyTo);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
|
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
|
||||||
|
|
|
@ -438,12 +438,8 @@ void ScheduledWidget::setupComposeControls() {
|
||||||
if (item->isScheduled() && item->history() == _history) {
|
if (item->isScheduled() && item->history() == _history) {
|
||||||
showAtPosition(item->position());
|
showAtPosition(item->position());
|
||||||
} else {
|
} else {
|
||||||
JumpToMessageClickHandler(
|
const auto highlight = to.highlight();
|
||||||
item,
|
JumpToMessageClickHandler(item, {}, highlight)->onClick({});
|
||||||
{},
|
|
||||||
to.quote,
|
|
||||||
to.quoteOffset
|
|
||||||
)->onClick({});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
|
@ -463,17 +463,16 @@ QSize Service::performCountCurrentSize(int newWidth) {
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
const auto mediaDisplayed = media && media->isDisplayed();
|
const auto mediaDisplayed = media && media->isDisplayed();
|
||||||
auto contentWidth = newWidth;
|
auto contentWidth = newWidth;
|
||||||
|
if (delegate()->elementChatMode() == ElementChatMode::Wide) {
|
||||||
|
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
|
||||||
|
}
|
||||||
|
contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins
|
||||||
|
if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
|
||||||
|
contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
|
||||||
|
}
|
||||||
if (mediaDisplayed && media->hideServiceText()) {
|
if (mediaDisplayed && media->hideServiceText()) {
|
||||||
newHeight += media->resizeGetHeight(newWidth) + marginBottom();
|
newHeight += media->resizeGetHeight(newWidth) + marginBottom();
|
||||||
} else if (!text().isEmpty()) {
|
} else if (!text().isEmpty()) {
|
||||||
if (delegate()->elementChatMode() == ElementChatMode::Wide) {
|
|
||||||
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
|
|
||||||
}
|
|
||||||
contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins
|
|
||||||
if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
|
|
||||||
contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
|
auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
|
||||||
newHeight += (contentWidth >= maxWidth())
|
newHeight += (contentWidth >= maxWidth())
|
||||||
? minHeight()
|
? minHeight()
|
||||||
|
|
|
@ -432,10 +432,14 @@ void SubsectionTabs::setupSlider(
|
||||||
.session = &session(),
|
.session = &session(),
|
||||||
}),
|
}),
|
||||||
}, paused);
|
}, paused);
|
||||||
slider->setActiveSectionFast(activeIndex);
|
|
||||||
|
const auto ignoreActiveScroll = (scrollSavingIndex >= 0);
|
||||||
|
slider->setActiveSectionFast(activeIndex, ignoreActiveScroll);
|
||||||
|
|
||||||
_sectionsSlice = _slice;
|
_sectionsSlice = _slice;
|
||||||
if (scrollSavingIndex >= 0) {
|
Assert(slider->sectionsCount() == _slice.size());
|
||||||
|
if (ignoreActiveScroll) {
|
||||||
|
Assert(scrollSavingIndex < slider->sectionsCount());
|
||||||
const auto position = scrollSavingShift
|
const auto position = scrollSavingShift
|
||||||
+ slider->lookupSectionPosition(scrollSavingIndex);
|
+ slider->lookupSectionPosition(scrollSavingIndex);
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
|
@ -702,6 +706,8 @@ void SubsectionTabs::refreshSlice() {
|
||||||
if (_slice != slice) {
|
if (_slice != slice) {
|
||||||
_slice = std::move(slice);
|
_slice = std::move(slice);
|
||||||
_refreshed.fire({});
|
_refreshed.fire({});
|
||||||
|
Assert((!_horizontal && !_vertical)
|
||||||
|
|| (_slice.size() == _sectionsSlice.size()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const auto push = [&](not_null<Data::Thread*> thread) {
|
const auto push = [&](not_null<Data::Thread*> thread) {
|
||||||
|
|
|
@ -334,9 +334,11 @@ void TodoList::updateTasks(bool skipAnimations) {
|
||||||
ClickHandlerPtr TodoList::createTaskClickHandler(
|
ClickHandlerPtr TodoList::createTaskClickHandler(
|
||||||
const Task &task) {
|
const Task &task) {
|
||||||
const auto id = task.id;
|
const auto id = task.id;
|
||||||
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
auto result = std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||||
toggleCompletion(id);
|
toggleCompletion(id);
|
||||||
}));
|
}));
|
||||||
|
result->setProperty(kTodoListItemIdProperty, id);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TodoList::startToggleAnimation(Task &task) {
|
void TodoList::startToggleAnimation(Task &task) {
|
||||||
|
@ -375,11 +377,24 @@ void TodoList::toggleCompletion(int id) {
|
||||||
if (i == end(_tasks)) {
|
if (i == end(_tasks)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto selected = (i->completionDate != 0);
|
const auto selected = (i->completionDate != 0);
|
||||||
i->completionDate = selected ? TimeId() : base::unixtime::now();
|
i->completionDate = selected ? TimeId() : base::unixtime::now();
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
i->setCompletedBy(_parent->history()->session().user());
|
i->setCompletedBy(_parent->history()->session().user());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto parentMedia = _parent->data()->media();
|
||||||
|
const auto baseList = parentMedia ? parentMedia->todolist() : nullptr;
|
||||||
|
if (baseList) {
|
||||||
|
const auto j = ranges::find(baseList->items, id, &TodoListItem::id);
|
||||||
|
if (j != end(baseList->items)) {
|
||||||
|
j->completionDate = i->completionDate;
|
||||||
|
j->completedBy = i->completedBy;
|
||||||
|
}
|
||||||
|
history()->owner().updateDependentMessages(_parent->data());
|
||||||
|
}
|
||||||
|
|
||||||
startToggleAnimation(*i);
|
startToggleAnimation(*i);
|
||||||
repaint();
|
repaint();
|
||||||
|
|
||||||
|
@ -467,6 +482,7 @@ void TodoList::draw(Painter &p, const PaintContext &context) const {
|
||||||
paintw,
|
paintw,
|
||||||
width(),
|
width(),
|
||||||
context);
|
context);
|
||||||
|
appendTaskHighlight(task.id, tshift, height, context);
|
||||||
if (was) {
|
if (was) {
|
||||||
heavy = true;
|
heavy = true;
|
||||||
} else if (!task.userpic.null()) {
|
} else if (!task.userpic.null()) {
|
||||||
|
@ -561,6 +577,33 @@ int TodoList::paintTask(
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TodoList::appendTaskHighlight(
|
||||||
|
int id,
|
||||||
|
int top,
|
||||||
|
int height,
|
||||||
|
const PaintContext &context) const {
|
||||||
|
if (context.highlight.todoItemId != id
|
||||||
|
|| context.highlight.collapsion <= 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto to = context.highlightInterpolateTo;
|
||||||
|
const auto toProgress = (1. - context.highlight.collapsion);
|
||||||
|
if (toProgress >= 1.) {
|
||||||
|
context.highlightPathCache->addRect(to);
|
||||||
|
} else if (toProgress <= 0.) {
|
||||||
|
context.highlightPathCache->addRect(0, top, width(), height);
|
||||||
|
} else {
|
||||||
|
const auto lerp = [=](int from, int to) {
|
||||||
|
return from + (to - from) * toProgress;
|
||||||
|
};
|
||||||
|
context.highlightPathCache->addRect(
|
||||||
|
lerp(0, to.x()),
|
||||||
|
lerp(top, to.y()),
|
||||||
|
lerp(width(), to.width()),
|
||||||
|
lerp(height, to.height()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TodoList::paintRadio(
|
void TodoList::paintRadio(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const Task &task,
|
const Task &task,
|
||||||
|
|
|
@ -117,6 +117,11 @@ private:
|
||||||
int top,
|
int top,
|
||||||
int paintw,
|
int paintw,
|
||||||
const PaintContext &context) const;
|
const PaintContext &context) const;
|
||||||
|
void appendTaskHighlight(
|
||||||
|
int id,
|
||||||
|
int top,
|
||||||
|
int height,
|
||||||
|
const PaintContext &context) const;
|
||||||
|
|
||||||
void radialAnimationCallback() const;
|
void radialAnimationCallback() const;
|
||||||
|
|
||||||
|
|
|
@ -835,12 +835,12 @@ InlineListData InlineListDataFromMessage(not_null<Element*> view) {
|
||||||
using Flag = InlineListData::Flag;
|
using Flag = InlineListData::Flag;
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
const auto &settings = AyuSettings::getInstance();
|
const auto &settings = AyuSettings::getInstance();
|
||||||
if (!settings.hideChannelReactions
|
if (!settings.showChannelReactions
|
||||||
&& item->history()->peer->isChannel()
|
&& item->history()->peer->isChannel()
|
||||||
&& !item->history()->peer->isMegagroup()) {
|
&& !item->history()->peer->isMegagroup()) {
|
||||||
return InlineListData();
|
return InlineListData();
|
||||||
}
|
}
|
||||||
if (!settings.hideGroupReactions
|
if (!settings.showGroupReactions
|
||||||
&& item->history()->peer->isMegagroup()) {
|
&& item->history()->peer->isMegagroup()) {
|
||||||
return InlineListData();
|
return InlineListData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1189,11 +1189,6 @@ bool AdjustMenuGeometryForSelector(
|
||||||
not_null<Ui::PopupMenu*> menu,
|
not_null<Ui::PopupMenu*> menu,
|
||||||
QPoint desiredPosition,
|
QPoint desiredPosition,
|
||||||
not_null<Selector*> selector) {
|
not_null<Selector*> selector) {
|
||||||
const auto &settings = AyuSettings::getInstance();
|
|
||||||
if (!AyuUi::needToShowItem(settings.showReactionsPanelInContextMenu)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto useTransparency = selector->useTransparency();
|
const auto useTransparency = selector->useTransparency();
|
||||||
const auto extend = useTransparency
|
const auto extend = useTransparency
|
||||||
? st::reactStripExtend
|
? st::reactStripExtend
|
||||||
|
@ -1362,6 +1357,12 @@ AttachSelectorResult AttachSelectorToMenu(
|
||||||
return AttachSelectorResult::Skipped;
|
return AttachSelectorResult::Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto peer = item->history()->peer;
|
||||||
|
if ((peer->isChannel() && !peer->isMegagroup() && !settings.showChannelReactions)
|
||||||
|
|| (peer->isMegagroup() && !settings.showGroupReactions)) {
|
||||||
|
return AttachSelectorResult::Skipped;
|
||||||
|
}
|
||||||
|
|
||||||
const auto result = AttachSelectorToMenu(
|
const auto result = AttachSelectorToMenu(
|
||||||
menu,
|
menu,
|
||||||
desiredPosition,
|
desiredPosition,
|
||||||
|
|
|
@ -731,8 +731,8 @@ manageDeleteGroupButton: SettingsCountButton(manageGroupNoIconButton) {
|
||||||
manageGroupReactions: IconButton(defaultIconButton) {
|
manageGroupReactions: IconButton(defaultIconButton) {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
icon: icon{{ "info/edit/stickers_add", historyComposeIconFg }};
|
icon: icon{{ "menu/add", historyComposeIconFg }};
|
||||||
iconOver: icon{{ "info/edit/stickers_add", historyComposeIconFgOver }};
|
iconOver: icon{{ "menu/add", historyComposeIconFgOver }};
|
||||||
}
|
}
|
||||||
manageGroupReactionsField: InputField(defaultInputField) {
|
manageGroupReactionsField: InputField(defaultInputField) {
|
||||||
textMargins: margins(1px, 12px, 24px, 8px);
|
textMargins: margins(1px, 12px, 24px, 8px);
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "api/api_premium.h"
|
#include "api/api_premium.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "boxes/star_gift_box.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_credits.h"
|
#include "data/data_credits.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -380,6 +381,7 @@ void InnerWidget::loadMore() {
|
||||||
_entries.clear();
|
_entries.clear();
|
||||||
}
|
}
|
||||||
_entries.reserve(_entries.size() + data.vgifts().v.size());
|
_entries.reserve(_entries.size() + data.vgifts().v.size());
|
||||||
|
auto hasUnique = false;
|
||||||
for (const auto &gift : data.vgifts().v) {
|
for (const auto &gift : data.vgifts().v) {
|
||||||
if (auto parsed = Api::FromTL(_peer, gift)) {
|
if (auto parsed = Api::FromTL(_peer, gift)) {
|
||||||
auto descriptor = DescriptorForGift(_peer, *parsed);
|
auto descriptor = DescriptorForGift(_peer, *parsed);
|
||||||
|
@ -387,10 +389,15 @@ void InnerWidget::loadMore() {
|
||||||
.gift = std::move(*parsed),
|
.gift = std::move(*parsed),
|
||||||
.descriptor = std::move(descriptor),
|
.descriptor = std::move(descriptor),
|
||||||
});
|
});
|
||||||
|
hasUnique = (parsed->info.unique != nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshButtons();
|
refreshButtons();
|
||||||
refreshAbout();
|
refreshAbout();
|
||||||
|
|
||||||
|
if (hasUnique) {
|
||||||
|
Ui::PreloadUniqueGiftResellPrices(&_peer->session());
|
||||||
|
}
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
_loadMoreRequestId = 0;
|
_loadMoreRequestId = 0;
|
||||||
_allLoaded = true;
|
_allLoaded = true;
|
||||||
|
|
|
@ -949,7 +949,20 @@ QString FormatCountDecimal(int64 number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FormatExactCountDecimal(float64 number) {
|
QString FormatExactCountDecimal(float64 number) {
|
||||||
return QLocale().toString(number, 'f', QLocale::FloatingPointShortest);
|
const auto locale = QLocale();
|
||||||
|
if (qFuzzyCompare(number, base::SafeRound(number))) {
|
||||||
|
return locale.toString(int64(base::SafeRound(number)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Somehow using QLocale::FloatingPointShortest sometimes produces
|
||||||
|
// "0.8500000000000001" on some systems / locales,
|
||||||
|
// so I want to stick to 6 digits max (default third argument value).
|
||||||
|
auto result = locale.toString(number, 'f');
|
||||||
|
const auto zero = locale.zeroDigit();
|
||||||
|
while (result.endsWith(zero)) {
|
||||||
|
result.chop(1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortenedCount FormatCreditsAmountToShort(CreditsAmount amount) {
|
ShortenedCount FormatCreditsAmountToShort(CreditsAmount amount) {
|
||||||
|
|
|
@ -1087,3 +1087,34 @@ mediaviewSponsoredButton: RoundButton(defaultActiveButton) {
|
||||||
|
|
||||||
ripple: universalRippleAnimation;
|
ripple: universalRippleAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mediaSponsoredSkip: 16px;
|
||||||
|
mediaSponsoredShift: 16px;
|
||||||
|
mediaSponsoredPadding: margins(12px, 8px, 8px, 8px);
|
||||||
|
mediaSponsoredThumb: 48px;
|
||||||
|
mediaSponsoredCloseTwice: 3px;
|
||||||
|
mediaSponsoredCloseSmall: 3px;
|
||||||
|
mediaSponsoredCloseSize: 11px;
|
||||||
|
mediaSponsoredCloseCorner: 6px;
|
||||||
|
mediaSponsoredCloseFull: 64px;
|
||||||
|
mediaSponsoredCloseStroke: 2px;
|
||||||
|
mediaSponsoredCloseRipple: 36px;
|
||||||
|
mediaSponsoredCloseDiameter: 24px;
|
||||||
|
mediaSponsoredCloseFont: font(12px bold);
|
||||||
|
|
||||||
|
mediaSponsoredAbout: RoundButton(defaultActiveButton) {
|
||||||
|
textFg: windowActiveTextFg;
|
||||||
|
textFgOver: windowActiveTextFg;
|
||||||
|
textBg: lightButtonBgOver;
|
||||||
|
textBgOver: lightButtonBgOver;
|
||||||
|
width: -12px;
|
||||||
|
height: 18px;
|
||||||
|
radius: 9px;
|
||||||
|
textTop: 0px;
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(12px);
|
||||||
|
}
|
||||||
|
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||||
|
color: lightButtonBgRipple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/view/media_view_pip.h"
|
#include "media/view/media_view_pip.h"
|
||||||
#include "media/view/media_view_overlay_raster.h"
|
#include "media/view/media_view_overlay_raster.h"
|
||||||
#include "media/view/media_view_overlay_opengl.h"
|
#include "media/view/media_view_overlay_opengl.h"
|
||||||
|
#include "media/view/media_view_playback_sponsored.h"
|
||||||
#include "media/stories/media_stories_share.h"
|
#include "media/stories/media_stories_share.h"
|
||||||
#include "media/stories/media_stories_view.h"
|
#include "media/stories/media_stories_view.h"
|
||||||
#include "media/streaming/media_streaming_document.h"
|
#include "media/streaming/media_streaming_document.h"
|
||||||
|
@ -339,6 +340,7 @@ struct OverlayWidget::Streamed {
|
||||||
|
|
||||||
Streaming::Instance instance;
|
Streaming::Instance instance;
|
||||||
std::unique_ptr<PlaybackControls> controls;
|
std::unique_ptr<PlaybackControls> controls;
|
||||||
|
std::unique_ptr<PlaybackSponsored> sponsored;
|
||||||
std::unique_ptr<base::PowerSaveBlocker> powerSaveBlocker;
|
std::unique_ptr<base::PowerSaveBlocker> powerSaveBlocker;
|
||||||
|
|
||||||
bool ready = false;
|
bool ready = false;
|
||||||
|
@ -1617,7 +1619,11 @@ void OverlayWidget::fillContextMenuActions(
|
||||||
if (const auto window = findWindow()) {
|
if (const auto window = findWindow()) {
|
||||||
const auto show = window->uiShow();
|
const auto show = window->uiShow();
|
||||||
const auto fullId = _message->fullId();
|
const auto fullId = _message->fullId();
|
||||||
Menu::FillSponsored(_body, addAction, show, fullId, true);
|
Menu::FillSponsored(
|
||||||
|
addAction,
|
||||||
|
show,
|
||||||
|
fullId,
|
||||||
|
{ .dark = true, .skipInfo = true });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3981,7 +3987,11 @@ bool OverlayWidget::initStreaming(const StartStreaming &startStreaming) {
|
||||||
&& !_streamed->instance.player().finished())) {
|
&& !_streamed->instance.player().finished())) {
|
||||||
startStreamingPlayer(startStreaming);
|
startStreamingPlayer(startStreaming);
|
||||||
} else {
|
} else {
|
||||||
_streamed->ready = _streamed->instance.player().ready();
|
if (_streamed->instance.player().ready()) {
|
||||||
|
markStreamedReady();
|
||||||
|
} else {
|
||||||
|
_streamed->ready = false;
|
||||||
|
}
|
||||||
updatePlaybackState();
|
updatePlaybackState();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -3994,7 +4004,7 @@ void OverlayWidget::startStreamingPlayer(
|
||||||
const auto &player = _streamed->instance.player();
|
const auto &player = _streamed->instance.player();
|
||||||
if (player.playing()) {
|
if (player.playing()) {
|
||||||
if (!_streamed->withSound) {
|
if (!_streamed->withSound) {
|
||||||
_streamed->ready = true;
|
markStreamedReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_pip = nullptr;
|
_pip = nullptr;
|
||||||
|
@ -4012,6 +4022,18 @@ void OverlayWidget::startStreamingPlayer(
|
||||||
restartAtSeekPosition(_streamedPosition);
|
restartAtSeekPosition(_streamedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OverlayWidget::markStreamedReady() {
|
||||||
|
Expects(_streamed != nullptr);
|
||||||
|
|
||||||
|
if (_streamed->ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_streamed->ready = true;
|
||||||
|
if (const auto sponsored = _streamed->sponsored.get()) {
|
||||||
|
sponsored->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OverlayWidget::initStreamingThumbnail() {
|
void OverlayWidget::initStreamingThumbnail() {
|
||||||
Expects(_photo || _document);
|
Expects(_photo || _document);
|
||||||
|
|
||||||
|
@ -4083,7 +4105,7 @@ void OverlayWidget::initStreamingThumbnail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayWidget::streamingReady(Streaming::Information &&info) {
|
void OverlayWidget::streamingReady(Streaming::Information &&info) {
|
||||||
_streamed->ready = true;
|
markStreamedReady();
|
||||||
if (videoShown()) {
|
if (videoShown()) {
|
||||||
applyVideoSize();
|
applyVideoSize();
|
||||||
_streamedQualityChangeFrame = QImage();
|
_streamedQualityChangeFrame = QImage();
|
||||||
|
@ -4105,6 +4127,7 @@ void OverlayWidget::applyVideoSize() {
|
||||||
|
|
||||||
bool OverlayWidget::createStreamingObjects() {
|
bool OverlayWidget::createStreamingObjects() {
|
||||||
Expects(_photo || _document);
|
Expects(_photo || _document);
|
||||||
|
Expects(!_streamed);
|
||||||
|
|
||||||
const auto origin = fileOrigin();
|
const auto origin = fileOrigin();
|
||||||
const auto callback = [=] { waitingAnimationCallback(); };
|
const auto callback = [=] { waitingAnimationCallback(); };
|
||||||
|
@ -4137,6 +4160,18 @@ bool OverlayWidget::createStreamingObjects() {
|
||||||
_body,
|
_body,
|
||||||
static_cast<PlaybackControls::Delegate*>(this));
|
static_cast<PlaybackControls::Delegate*>(this));
|
||||||
_streamed->controls->show();
|
_streamed->controls->show();
|
||||||
|
_streamed->sponsored = PlaybackSponsored::Has(_message)
|
||||||
|
? std::make_unique<PlaybackSponsored>(
|
||||||
|
_streamed->controls.get(),
|
||||||
|
uiShow(),
|
||||||
|
_message)
|
||||||
|
: nullptr;
|
||||||
|
if (const auto sponsored = _streamed->sponsored.get()) {
|
||||||
|
_layerBg->layerShownValue(
|
||||||
|
) | rpl::start_with_next([=](bool shown) {
|
||||||
|
sponsored->setPaused(shown);
|
||||||
|
}, sponsored->lifetime());
|
||||||
|
}
|
||||||
refreshClipControllerGeometry();
|
refreshClipControllerGeometry();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -78,6 +78,7 @@ struct ContentLayout;
|
||||||
|
|
||||||
namespace Media::View {
|
namespace Media::View {
|
||||||
|
|
||||||
|
class PlaybackSponsored;
|
||||||
class GroupThumbs;
|
class GroupThumbs;
|
||||||
class Pip;
|
class Pip;
|
||||||
|
|
||||||
|
@ -412,6 +413,7 @@ private:
|
||||||
const StartStreaming &startStreaming = StartStreaming());
|
const StartStreaming &startStreaming = StartStreaming());
|
||||||
void startStreamingPlayer(const StartStreaming &startStreaming);
|
void startStreamingPlayer(const StartStreaming &startStreaming);
|
||||||
void initStreamingThumbnail();
|
void initStreamingThumbnail();
|
||||||
|
void markStreamedReady();
|
||||||
void streamingReady(Streaming::Information &&info);
|
void streamingReady(Streaming::Information &&info);
|
||||||
[[nodiscard]] bool createStreamingObjects();
|
[[nodiscard]] bool createStreamingObjects();
|
||||||
void handleStreamingUpdate(Streaming::Update &&update);
|
void handleStreamingUpdate(Streaming::Update &&update);
|
||||||
|
|
|
@ -19,14 +19,13 @@ class MediaSlider;
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Media {
|
namespace Media::Player {
|
||||||
namespace Player {
|
|
||||||
struct TrackState;
|
struct TrackState;
|
||||||
class SettingsButton;
|
class SettingsButton;
|
||||||
class SpeedController;
|
class SpeedController;
|
||||||
} // namespace Player
|
} // namespace Media::Player
|
||||||
|
|
||||||
namespace View {
|
namespace Media::View {
|
||||||
|
|
||||||
class PlaybackProgress;
|
class PlaybackProgress;
|
||||||
|
|
||||||
|
@ -131,5 +130,4 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace View
|
} // namespace Media::View
|
||||||
} // namespace Media
|
|
||||||
|
|
|
@ -0,0 +1,767 @@
|
||||||
|
/*
|
||||||
|
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 "media/view/media_view_playback_sponsored.h"
|
||||||
|
|
||||||
|
#include "boxes/premium_preview_box.h"
|
||||||
|
#include "data/components/sponsored_messages.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
|
#include "data/data_photo.h"
|
||||||
|
#include "data/data_photo_media.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "menu/menu_sponsored.h"
|
||||||
|
#include "ui/effects/numbers_animation.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||||
|
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/basic_click_handlers.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "ui/ui_utility.h"
|
||||||
|
#include "ui/cached_round_corners.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_media_view.h"
|
||||||
|
|
||||||
|
namespace Media::View {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kStartDelayMin = crl::time(1000);
|
||||||
|
constexpr auto kDurationMin = 5 * crl::time(1000);
|
||||||
|
|
||||||
|
enum class Action {
|
||||||
|
Close,
|
||||||
|
PromotePremium,
|
||||||
|
Pause,
|
||||||
|
Unpause,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Close final : public Ui::RippleButton {
|
||||||
|
public:
|
||||||
|
Close(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
const style::RippleAnimation &st,
|
||||||
|
rpl::producer<crl::time> allowCloseAt);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<Action> actions() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
void updateProgress(crl::time now);
|
||||||
|
|
||||||
|
rpl::event_stream<Action> _actions;
|
||||||
|
|
||||||
|
Ui::NumbersAnimation _countdown;
|
||||||
|
Ui::Animations::Basic _progress;
|
||||||
|
base::Timer _noAnimationTimer;
|
||||||
|
crl::time _allowCloseAt = 0;
|
||||||
|
crl::time _startedAt = 0;
|
||||||
|
crl::time _pausedAt = 0;
|
||||||
|
int _secondsTill = 0;
|
||||||
|
int _rippleSize = 0;
|
||||||
|
QPoint _rippleOrigin;
|
||||||
|
bool _allowClose = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Close::Close(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
const style::RippleAnimation &st,
|
||||||
|
rpl::producer<crl::time> allowCloseAt)
|
||||||
|
: RippleButton(parent, st)
|
||||||
|
, _countdown(st::mediaSponsoredCloseFont, [=] { update(); })
|
||||||
|
, _progress([=](crl::time now) { updateProgress(now); })
|
||||||
|
, _noAnimationTimer([=] { updateProgress(crl::now()); })
|
||||||
|
, _startedAt(crl::now()) {
|
||||||
|
resize(st::mediaSponsoredCloseFull, st::mediaSponsoredCloseFull);
|
||||||
|
|
||||||
|
const auto size = st::mediaSponsoredCloseRipple;
|
||||||
|
const auto cut = int(base::SafeRound((width() - size) / 2.));
|
||||||
|
_rippleSize = std::min(width() - 2 * cut, height() - 2 * cut);
|
||||||
|
_rippleOrigin = QPoint(
|
||||||
|
(width() - _rippleSize) / 2,
|
||||||
|
(height() - _rippleSize) / 2);
|
||||||
|
|
||||||
|
std::move(
|
||||||
|
allowCloseAt
|
||||||
|
) | rpl::start_with_next([=](crl::time at) {
|
||||||
|
const auto now = crl::now();
|
||||||
|
if (!at) {
|
||||||
|
updateProgress(now);
|
||||||
|
_pausedAt = now;
|
||||||
|
_progress.stop();
|
||||||
|
} else {
|
||||||
|
if (_pausedAt) {
|
||||||
|
_startedAt += now - base::take(_pausedAt);
|
||||||
|
}
|
||||||
|
_allowCloseAt = at;
|
||||||
|
updateProgress(now);
|
||||||
|
if (!anim::Disabled()) {
|
||||||
|
_progress.start();
|
||||||
|
} else if (!_allowClose) {
|
||||||
|
_noAnimationTimer.callEach(crl::time(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
updateProgress(_startedAt);
|
||||||
|
|
||||||
|
setClickedCallback([=] {
|
||||||
|
_actions.fire(_allowClose ? Action::Close : Action::PromotePremium);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Action> Close::actions() const {
|
||||||
|
return _actions.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close::updateProgress(crl::time now) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint Close::prepareRippleStartPosition() const {
|
||||||
|
return mapFromGlobal(QCursor::pos()) - _rippleOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Close::prepareRippleMask() const {
|
||||||
|
return Ui::RippleAnimation::EllipseMask({ _rippleSize, _rippleSize });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close::paintEvent(QPaintEvent *e) {
|
||||||
|
auto p = QPainter(this);
|
||||||
|
|
||||||
|
paintRipple(p, _rippleOrigin);
|
||||||
|
|
||||||
|
const auto now = crl::now();
|
||||||
|
if (!_pausedAt) {
|
||||||
|
_allowClose = (now >= _allowCloseAt);
|
||||||
|
}
|
||||||
|
const auto msTill = _allowCloseAt - (_pausedAt ? _pausedAt : now);
|
||||||
|
const auto msFull = _allowCloseAt - _startedAt;
|
||||||
|
const auto secondsTill = (std::max(msTill, crl::time()) + 999) / 1000;
|
||||||
|
const auto secondsFull = (std::max(msFull, crl::time()) + 999) / 1000;
|
||||||
|
const auto allowCloseLeft = anim::Disabled()
|
||||||
|
? (secondsFull ? (secondsTill / float64(secondsFull)) : 0)
|
||||||
|
: std::max(msFull ? (msTill / float64(msFull)) : 0., 0.);
|
||||||
|
const auto duration = crl::time(st::fadeWrapDuration);
|
||||||
|
const auto allowedProgress = anim::Disabled()
|
||||||
|
? (secondsTill ? 0. : 1.)
|
||||||
|
: std::clamp(-msTill, crl::time(), duration) / float64(duration);
|
||||||
|
|
||||||
|
if (_secondsTill != secondsTill) {
|
||||||
|
const auto initial = !_secondsTill;
|
||||||
|
_secondsTill = secondsTill;
|
||||||
|
_countdown.setText(QString::number(_secondsTill), _secondsTill);
|
||||||
|
if (initial) {
|
||||||
|
_countdown.finishAnimating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pen = st::mediaviewTextLinkFg->p;
|
||||||
|
if (allowedProgress < 1.) {
|
||||||
|
if (allowedProgress > 0.) {
|
||||||
|
p.setOpacity(1. - allowedProgress);
|
||||||
|
}
|
||||||
|
p.setPen(pen);
|
||||||
|
|
||||||
|
const auto inner = QRect(
|
||||||
|
(width() - st::mediaSponsoredCloseDiameter) / 2,
|
||||||
|
(height() - st::mediaSponsoredCloseDiameter) / 2,
|
||||||
|
st::mediaSponsoredCloseDiameter,
|
||||||
|
st::mediaSponsoredCloseDiameter);
|
||||||
|
p.setFont(st::mediaSponsoredCloseFont);
|
||||||
|
_countdown.paint(
|
||||||
|
p,
|
||||||
|
inner.x() + (inner.width() - _countdown.countWidth()) / 2,
|
||||||
|
(inner.y()
|
||||||
|
+ (inner.height()
|
||||||
|
- st::mediaSponsoredCloseFont->height) / 2),
|
||||||
|
width());
|
||||||
|
|
||||||
|
const auto skip = 0.23;
|
||||||
|
const auto len = int(base::SafeRound(
|
||||||
|
arc::kFullLength * (1. - skip) * allowCloseLeft));
|
||||||
|
if (len > 0) {
|
||||||
|
const auto from = arc::kFullLength / 4;
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
pen.setWidthF(st::mediaSponsoredCloseStroke);
|
||||||
|
pen.setCapStyle(Qt::RoundCap);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.drawArc(inner, from, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sizeFinal = st::mediaSponsoredCloseSize;
|
||||||
|
const auto sizeSmall = st::mediaSponsoredCloseCorner;
|
||||||
|
const auto twiceFinal = st::mediaSponsoredCloseTwice;
|
||||||
|
const auto twiceSmall = st::mediaSponsoredCloseSmall;
|
||||||
|
const auto size = sizeSmall + allowedProgress * (sizeFinal - sizeSmall);
|
||||||
|
const auto twice = twiceSmall
|
||||||
|
+ allowedProgress * (twiceFinal - twiceSmall);
|
||||||
|
const auto leftFinal = (width() - size) / 2.;
|
||||||
|
const auto leftSmall = (width() + st::mediaSponsoredCloseDiameter) / 2.
|
||||||
|
- (st::mediaSponsoredCloseStroke / 2.)
|
||||||
|
- sizeSmall;
|
||||||
|
const auto topFinal = (height() - size) / 2.;
|
||||||
|
const auto topSmall = (height() - st::mediaSponsoredCloseDiameter) / 2.;
|
||||||
|
const auto left = leftSmall + allowedProgress * (leftFinal - leftSmall);
|
||||||
|
const auto top = topSmall + allowedProgress * (topFinal - topSmall);
|
||||||
|
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
pen.setWidthF(twice / 2.);
|
||||||
|
p.setPen(pen);
|
||||||
|
p.drawLine(QPointF(left, top), QPointF(left + size, top + size));
|
||||||
|
p.drawLine(QPointF(left + size, top), QPointF(left, top + size));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] style::RoundButton PrepareAboutStyle() {
|
||||||
|
static auto textBg = style::complex_color([] {
|
||||||
|
auto result = st::mediaviewTextLinkFg->c;
|
||||||
|
result.setAlphaF(result.alphaF() * 0.1);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
static auto textBgOver = style::complex_color([] {
|
||||||
|
auto result = st::mediaviewTextLinkFg->c;
|
||||||
|
result.setAlphaF(result.alphaF() * 0.15);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
static auto rippleColor = style::complex_color([] {
|
||||||
|
auto result = st::mediaviewTextLinkFg->c;
|
||||||
|
result.setAlphaF(result.alphaF() * 0.2);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto result = st::mediaSponsoredAbout;
|
||||||
|
result.textFg = st::mediaviewTextLinkFg;
|
||||||
|
result.textFgOver = st::mediaviewTextLinkFg;
|
||||||
|
result.textBg = textBg.color();
|
||||||
|
result.textBgOver = textBgOver.color();
|
||||||
|
result.ripple.color = rippleColor.color();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class PlaybackSponsored::Message final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
Message(
|
||||||
|
QWidget *parent,
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
const Data::SponsoredMessage &data,
|
||||||
|
rpl::producer<crl::time> allowCloseAt);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<Action> actions() const;
|
||||||
|
|
||||||
|
void setFinalPosition(int x, int y);
|
||||||
|
|
||||||
|
void fadeIn();
|
||||||
|
void fadeOut(Fn<void()> hidden);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
|
void populate();
|
||||||
|
void startFadeIn();
|
||||||
|
void updateShown(Fn<void()> finished = nullptr);
|
||||||
|
void startFade(Fn<void()> finished);
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||||
|
const Data::SponsoredMessage _data;
|
||||||
|
|
||||||
|
style::RoundButton _aboutSt;
|
||||||
|
std::unique_ptr<Ui::RoundButton> _about;
|
||||||
|
std::unique_ptr<Close> _close;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
rpl::event_stream<Action> _actions;
|
||||||
|
|
||||||
|
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||||
|
Ui::Text::String _title;
|
||||||
|
Ui::Text::String _text;
|
||||||
|
|
||||||
|
QPoint _finalPosition;
|
||||||
|
int _left = 0;
|
||||||
|
int _top = 0;
|
||||||
|
int _titleHeight = 0;
|
||||||
|
int _textHeight = 0;
|
||||||
|
|
||||||
|
QImage _cache;
|
||||||
|
Ui::Animations::Simple _showAnimation;
|
||||||
|
bool _shown = false;
|
||||||
|
bool _over = false;
|
||||||
|
bool _pressed = false;
|
||||||
|
|
||||||
|
rpl::lifetime _photoLifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
PlaybackSponsored::Message::Message(
|
||||||
|
QWidget *parent,
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
const Data::SponsoredMessage &data,
|
||||||
|
rpl::producer<crl::time> allowCloseAt)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _session(&data.history->session())
|
||||||
|
, _show(std::move(show))
|
||||||
|
, _data(data)
|
||||||
|
, _aboutSt(PrepareAboutStyle())
|
||||||
|
, _about(std::make_unique<Ui::RoundButton>(
|
||||||
|
this,
|
||||||
|
tr::lng_search_sponsored_button(),
|
||||||
|
_aboutSt))
|
||||||
|
, _close(
|
||||||
|
std::make_unique<Close>(
|
||||||
|
this,
|
||||||
|
_aboutSt.ripple,
|
||||||
|
std::move(allowCloseAt))) {
|
||||||
|
_about->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||||
|
setMouseTracking(true);
|
||||||
|
populate();
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Action> PlaybackSponsored::Message::actions() const {
|
||||||
|
return rpl::merge(_actions.events(), _close->actions());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::setFinalPosition(int x, int y) {
|
||||||
|
_finalPosition = { x, y };
|
||||||
|
if (_shown) {
|
||||||
|
updateShown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::fadeIn() {
|
||||||
|
_shown = true;
|
||||||
|
if (!_photo || _photo->loaded()) {
|
||||||
|
startFadeIn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_photo->owner()->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return _photo->loaded();
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
_photoLifetime.destroy();
|
||||||
|
startFadeIn();
|
||||||
|
}, _photoLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::startFadeIn() {
|
||||||
|
if (!_shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startFade([=] {
|
||||||
|
_session->sponsoredMessages().view(_data.randomId);
|
||||||
|
});
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::fadeOut(Fn<void()> hidden) {
|
||||||
|
if (!_shown) {
|
||||||
|
if (const auto onstack = hidden) {
|
||||||
|
onstack();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shown = false;
|
||||||
|
startFade(std::move(hidden));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::startFade(Fn<void()> finished) {
|
||||||
|
_cache = Ui::GrabWidgetToImage(this);
|
||||||
|
_about->hide();
|
||||||
|
_close->hide();
|
||||||
|
const auto from = _shown ? 0. : 1.;
|
||||||
|
const auto till = _shown ? 1. : 0.;
|
||||||
|
_showAnimation.start([=] {
|
||||||
|
updateShown(finished);
|
||||||
|
}, from, till, st::fadeWrapDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::updateShown(Fn<void()> finished) {
|
||||||
|
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
||||||
|
const auto shift = anim::interpolate(st::mediaSponsoredShift, 0, shown);
|
||||||
|
move(_finalPosition.x(), _finalPosition.y() + shift);
|
||||||
|
update();
|
||||||
|
if (!_showAnimation.animating()) {
|
||||||
|
_cache = QImage();
|
||||||
|
_close->show();
|
||||||
|
_about->show();
|
||||||
|
if (const auto onstack = finished) {
|
||||||
|
onstack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::paintEvent(QPaintEvent *e) {
|
||||||
|
auto p = QPainter(this);
|
||||||
|
|
||||||
|
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
||||||
|
if (!_cache.isNull()) {
|
||||||
|
p.setOpacity(shown);
|
||||||
|
p.drawImage(0, 0, _cache);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ui::FillRoundRect(
|
||||||
|
p,
|
||||||
|
rect(),
|
||||||
|
st::mediaviewSaveMsgBg,
|
||||||
|
Ui::MediaviewSaveCorners);
|
||||||
|
|
||||||
|
const auto &padding = st::mediaSponsoredPadding;
|
||||||
|
if (_photo) {
|
||||||
|
if (const auto image = _photo->image(Data::PhotoSize::Large)) {
|
||||||
|
const auto size = st::mediaSponsoredThumb;
|
||||||
|
const auto x = padding.left();
|
||||||
|
const auto y = (height() - size) / 2;
|
||||||
|
p.drawPixmap(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
image->pixSingle(
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
{ .options = Images::Option::RoundCircle }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setPen(st::mediaviewControlFg);
|
||||||
|
|
||||||
|
_title.draw(p, {
|
||||||
|
.position = { _left, _top },
|
||||||
|
.availableWidth = _about->x() - _left,
|
||||||
|
.palette = &st::mediaviewTextPalette,
|
||||||
|
});
|
||||||
|
|
||||||
|
_text.draw(p, {
|
||||||
|
.position = { _left, _top + _titleHeight },
|
||||||
|
.availableWidth = _close->x() - _left,
|
||||||
|
.palette = &st::mediaviewTextPalette,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
const auto &padding = st::mediaSponsoredPadding;
|
||||||
|
const auto point = e->pos();
|
||||||
|
const auto about = _about->geometry();
|
||||||
|
const auto close = _close->geometry();
|
||||||
|
const auto over = !about.marginsAdded(padding).contains(point)
|
||||||
|
&& !close.marginsAdded(padding).contains(point);
|
||||||
|
if (_over != over) {
|
||||||
|
_over = over;
|
||||||
|
setCursor(_over ? style::cur_pointer : style::cur_default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::mousePressEvent(QMouseEvent *e) {
|
||||||
|
if (_over) {
|
||||||
|
_pressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
if (base::take(_pressed) && _over) {
|
||||||
|
_session->sponsoredMessages().clicked(_data.randomId, false, false);
|
||||||
|
UrlClickHandler::Open(_data.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PlaybackSponsored::Message::resizeGetHeight(int newWidth) {
|
||||||
|
const auto &padding = st::mediaSponsoredPadding;
|
||||||
|
const auto userpic = st::mediaSponsoredThumb;
|
||||||
|
const auto innerWidth = newWidth - _left - _close->width();
|
||||||
|
const auto titleWidth = innerWidth - _about->width() - padding.right();
|
||||||
|
_titleHeight = _title.countHeight(titleWidth);
|
||||||
|
_textHeight = _text.countHeight(innerWidth);
|
||||||
|
|
||||||
|
const auto use = std::max(_titleHeight + _textHeight, userpic);
|
||||||
|
|
||||||
|
const auto height = padding.top() + use + padding.bottom();
|
||||||
|
_left = padding.left() + (_photo ? (userpic + padding.left()) : 0);
|
||||||
|
_top = padding.top() + (use - _titleHeight - _textHeight) / 2;
|
||||||
|
|
||||||
|
_about->move(
|
||||||
|
_left + std::min(titleWidth, _title.maxWidth()) + padding.right(),
|
||||||
|
_top);
|
||||||
|
_close->move(
|
||||||
|
newWidth - _close->width(),
|
||||||
|
(height - _close->height()) / 2);
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::Message::populate() {
|
||||||
|
const auto &from = _data.from;
|
||||||
|
const auto photo = from.photoId
|
||||||
|
? _data.history->owner().photo(from.photoId).get()
|
||||||
|
: nullptr;
|
||||||
|
if (photo) {
|
||||||
|
_photo = photo->createMediaView();
|
||||||
|
photo->load({}, LoadFromCloudOrLocal, true);
|
||||||
|
}
|
||||||
|
_title = Ui::Text::String(
|
||||||
|
st::semiboldTextStyle,
|
||||||
|
from.title,
|
||||||
|
kDefaultTextOptions,
|
||||||
|
st::msgMinWidth);
|
||||||
|
_text = Ui::Text::String(
|
||||||
|
st::defaultTextStyle,
|
||||||
|
_data.textWithEntities,
|
||||||
|
kMarkupTextOptions,
|
||||||
|
st::msgMinWidth);
|
||||||
|
|
||||||
|
_about->setClickedCallback([=] {
|
||||||
|
_menu = nullptr;
|
||||||
|
const auto parent = parentWidget();
|
||||||
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
parent,
|
||||||
|
st::mediaviewPopupMenu);
|
||||||
|
const auto raw = _menu.get();
|
||||||
|
const auto addAction = Ui::Menu::CreateAddActionCallback(raw);
|
||||||
|
Menu::FillSponsored(
|
||||||
|
addAction,
|
||||||
|
_show,
|
||||||
|
Menu::SponsoredPhrases::Channel,
|
||||||
|
_session->sponsoredMessages().lookupDetails(_data),
|
||||||
|
_session->sponsoredMessages().createReportCallback(
|
||||||
|
_data.randomId,
|
||||||
|
crl::guard(this, [=] { _actions.fire(Action::Close); })),
|
||||||
|
{ .dark = true });
|
||||||
|
_actions.fire(Action::Pause);
|
||||||
|
Ui::Connect(raw, &QObject::destroyed, this, [=] {
|
||||||
|
_actions.fire(Action::Unpause);
|
||||||
|
});
|
||||||
|
raw->popup(QCursor::pos());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaybackSponsored::PlaybackSponsored(
|
||||||
|
not_null<Ui::RpWidget*> controls,
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
not_null<HistoryItem*> item)
|
||||||
|
: _parent(controls->parentWidget())
|
||||||
|
, _session(&item->history()->session())
|
||||||
|
, _show(std::move(show))
|
||||||
|
, _itemId(item->fullId())
|
||||||
|
, _controlsGeometry(controls->geometryValue())
|
||||||
|
, _timer([=] { update(); }) {
|
||||||
|
_session->sponsoredMessages().requestForVideo(item, crl::guard(this, [=](
|
||||||
|
Data::SponsoredForVideo data) {
|
||||||
|
if (data.list.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_data = std::move(data);
|
||||||
|
if (_data->state.initial()
|
||||||
|
|| (_data->state.itemIndex > _data->list.size())
|
||||||
|
|| (_data->state.itemIndex == _data->list.size()
|
||||||
|
&& _data->state.leftTillShow <= 0)) {
|
||||||
|
_data->state.itemIndex = 0;
|
||||||
|
_data->state.leftTillShow = std::max(
|
||||||
|
_data->startDelay,
|
||||||
|
kStartDelayMin);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaybackSponsored::~PlaybackSponsored() {
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::start() {
|
||||||
|
_started = true;
|
||||||
|
if (!_paused) {
|
||||||
|
_start = crl::now();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::setPaused(bool paused) {
|
||||||
|
setPausedOutside(paused);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::updatePaused() {
|
||||||
|
const auto paused = _pausedInside || _pausedOutside;
|
||||||
|
if (_paused == paused) {
|
||||||
|
return;
|
||||||
|
} else if (_started && paused) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
_paused = paused;
|
||||||
|
if (!_started) {
|
||||||
|
return;
|
||||||
|
} else if (_paused) {
|
||||||
|
_start = 0;
|
||||||
|
_timer.cancel();
|
||||||
|
_allowCloseAt = 0;
|
||||||
|
} else {
|
||||||
|
_start = crl::now();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::setPausedInside(bool paused) {
|
||||||
|
if (_pausedInside == paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_pausedInside = paused;
|
||||||
|
updatePaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::setPausedOutside(bool paused) {
|
||||||
|
if (_pausedOutside == paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_pausedOutside = paused;
|
||||||
|
updatePaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::finish() {
|
||||||
|
_timer.cancel();
|
||||||
|
if (_data) {
|
||||||
|
saveState();
|
||||||
|
_data = std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::update() {
|
||||||
|
if (!_data || !_start) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto [now, state] = computeState();
|
||||||
|
const auto message = (_data->state.itemIndex < _data->list.size())
|
||||||
|
? &_data->list[state.itemIndex]
|
||||||
|
: nullptr;
|
||||||
|
const auto duration = message
|
||||||
|
? std::max(
|
||||||
|
message->durationMin + kDurationMin,
|
||||||
|
message->durationMax)
|
||||||
|
: crl::time(0);
|
||||||
|
if (_data->state.leftTillShow > 0 && state.leftTillShow <= 0) {
|
||||||
|
_data->state.leftTillShow = 0;
|
||||||
|
if (duration) {
|
||||||
|
_allowCloseAt = now + message->durationMin;
|
||||||
|
show(*message);
|
||||||
|
|
||||||
|
_start = now;
|
||||||
|
_timer.callOnce(duration);
|
||||||
|
saveState();
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
} else if (_data->state.leftTillShow <= 0
|
||||||
|
&& state.leftTillShow <= -duration) {
|
||||||
|
hide(now);
|
||||||
|
} else {
|
||||||
|
if (state.leftTillShow <= 0 && duration) {
|
||||||
|
_allowCloseAt = now + state.leftTillShow + message->durationMin;
|
||||||
|
if (!_widget) {
|
||||||
|
show(*message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_data->state = state;
|
||||||
|
_timer.callOnce((state.leftTillShow > 0)
|
||||||
|
? state.leftTillShow
|
||||||
|
: (state.leftTillShow + duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlaybackSponsored::show(const Data::SponsoredMessage &data) {
|
||||||
|
_widget = std::make_unique<Message>(
|
||||||
|
_parent,
|
||||||
|
_show,
|
||||||
|
data,
|
||||||
|
_allowCloseAt.value());
|
||||||
|
const auto raw = _widget.get();
|
||||||
|
|
||||||
|
_controlsGeometry.value() | rpl::start_with_next([=](QRect controls) {
|
||||||
|
raw->resizeToWidth(controls.width());
|
||||||
|
raw->setFinalPosition(
|
||||||
|
controls.x(),
|
||||||
|
controls.y() - st::mediaSponsoredSkip - raw->height());
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
raw->actions() | rpl::start_with_next([=](Action action) {
|
||||||
|
switch (action) {
|
||||||
|
case Action::Close: hide(crl::now()); break;
|
||||||
|
case Action::PromotePremium: showPremiumPromo(); break;
|
||||||
|
case Action::Pause: setPausedInside(true); break;
|
||||||
|
case Action::Unpause: setPausedInside(false); break;
|
||||||
|
}
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
raw->fadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::showPremiumPromo() {
|
||||||
|
ShowPremiumPreviewBox(_show, PremiumFeature::NoAds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::hide(crl::time now) {
|
||||||
|
Expects(_widget != nullptr);
|
||||||
|
|
||||||
|
_widget->fadeOut([this, raw = _widget.get()] {
|
||||||
|
if (_widget.get() == raw) {
|
||||||
|
_widget = nullptr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
++_data->state.itemIndex;
|
||||||
|
_data->state.leftTillShow = std::max(
|
||||||
|
_data->betweenDelay,
|
||||||
|
kStartDelayMin);
|
||||||
|
_start = now;
|
||||||
|
_timer.callOnce(_data->state.leftTillShow);
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaybackSponsored::saveState() {
|
||||||
|
_session->sponsoredMessages().updateForVideo(
|
||||||
|
_itemId,
|
||||||
|
computeState().data);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaybackSponsored::State PlaybackSponsored::computeState() const {
|
||||||
|
auto result = State{ crl::now() };
|
||||||
|
if (!_data) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.data = _data->state;
|
||||||
|
if (!_start) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const auto elapsed = result.now - _start;
|
||||||
|
result.data.leftTillShow -= elapsed;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::lifetime &PlaybackSponsored::lifetime() {
|
||||||
|
return _lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaybackSponsored::Has(HistoryItem *item) {
|
||||||
|
return item
|
||||||
|
&& item->history()->session().sponsoredMessages().canHaveFor(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Media::View
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
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 "base/timer.h"
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
#include "data/components/sponsored_messages.h"
|
||||||
|
|
||||||
|
namespace ChatHelpers {
|
||||||
|
class Show;
|
||||||
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class RpWidget;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Media::View {
|
||||||
|
|
||||||
|
class PlaybackSponsored final : public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
PlaybackSponsored(
|
||||||
|
not_null<Ui::RpWidget*> controls,
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
not_null<HistoryItem*> item);
|
||||||
|
~PlaybackSponsored();
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void setPaused(bool paused);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
[[nodiscard]] static bool Has(HistoryItem *item);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Message;
|
||||||
|
struct State {
|
||||||
|
crl::time now = 0;
|
||||||
|
Data::SponsoredForVideoState data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void update();
|
||||||
|
void finish();
|
||||||
|
void updatePaused();
|
||||||
|
void showPremiumPromo();
|
||||||
|
void setPausedInside(bool paused);
|
||||||
|
void setPausedOutside(bool paused);
|
||||||
|
void show(const Data::SponsoredMessage &data);
|
||||||
|
void hide(crl::time now);
|
||||||
|
[[nodiscard]] State computeState() const;
|
||||||
|
void saveState();
|
||||||
|
|
||||||
|
const not_null<QWidget*> _parent;
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||||
|
const FullMsgId _itemId;
|
||||||
|
|
||||||
|
rpl::variable<QRect> _controlsGeometry;
|
||||||
|
std::unique_ptr<Message> _widget;
|
||||||
|
|
||||||
|
rpl::variable<crl::time> _allowCloseAt;
|
||||||
|
crl::time _start = 0;
|
||||||
|
bool _started = false;
|
||||||
|
bool _paused = false;
|
||||||
|
bool _pausedInside = false;
|
||||||
|
bool _pausedOutside = false;
|
||||||
|
base::Timer _timer;
|
||||||
|
|
||||||
|
std::optional<Data::SponsoredForVideo> _data;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Media::View
|
|
@ -287,14 +287,12 @@ void AboutBox(
|
||||||
top->setForceRippled(false);
|
top->setForceRippled(false);
|
||||||
});
|
});
|
||||||
FillSponsored(
|
FillSponsored(
|
||||||
top,
|
|
||||||
Ui::Menu::CreateAddActionCallback(menu->get()),
|
Ui::Menu::CreateAddActionCallback(menu->get()),
|
||||||
show,
|
show,
|
||||||
phrases,
|
phrases,
|
||||||
details,
|
details,
|
||||||
report,
|
report,
|
||||||
false,
|
{ .skipAbout = true });
|
||||||
true);
|
|
||||||
const auto global = top->mapToGlobal(
|
const auto global = top->mapToGlobal(
|
||||||
QPoint(top->width() / 4 * 3, top->height() / 2));
|
QPoint(top->width() / 4 * 3, top->height() / 2));
|
||||||
raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
||||||
|
@ -390,18 +388,17 @@ void ShowReportSponsoredBox(
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void FillSponsored(
|
void FillSponsored(
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
const Ui::Menu::MenuCallback &addAction,
|
const Ui::Menu::MenuCallback &addAction,
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
SponsoredPhrases phrases,
|
SponsoredPhrases phrases,
|
||||||
const Data::SponsoredMessages::Details &details,
|
const Data::SponsoredMessages::Details &details,
|
||||||
Data::SponsoredReportAction report,
|
Data::SponsoredReportAction report,
|
||||||
bool mediaViewer,
|
SponsoredMenuSettings settings) {
|
||||||
bool skipAbout) {
|
|
||||||
const auto session = &show->session();
|
const auto session = &show->session();
|
||||||
const auto &info = details.info;
|
const auto &info = details.info;
|
||||||
|
const auto dark = settings.dark;
|
||||||
|
|
||||||
if (!mediaViewer && !info.empty()) {
|
if (!settings.skipInfo && !info.empty()) {
|
||||||
auto fillSubmenu = [&](not_null<Ui::PopupMenu*> menu) {
|
auto fillSubmenu = [&](not_null<Ui::PopupMenu*> menu) {
|
||||||
const auto allText = ranges::accumulate(
|
const auto allText = ranges::accumulate(
|
||||||
info,
|
info,
|
||||||
|
@ -416,8 +413,10 @@ void FillSponsored(
|
||||||
for (const auto &i : info) {
|
for (const auto &i : info) {
|
||||||
auto item = base::make_unique_q<Ui::Menu::MultilineAction>(
|
auto item = base::make_unique_q<Ui::Menu::MultilineAction>(
|
||||||
menu,
|
menu,
|
||||||
st::defaultMenu,
|
dark ? st::storiesMenu : st::defaultMenu,
|
||||||
st::historySponsorInfoItem,
|
(dark
|
||||||
|
? st::historySponsorInfoItemDark
|
||||||
|
: st::historySponsorInfoItem),
|
||||||
st::historyHasCustomEmojiPosition,
|
st::historyHasCustomEmojiPosition,
|
||||||
base::duplicate(i));
|
base::duplicate(i));
|
||||||
item->clicks(
|
item->clicks(
|
||||||
|
@ -431,27 +430,31 @@ void FillSponsored(
|
||||||
addAction({
|
addAction({
|
||||||
.text = tr::lng_sponsored_info_menu(tr::now),
|
.text = tr::lng_sponsored_info_menu(tr::now),
|
||||||
.handler = nullptr,
|
.handler = nullptr,
|
||||||
.icon = &st::menuIconChannel,
|
.icon = (dark
|
||||||
|
? &st::mediaMenuIconChannel
|
||||||
|
: &st::menuIconChannel),
|
||||||
.fillSubmenu = std::move(fillSubmenu),
|
.fillSubmenu = std::move(fillSubmenu),
|
||||||
});
|
});
|
||||||
addAction({
|
addAction({
|
||||||
.separatorSt = &st::expandedMenuSeparator,
|
.separatorSt = (dark
|
||||||
|
? &st::mediaviewMenuSeparator
|
||||||
|
: &st::expandedMenuSeparator),
|
||||||
.isSeparator = true,
|
.isSeparator = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (details.canReport) {
|
if (details.canReport) {
|
||||||
if (!skipAbout) {
|
if (!settings.skipAbout) {
|
||||||
addAction(tr::lng_sponsored_menu_revenued_about(tr::now), [=] {
|
addAction(tr::lng_sponsored_menu_revenued_about(tr::now), [=] {
|
||||||
show->show(Box(AboutBox, show, phrases, details, report));
|
show->show(Box(AboutBox, show, phrases, details, report));
|
||||||
}, (mediaViewer ? &st::mediaMenuIconInfo : &st::menuIconInfo));
|
}, (dark ? &st::mediaMenuIconInfo : &st::menuIconInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
addAction(tr::lng_sponsored_menu_revenued_report(tr::now), [=] {
|
addAction(tr::lng_sponsored_menu_revenued_report(tr::now), [=] {
|
||||||
ShowReportSponsoredBox(show, report);
|
ShowReportSponsoredBox(show, report);
|
||||||
}, (mediaViewer ? &st::mediaMenuIconBlock : &st::menuIconBlock));
|
}, (dark ? &st::mediaMenuIconBlock : &st::menuIconBlock));
|
||||||
|
|
||||||
addAction({
|
addAction({
|
||||||
.separatorSt = (mediaViewer
|
.separatorSt = (dark
|
||||||
? &st::mediaviewMenuSeparator
|
? &st::mediaviewMenuSeparator
|
||||||
: &st::expandedMenuSeparator),
|
: &st::expandedMenuSeparator),
|
||||||
.isSeparator = true,
|
.isSeparator = true,
|
||||||
|
@ -464,26 +467,22 @@ void FillSponsored(
|
||||||
} else {
|
} else {
|
||||||
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
|
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
|
||||||
}
|
}
|
||||||
}, (mediaViewer ? &st::mediaMenuIconCancel : &st::menuIconCancel));
|
}, (dark ? &st::mediaMenuIconCancel : &st::menuIconCancel));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillSponsored(
|
void FillSponsored(
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
const Ui::Menu::MenuCallback &addAction,
|
const Ui::Menu::MenuCallback &addAction,
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
const FullMsgId &fullId,
|
const FullMsgId &fullId,
|
||||||
bool mediaViewer,
|
SponsoredMenuSettings settings) {
|
||||||
bool skipAbout) {
|
|
||||||
const auto session = &show->session();
|
const auto session = &show->session();
|
||||||
FillSponsored(
|
FillSponsored(
|
||||||
parent,
|
|
||||||
addAction,
|
addAction,
|
||||||
show,
|
show,
|
||||||
PhrasesForMessage(fullId),
|
PhrasesForMessage(fullId),
|
||||||
session->sponsoredMessages().lookupDetails(fullId),
|
session->sponsoredMessages().lookupDetails(fullId),
|
||||||
session->sponsoredMessages().createReportCallback(fullId),
|
session->sponsoredMessages().createReportCallback(fullId),
|
||||||
mediaViewer,
|
settings);
|
||||||
skipAbout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowSponsored(
|
void ShowSponsored(
|
||||||
|
@ -495,11 +494,9 @@ void ShowSponsored(
|
||||||
st::popupMenuWithIcons);
|
st::popupMenuWithIcons);
|
||||||
|
|
||||||
FillSponsored(
|
FillSponsored(
|
||||||
parent,
|
|
||||||
Ui::Menu::CreateAddActionCallback(menu),
|
Ui::Menu::CreateAddActionCallback(menu),
|
||||||
show,
|
show,
|
||||||
fullId,
|
fullId);
|
||||||
false);
|
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
menu->popup(QCursor::pos());
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,23 +33,25 @@ enum class SponsoredPhrases {
|
||||||
Search,
|
Search,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SponsoredMenuSettings {
|
||||||
|
bool dark = false;
|
||||||
|
bool skipAbout = false;
|
||||||
|
bool skipInfo = false;
|
||||||
|
};
|
||||||
|
|
||||||
void FillSponsored(
|
void FillSponsored(
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
const Ui::Menu::MenuCallback &addAction,
|
const Ui::Menu::MenuCallback &addAction,
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
SponsoredPhrases phrases,
|
SponsoredPhrases phrases,
|
||||||
const Data::SponsoredMessageDetails &details,
|
const Data::SponsoredMessageDetails &details,
|
||||||
Data::SponsoredReportAction report,
|
Data::SponsoredReportAction report,
|
||||||
bool mediaViewer,
|
SponsoredMenuSettings settings = {});
|
||||||
bool skipAbout);
|
|
||||||
|
|
||||||
void FillSponsored(
|
void FillSponsored(
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
const Ui::Menu::MenuCallback &addAction,
|
const Ui::Menu::MenuCallback &addAction,
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
const FullMsgId &fullId,
|
const FullMsgId &fullId,
|
||||||
bool mediaViewer,
|
SponsoredMenuSettings settings = {});
|
||||||
bool skipAbout = false);
|
|
||||||
|
|
||||||
void ShowSponsored(
|
void ShowSponsored(
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
|
|
@ -1344,7 +1344,7 @@ messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> use
|
||||||
|
|
||||||
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
|
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
|
||||||
|
|
||||||
messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector<MessageEntity> quote_offset:flags.10?int = MessageReplyHeader;
|
messageReplyHeader#6917560b flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector<MessageEntity> quote_offset:flags.10?int todo_item_id:flags.11?int = MessageReplyHeader;
|
||||||
messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader;
|
messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader;
|
||||||
|
|
||||||
messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
|
messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
|
||||||
|
@ -1649,7 +1649,7 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count
|
||||||
|
|
||||||
stories.storyViews#de9eed1d views:Vector<StoryViews> users:Vector<User> = stories.StoryViews;
|
stories.storyViews#de9eed1d views:Vector<StoryViews> users:Vector<User> = stories.StoryViews;
|
||||||
|
|
||||||
inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo;
|
inputReplyToMessage#869fbe10 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer todo_item_id:flags.6?int = InputReplyTo;
|
||||||
inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo;
|
inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo;
|
||||||
inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo;
|
inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo;
|
||||||
|
|
||||||
|
@ -2720,4 +2720,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
|
||||||
|
|
||||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
||||||
|
|
||||||
// LAYER 207
|
// LAYER 209
|
||||||
|
|
|
@ -117,7 +117,7 @@ bool DarkTasbarValueValid/* = false*/;
|
||||||
p.setPen(Qt::NoPen);
|
p.setPen(Qt::NoPen);
|
||||||
p.drawEllipse(QRectF( // cx=3.9, cy=12.7, r=2.2
|
p.drawEllipse(QRectF( // cx=3.9, cy=12.7, r=2.2
|
||||||
1.7 * xm,
|
1.7 * xm,
|
||||||
10.5 * ym,
|
9.5 * ym,
|
||||||
4.4 * xm,
|
4.4 * xm,
|
||||||
4.4 * ym));
|
4.4 * ym));
|
||||||
return image;
|
return image;
|
||||||
|
|
|
@ -939,7 +939,8 @@ void FillUniqueGiftMenu(
|
||||||
&& e.id.isEmpty()
|
&& e.id.isEmpty()
|
||||||
&& (e.in || (giftChannel && giftChannel->canManageGifts()))
|
&& (e.in || (giftChannel && giftChannel->canManageGifts()))
|
||||||
&& !e.giftTransferred
|
&& !e.giftTransferred
|
||||||
&& !e.giftRefunded;
|
&& !e.giftRefunded
|
||||||
|
&& !e.converted;
|
||||||
|
|
||||||
const auto unique = e.uniqueGift;
|
const auto unique = e.uniqueGift;
|
||||||
if (unique
|
if (unique
|
||||||
|
@ -1148,7 +1149,6 @@ void GenericCreditsEntryBox(
|
||||||
const auto isStarGift = e.stargift || e.soldOutInfo;
|
const auto isStarGift = e.stargift || e.soldOutInfo;
|
||||||
const auto creditsHistoryStarGift = isStarGift && !e.id.isEmpty();
|
const auto creditsHistoryStarGift = isStarGift && !e.id.isEmpty();
|
||||||
const auto sentStarGift = creditsHistoryStarGift && !e.in;
|
const auto sentStarGift = creditsHistoryStarGift && !e.in;
|
||||||
const auto convertedStarGift = creditsHistoryStarGift && e.converted;
|
|
||||||
const auto giftToSelf = isStarGift
|
const auto giftToSelf = isStarGift
|
||||||
&& (e.barePeerId == selfPeerId)
|
&& (e.barePeerId == selfPeerId)
|
||||||
&& (e.in || e.bareGiftOwnerId == selfPeerId);
|
&& (e.in || e.bareGiftOwnerId == selfPeerId);
|
||||||
|
@ -1164,7 +1164,8 @@ void GenericCreditsEntryBox(
|
||||||
const auto starGiftCanManage = isStarGift
|
const auto starGiftCanManage = isStarGift
|
||||||
&& !creditsHistoryStarGift
|
&& !creditsHistoryStarGift
|
||||||
&& (e.in || giftToChannelCanManage)
|
&& (e.in || giftToChannelCanManage)
|
||||||
&& !e.fromGiftSlug;
|
&& !e.fromGiftSlug
|
||||||
|
&& !e.converted;
|
||||||
const auto starGiftCanTransfer = isStarGift
|
const auto starGiftCanTransfer = isStarGift
|
||||||
&& !creditsHistoryStarGift
|
&& !creditsHistoryStarGift
|
||||||
&& (e.in || giftToChannelCanTransfer);
|
&& (e.in || giftToChannelCanTransfer);
|
||||||
|
@ -1250,12 +1251,13 @@ void GenericCreditsEntryBox(
|
||||||
EntryToSavedStarGiftId(session, e),
|
EntryToSavedStarGiftId(session, e),
|
||||||
style);
|
style);
|
||||||
};
|
};
|
||||||
|
const auto canResell = CanResellGift(session, e);
|
||||||
AddUniqueGiftCover(
|
AddUniqueGiftCover(
|
||||||
content,
|
content,
|
||||||
rpl::single(*uniqueGift),
|
rpl::single(*uniqueGift),
|
||||||
{},
|
{},
|
||||||
std::move(price),
|
std::move(price),
|
||||||
CanResellGift(session, e) ? std::move(change) : Fn<void()>());
|
canResell ? std::move(change) : Fn<void()>());
|
||||||
|
|
||||||
AddSkip(content, st::defaultVerticalListSkip * 2);
|
AddSkip(content, st::defaultVerticalListSkip * 2);
|
||||||
|
|
||||||
|
@ -1263,6 +1265,10 @@ void GenericCreditsEntryBox(
|
||||||
const auto type = SavedStarGiftMenuType::View;
|
const auto type = SavedStarGiftMenuType::View;
|
||||||
FillUniqueGiftMenu(show, menu, e, type, st);
|
FillUniqueGiftMenu(show, menu, e, type, st);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (canResell) {
|
||||||
|
Ui::PreloadUniqueGiftResellPrices(session);
|
||||||
|
}
|
||||||
} else if (const auto callback = Ui::PaintPreviewCallback(session, e)) {
|
} else if (const auto callback = Ui::PaintPreviewCallback(session, e)) {
|
||||||
const auto thumb = content->add(object_ptr<Ui::CenterWrap<>>(
|
const auto thumb = content->add(object_ptr<Ui::CenterWrap<>>(
|
||||||
content,
|
content,
|
||||||
|
@ -1419,7 +1425,7 @@ void GenericCreditsEntryBox(
|
||||||
? tr::lng_credits_box_history_entry_gift_unavailable(tr::now)
|
? tr::lng_credits_box_history_entry_gift_unavailable(tr::now)
|
||||||
: sentStarGift
|
: sentStarGift
|
||||||
? tr::lng_credits_box_history_entry_gift_sent(tr::now)
|
? tr::lng_credits_box_history_entry_gift_sent(tr::now)
|
||||||
: convertedStarGift
|
: e.converted
|
||||||
? tr::lng_credits_box_history_entry_gift_converted(tr::now)
|
? tr::lng_credits_box_history_entry_gift_converted(tr::now)
|
||||||
: (isStarGift && !starGiftCanManage)
|
: (isStarGift && !starGiftCanManage)
|
||||||
? tr::lng_gift_link_label_gift(tr::now)
|
? tr::lng_gift_link_label_gift(tr::now)
|
||||||
|
@ -1622,7 +1628,7 @@ void GenericCreditsEntryBox(
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji);
|
const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji);
|
||||||
if (!uniqueGift && starGiftCanManage) {
|
if (!uniqueGift && (starGiftCanManage || e.converted)) {
|
||||||
Ui::AddSkip(content);
|
Ui::AddSkip(content);
|
||||||
const auto about = box->addRow(
|
const auto about = box->addRow(
|
||||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||||
|
@ -1751,7 +1757,8 @@ void GenericCreditsEntryBox(
|
||||||
|
|
||||||
const auto canToggle = starGiftCanManage
|
const auto canToggle = starGiftCanManage
|
||||||
&& !e.giftTransferred
|
&& !e.giftTransferred
|
||||||
&& !e.giftRefunded;
|
&& !e.giftRefunded
|
||||||
|
&& !e.converted;
|
||||||
const auto toggleVisibility = [=, weak = Ui::MakeWeak(box)](bool save) {
|
const auto toggleVisibility = [=, weak = Ui::MakeWeak(box)](bool save) {
|
||||||
const auto showSection = !e.fromGiftsList;
|
const auto showSection = !e.fromGiftsList;
|
||||||
const auto savedId = EntryToSavedStarGiftId(&show->session(), e);
|
const auto savedId = EntryToSavedStarGiftId(&show->session(), e);
|
||||||
|
|
|
@ -843,7 +843,6 @@ void SetupPremium(
|
||||||
button->addClickHandler([=] {
|
button->addClickHandler([=] {
|
||||||
showOther(BusinessId());
|
showOther(BusinessId());
|
||||||
});
|
});
|
||||||
Ui::NewBadge::AddToRight(button);
|
|
||||||
|
|
||||||
if (controller->session().premiumCanBuy()) {
|
if (controller->session().premiumCanBuy()) {
|
||||||
const auto button = AddButtonWithIcon(
|
const auto button = AddButtonWithIcon(
|
||||||
|
@ -852,6 +851,8 @@ void SetupPremium(
|
||||||
st::settingsButton,
|
st::settingsButton,
|
||||||
{ .icon = &st::menuIconGiftPremium }
|
{ .icon = &st::menuIconGiftPremium }
|
||||||
);
|
);
|
||||||
|
Ui::NewBadge::AddToRight(button);
|
||||||
|
|
||||||
button->addClickHandler([=] {
|
button->addClickHandler([=] {
|
||||||
Ui::ChooseStarGiftRecipient(controller);
|
Ui::ChooseStarGiftRecipient(controller);
|
||||||
});
|
});
|
||||||
|
|