Start shortcut messages sending.

This commit is contained in:
John Preston 2024-02-23 21:23:15 +04:00
parent dd7ccada2f
commit d05c4e0990
38 changed files with 2109 additions and 70 deletions

View File

@ -454,6 +454,8 @@ PRIVATE
data/business/data_business_common.h
data/business/data_business_info.cpp
data/business/data_business_info.h
data/business/data_shortcut_messages.cpp
data/business/data_shortcut_messages.h
data/notify/data_notify_settings.cpp
data/notify/data_notify_settings.h
data/notify/data_peer_notify_settings.cpp
@ -1287,6 +1289,8 @@ PRIVATE
profile/profile_cover_drop_area.h
settings/business/settings_away_message.cpp
settings/business/settings_away_message.h
settings/business/settings_shortcut_messages.cpp
settings/business/settings_shortcut_messages.h
settings/business/settings_chatbots.cpp
settings/business/settings_chatbots.h
settings/business/settings_greeting.cpp

View File

@ -22,6 +22,7 @@ inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
struct SendOptions {
PeerData *sendAs = nullptr;
TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0;
bool silent = false;
bool handleSupportSwitch = false;
bool hideViaBot = false;

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_updates.h"
#include "apiwrap.h"
#include "base/random.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_changes.h"
#include "data/data_histories.h"
#include "data/data_poll.h"
@ -64,6 +65,9 @@ void Polls::create(
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (action.options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
@ -85,7 +89,7 @@ void Polls::create(
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "base/random.h"
#include "base/unixtime.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_channel.h" // ChannelData::addsSignature.
@ -128,6 +129,9 @@ void SendExistingMedia(
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (message.action.options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
session->data().registerMessageRandomId(randomId, newId);
@ -146,10 +150,12 @@ void SendExistingMedia(
const auto performRequest = [=](const auto &repeatRequest) -> void {
auto &histories = history->owner().histories();
const auto session = &history->session();
const auto &action = message.action;
const auto usedFileReference = media->fileReference();
histories.sendPreparedMessage(
history,
message.action.replyTo,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
@ -160,9 +166,9 @@ void SendExistingMedia(
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(session, action.options.shortcutId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
if (error.code() == 400
@ -260,7 +266,10 @@ bool SendDice(MessageToSend &message) {
message.textWithTags = TextWithTags();
message.action.clearDraft = false;
message.action.generateLocal = true;
api->sendAction(message.action);
const auto &action = message.action;
api->sendAction(action);
const auto newId = FullMsgId(
peer->id,
@ -270,17 +279,17 @@ bool SendDice(MessageToSend &message) {
auto &histories = history->owner().histories();
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (message.action.replyTo) {
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, message.action.options);
InnerFillMessagePostFlags(message.action.options, peer, flags);
const auto silentPost = ShouldSendSilent(peer, action.options);
InnerFillMessagePostFlags(action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sendAs = message.action.options.sendAs;
const auto sendAs = action.options.sendAs;
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost
@ -293,10 +302,13 @@ bool SendDice(MessageToSend &message) {
? session->user()->name()
: QString();
if (message.action.options.scheduled) {
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (action.options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
session->data().registerMessageRandomId(randomId, newId);
@ -305,8 +317,8 @@ bool SendDice(MessageToSend &message) {
newId.msg,
flags,
viaBotId,
message.action.replyTo,
HistoryItem::NewMessageDate(message.action.options.scheduled),
action.replyTo,
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
TextWithEntities(),
@ -314,7 +326,7 @@ bool SendDice(MessageToSend &message) {
HistoryMessageMarkupData());
histories.sendPreparedMessage(
history,
message.action.replyTo,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
@ -325,14 +337,14 @@ bool SendDice(MessageToSend &message) {
MTP_long(randomId),
MTPReplyMarkup(),
MTP_vector<MTPMessageEntity>(),
MTP_int(message.action.options.scheduled),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(session, action.options.shortcutId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId, newId);
});
api->finishForwarding(message.action);
api->finishForwarding(action);
return true;
}

View File

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_premium.h"
#include "api/api_user_names.h"
#include "api/api_websites.h"
#include "data/business/data_shortcut_messages.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_changes.h"
#include "data/data_web_page.h"
@ -3140,7 +3141,9 @@ void ApiWrap::sharedMediaDone(
}
void ApiWrap::sendAction(const SendAction &action) {
if (!action.options.scheduled && !action.replaceMediaOf) {
if (!action.options.scheduled
&& !action.options.shortcutId
&& !action.replaceMediaOf) {
const auto topicRootId = action.replyTo.topicRootId;
const auto topic = topicRootId
? action.history->peer->forumTopicFor(topicRootId)
@ -3175,11 +3178,13 @@ void ApiWrap::finishForwarding(const SendAction &action) {
}
_session->data().sendHistoryChangeNotifications();
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
if (!action.options.shortcutId) {
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
}
}
void ApiWrap::forwardMessages(
@ -3208,7 +3213,7 @@ void ApiWrap::forwardMessages(
const auto history = action.history;
const auto peer = history->peer;
if (!action.options.scheduled) {
if (!action.options.scheduled && !action.options.shortcutId) {
histories.readInbox(history);
}
const auto anonymousPost = peer->amAnonymous();
@ -3226,6 +3231,9 @@ void ApiWrap::forwardMessages(
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= SendFlag::f_schedule_date;
}
if (action.options.shortcutId) {
sendFlags |= SendFlag::f_quick_reply_shortcut;
}
if (draft.options != Data::ForwardOptions::PreserveInfo) {
sendFlags |= SendFlag::f_drop_author;
}
@ -3265,7 +3273,7 @@ void ApiWrap::forwardMessages(
MTP_int(topMsgId),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
if (shared && !--shared->requestsLeft) {
@ -3728,6 +3736,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (action.options.shortcutId) {
sendFlags |= MTPmessages_SendMessage::Flag::f_quick_reply_shortcut;
mediaFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
const auto viaBotId = UserId();
lastMessage = history->addNewLocalMessage(
newId.msg,
@ -3763,6 +3775,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
UnixtimeFromMsgId(response.outerMsgId));
}
};
const auto mtpShortcut = Data::ShortcutIdToMTP(
_session,
action.options.shortcutId);
if (exactWebPage
&& !ignoreWebPage
&& (manualWebPage || sending.empty())) {
@ -3779,9 +3794,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
mtpShortcut
), done, fail);
} else {
histories.sendPreparedMessage(
@ -3798,7 +3813,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
mtpShortcut
), done, fail);
}
isFirst = false;
@ -3887,6 +3902,9 @@ void ApiWrap::sendInlineResult(
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= SendFlag::f_schedule_date;
}
if (action.options.shortcutId) {
sendFlags |= SendFlag::f_quick_reply_shortcut;
}
if (action.options.hideViaBot) {
sendFlags |= SendFlag::f_hide_via;
}
@ -3932,7 +3950,7 @@ void ApiWrap::sendInlineResult(
MTP_string(data->getId()),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
history->finishSavingCloudDraft(
topicRootId,
@ -4061,7 +4079,8 @@ void ApiWrap::sendMediaWithRandomId(
: Flag(0))
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
| (options.sendAs ? Flag::f_send_as : Flag(0));
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
@ -4081,7 +4100,7 @@ void ApiWrap::sendMediaWithRandomId(
sentEntities,
MTP_int(options.scheduled),
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(_session, options.shortcutId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
if (updateRecentStickers) {
@ -4166,7 +4185,10 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
? Flag::f_silent
: Flag(0))
| (album->options.scheduled ? Flag::f_schedule_date : Flag(0))
| (sendAs ? Flag::f_send_as : Flag(0));
| (sendAs ? Flag::f_send_as : Flag(0))
| (album->options.shortcutId
? Flag::f_quick_reply_shortcut
: Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
histories.sendPreparedMessage(
@ -4180,7 +4202,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(album->options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(_session, album->options.shortcutId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
_sendingAlbums.remove(groupId);
}, [=](const MTP::Error &error, const MTP::Response &response) {

View File

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/share_message_phrase_factory.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_channel.h"
#include "data/data_game.h"
#include "data/data_histories.h"
@ -1543,11 +1544,15 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
const auto threadHistory = thread->owningHistory();
histories.sendRequest(threadHistory, requestType, [=](
Fn<void()> finish) {
auto &api = threadHistory->session().api();
const auto session = &threadHistory->session();
auto &api = session->api();
const auto sendFlags = commonSendFlags
| (topMsgId ? Flag::f_top_msg_id : Flag(0))
| (ShouldSendSilent(peer, options)
? Flag::f_silent
: Flag(0))
| (options.shortcutId
? Flag::f_quick_reply_shortcut
: Flag(0));
threadHistory->sendRequestId = api.request(
MTPmessages_ForwardMessages(
@ -1559,7 +1564,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_int(topMsgId),
MTP_int(options.scheduled),
MTP_inputPeerEmpty(), // send_as
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(session, options.shortcutId)
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId);

View File

@ -191,7 +191,7 @@ struct AwaySchedule {
struct AwaySettings {
BusinessRecipients recipients;
AwaySchedule schedule;
int shortcutId = 0;
BusinessShortcutId shortcutId = 0;
explicit operator bool() const {
return schedule.type != AwayScheduleType::Never;
@ -205,7 +205,7 @@ struct AwaySettings {
struct GreetingSettings {
BusinessRecipients recipients;
int noActivityDays = 0;
int shortcutId = 0;
BusinessShortcutId shortcutId = 0;
explicit operator bool() const {
return noActivityDays > 0;

View File

@ -0,0 +1,539 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/business/data_shortcut_messages.h"
#include "api/api_hash.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "api/api_text_entities.h"
#include "main/main_session.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "apiwrap.h"
namespace Data {
namespace {
constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
[[nodiscard]] MsgId RemoteToLocalMsgId(MsgId id) {
Expects(IsServerMsgId(id));
return ServerMaxMsgId + id + 1;
}
[[nodiscard]] MsgId LocalToRemoteMsgId(MsgId id) {
Expects(IsShortcutMsgId(id));
return (id - ServerMaxMsgId - 1);
}
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
return (received > 0) && (received + kRequestTimeLimit > crl::now());
}
[[nodiscard]] MTPMessage PrepareMessage(
BusinessShortcutId shortcutId,
const MTPMessage &message) {
return message.match([&](const MTPDmessageEmpty &data) {
return MTP_messageEmpty(
data.vflags(),
data.vid(),
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
}, [&](const MTPDmessageService &data) {
return MTP_messageService(
data.vflags(),
data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
data.vpeer_id(),
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
data.vdate(),
data.vaction(),
MTP_int(data.vttl_period().value_or_empty()));
}, [&](const MTPDmessage &data) {
return MTP_message(
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_quick_reply_shortcut_id),
data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
MTPint(), // from_boosts_applied
data.vpeer_id(),
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(data.vvia_bot_id().value_or_empty()),
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
data.vdate(),
data.vmessage(),
data.vmedia() ? *data.vmedia() : MTPMessageMedia(),
data.vreply_markup() ? *data.vreply_markup() : MTPReplyMarkup(),
(data.ventities()
? *data.ventities()
: MTPVector<MTPMessageEntity>()),
MTP_int(data.vviews().value_or_empty()),
MTP_int(data.vforwards().value_or_empty()),
data.vreplies() ? *data.vreplies() : MTPMessageReplies(),
MTP_int(data.vedit_date().value_or_empty()),
MTP_bytes(data.vpost_author().value_or_empty()),
MTP_long(data.vgrouped_id().value_or_empty()),
MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTP_int(data.vttl_period().value_or_empty()),
MTP_int(shortcutId));
});
}
} // namespace
bool IsShortcutMsgId(MsgId id) {
return (id > ScheduledMaxMsgId) && (id < ShortcutMaxMsgId);
}
ShortcutMessages::ShortcutMessages(not_null<Session*> owner)
: _session(&owner->session())
, _history(owner->history(_session->userPeerId()))
, _clearTimer([=] { clearOldRequests(); }) {
owner->itemRemoved(
) | rpl::filter([](not_null<const HistoryItem*> item) {
return item->isBusinessShortcut();
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
remove(item);
}, _lifetime);
}
ShortcutMessages::~ShortcutMessages() {
for (const auto &request : _requests) {
_session->api().request(request.second.requestId).cancel();
}
}
void ShortcutMessages::clearOldRequests() {
const auto now = crl::now();
while (true) {
const auto i = ranges::find_if(_requests, [&](const auto &value) {
const auto &request = value.second;
return !request.requestId
&& (request.lastReceived + kRequestTimeLimit <= now);
});
if (i == end(_requests)) {
break;
}
_requests.erase(i);
}
}
MsgId ShortcutMessages::localMessageId(MsgId remoteId) const {
return RemoteToLocalMsgId(remoteId);
}
MsgId ShortcutMessages::lookupId(not_null<const HistoryItem*> item) const {
Expects(item->isBusinessShortcut());
Expects(!item->isSending());
Expects(!item->hasFailed());
return LocalToRemoteMsgId(item->id);
}
int ShortcutMessages::count(BusinessShortcutId shortcutId) const {
const auto i = _data.find(shortcutId);
return (i != end(_data)) ? i->second.items.size() : 0;
}
void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) {
const auto &message = update.vmessage();
const auto shortcutId = BusinessShortcutIdFromMessage(message);
if (!shortcutId) {
return;
}
auto &list = _data[shortcutId];
append(shortcutId, list, message);
sort(list);
_updates.fire_copy(shortcutId);
}
void ShortcutMessages::apply(
const MTPDupdateDeleteQuickReplyMessages &update) {
const auto shortcutId = update.vshortcut_id().v;
if (!shortcutId) {
return;
}
auto i = _data.find(shortcutId);
if (i == end(_data)) {
return;
}
for (const auto &id : update.vmessages().v) {
const auto &list = i->second;
const auto j = list.itemById.find(id.v);
if (j != end(list.itemById)) {
j->second->destroy();
i = _data.find(shortcutId);
if (i == end(_data)) {
break;
}
}
}
_updates.fire_copy(shortcutId);
}
void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
const auto shortcutId = update.vshortcut_id().v;
if (!shortcutId) {
return;
}
auto i = _data.find(shortcutId);
while (i != end(_data)) {
Assert(!i->second.itemById.empty());
i->second.itemById.back().second->destroy();
i = _data.find(shortcutId);
}
_updates.fire_copy(shortcutId);
}
void ShortcutMessages::apply(
const MTPDupdateMessageID &update,
not_null<HistoryItem*> local) {
const auto id = update.vid().v;
const auto i = _data.find(local->shortcutId());
Assert(i != end(_data));
auto &list = i->second;
const auto j = list.itemById.find(id);
if (j != end(list.itemById) || !IsServerMsgId(id)) {
local->destroy();
} else {
Assert(!list.itemById.contains(local->id));
local->setRealId(localMessageId(id));
list.itemById.emplace(id, local);
}
}
void ShortcutMessages::appendSending(not_null<HistoryItem*> item) {
Expects(item->isSending());
Expects(item->isBusinessShortcut());
const auto shortcutId = item->shortcutId();
auto &list = _data[shortcutId];
list.items.emplace_back(item);
sort(list);
_updates.fire_copy(shortcutId);
}
void ShortcutMessages::removeSending(not_null<HistoryItem*> item) {
Expects(item->isSending() || item->hasFailed());
Expects(item->isBusinessShortcut());
item->destroy();
}
rpl::producer<> ShortcutMessages::updates(BusinessShortcutId shortcutId) {
request(shortcutId);
return _updates.events(
) | rpl::filter([=](BusinessShortcutId value) {
return (value == shortcutId);
}) | rpl::to_empty;
}
Data::MessagesSlice ShortcutMessages::list(BusinessShortcutId shortcutId) {
auto result = Data::MessagesSlice();
const auto i = _data.find(shortcutId);
if (i == end(_data)) {
const auto i = _requests.find(shortcutId);
if (i == end(_requests)) {
return result;
}
result.fullCount = result.skippedAfter = result.skippedBefore = 0;
return result;
}
const auto &list = i->second.items;
result.skippedAfter = result.skippedBefore = 0;
result.fullCount = int(list.size());
result.ids = ranges::views::all(
list
) | ranges::views::transform(
&HistoryItem::fullId
) | ranges::to_vector;
return result;
}
void ShortcutMessages::preloadShortcuts() {
if (_shortcutsLoaded || _shortcutsRequestId) {
return;
}
const auto owner = &_session->data();
_shortcutsRequestId = owner->session().api().request(
MTPmessages_GetQuickReplies(MTP_long(_shortcutsHash))
).done([=](const MTPmessages_QuickReplies &result) {
result.match([&](const MTPDmessages_quickReplies &data) {
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
owner->processMessages(
data.vmessages(),
NewMessageType::Existing);
auto shortcuts = Shortcuts();
const auto messages = &owner->shortcutMessages();
for (const auto &reply : data.vquick_replies().v) {
const auto &data = reply.data();
const auto id = BusinessShortcutId(data.vshortcut_id().v);
shortcuts.list.emplace(id, Shortcut{
.name = qs(data.vshortcut()),
.topMessageId = messages->localMessageId(
data.vtop_message().v),
.count = data.vcount().v,
});
}
for (auto &[id, shortcut] : _shortcuts.list) {
if (id < 0) {
shortcuts.list.emplace(id, shortcut);
}
}
const auto changed = !_shortcutsLoaded
|| (shortcuts != _shortcuts);
if (changed) {
_shortcuts = std::move(shortcuts);
_shortcutsLoaded = true;
_shortcutsChanged.fire({});
}
}, [&](const MTPDmessages_quickRepliesNotModified &) {
if (!_shortcutsLoaded) {
_shortcutsLoaded = true;
_shortcutsChanged.fire({});
}
});
}).send();
}
const Shortcuts &ShortcutMessages::shortcuts() const {
return _shortcuts;
}
bool ShortcutMessages::shortcutsLoaded() const {
return _shortcutsLoaded;
}
rpl::producer<> ShortcutMessages::shortcutsChanged() const {
return _shortcutsChanged.events();
}
BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
Expects(_shortcutsLoaded);
for (auto &[id, shortcut] : _shortcuts.list) {
if (shortcut.name == name) {
return id;
}
}
const auto result = --_localShortcutId;
_shortcuts.list.emplace(result, Shortcut{ name });
return result;
}
Shortcut ShortcutMessages::lookupShortcut(BusinessShortcutId id) const {
const auto i = _shortcuts.list.find(id);
Ensures(i != end(_shortcuts.list));
return i->second;
}
void ShortcutMessages::request(BusinessShortcutId shortcutId) {
auto &request = _requests[shortcutId];
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
return;
}
const auto i = _data.find(shortcutId);
const auto hash = (i != end(_data))
? countListHash(i->second)
: uint64(0);
request.requestId = _session->api().request(
MTPmessages_GetQuickReplyMessages(
MTP_flags(0),
MTP_int(shortcutId),
MTPVector<MTPint>(),
MTP_long(hash))
).done([=](const MTPmessages_Messages &result) {
parse(shortcutId, result);
}).fail([=] {
_requests.remove(shortcutId);
}).send();
}
void ShortcutMessages::parse(
BusinessShortcutId shortcutId,
const MTPmessages_Messages &list) {
auto &request = _requests[shortcutId];
request.lastReceived = crl::now();
request.requestId = 0;
if (!_clearTimer.isActive()) {
_clearTimer.callOnce(kRequestTimeLimit * 2);
}
list.match([&](const MTPDmessages_messagesNotModified &data) {
}, [&](const auto &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
const auto &messages = data.vmessages().v;
if (messages.isEmpty()) {
clearNotSending(shortcutId);
return;
}
auto received = base::flat_set<not_null<HistoryItem*>>();
auto clear = base::flat_set<not_null<HistoryItem*>>();
auto &list = _data.emplace(shortcutId, List()).first->second;
for (const auto &message : messages) {
if (const auto item = append(shortcutId, list, message)) {
received.emplace(item);
}
}
for (const auto &owned : list.items) {
const auto item = owned.get();
if (!item->isSending() && !received.contains(item)) {
clear.emplace(item);
}
}
updated(shortcutId, received, clear);
});
}
HistoryItem *ShortcutMessages::append(
BusinessShortcutId shortcutId,
List &list,
const MTPMessage &message) {
const auto id = message.match([&](const auto &data) {
return data.vid().v;
});
const auto i = list.itemById.find(id);
if (i != end(list.itemById)) {
const auto existing = i->second;
message.match([&](const MTPDmessage &data) {
if (data.is_edit_hide()) {
existing->applyEdition(HistoryMessageEdition(_session, data));
} else {
existing->updateSentContent({
qs(data.vmessage()),
Api::EntitiesFromMTP(
_session,
data.ventities().value_or_empty())
}, data.vmedia());
existing->updateReplyMarkup(
HistoryMessageMarkupData(data.vreply_markup()));
existing->updateForwardedInfo(data.vfwd_from());
}
existing->updateDate(data.vdate().v);
_history->owner().requestItemTextRefresh(existing);
}, [&](const auto &data) {});
return existing;
}
if (!IsServerMsgId(id)) {
LOG(("API Error: Bad id in quick reply messages: %1.").arg(id));
return nullptr;
}
const auto item = _session->data().addNewMessage(
localMessageId(id),
PrepareMessage(shortcutId, message),
MessageFlags(), // localFlags
NewMessageType::Existing);
if (!item
|| item->history() != _history
|| item->shortcutId() != shortcutId) {
LOG(("API Error: Bad data received in quick reply messages."));
return nullptr;
}
list.items.emplace_back(item);
list.itemById.emplace(id, item);
return item;
}
void ShortcutMessages::clearNotSending(BusinessShortcutId shortcutId) {
const auto i = _data.find(shortcutId);
if (i == end(_data)) {
return;
}
auto clear = base::flat_set<not_null<HistoryItem*>>();
for (const auto &owned : i->second.items) {
if (!owned->isSending() && !owned->hasFailed()) {
clear.emplace(owned.get());
}
}
updated(shortcutId, {}, clear);
}
void ShortcutMessages::updated(
BusinessShortcutId shortcutId,
const base::flat_set<not_null<HistoryItem*>> &added,
const base::flat_set<not_null<HistoryItem*>> &clear) {
if (!clear.empty()) {
for (const auto &item : clear) {
item->destroy();
}
}
const auto i = _data.find(shortcutId);
if (i != end(_data)) {
sort(i->second);
}
if (!added.empty() || !clear.empty()) {
_updates.fire_copy(shortcutId);
}
}
void ShortcutMessages::sort(List &list) {
ranges::sort(list.items, ranges::less(), &HistoryItem::position);
}
void ShortcutMessages::remove(not_null<const HistoryItem*> item) {
const auto shortcutId = item->shortcutId();
const auto i = _data.find(shortcutId);
Assert(i != end(_data));
auto &list = i->second;
if (!item->isSending() && !item->hasFailed()) {
list.itemById.remove(lookupId(item));
}
const auto k = ranges::find(list.items, item, &OwnedItem::get);
Assert(k != list.items.end());
k->release();
list.items.erase(k);
if (list.items.empty()) {
_data.erase(i);
}
_updates.fire_copy(shortcutId);
}
uint64 ShortcutMessages::countListHash(const List &list) const {
using namespace Api;
auto hash = HashInit();
auto &&serverside = ranges::views::all(
list.items
) | ranges::views::filter([](const OwnedItem &item) {
return !item->isSending() && !item->hasFailed();
}) | ranges::views::reverse;
for (const auto &item : serverside) {
HashUpdate(hash, lookupId(item.get()).bare);
if (const auto edited = item->Get<HistoryMessageEdited>()) {
HashUpdate(hash, edited->date);
} else {
HashUpdate(hash, TimeId(0));
}
}
return HashFinalize(hash);
}
MTPInputQuickReplyShortcut ShortcutIdToMTP(
not_null<Main::Session*> session,
BusinessShortcutId id) {
if (id >= 0) {
return MTP_inputQuickReplyShortcutId(MTP_int(id));
}
return MTP_inputQuickReplyShortcut(MTP_string(
session->data().shortcutMessages().lookupShortcut(id).name));
}
} // namespace Data

View File

@ -0,0 +1,125 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/history_item.h"
#include "base/timer.h"
class History;
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
struct MessagesSlice;
struct Shortcut {
QString name;
MsgId topMessageId = 0;
int count = 0;
friend inline bool operator==(
const Shortcut &a,
const Shortcut &b) = default;
};
struct Shortcuts {
base::flat_map<BusinessShortcutId, Shortcut> list;
friend inline bool operator==(
const Shortcuts &a,
const Shortcuts &b) = default;
};
[[nodiscard]] bool IsShortcutMsgId(MsgId id);
class ShortcutMessages final {
public:
explicit ShortcutMessages(not_null<Session*> owner);
~ShortcutMessages();
[[nodiscard]] MsgId lookupId(not_null<const HistoryItem*> item) const;
[[nodiscard]] int count(BusinessShortcutId shortcutId) const;
[[nodiscard]] MsgId localMessageId(MsgId remoteId) const;
void apply(const MTPDupdateQuickReplyMessage &update);
void apply(const MTPDupdateDeleteQuickReplyMessages &update);
void apply(const MTPDupdateDeleteQuickReply &update);
void apply(
const MTPDupdateMessageID &update,
not_null<HistoryItem*> local);
void appendSending(not_null<HistoryItem*> item);
void removeSending(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<> updates(BusinessShortcutId shortcutId);
[[nodiscard]] Data::MessagesSlice list(BusinessShortcutId shortcutId);
void preloadShortcuts();
[[nodiscard]] const Shortcuts &shortcuts() const;
[[nodiscard]] bool shortcutsLoaded() const;
[[nodiscard]] rpl::producer<> shortcutsChanged() const;
[[nodiscard]] BusinessShortcutId emplaceShortcut(QString name);
[[nodiscard]] Shortcut lookupShortcut(BusinessShortcutId id) const;
private:
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
struct List {
std::vector<OwnedItem> items;
base::flat_map<MsgId, not_null<HistoryItem*>> itemById;
};
struct Request {
mtpRequestId requestId = 0;
crl::time lastReceived = 0;
};
void request(BusinessShortcutId shortcutId);
void parse(
BusinessShortcutId shortcutId,
const MTPmessages_Messages &list);
HistoryItem *append(
BusinessShortcutId shortcutId,
List &list,
const MTPMessage &message);
void clearNotSending(BusinessShortcutId shortcutId);
void updated(
BusinessShortcutId shortcutId,
const base::flat_set<not_null<HistoryItem*>> &added,
const base::flat_set<not_null<HistoryItem*>> &clear);
void sort(List &list);
void remove(not_null<const HistoryItem*> item);
[[nodiscard]] uint64 countListHash(const List &list) const;
void clearOldRequests();
const not_null<Main::Session*> _session;
const not_null<History*> _history;
base::Timer _clearTimer;
base::flat_map<BusinessShortcutId, List> _data;
base::flat_map<BusinessShortcutId, Request> _requests;
rpl::event_stream<BusinessShortcutId> _updates;
Shortcuts _shortcuts;
rpl::event_stream<> _shortcutsChanged;
BusinessShortcutId _localShortcutId = 0;
uint64 _shortcutsHash = 0;
mtpRequestId _shortcutsRequestId = 0;
bool _shortcutsLoaded = false;
rpl::lifetime _lifetime;
};
[[nodiscard]] MTPInputQuickReplyShortcut ShortcutIdToMTP(
not_null<Main::Session*> session,
BusinessShortcutId id);
} // namespace Data

View File

@ -54,6 +54,7 @@ Q_DECLARE_METATYPE(MsgId);
}
using StoryId = int32;
using BusinessShortcutId = int32;
struct FullStoryId {
PeerId peer = 0;
@ -77,7 +78,8 @@ constexpr auto ServerMaxStoryId = StoryId(1 << 30);
constexpr auto StoryMsgIds = int64(ServerMaxStoryId);
constexpr auto EndStoryMsgId = MsgId(StartStoryMsgId.bare + StoryMsgIds);
constexpr auto ServerMaxMsgId = MsgId(1LL << 56);
constexpr auto ScheduledMsgIdsRange = (1LL << 32);
constexpr auto ScheduledMaxMsgId = MsgId(ServerMaxMsgId + (1LL << 32));
constexpr auto ShortcutMaxMsgId = MsgId(ScheduledMaxMsgId + (1LL << 32));
constexpr auto ShowAtUnreadMsgId = MsgId(0);
constexpr auto SpecialMsgIdShift = EndStoryMsgId.bare;

View File

@ -96,8 +96,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
} // namespace
bool IsScheduledMsgId(MsgId id) {
return (id > ServerMaxMsgId)
&& (id < ServerMaxMsgId + ScheduledMsgIdsRange);
return (id > ServerMaxMsgId) && (id < ScheduledMaxMsgId);
}
ScheduledMessages::ScheduledMessages(not_null<Session*> owner)

View File

@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
#include "data/business/data_business_chatbots.h"
#include "data/business/data_business_info.h"
#include "data/business/data_shortcut_messages.h"
#include "data/stickers/data_stickers.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_bot_app.h"
@ -256,14 +257,12 @@ Session::Session(not_null<Main::Session*> session)
, _watchForOfflineTimer([=] { checkLocalUsersWentOffline(); })
, _groups(this)
, _chatsFilters(std::make_unique<ChatFilters>(this))
, _scheduledMessages(std::make_unique<ScheduledMessages>(this))
, _cloudThemes(std::make_unique<CloudThemes>(session))
, _sendActionManager(std::make_unique<SendActionManager>())
, _streaming(std::make_unique<Streaming>(this))
, _mediaRotation(std::make_unique<MediaRotation>())
, _histories(std::make_unique<Histories>(this))
, _stickers(std::make_unique<Stickers>(this))
, _sponsoredMessages(std::make_unique<SponsoredMessages>(this))
, _reactions(std::make_unique<Reactions>(this))
, _emojiStatuses(std::make_unique<EmojiStatuses>(this))
, _forumIcons(std::make_unique<ForumIcons>(this))
@ -272,7 +271,10 @@ Session::Session(not_null<Main::Session*> session)
, _stories(std::make_unique<Stories>(this))
, _savedMessages(std::make_unique<SavedMessages>(this))
, _chatbots(std::make_unique<Chatbots>(this))
, _businessInfo(std::make_unique<BusinessInfo>(this)) {
, _businessInfo(std::make_unique<BusinessInfo>(this))
, _scheduledMessages(std::make_unique<ScheduledMessages>(this))
, _shortcutMessages(std::make_unique<ShortcutMessages>(this))
, _sponsoredMessages(std::make_unique<SponsoredMessages>(this)) {
_cache->open(_session->local().cacheKey());
_bigFileCache->open(_session->local().cacheBigFileKey());
@ -396,6 +398,7 @@ void Session::clear() {
_histories->unloadAll();
_scheduledMessages = nullptr;
_shortcutMessages = nullptr;
_sponsoredMessages = nullptr;
_dependentMessages.clear();
base::take(_messages);

View File

@ -44,6 +44,7 @@ class Folder;
class LocationPoint;
class WallPaper;
class ScheduledMessages;
class ShortcutMessages;
class SendActionManager;
class SponsoredMessages;
class Reactions;
@ -102,6 +103,9 @@ public:
[[nodiscard]] ScheduledMessages &scheduledMessages() const {
return *_scheduledMessages;
}
[[nodiscard]] ShortcutMessages &shortcutMessages() const {
return *_shortcutMessages;
}
[[nodiscard]] SendActionManager &sendActionManager() const {
return *_sendActionManager;
}
@ -1058,14 +1062,12 @@ private:
Groups _groups;
const std::unique_ptr<ChatFilters> _chatsFilters;
std::unique_ptr<ScheduledMessages> _scheduledMessages;
const std::unique_ptr<CloudThemes> _cloudThemes;
const std::unique_ptr<SendActionManager> _sendActionManager;
const std::unique_ptr<Streaming> _streaming;
const std::unique_ptr<MediaRotation> _mediaRotation;
const std::unique_ptr<Histories> _histories;
const std::unique_ptr<Stickers> _stickers;
std::unique_ptr<SponsoredMessages> _sponsoredMessages;
const std::unique_ptr<Reactions> _reactions;
const std::unique_ptr<EmojiStatuses> _emojiStatuses;
const std::unique_ptr<ForumIcons> _forumIcons;
@ -1075,8 +1077,11 @@ private:
const std::unique_ptr<SavedMessages> _savedMessages;
const std::unique_ptr<Chatbots> _chatbots;
const std::unique_ptr<BusinessInfo> _businessInfo;
std::unique_ptr<ScheduledMessages> _scheduledMessages;
std::unique_ptr<ShortcutMessages> _shortcutMessages;
std::unique_ptr<SponsoredMessages> _sponsoredMessages;
MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange;
MsgId _nonHistoryEntryId = ShortcutMaxMsgId;
rpl::lifetime _lifetime;

View File

@ -27,8 +27,7 @@ using SparseUnsortedIdsSlice = AbstractSparseIds<std::vector<MsgId>>;
class SparseIdsMergedSlice {
public:
using UniversalMsgId = MsgId;
static constexpr MsgId kScheduledTopicId
= ServerMaxMsgId + ScheduledMsgIdsRange;
static constexpr MsgId kScheduledTopicId = ScheduledMaxMsgId;
struct Key {
Key(

View File

@ -145,6 +145,15 @@ TimeId DateFromMessage(const MTPmessage &message) {
});
}
BusinessShortcutId BusinessShortcutIdFromMessage(
const MTPmessage &message) {
return message.match([](const MTPDmessage &data) {
return data.vquick_reply_shortcut_id().value_or_empty();
}, [](const auto &) {
return BusinessShortcutId();
});
}
bool GoodStickerDimensions(int width, int height) {
// Show all .webp (except very large ones) as stickers,
// allow to open them in media viewer to see details.

View File

@ -109,10 +109,13 @@ using FilterId = int32;
using MessageIdsList = std::vector<FullMsgId>;
PeerId PeerFromMessage(const MTPmessage &message);
MTPDmessage::Flags FlagsFromMessage(const MTPmessage &message);
MsgId IdFromMessage(const MTPmessage &message);
TimeId DateFromMessage(const MTPmessage &message);
[[nodiscard]] PeerId PeerFromMessage(const MTPmessage &message);
[[nodiscard]] MTPDmessage::Flags FlagsFromMessage(
const MTPmessage &message);
[[nodiscard]] MsgId IdFromMessage(const MTPmessage &message);
[[nodiscard]] TimeId DateFromMessage(const MTPmessage &message);
[[nodiscard]] BusinessShortcutId BusinessShortcutIdFromMessage(
const MTPmessage &message);
[[nodiscard]] inline MTPint MTP_int(MsgId id) noexcept {
return MTP_int(id.bare);

View File

@ -210,6 +210,12 @@ public:
[[nodiscard]] bool isSponsored() const;
[[nodiscard]] bool skipNotification() const;
[[nodiscard]] bool isUserpicSuggestion() const;
[[nodiscard]] BusinessShortcutId shortcutId() const {
return _shortcutId;
}
[[nodiscard]] bool isBusinessShortcut() const {
return _shortcutId != 0;
}
void addLogEntryOriginal(
WebPageId localId,
@ -662,6 +668,7 @@ private:
TimeId _date = 0;
TimeId _ttlDestroyAt = 0;
int _boostsApplied = 0;
BusinessShortcutId _shortcutId = 0;
HistoryView::Element *_mainView = nullptr;
MessageGroupId _groupId = MessageGroupId();

View File

@ -203,6 +203,13 @@ void ContentWidget::applyAdditionalScroll(int additionalScroll) {
}
}
void ContentWidget::applyMaxVisibleHeight(int maxVisibleHeight) {
if (_maxVisibleHeight != maxVisibleHeight) {
_maxVisibleHeight = maxVisibleHeight;
update();
}
}
rpl::producer<int> ContentWidget::desiredHeightValue() const {
using namespace rpl::mappers;
return rpl::combine(
@ -328,6 +335,10 @@ rpl::producer<bool> ContentWidget::desiredBottomShadowVisibility() const {
});
}
not_null<Ui::ScrollArea*> ContentWidget::scroll() const {
return _scroll.data();
}
Key ContentMemento::key() const {
if (const auto topic = this->topic()) {
return Key(topic);

View File

@ -81,6 +81,7 @@ public:
const QRect &newGeometry,
int topDelta);
void applyAdditionalScroll(int additionalScroll);
void applyMaxVisibleHeight(int maxVisibleHeight);
int scrollTillBottom(int forHeight) const;
[[nodiscard]] rpl::producer<int> scrollTillBottomChanges() const;
[[nodiscard]] virtual const Ui::RoundRect *bottomSkipRounding() const {
@ -115,9 +116,13 @@ protected:
doSetInnerWidget(std::move(inner)));
}
not_null<Controller*> controller() const {
[[nodiscard]] not_null<Controller*> controller() const {
return _controller;
}
[[nodiscard]] not_null<Ui::ScrollArea*> scroll() const;
[[nodiscard]] int maxVisibleHeight() const {
return _maxVisibleHeight;
}
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
@ -151,6 +156,7 @@ private:
base::unique_qptr<Ui::RpWidget> _searchWrap = nullptr;
QPointer<Ui::InputField> _searchField;
int _innerDesiredHeight = 0;
int _maxVisibleHeight = 0;
bool _isStackBottom = false;
// Saving here topDelta in setGeometryWithTopMoved() to get it passed to resizeEvent().

View File

@ -291,9 +291,10 @@ QRect LayerWidget::countGeometry(int newWidth) {
const auto newBottom = newTop;
const auto bottomRadius = st::boxRadius;
const auto maxVisibleHeight = windowHeight - newTop;
// Top rounding is included in _contentHeight.
auto desiredHeight = _contentHeight + bottomRadius;
accumulate_min(desiredHeight, windowHeight - newTop - newBottom);
accumulate_min(desiredHeight, maxVisibleHeight - newBottom);
// First resize content to new width and get the new desired height.
const auto contentLeft = 0;
@ -308,7 +309,7 @@ QRect LayerWidget::countGeometry(int newWidth) {
desiredHeight += additionalScroll;
contentHeight += additionalScroll;
_tillBottom = (newTop + desiredHeight >= windowHeight);
_tillBottom = (desiredHeight >= maxVisibleHeight);
if (_tillBottom) {
additionalScroll += contentBottom;
}
@ -321,7 +322,7 @@ QRect LayerWidget::countGeometry(int newWidth) {
contentTop,
contentWidth,
contentHeight,
}, expanding, additionalScroll);
}, expanding, additionalScroll, maxVisibleHeight);
return QRect(newLeft, newTop, newWidth, desiredHeight);
}

View File

@ -54,7 +54,11 @@ void SectionWidget::init() {
const auto full = !_content->scrollBottomSkip();
const auto height = size.height() - (full ? 0 : st::boxRadius);
const auto wrapGeometry = QRect{ 0, 0, size.width(), height };
_content->updateGeometry(wrapGeometry, expanding, additionalScroll);
_content->updateGeometry(
wrapGeometry,
expanding,
additionalScroll,
size.height());
}, lifetime());
_connecting = std::make_unique<Window::ConnectionState>(

View File

@ -934,13 +934,17 @@ object_ptr<Ui::RpWidget> WrapWidget::createTopBarSurrogate(
void WrapWidget::updateGeometry(
QRect newGeometry,
bool expanding,
int additionalScroll) {
int additionalScroll,
int maxVisibleHeight) {
auto scrollChanged = (_additionalScroll != additionalScroll);
auto geometryChanged = (geometry() != newGeometry);
auto shrinkingContent = (additionalScroll < _additionalScroll);
_additionalScroll = additionalScroll;
_maxVisibleHeight = maxVisibleHeight;
_expanding = expanding;
_content->applyMaxVisibleHeight(maxVisibleHeight);
if (geometryChanged) {
if (shrinkingContent) {
setGeometry(newGeometry);

View File

@ -124,7 +124,8 @@ public:
void updateGeometry(
QRect newGeometry,
bool expanding,
int additionalScroll);
int additionalScroll,
int maxVisibleHeight);
[[nodiscard]] int scrollBottomSkip() const;
[[nodiscard]] int scrollTillBottom(int forHeight) const;
[[nodiscard]] rpl::producer<int> scrollTillBottomChanges() const;
@ -207,6 +208,7 @@ private:
std::unique_ptr<Controller> _controller;
object_ptr<ContentWidget> _content = { nullptr };
int _additionalScroll = 0;
int _maxVisibleHeight = 0;
bool _expanding = false;
rpl::variable<bool> _grabbingForExpanding = false;
object_ptr<TopBar> _topBar = { nullptr };

View File

@ -44,7 +44,10 @@ Widget::Widget(
, _self(controller->key().settingsSelf())
, _type(controller->section().settingsType())
, _inner([&] {
auto inner = _type->create(this, controller->parentController());
auto inner = _type->create(
this,
controller->parentController(),
scroll());
if (inner->hasFlexibleTopBar()) {
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
filler->resize(1, 1);
@ -229,6 +232,12 @@ rpl::producer<QString> Widget::title() {
return _inner->title();
}
void Widget::paintEvent(QPaintEvent *e) {
if (!_inner->paintOuter(this, maxVisibleHeight(), e->rect())) {
ContentWidget::paintEvent(e);
}
}
std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
auto result = std::make_shared<Memento>(self(), _type);
saveState(result.get());

View File

@ -84,6 +84,8 @@ private:
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
void paintEvent(QPaintEvent *e) override;
std::shared_ptr<ContentMemento> doCreateMemento() override;
not_null<UserData*> _self;

View File

@ -79,7 +79,8 @@ Session::Session(
not_null<Account*> account,
const MTPUser &user,
std::unique_ptr<SessionSettings> settings)
: _account(account)
: _userId(user.c_user().vid())
, _account(account)
, _settings(std::move(settings))
, _changes(std::make_unique<Data::Changes>(this))
, _api(std::make_unique<ApiWrap>(this))
@ -89,7 +90,6 @@ Session::Session(
, _uploader(std::make_unique<Storage::Uploader>(_api.get()))
, _storage(std::make_unique<Storage::Facade>())
, _data(std::make_unique<Data::Session>(this))
, _userId(user.c_user().vid())
, _user(_data->processUser(user))
, _emojiStickersPack(std::make_unique<Stickers::EmojiPack>(this))
, _diceStickersPacks(std::make_unique<Stickers::DicePacks>(this))

View File

@ -199,6 +199,7 @@ private:
void parseColorIndices(const MTPDhelp_peerColors &data);
const UserId _userId;
const not_null<Account*> _account;
const std::unique_ptr<SessionSettings> _settings;
@ -212,7 +213,6 @@ private:
// _data depends on _downloader / _uploader.
const std::unique_ptr<Data::Session> _data;
const UserId _userId;
const not_null<UserData*> _user;
// _emojiStickersPack depends on _data.

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/random.h"
#include "boxes/share_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_chat_participant_status.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h"
@ -119,6 +120,7 @@ namespace Media::Stories {
message.action.clearDraft = false;
api->sendMessage(std::move(message));
}
const auto session = &thread->session();
const auto threadPeer = thread->peer();
const auto threadHistory = thread->owningHistory();
const auto randomId = base::RandomValue<uint64>();
@ -132,6 +134,12 @@ namespace Media::Stories {
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
if (options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
const auto done = [=] {
if (!--state->requests) {
if (show->valid()) {
@ -155,7 +163,7 @@ namespace Media::Stories {
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
MTP_inputPeerEmpty(),
MTPInputQuickReplyShortcut()
Data::ShortcutIdToMTP(session, action.options.shortcutId)
), [=](
const MTPUpdates &result,
const MTP::Response &response) {

View File

@ -10,10 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "core/application.h"
#include "data/business/data_business_info.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/business/settings_recipients_helper.h"
#include "settings/business/settings_shortcut_messages.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
@ -37,10 +39,13 @@ public:
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
void save();
rpl::event_stream<Type> _showOther;
rpl::variable<Data::BusinessRecipients> _recipients;
rpl::variable<Data::AwaySchedule> _schedule;
rpl::variable<bool> _enabled;
@ -197,6 +202,10 @@ rpl::producer<QString> AwayMessage::title() {
return tr::lng_away_title();
}
rpl::producer<Type> AwayMessage::sectionShowOther() {
return _showOther.events();
}
void AwayMessage::setupContent(
not_null<Window::SessionController*> controller) {
using namespace Data;
@ -258,7 +267,9 @@ void AwayMessage::setupContent(
st::settingsButtonLightNoIcon
));
create->setClickedCallback([=] {
const auto owner = &controller->session().data();
const auto id = owner->shortcutMessages().emplaceShortcut("away");
_showOther.fire(ShortcutMessagesId(id));
});
Ui::AddSkip(createInner);
Ui::AddDivider(createInner);

View File

@ -6,7 +6,7 @@ For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/business/settings_chatbots.h"
//
#include "core/application.h"
#include "data/business/data_business_chatbots.h"
#include "data/data_session.h"

View File

@ -10,9 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h"
#include "core/application.h"
#include "data/business/data_business_info.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/business/settings_shortcut_messages.h"
#include "settings/business/settings_recipients_helper.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
@ -50,6 +52,7 @@ private:
Ui::RoundRect _bottomSkipRounding;
rpl::event_stream<Type> _showOther;
rpl::variable<Data::BusinessRecipients> _recipients;
rpl::variable<int> _noActivityDays;
rpl::variable<bool> _enabled;
@ -229,6 +232,28 @@ void Greeting::setupContent(
object_ptr<Ui::VerticalLayout>(content)));
const auto inner = wrap->entity();
const auto createWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
const auto createInner = createWrap->entity();
Ui::AddSkip(createInner);
const auto create = createInner->add(object_ptr<Ui::SettingsButton>(
createInner,
tr::lng_greeting_create(),
st::settingsButtonLightNoIcon
));
create->setClickedCallback([=] {
const auto owner = &controller->session().data();
const auto id = owner->shortcutMessages().emplaceShortcut("hello");
_showOther.fire(ShortcutMessagesId(id));
});
Ui::AddSkip(createInner);
Ui::AddDivider(createInner);
createWrap->toggleOn(rpl::single(true));
Ui::AddSkip(inner);
AddBusinessRecipientsSelector(inner, {
.controller = controller,
.title = tr::lng_greeting_recipients(),

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "settings/settings_type.h"
namespace Settings {
[[nodiscard]] Type ShortcutMessagesId(int shortcutId);
} // namespace Settings

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h" // AmPremiumValue.
#include "data/data_session.h"
#include "data/business/data_business_info.h"
#include "data/business/data_shortcut_messages.h"
#include "info/info_wrap_widget.h" // Info::Wrap.
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "lang/lang_keys.h"
@ -359,6 +360,7 @@ void Business::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
_controller->session().data().businessInfo().preloadTimezones();
_controller->session().data().shortcutMessages().preloadShortcuts();
Ui::AddSkip(content, st::settingsFromFileTop);
@ -566,7 +568,8 @@ template <>
struct SectionFactory<Business> : AbstractSectionFactory {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll
) const final override {
return object_ptr<Business>(parent, controller);
}

View File

@ -90,6 +90,13 @@ public:
}
virtual void setStepDataReference(std::any &data) {
}
virtual bool paintOuter(
not_null<QWidget*> outer,
int maxVisibleHeight,
QRect clip) {
return false;
}
};
enum class IconType {

View File

@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
#include "settings/settings_type.h"
namespace Ui {
class ScrollArea;
} // namespace Ui
namespace Ui::Menu {
struct MenuCallback;
} // namespace Ui::Menu
@ -27,7 +31,8 @@ class AbstractSection;
struct AbstractSectionFactory {
[[nodiscard]] virtual object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller) const = 0;
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll) const = 0;
[[nodiscard]] virtual bool hasCustomTopBar() const {
return false;
}
@ -39,7 +44,8 @@ template <typename SectionType>
struct SectionFactory : AbstractSectionFactory {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll
) const final override {
return object_ptr<SectionType>(parent, controller);
}

View File

@ -43,7 +43,8 @@ struct Factory : AbstractSectionFactory {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll
) const final override {
return object_ptr<NotificationsType>(parent, controller, type);
}

View File

@ -1267,7 +1267,8 @@ template <>
struct SectionFactory<Premium> : AbstractSectionFactory {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
not_null<Window::SessionController*> controller,
not_null<Ui::ScrollArea*> scroll
) const final override {
return object_ptr<Premium>(parent, controller);
}