[Option][GUI] Unquoted forward and forward options

This commit is contained in:
Eric Kotato 2022-09-11 05:50:27 +03:00 committed by Eric Kotato
parent 7c288a6f28
commit e1617915ff
24 changed files with 1717 additions and 160 deletions

View file

@ -118,7 +118,21 @@
"ktg_group_id_copied": "Group ID copied to clipboard.",
"ktg_supergroup_id_copied": "Supergroup ID copied to clipboard.",
"ktg_channel_id_copied": "Channel ID copied to clipboard.",
"ktg_forward_go_to_chat": "Go to chat",
"ktg_settings_forward": "Forward",
"ktg_settings_forward_retain_selection": "Retain selection after forward",
"ktg_settings_forward_chat_on_click": "Open chat on click",
"ktg_settings_forward_chat_on_click_description": "You can hold Ctrl to select multiple chats regardless of this option.",
"ktg_forward_menu_quoted": "Quoted",
"ktg_forward_menu_unquoted": "Unquoted with captions",
"ktg_forward_menu_uncaptioned": "Unquoted without captions",
"ktg_forward_menu_default_albums": "Preserve albums",
"ktg_forward_menu_group_all_media": "Group all media",
"ktg_forward_menu_separate_messages": "Separate messages",
"ktg_forward_subtitle_unquoted": "unquoted",
"ktg_forward_subtitle_uncaptioned": "uncaptioned",
"ktg_forward_subtitle_group_all_media": "as albums",
"ktg_forward_subtitle_separate_messages": "one by one",
"ktg_filters_exclude_not_owned": "Not owned",
"ktg_filters_exclude_not_admin": "Not administrated",
"ktg_filters_exclude_owned": "Owned",
@ -140,6 +154,20 @@
"ktg_filters_hide_all_chats_toast": "\"All Chats\" folder is hidden.\nYou can enable it back in Kotatogram Settings.",
"ktg_filters_hide_edit_toast": "Edit button is hidden.\nYou can enable it back in Kotatogram Settings.",
"ktg_settings_telegram_sites_autologin": "Auto-login on Telegram sites",
"ktg_forward_sender_names_and_captions_removed": "Sender names and captions removed",
"ktg_forward_remember_mode": "Remember forward mode",
"ktg_forward_mode": "Forward mode",
"ktg_forward_mode_quoted": "Quoted",
"ktg_forward_mode_unquoted": "Unquoted",
"ktg_forward_mode_uncaptioned": "Uncaptioned",
"ktg_forward_grouping_mode": "Grouping mode",
"ktg_forward_grouping_mode_preserve_albums": "Same as original",
"ktg_forward_grouping_mode_regroup": "Regroup media",
"ktg_forward_grouping_mode_regroup_desc": "Unquoted and uncaptioned only",
"ktg_forward_grouping_mode_separate": "Separate",
"ktg_forward_force_old_unquoted": "Old unquoted forward method",
"ktg_forward_force_old_unquoted_desc": "Old method copies messages content on client rather than server. Currently it's used only for \"Regroup media\" grouping mode, since new one doesn't support it. If for some reason unquoted forward doesn't work correctly, try switching this option.",
"ktg_forward_quiz_unquoted": "Sorry, quizzes that are currently open and unvoted on cannot be forwarded unquoted.",
"ktg_in_app_update_disabled": "In-app updater is disabled.",
"dummy_last_string": ""
}

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_location.h"
#include "data/data_channel.h" // ChannelData::addsSignature.
#include "data/data_user.h" // UserData::name
#include "data/data_session.h"
@ -64,7 +65,9 @@ void SendExistingMedia(
not_null<MediaData*> media,
Fn<MTPInputMedia()> inputMedia,
Data::FileOrigin origin,
std::optional<MsgId> localMessageId) {
std::optional<MsgId> localMessageId,
Fn<void()> doneCallback = nullptr,
bool forwarding = false) {
const auto history = message.action.history;
const auto peer = history->peer;
const auto session = &history->session();
@ -145,7 +148,6 @@ void SendExistingMedia(
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto usedFileReference = media->fileReference();
history->sendRequestId = api->request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
@ -157,12 +159,16 @@ void SendExistingMedia(
sentEntities,
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
)).done([=](const MTPUpdates &result, mtpRequestId requestId) {
api->applyUpdates(result, randomId);
if (doneCallback) {
doneCallback();
}
finish();
}).fail([=](const MTP::Error &error) {
if (error.code() == 400
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
const auto usedFileReference = media->fileReference();
api->refreshFileReference(origin, [=](const auto &result) {
if (media->fileReference() != usedFileReference) {
repeatRequest(repeatRequest);
@ -181,15 +187,41 @@ void SendExistingMedia(
};
performRequest(performRequest);
api->finishForwarding(message.action);
if (!forwarding) {
api->finishForwarding(message.action);
}
}
} // namespace
void SendWebDocument(
Api::MessageToSend &&message,
not_null<DocumentData*> document,
std::optional<MsgId> localMessageId,
Fn<void()> doneCallback,
bool forwarding) {
const auto inputMedia = [=] {
return MTP_inputMediaDocumentExternal(
MTP_flags(0),
MTP_string(document->url()),
MTPint()); // ttl_seconds
};
SendExistingMedia(
std::move(message),
document,
inputMedia,
document->stickerOrGifOrigin(),
std::move(localMessageId),
(doneCallback ? std::move(doneCallback) : nullptr),
forwarding);
}
void SendExistingDocument(
MessageToSend &&message,
not_null<DocumentData*> document,
std::optional<MsgId> localMessageId) {
std::optional<MsgId> localMessageId,
Fn<void()> doneCallback,
bool forwarding) {
const auto inputMedia = [=] {
return MTP_inputMediaDocument(
MTP_flags(0),
@ -202,7 +234,9 @@ void SendExistingDocument(
document,
inputMedia,
document->stickerOrGifOrigin(),
std::move(localMessageId));
std::move(localMessageId),
(doneCallback ? std::move(doneCallback) : nullptr),
forwarding);
if (document->sticker()) {
document->owner().stickers().incrementSticker(document);
@ -212,7 +246,9 @@ void SendExistingDocument(
void SendExistingPhoto(
MessageToSend &&message,
not_null<PhotoData*> photo,
std::optional<MsgId> localMessageId) {
std::optional<MsgId> localMessageId,
Fn<void()> doneCallback,
bool forwarding) {
const auto inputMedia = [=] {
return MTP_inputMediaPhoto(
MTP_flags(0),
@ -224,10 +260,15 @@ void SendExistingPhoto(
photo,
inputMedia,
Data::FileOrigin(),
std::move(localMessageId));
std::move(localMessageId),
(doneCallback ? std::move(doneCallback) : nullptr),
forwarding);
}
bool SendDice(MessageToSend &message) {
bool SendDice(
MessageToSend &message,
Fn<void(const MTPUpdates &, mtpRequestId)> doneCallback,
bool forwarding) {
const auto full = QStringView(message.textWithTags.text).trimmed();
auto length = 0;
if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
@ -327,8 +368,11 @@ bool SendDice(MessageToSend &message) {
MTP_vector<MTPMessageEntity>(),
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
)).done([=](const MTPUpdates &result, mtpRequestId requestId) {
api->applyUpdates(result, randomId);
if (doneCallback) {
doneCallback(result, requestId);
}
finish();
}).fail([=](const MTP::Error &error) {
api->sendMessageFail(error, peer, randomId, newId);
@ -337,7 +381,9 @@ bool SendDice(MessageToSend &message) {
).send();
return history->sendRequestId;
});
api->finishForwarding(message.action);
if (!forwarding) {
api->finishForwarding(message.action);
}
return true;
}
@ -491,4 +537,71 @@ void SendConfirmedFile(
}
}
void SendLocationPoint(
const Data::LocationPoint &data,
const SendAction &action,
Fn<void()> done,
Fn<void(const MTP::Error &error)> fail) {
const auto history = action.history;
const auto session = &history->session();
const auto api = &session->api();
const auto peer = history->peer;
api->sendAction(action);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
if (action.clearDraft) {
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearLocalDraft();
history->clearCloudDraft();
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto silentPost = ShouldSendSilent(peer, action.options);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto replyTo = action.replyTo;
history->sendRequestId = api->request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
MTP_inputMediaGeoPoint(
MTP_inputGeoPoint(
MTP_flags(0),
MTP_double(data.lat()),
MTP_double(data.lon()),
MTP_int(0))),
MTP_string(),
MTP_long(base::RandomValue<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) mutable {
api->applyUpdates(result);
done();
finish();
}).fail([=](const MTP::Error &error) mutable {
if (fail) {
fail(error);
}
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
}
} // namespace Api

View file

@ -16,22 +16,44 @@ class PhotoData;
class DocumentData;
struct FileLoadResult;
namespace MTP {
class Error;
} // namespace MTP
namespace Data {
class LocationPoint;
} // namespace Data
namespace Api {
struct MessageToSend;
struct SendAction;
void SendWebDocument(
MessageToSend &&message,
not_null<DocumentData*> document,
std::optional<MsgId> localMessageId = std::nullopt,
Fn<void()> doneCallback = nullptr,
bool forwarding = false);
void SendExistingDocument(
MessageToSend &&message,
not_null<DocumentData*> document,
std::optional<MsgId> localMessageId = std::nullopt);
std::optional<MsgId> localMessageId = std::nullopt,
Fn<void()> doneCallback = nullptr,
bool forwarding = false);
void SendExistingPhoto(
MessageToSend &&message,
not_null<PhotoData*> photo,
std::optional<MsgId> localMessageId = std::nullopt);
std::optional<MsgId> localMessageId = std::nullopt,
Fn<void()> doneCallback = nullptr,
bool forwarding = false);
bool SendDice(MessageToSend &message);
bool SendDice(
MessageToSend &message,
Fn<void(const MTPUpdates &, mtpRequestId)> doneCallback = nullptr,
bool forwarding = false);
void FillMessagePostFlags(
const SendAction &action,
@ -42,4 +64,10 @@ void SendConfirmedFile(
not_null<Main::Session*> session,
const std::shared_ptr<FileLoadResult> &file);
void SendLocationPoint(
const Data::LocationPoint &data,
const SendAction &action,
Fn<void()> done,
Fn<void(const MTP::Error &error)> fail);
} // namespace Api

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "apiwrap.h"
#include "kotato/kotato_settings.h"
#include "api/api_authorizations.h"
#include "api/api_attached_stickers.h"
#include "api/api_blocked_peers.h"
@ -35,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_drafts.h"
#include "data/data_changes.h"
#include "data/data_photo.h"
#include "data/data_poll.h"
#include "data/data_web_page.h"
#include "data/data_folder.h"
#include "data/data_media_types.h"
@ -3016,6 +3018,12 @@ void ApiWrap::forwardMessages(
Data::ResolvedForwardDraft &&draft,
const SendAction &action,
FnMut<void()> &&successCallback) {
if (draft.options != Data::ForwardOptions::PreserveInfo
&& (draft.groupOptions == Data::GroupingOptions::RegroupAll
|| ::Kotato::JsonSettings::GetBool("forward_force_old_unquoted"))) {
forwardMessagesUnquoted(std::move(draft), action, std::move(successCallback));
return;
}
Expects(!draft.items.empty());
auto &histories = _session->data().histories();
@ -3066,6 +3074,7 @@ void ApiWrap::forwardMessages(
}
auto forwardFrom = draft.items.front()->history()->peer;
auto forwardGroupId = draft.items.front()->groupId();
auto ids = QVector<MTPint>();
auto randomIds = QVector<MTPlong>();
auto localIds = std::shared_ptr<base::flat_map<uint64, FullMsgId>>();
@ -3142,9 +3151,14 @@ void ApiWrap::forwardMessages(
localIds->emplace(randomId, newId);
}
const auto newFrom = item->history()->peer;
if (forwardFrom != newFrom) {
const auto newGroupId = item->groupId();
if (item != draft.items.front() &&
((draft.groupOptions == Data::GroupingOptions::GroupAsIs
&& (forwardGroupId != newGroupId || forwardFrom != newFrom))
|| draft.groupOptions == Data::GroupingOptions::Separate)) {
sendAccumulated();
forwardFrom = newFrom;
forwardGroupId = newGroupId;
}
ids.push_back(MTP_int(item->id));
randomIds.push_back(MTP_long(randomId));
@ -3153,6 +3167,489 @@ void ApiWrap::forwardMessages(
_session->data().sendHistoryChangeNotifications();
}
void ApiWrap::forwardMessagesUnquoted(
Data::ResolvedForwardDraft &&draft,
const SendAction &action,
FnMut<void()> &&successCallback) {
Expects(!draft.items.empty());
auto &histories = _session->data().histories();
struct SharedCallback {
int requestsLeft = 0;
FnMut<void()> callback;
};
enum LastGroupType {
None,
Music,
Documents,
Medias,
};
const auto shared = successCallback
? std::make_shared<SharedCallback>()
: std::shared_ptr<SharedCallback>();
if (successCallback) {
shared->callback = std::move(successCallback);
}
const auto count = int(draft.items.size());
const auto history = action.history;
const auto peer = history->peer;
histories.readInbox(history);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
const auto sendAs = action.options.sendAs;
const auto self = _session->user();
const auto messageFromId = sendAs
? sendAs->id
: anonymousPost
? PeerId(0)
: self->id;
auto flags = MessageFlags();
auto sendFlags = MTPmessages_ForwardMessages::Flags(0);
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
}
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date;
}
if (sendAs) {
sendFlags |= MTPmessages_ForwardMessages::Flag::f_send_as;
}
auto forwardFrom = draft.items.front()->history()->peer;
auto currentGroupId = draft.items.front()->groupId();
auto lastGroup = LastGroupType::None;
auto ids = QVector<MTPint>();
auto randomIds = QVector<uint64>();
auto fromIter = draft.items.begin();
auto toIter = draft.items.begin();
auto messageGroupCount = 0;
auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name() : QString();
const auto needNextGroup = [&] (not_null<HistoryItem *> item) {
auto lastGroupCheck = false;
if (item->media() && item->media()->canBeGrouped()) {
lastGroupCheck = lastGroup != ((item->media()->photo()
|| (item->media()->document()
&& item->media()->document()->isVideoFile()))
? LastGroupType::Medias
: (item->media()->document()
&& item->media()->document()->isSharedMediaMusic())
? LastGroupType::Music
: LastGroupType::Documents);
} else {
lastGroupCheck = lastGroup != LastGroupType::None;
}
switch (draft.groupOptions) {
case Data::GroupingOptions::GroupAsIs:
return forwardFrom != item->history()->peer
|| !currentGroupId
|| currentGroupId != item->groupId()
|| lastGroupCheck
|| messageGroupCount >= 10;
case Data::GroupingOptions::RegroupAll:
return lastGroupCheck
|| messageGroupCount >= 10;
case Data::GroupingOptions::Separate:
return true;
default:
Unexpected("draft.groupOptions in ApiWrap::forwardMessagesUnquoted::needNextGroup.");
}
return false;
};
const auto isGrouped = [&] {
return lastGroup != LastGroupType::None
&& messageGroupCount > 1
&& messageGroupCount <= 10;
};
const auto forwardQuotedSingle = [&] (not_null<HistoryItem *> item) {
if (shared) {
++shared->requestsLeft;
}
auto currentIds = QVector<MTPint>();
currentIds.push_back(MTP_int(item->id));
auto currentRandomId = MTP_long(randomIds.takeFirst());
auto currentRandomIds = QVector<MTPlong>();
currentRandomIds.push_back(currentRandomId);
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
forwardFrom->input,
MTP_vector<MTPint>(currentIds),
MTP_vector<MTPlong>(currentRandomIds),
peer->input,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
if (shared && !--shared->requestsLeft) {
shared->callback();
}
finish();
}).fail([=](const MTP::Error &error) {
sendMessageFail(error, peer);
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
};
const auto forwardAlbumUnquoted = [&] {
if (shared) {
++shared->requestsLeft;
}
const auto medias = std::make_shared<QVector<Data::Media*>>();
const auto mediaInputs = std::make_shared<QVector<MTPInputSingleMedia>>();
const auto mediaRefs = std::make_shared<QVector<QByteArray>>();
mediaInputs->reserve(ids.size());
mediaRefs->reserve(ids.size());
const auto newGroupId = base::RandomValue<uint64>();
auto msgFlags = NewMessageFlags(peer);
FillMessagePostFlags(action, peer, msgFlags);
if (action.options.scheduled) {
msgFlags |= MessageFlag::IsOrWasScheduled;
}
for (auto i = fromIter, e = toIter; i != e; i++) {
const auto item = *i;
const auto media = item->media();
medias->push_back(media);
const auto inputMedia = media->photo()
? MTP_inputMediaPhoto(MTP_flags(0), media->photo()->mtpInput(), MTPint())
: MTP_inputMediaDocument(MTP_flags(0), media->document()->mtpInput(), MTPint(), MTPstring());
auto caption = (draft.options != Data::ForwardOptions::NoNamesAndCaptions)
? item->originalText()
: TextWithEntities();
auto sentEntities = Api::EntitiesToMTP(
_session,
caption.entities,
Api::ConvertOption::SkipLocal);
const auto flags = !sentEntities.v.isEmpty()
? MTPDinputSingleMedia::Flag::f_entities
: MTPDinputSingleMedia::Flag(0);
const auto newId = FullMsgId(
peer->id,
_session->data().nextLocalMessageId());
auto randomId = randomIds.takeFirst();
mediaInputs->push_back(MTP_inputSingleMedia(
MTP_flags(flags),
inputMedia,
MTP_long(randomId),
MTP_string(caption.text),
sentEntities));
_session->data().registerMessageRandomId(randomId, newId);
if (const auto photo = media->photo()) {
history->addNewLocalMessage(
newId.msg,
msgFlags,
0, // viaBotId
0, // replyTo
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
photo,
caption,
HistoryMessageMarkupData(),
newGroupId);
} else if (const auto document = media->document()) {
history->addNewLocalMessage(
newId.msg,
msgFlags,
0, // viaBotId
0, // replyTo
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
document,
caption,
HistoryMessageMarkupData(),
newGroupId);
}
}
const auto finalFlags = MTPmessages_SendMultiMedia::Flags(0)
| (action.options.silent
? MTPmessages_SendMultiMedia::Flag::f_silent
: MTPmessages_SendMultiMedia::Flag(0))
| (action.options.scheduled
? MTPmessages_SendMultiMedia::Flag::f_schedule_date
: MTPmessages_SendMultiMedia::Flag(0));
const auto requestType = Data::Histories::RequestType::Send;
auto performRequest = [=, &histories](const auto &repeatRequest) -> void {
mediaRefs->clear();
for (auto i = medias->begin(), e = medias->end(); i != e; i++) {
const auto media = *i;
mediaRefs->push_back(media->photo()
? media->photo()->fileReference()
: media->document()->fileReference());
}
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendMultiMedia(
MTP_flags(finalFlags),
peer->input,
MTPint(),
MTP_vector<MTPInputSingleMedia>(*mediaInputs),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
if (shared && !--shared->requestsLeft) {
shared->callback();
}
finish();
}).fail([=](const MTP::Error &error) {
if (error.code() == 400
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
auto refreshRequests = mediaRefs->size();
auto index = 0;
auto wasUpdated = false;
for (auto i = medias->begin(), e = medias->end(); i != e; i++) {
const auto media = *i;
const auto origin = media->document()
? media->document()->stickerOrGifOrigin()
: Data::FileOrigin();
const auto usedFileReference = mediaRefs->value(index);
refreshFileReference(origin, [=, &refreshRequests, &wasUpdated](const auto &result) {
const auto currentMediaReference = media->photo()
? media->photo()->fileReference()
: media->document()->fileReference();
if (currentMediaReference != usedFileReference) {
wasUpdated = true;
}
if (refreshRequests > 0) {
refreshRequests--;
return;
}
if (wasUpdated) {
repeatRequest(repeatRequest);
} else {
sendMessageFail(error, peer);
}
});
index++;
}
} else {
sendMessageFail(error, peer);
}
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
};
performRequest(performRequest);
};
const auto forwardMediaUnquoted = [&] (not_null<HistoryItem *> item) {
if (shared) {
++shared->requestsLeft;
}
const auto media = item->media();
auto message = MessageToSend(action);
const auto caption = (draft.options != Data::ForwardOptions::NoNamesAndCaptions
&& !media->geoPoint()
&& !media->sharedContact())
? item->originalText()
: TextWithEntities();
message.textWithTags = TextWithTags{
caption.text,
TextUtilities::ConvertEntitiesToTextTags(caption.entities)
};
message.action.clearDraft = false;
auto doneCallback = [=] () {
if (shared && !--shared->requestsLeft) {
shared->callback();
}
};
if (media->poll()) {
const auto poll = *(media->poll());
_polls->create(poll,
message.action,
std::move(doneCallback),
nullptr);
} else if (media->geoPoint()) {
const auto location = *(media->geoPoint());
Api::SendLocationPoint(
location,
message.action,
std::move(doneCallback),
nullptr);
} else if (media->sharedContact()) {
const auto contact = media->sharedContact();
shareContact(
contact->phoneNumber,
contact->firstName,
contact->lastName,
message.action);
} else if (media->photo()) {
Api::SendExistingPhoto(
std::move(message),
media->photo(),
std::nullopt,
std::move(doneCallback),
true); // forwarding
} else if (media->document()) {
Api::SendExistingDocument(
std::move(message),
media->document(),
std::nullopt,
std::move(doneCallback),
true); // forwarding
} else {
Unexpected("Media type in ApiWrap::forwardMessages.");
}
};
const auto forwardDiceUnquoted = [&] (not_null<HistoryItem *> item) {
if (shared) {
++shared->requestsLeft;
}
const auto dice = dynamic_cast<Data::MediaDice*>(item->media());
if (!dice) {
Unexpected("Non-dice in ApiWrap::forwardMessages.");
}
auto message = MessageToSend(action);
message.textWithTags.text = dice->emoji();
message.action.clearDraft = false;
Api::SendDice(message, [=] (const MTPUpdates &result, mtpRequestId requestId) {
if (shared && !--shared->requestsLeft) {
shared->callback();
}
}, true); // forwarding
};
const auto forwardMessageUnquoted = [&] (not_null<HistoryItem *> item) {
if (shared) {
++shared->requestsLeft;
}
const auto media = item->media();
const auto webPageId = (!media || !media->webpage())
? CancelledWebPageId
: media->webpage()->id;
auto message = MessageToSend(action);
message.textWithTags = TextWithTags{
item->originalText().text,
TextUtilities::ConvertEntitiesToTextTags(item->originalText().entities)
};
message.action.clearDraft = false;
message.webPageId = webPageId;
session().api().sendMessage(
std::move(message),
[=] (const MTPUpdates &result, mtpRequestId requestId) {
if (shared && !--shared->requestsLeft) {
shared->callback();
}
}, true); // forwarding
};
const auto sendAccumulated = [&] {
if (isGrouped()) {
forwardAlbumUnquoted();
} else {
for (auto i = fromIter, e = toIter; i != e; i++) {
const auto item = *i;
const auto media = item->media();
if (media && !media->webpage()) {
if (const auto dice = dynamic_cast<Data::MediaDice*>(media)) {
forwardDiceUnquoted(item);
} else if ((media->poll() && !history->peer->isUser())
|| media->geoPoint()
|| media->sharedContact()
|| media->photo()
|| media->document()) {
forwardMediaUnquoted(item);
} else {
forwardQuotedSingle(item);
}
} else {
forwardMessageUnquoted(item);
}
}
}
ids.resize(0);
randomIds.resize(0);
};
ids.reserve(count);
randomIds.reserve(count);
for (auto i = draft.items.begin(), e = draft.items.end(); i != e; /* ++i is in the end */) {
const auto item = *i;
const auto randomId = base::RandomValue<uint64>();
if (needNextGroup(item)) {
sendAccumulated();
messageGroupCount = 0;
forwardFrom = item->history()->peer;
currentGroupId = item->groupId();
fromIter = i;
}
ids.push_back(MTP_int(item->id));
randomIds.push_back(randomId);
if (item->media() && item->media()->canBeGrouped()) {
lastGroup = ((item->media()->photo()
|| (item->media()->document()
&& item->media()->document()->isVideoFile()))
? LastGroupType::Medias
: (item->media()->document()
&& item->media()->document()->isSharedMediaMusic())
? LastGroupType::Music
: LastGroupType::Documents);
} else {
lastGroup = LastGroupType::None;
}
toIter = ++i;
messageGroupCount++;
}
sendAccumulated();
_session->data().sendHistoryChangeNotifications();
}
void ApiWrap::shareContact(
const QString &phone,
const QString &firstName,
@ -3385,7 +3882,10 @@ void ApiWrap::cancelLocalItem(not_null<HistoryItem*> item) {
}
}
void ApiWrap::sendMessage(MessageToSend &&message) {
void ApiWrap::sendMessage(
MessageToSend &&message,
Fn<void(const MTPUpdates &, mtpRequestId)> doneCallback,
bool forwarding) {
const auto history = message.action.history;
const auto peer = history->peer;
auto &textWithTags = message.textWithTags;
@ -3394,7 +3894,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
action.generateLocal = true;
sendAction(action);
if (!peer->canWrite() || Api::SendDice(message)) {
if (!peer->canWrite()
|| Api::SendDice(message, [=] (const MTPUpdates &result, mtpRequestId requestId) {
if (doneCallback) {
doneCallback(result, requestId);
}
}, forwarding)) {
return;
}
local().saveRecentSentHashtags(textWithTags.text);
@ -3509,11 +4014,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
if (doneCallback) {
doneCallback(result, response.requestId);
}
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) {
if (error.type() == qstr("MESSAGE_EMPTY")) {
if (error.type() == qstr("MESSAGE_EMPTY") && !forwarding) {
lastMessage->destroy();
} else {
sendMessageFail(error, peer, randomId, newId);
@ -3529,7 +4037,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
});
}
finishForwarding(action);
if (!forwarding) {
finishForwarding(action);
}
}
void ApiWrap::sendBotStart(

View file

@ -283,6 +283,10 @@ public:
Data::ResolvedForwardDraft &&draft,
const SendAction &action,
FnMut<void()> &&successCallback = nullptr);
void forwardMessagesUnquoted(
Data::ResolvedForwardDraft &&draft,
const SendAction &action,
FnMut<void()> &&successCallback = nullptr);
void shareContact(
const QString &phone,
const QString &firstName,
@ -326,7 +330,10 @@ public:
void cancelLocalItem(not_null<HistoryItem*> item);
void sendMessage(MessageToSend &&message);
void sendMessage(
MessageToSend &&message,
Fn<void(const MTPUpdates &, mtpRequestId)> doneCallback = nullptr,
bool forwarding = false);
void sendBotStart(
not_null<UserData*> bot,
PeerData *chat = nullptr,

View file

@ -1143,7 +1143,8 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
std::vector<not_null<PeerData*>> &&result,
TextWithTags &&comment,
Api::SendOptions options,
Data::ForwardOptions) {
Data::ForwardOptions,
Data::GroupingOptions) {
if (*sending || result.empty()) {
return;
}

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/share_box.h"
#include "kotato/kotato_lang.h"
#include "kotato/kotato_settings.h"
#include "base/random.h"
#include "dialogs/dialogs_indexed_list.h"
#include "lang/lang_keys.h"
@ -21,18 +23,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "chat_helpers/message_field.h"
#include "menu/menu_check_item.h"
#include "menu/menu_send.h"
#include "history/history.h"
#include "history/history_message.h"
#include "history/view/history_view_element.h" // HistoryView::Context.
#include "history/view/history_view_context_menu.h" // CopyPostLink.
#include "history/view/history_view_schedule_box.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
#include "boxes/peer_list_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
@ -49,21 +53,65 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
#include "styles/style_media_player.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace {
class ForwardOptionItem final : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void init(bool checked) {
enableMouseSelecting();
AbstractButton::setDisabled(true);
_checkView = std::make_unique<Ui::ToggleView>(st::defaultToggle, false);
_checkView->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
setIcon(checked ? &st::mediaPlayerMenuCheck : nullptr);
}, lifetime());
_checkView->setLocked(checked);
_checkView->setChecked(checked, anim::type::normal);
AbstractButton::clicks(
) | rpl::start_with_next([=] {
if (!_checkView->isLocked()) {
_checkView->setChecked(
!_checkView->checked(),
anim::type::normal);
}
}, lifetime());
}
not_null<Ui::ToggleView*> checkView() const {
return _checkView.get();
}
private:
std::unique_ptr<Ui::ToggleView> _checkView;
};
} // namespace
class ShareBox::Inner final : public Ui::RpWidget {
public:
Inner(QWidget *parent, const Descriptor &descriptor);
void setPeerSelectedChangedCallback(
Fn<void(PeerData *peer, bool selected)> callback);
void setSubmitRequest(Fn<void()> callback);
void setGoToChatRequest(Fn<void()> callback);
void peerUnselected(not_null<PeerData*> peer);
std::vector<not_null<PeerData*>> selected() const;
bool hasSelected() const;
Fn<void()> goToChatRequest() const;
void peopleReceived(
const QString &query,
@ -75,6 +123,8 @@ public:
void activateSkipPage(int pageHeight, int direction);
void updateFilter(QString filter = QString());
void selectActive();
void tryGoToChat();
void selectionMade();
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
rpl::producer<> searchRequests() const;
@ -154,6 +204,8 @@ private:
base::flat_set<not_null<PeerData*>> _selected;
Fn<void(PeerData *peer, bool selected)> _peerSelectedChangedCallback;
Fn<void()> _submitRequest;
Fn<void()> _goToChatRequest;
bool _searching = false;
QString _lastQuery;
@ -163,6 +215,7 @@ private:
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
rpl::event_stream<> _searchRequests;
bool _hadSelection = false;
};
ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
@ -243,7 +296,30 @@ void ShareBox::prepare() {
_select->resizeToWidth(st::boxWideWidth);
Ui::SendPendingMoveResizeEvents(_select);
setTitle(tr::lng_share_title());
setTitle(_descriptor.forwardOptions.isShare ? tr::lng_share_title() : tr::lng_selected_forward());
const auto forwardOptions = [] {
switch (::Kotato::JsonSettings::GetInt("forward_mode")) {
case 1: return Data::ForwardOptions::NoSenderNames;
case 2: return Data::ForwardOptions::NoNamesAndCaptions;
default: return Data::ForwardOptions::PreserveInfo;
}
}();
const auto groupOptions = [] {
switch (::Kotato::JsonSettings::GetInt("forward_grouping_mode")) {
case 1: return Data::GroupingOptions::RegroupAll;
case 2: return Data::GroupingOptions::Separate;
default: return Data::GroupingOptions::GroupAsIs;
}
}();
_forwardOptions.hasCaptions = _descriptor.forwardOptions.hasCaptions;
_forwardOptions.dropNames = (forwardOptions != Data::ForwardOptions::PreserveInfo);
_forwardOptions.dropCaptions = (forwardOptions == Data::ForwardOptions::NoNamesAndCaptions);
_groupOptions = groupOptions;
updateAdditionalTitle();
_inner = setInnerWidget(
object_ptr<Inner>(this, _descriptor),
@ -266,11 +342,22 @@ void ShareBox::prepare() {
});
_select->setResizedCallback([=] { updateScrollSkips(); });
_select->setSubmittedCallback([=](Qt::KeyboardModifiers modifiers) {
if (modifiers.testFlag(Qt::ControlModifier)
if ((modifiers.testFlag(Qt::ControlModifier)
&& !::Kotato::JsonSettings::GetBool("forward_on_click"))
|| modifiers.testFlag(Qt::MetaModifier)) {
submit({});
} else if (modifiers.testFlag(Qt::ShiftModifier)) {
if (_inner->selected().size() == 1 && _inner->goToChatRequest()) {
_inner->goToChatRequest()();
}
} else {
_inner->selectActive();
if (!modifiers.testFlag(Qt::ControlModifier)
|| ::Kotato::JsonSettings::GetBool("forward_on_click")) {
_inner->tryGoToChat();
} else {
_inner->selectionMade();
}
}
});
rpl::combine(
@ -296,6 +383,17 @@ void ShareBox::prepare() {
innerSelectedChanged(peer, checked);
});
_inner->setSubmitRequest([=] {
submit({});
});
if (_descriptor.goToChatCallback) {
_inner->setGoToChatRequest([=] {
const auto singleChat = _inner->selected().at(0);
goToChat(singleChat);
});
}
Ui::Emoji::SuggestionsController::Init(
getDelegate()->outerContainer(),
_comment->entity(),
@ -431,6 +529,8 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
_inner->activateSkipPage(contentHeight(), -1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->activateSkipPage(contentHeight(), 1);
} else if (e->key() == Qt::Key_Escape && !_select->getQuery().isEmpty()) {
_select->clearQuery();
} else {
BoxContent::keyPressEvent(e);
}
@ -455,34 +555,6 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
}
_menu.emplace(parent, st::popupMenuWithIcons);
if (_descriptor.forwardOptions.show) {
auto createView = [&](rpl::producer<QString> &&text, bool checked) {
auto item = base::make_unique_q<Menu::ItemWithCheck>(
_menu->menu(),
st::popupMenuWithIcons.menu,
new QAction(QString(), _menu->menu()),
nullptr,
nullptr);
std::move(
text
) | rpl::start_with_next([action = item->action()](QString text) {
action->setText(text);
}, item->lifetime());
item->init(checked);
const auto view = item->checkView();
_menu->addAction(std::move(item));
return view;
};
Ui::FillForwardOptions(
std::move(createView),
_descriptor.forwardOptions.messagesCount,
_forwardOptions,
[=](Ui::ForwardOptions value) { _forwardOptions = value; },
_menu->lifetime());
_menu->addSeparator();
}
const auto result = SendMenu::FillSendMenu(
_menu.get(),
sendMenuType(),
@ -497,11 +569,20 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
void ShareBox::createButtons() {
clearButtons();
if (!_descriptor.forwardOptions.isShare && _descriptor.forwardOptions.show) {
const auto moreButton = addTopButton(st::infoTopBarMenu);
moreButton->setClickedCallback([=] { showForwardMenu(moreButton.data()); });
}
if (_hasSelected) {
if (_descriptor.goToChatCallback && _inner->selected().size() == 1) {
const auto singleChat = _inner->selected().at(0);
addLeftButton(rktr("ktg_forward_go_to_chat"), [=] { goToChat(singleChat); });
}
const auto send = addButton(tr::lng_share_confirm(), [=] {
submit({});
});
_forwardOptions.hasCaptions = _descriptor.forwardOptions.hasCaptions;
send->setAcceptBoth();
send->clicks(
@ -516,6 +597,204 @@ void ShareBox::createButtons() {
addButton(tr::lng_cancel(), [=] { closeBox(); });
}
bool ShareBox::showForwardMenu(not_null<Ui::IconButton*> button) {
if (_topMenu) {
_topMenu->hideAnimated(Ui::InnerDropdown::HideOption::IgnoreShow);
return true;
}
_topMenu = base::make_unique_q<Ui::DropdownMenu>(window());
const auto weak = _topMenu.get();
_topMenu->setHiddenCallback([=] {
weak->deleteLater();
if (_topMenu == weak) {
button->setForceRippled(false);
}
});
_topMenu->setShowStartCallback([=] {
if (_topMenu == weak) {
button->setForceRippled(true);
}
});
_topMenu->setHideStartCallback([=] {
if (_topMenu == weak) {
button->setForceRippled(false);
}
});
button->installEventFilter(_topMenu);
auto createView = [&](rpl::producer<QString> &&text, bool checked) {
auto item = base::make_unique_q<ForwardOptionItem>(
_topMenu->menu(),
st::popupMenuWithIcons.menu,
new QAction(QString(), _topMenu->menu()),
nullptr,
nullptr);
std::move(
text
) | rpl::start_with_next([action = item->action()](QString text) {
action->setText(text);
}, item->lifetime());
item->init(checked);
const auto view = item->checkView();
_topMenu->addAction(std::move(item));
return view;
};
const auto forwardOptions = (_forwardOptions.dropCaptions)
? Data::ForwardOptions::NoNamesAndCaptions
: _forwardOptions.dropNames
? Data::ForwardOptions::NoSenderNames
: Data::ForwardOptions::PreserveInfo;
const auto quoted = createView(
rktr("ktg_forward_menu_quoted"),
forwardOptions == Data::ForwardOptions::PreserveInfo);
const auto noNames = createView(
rktr("ktg_forward_menu_unquoted"),
forwardOptions == Data::ForwardOptions::NoSenderNames);
const auto noCaptions = createView(
rktr("ktg_forward_menu_uncaptioned"),
forwardOptions == Data::ForwardOptions::NoNamesAndCaptions);
const auto onForwardOptionChange = [=, this](int mode, bool value) {
if (value) {
quoted->setLocked(mode == 0 && value);
noNames->setLocked(mode == 1 && value);
noCaptions->setLocked(mode == 2 && value);
quoted->setChecked(quoted->isLocked(), anim::type::normal);
noNames->setChecked(noNames->isLocked(), anim::type::normal);
noCaptions->setChecked(noCaptions->isLocked(), anim::type::normal);
_forwardOptions.dropNames = (mode != 0 && value);
_forwardOptions.dropCaptions = (mode == 2 && value);
if (::Kotato::JsonSettings::GetBool("forward_remember_mode")) {
::Kotato::JsonSettings::Set("forward_mode", mode);
::Kotato::JsonSettings::Write();
}
updateAdditionalTitle();
}
};
quoted->checkedChanges(
) | rpl::start_with_next([=](bool value) {
onForwardOptionChange(0, value);
}, _topMenu->lifetime());
noNames->checkedChanges(
) | rpl::start_with_next([=](bool value) {
onForwardOptionChange(1, value);
}, _topMenu->lifetime());
noCaptions->checkedChanges(
) | rpl::start_with_next([=](bool value) {
onForwardOptionChange(2, value);
}, _topMenu->lifetime());
if (_descriptor.forwardOptions.hasMedia) {
_topMenu->addSeparator();
const auto groupAsIs = createView(
rktr("ktg_forward_menu_default_albums"),
_groupOptions == Data::GroupingOptions::GroupAsIs);
const auto groupAll = createView(
rktr("ktg_forward_menu_group_all_media"),
_groupOptions == Data::GroupingOptions::RegroupAll);
const auto groupNone = createView(
rktr("ktg_forward_menu_separate_messages"),
_groupOptions == Data::GroupingOptions::Separate);
const auto onGroupOptionChange = [=, this](int mode, bool value) {
if (value) {
groupAsIs->setLocked(mode == 0 && value);
groupAll->setLocked(mode == 1 && value);
groupNone->setLocked(mode == 2 && value);
groupAsIs->setChecked(groupAsIs->isLocked(), anim::type::normal);
groupAll->setChecked(groupAll->isLocked(), anim::type::normal);
groupNone->setChecked(groupNone->isLocked(), anim::type::normal);
_groupOptions = (mode == 2)
? Data::GroupingOptions::Separate
: (mode == 1)
? Data::GroupingOptions::RegroupAll
: Data::GroupingOptions::GroupAsIs;
if (::Kotato::JsonSettings::GetBool("forward_remember_mode")) {
::Kotato::JsonSettings::Set("forward_grouping_mode", mode);
::Kotato::JsonSettings::Write();
}
updateAdditionalTitle();
}
};
groupAsIs->checkedChanges(
) | rpl::start_with_next([=](bool value) {
onGroupOptionChange(0, value);
}, _topMenu->lifetime());
groupAll->checkedChanges(
) | rpl::start_with_next([=](bool value) {
onGroupOptionChange(1, value);
}, _topMenu->lifetime());
groupNone->checkedChanges(
) | rpl::start_with_next([=](bool value) {
onGroupOptionChange(2, value);
}, _topMenu->lifetime());
}
const auto parentTopLeft = window()->mapToGlobal(QPoint());
const auto buttonTopLeft = button->mapToGlobal(QPoint());
const auto parentRect = QRect(parentTopLeft, window()->size());
const auto buttonRect = QRect(buttonTopLeft, button->size());
_topMenu->move(
buttonRect.x() + buttonRect.width() - _topMenu->width() - parentRect.x(),
buttonRect.y() + buttonRect.height() - parentRect.y() - style::ConvertScale(18));
_topMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
return true;
}
void ShareBox::updateAdditionalTitle() {
if (!_descriptor.forwardOptions.show || _descriptor.forwardOptions.isShare) {
return;
}
QString result;
const auto forwardOptions = (_forwardOptions.dropCaptions)
? Data::ForwardOptions::NoNamesAndCaptions
: _forwardOptions.dropNames
? Data::ForwardOptions::NoSenderNames
: Data::ForwardOptions::PreserveInfo;
switch (forwardOptions) {
case Data::ForwardOptions::NoSenderNames:
result += ktr("ktg_forward_subtitle_unquoted");
break;
case Data::ForwardOptions::NoNamesAndCaptions:
result += ktr("ktg_forward_subtitle_uncaptioned");
break;
}
if (_descriptor.forwardOptions.hasMedia
&& _groupOptions != Data::GroupingOptions::GroupAsIs) {
if (!result.isEmpty()) {
result += ", ";
}
switch (_groupOptions) {
case Data::GroupingOptions::RegroupAll:
result += ktr("ktg_forward_subtitle_group_all_media");
break;
case Data::GroupingOptions::Separate:
result += ktr("ktg_forward_subtitle_separate_messages");
break;
}
}
setAdditionalTitle(rpl::single(result));
}
void ShareBox::applyFilterUpdate(const QString &query) {
scrollToY(0);
_inner->updateFilter(query);
@ -555,7 +834,8 @@ void ShareBox::submit(Api::SendOptions options) {
_inner->selected(),
_comment->entity()->getTextWithAppliedMarkdown(),
options,
forwardOptions);
forwardOptions,
_groupOptions);
}
}
@ -581,14 +861,29 @@ void ShareBox::copyLink() {
}
}
void ShareBox::goToChat(not_null<PeerData*> peer) {
if (_descriptor.goToChatCallback) {
const auto forwardOptions = (_forwardOptions.hasCaptions
&& _forwardOptions.dropCaptions)
? Data::ForwardOptions::NoNamesAndCaptions
: _forwardOptions.dropNames
? Data::ForwardOptions::NoSenderNames
: Data::ForwardOptions::PreserveInfo;
_descriptor.goToChatCallback(
peer,
forwardOptions,
_groupOptions);
}
}
void ShareBox::selectedChanged() {
auto hasSelected = _inner->hasSelected();
if (_hasSelected != hasSelected) {
_hasSelected = hasSelected;
createButtons();
_comment->toggle(_hasSelected, anim::type::normal);
_comment->resizeToWidth(st::boxWideWidth);
}
createButtons();
update();
}
@ -1005,6 +1300,11 @@ void ShareBox::Inner::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton) {
updateUpon(e->pos());
changeCheckState(getChatAtIndex(_upon));
if (!e->modifiers().testFlag(Qt::ControlModifier)) {
tryGoToChat();
} else {
selectionMade();
}
}
}
@ -1012,6 +1312,25 @@ void ShareBox::Inner::selectActive() {
changeCheckState(getChatAtIndex(_active > 0 ? _active : 0));
}
void ShareBox::Inner::tryGoToChat() {
if (!_hadSelection
&& _selected.size() == 1) {
if (_submitRequest && _selected.front()->isSelf()) {
_submitRequest();
} else if (_goToChatRequest
&& ::Kotato::JsonSettings::GetBool("forward_on_click")) {
_goToChatRequest();
}
_hadSelection = true;
}
}
void ShareBox::Inner::selectionMade() {
if (!_hadSelection) {
_hadSelection = true;
}
}
void ShareBox::Inner::resizeEvent(QResizeEvent *e) {
_columnSkip = (width() - _columnCount * _st.item.checkbox.imageRadius * 2) / float64(_columnCount + 1);
_rowWidthReal = _st.item.checkbox.imageRadius * 2 + _columnSkip;
@ -1052,6 +1371,14 @@ void ShareBox::Inner::setPeerSelectedChangedCallback(
_peerSelectedChangedCallback = std::move(callback);
}
void ShareBox::Inner::setSubmitRequest(Fn<void()> callback) {
_submitRequest = std::move(callback);
}
void ShareBox::Inner::setGoToChatRequest(Fn<void()> callback) {
_goToChatRequest = std::move(callback);
}
void ShareBox::Inner::changePeerCheckState(
not_null<Chat*> chat,
bool checked,
@ -1073,6 +1400,10 @@ bool ShareBox::Inner::hasSelected() const {
return _selected.size();
}
Fn<void()> ShareBox::Inner::goToChatRequest() const {
return _goToChatRequest;
}
void ShareBox::Inner::updateFilter(QString filter) {
_lastQuery = filter.toLower().trimmed();
@ -1220,6 +1551,10 @@ QString AppendShareGameScoreUrl(
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
Window::ShowForwardMessagesBox(
controller,
item->history()->owner().itemOrItsGroup(item));
/*
struct ShareData {
ShareData(not_null<PeerData*> peer, MessageIdsList &&ids)
: peer(peer)
@ -1421,6 +1756,7 @@ void FastShareMessage(
},
}),
Ui::LayerOption::CloseOther);
*/
}
void ShareGameScoreByHash(

View file

@ -44,11 +44,13 @@ class IndexedList;
namespace Data {
enum class ForwardOptions;
enum class GroupingOptions;
} // namespace Data
namespace Ui {
class MultiSelect;
class InputField;
class DropdownMenu;
struct ScrollToRequest;
template <typename Widget>
class SlideWrap;
@ -73,14 +75,20 @@ public:
std::vector<not_null<PeerData*>>&&,
TextWithTags&&,
Api::SendOptions,
Data::ForwardOptions option)>;
Data::ForwardOptions option,
Data::GroupingOptions groupOption)>;
using FilterCallback = Fn<bool(PeerData*)>;
using GoToChatCallback = Fn<void(
PeerData*,
Data::ForwardOptions option,
Data::GroupingOptions groupOption)>;
struct Descriptor {
not_null<Main::Session*> session;
CopyCallback copyCallback;
SubmitCallback submitCallback;
FilterCallback filterCallback;
GoToChatCallback goToChatCallback;
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
rpl::producer<QString> copyLinkText;
const style::MultiSelect *stMultiSelect = nullptr;
@ -91,6 +99,8 @@ public:
int messagesCount = 0;
bool show = false;
bool hasCaptions = false;
bool hasMedia = false;
bool isShare = true;
} forwardOptions;
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
};
@ -111,6 +121,7 @@ private:
void submitSilent();
void submitScheduled();
void copyLink();
void goToChat(not_null<PeerData*> peer);
bool searchByUsername(bool useCache = false);
SendMenu::Type sendMenuType() const;
@ -120,6 +131,8 @@ private:
void applyFilterUpdate(const QString &query);
void selectedChanged();
void createButtons();
bool showForwardMenu(not_null<Ui::IconButton*> button);
void updateAdditionalTitle();
int getTopScrollSkip() const;
int getBottomScrollSkip() const;
int contentHeight() const;
@ -145,7 +158,9 @@ private:
object_ptr<Ui::RpWidget> _bottomWidget;
base::unique_qptr<Ui::PopupMenu> _menu;
base::unique_qptr<Ui::DropdownMenu> _topMenu;
Ui::ForwardOptions _forwardOptions;
Data::GroupingOptions _groupOptions;
class Inner;
QPointer<Inner> _inner;

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "boxes/share_box.h"
#include "history/view/history_view_schedule_box.h"
#include "history/history.h"
#include "history/history_message.h" // GetErrorTextForSending.
#include "data/data_histories.h"
#include "data/data_session.h"
@ -135,7 +136,8 @@ object_ptr<ShareBox> ShareInviteLinkBox(
std::vector<not_null<PeerData*>> &&result,
TextWithTags &&comment,
Api::SendOptions options,
Data::ForwardOptions) {
Data::ForwardOptions,
Data::GroupingOptions) {
if (*sending || result.empty()) {
return;
}

View file

@ -1351,6 +1351,10 @@ void DocumentData::refreshFileReference(const QByteArray &value) {
_videoThumbnail.location.refreshFileReference(value);
}
QString DocumentData::url() const {
return _url;
}
QString DocumentData::filename() const {
return _filename;
}

View file

@ -243,6 +243,7 @@ public:
// to (this) received from the server "same" document.
void collectLocalData(not_null<DocumentData*> local);
[[nodiscard]] QString url() const;
[[nodiscard]] QString filename() const;
[[nodiscard]] QString mimeString() const;
[[nodiscard]] bool hasMimeType(QLatin1String mime) const;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_media_types.h"
#include "kotato/kotato_lang.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_location_manager.h"
@ -331,6 +332,10 @@ PollData *Media::poll() const {
return nullptr;
}
const LocationPoint *Media::geoPoint() const {
return nullptr;
}
bool Media::uploading() const {
return false;
}
@ -391,7 +396,7 @@ bool Media::forceForwardedInfo() const {
return false;
}
QString Media::errorTextForForward(not_null<PeerData*> peer) const {
QString Media::errorTextForForward(not_null<PeerData*> peer, bool unquoted) const {
return QString();
}
@ -580,7 +585,7 @@ bool MediaPhoto::allowsEditMedia() const {
return true;
}
QString MediaPhoto::errorTextForForward(not_null<PeerData*> peer) const {
QString MediaPhoto::errorTextForForward(not_null<PeerData*> peer, bool unquoted) const {
return Data::RestrictionError(
peer,
ChatRestriction::SendMedia
@ -908,7 +913,7 @@ bool MediaFile::dropForwardedInfo() const {
return _document->isSong();
}
QString MediaFile::errorTextForForward(not_null<PeerData*> peer) const {
QString MediaFile::errorTextForForward(not_null<PeerData*> peer, bool unquoted) const {
if (const auto sticker = _document->sticker()) {
if (const auto error = Data::RestrictionError(
peer,
@ -1134,6 +1139,10 @@ Data::CloudImage *MediaLocation::location() const {
return _location;
}
const LocationPoint *MediaLocation::geoPoint() const {
return &_point;
}
ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
const auto type = tr::lng_maps_point(tr::now);
const auto hasMiniImages = false;
@ -1432,7 +1441,7 @@ TextForMimeData MediaGame::clipboardText() const {
return TextForMimeData();
}
QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
QString MediaGame::errorTextForForward(not_null<PeerData*> peer, bool unquoted) const {
return Data::RestrictionError(
peer,
ChatRestriction::SendGames
@ -1582,10 +1591,13 @@ TextForMimeData MediaPoll::clipboardText() const {
return TextForMimeData::Simple(text);
}
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer) const {
QString MediaPoll::errorTextForForward(not_null<PeerData*> peer, bool unquoted) const {
if (_poll->publicVotes() && peer->isChannel() && !peer->isMegagroup()) {
return tr::lng_restricted_send_public_polls(tr::now);
}
if (unquoted && _poll->quiz() && !_poll->voted() && !_poll->closed()) {
return ktr("ktg_forward_quiz_unquoted");
}
return Data::RestrictionError(
peer,
ChatRestriction::SendPolls

View file

@ -90,6 +90,7 @@ public:
virtual const Invoice *invoice() const;
virtual Data::CloudImage *location() const;
virtual PollData *poll() const;
virtual const LocationPoint *geoPoint() const;
virtual bool uploading() const;
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
@ -111,7 +112,7 @@ public:
virtual bool forwardedBecomesUnread() const;
virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const;
virtual QString errorTextForForward(not_null<PeerData*> peer) const;
virtual QString errorTextForForward(not_null<PeerData*> peer, bool unquoted = false) const;
[[nodiscard]] virtual bool consumeMessageText(
const TextWithEntities &text);
@ -166,7 +167,7 @@ public:
TextForMimeData clipboardText() const override;
bool allowsEditCaption() const override;
bool allowsEditMedia() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
QString errorTextForForward(not_null<PeerData*> peer, bool unquoted = false) const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
@ -207,7 +208,7 @@ public:
bool allowsEditMedia() const override;
bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
QString errorTextForForward(not_null<PeerData*> peer, bool unquoted = false) const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
@ -266,6 +267,7 @@ public:
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
Data::CloudImage *location() const override;
const LocationPoint *geoPoint() const override;
ItemPreview toPreview(ToPreviewOptions options) const override;
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
@ -366,7 +368,7 @@ public:
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
QString errorTextForForward(not_null<PeerData*> peer, bool unquoted = false) const override;
bool dropForwardedInfo() const override;
bool consumeMessageText(const TextWithEntities &text) override;
@ -428,7 +430,7 @@ public:
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
QString errorTextForForward(not_null<PeerData*> peer, bool unquoted = false) const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;

View file

@ -352,6 +352,7 @@ Data::ResolvedForwardDraft History::resolveForwardDraft(
return Data::ResolvedForwardDraft{
.items = owner().idsToItems(draft.ids),
.options = draft.options,
.groupOptions = draft.groupOptions,
};
}
@ -361,6 +362,7 @@ Data::ResolvedForwardDraft History::resolveForwardDraft() {
setForwardDraft({
.ids = owner().itemsToIds(result.items),
.options = result.options,
.groupOptions = result.groupOptions,
});
}
return result;
@ -615,7 +617,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
const QString &postAuthor,
not_null<DocumentData*> document,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup) {
HistoryMessageMarkupData &&markup,
uint64 newGroupId) {
return addNewItem(
makeMessage(
id,
@ -627,7 +630,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
postAuthor,
document,
caption,
std::move(markup)),
std::move(markup),
newGroupId),
true);
}
@ -641,7 +645,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
const QString &postAuthor,
not_null<PhotoData*> photo,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup) {
HistoryMessageMarkupData &&markup,
uint64 newGroupId) {
return addNewItem(
makeMessage(
id,
@ -653,7 +658,8 @@ not_null<HistoryItem*> History::addNewLocalMessage(
postAuthor,
photo,
caption,
std::move(markup)),
std::move(markup),
newGroupId),
true);
}

View file

@ -51,14 +51,22 @@ enum class ForwardOptions {
NoNamesAndCaptions,
};
enum class GroupingOptions {
GroupAsIs,
RegroupAll,
Separate,
};
struct ForwardDraft {
MessageIdsList ids;
ForwardOptions options = ForwardOptions::PreserveInfo;
GroupingOptions groupOptions = GroupingOptions::GroupAsIs;
};
struct ResolvedForwardDraft {
HistoryItemsList items;
ForwardOptions options = ForwardOptions::PreserveInfo;
GroupingOptions groupOptions = GroupingOptions::GroupAsIs;
};
} // namespace Data
@ -193,7 +201,8 @@ public:
const QString &postAuthor,
not_null<DocumentData*> document,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup);
HistoryMessageMarkupData &&markup,
uint64 newGroupId = 0);
not_null<HistoryItem*> addNewLocalMessage(
MsgId id,
MessageFlags flags,
@ -204,7 +213,8 @@ public:
const QString &postAuthor,
not_null<PhotoData*> photo,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup);
HistoryMessageMarkupData &&markup,
uint64 newGroupId = 0);
not_null<HistoryItem*> addNewLocalMessage(
MsgId id,
MessageFlags flags,

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h"
#include "ui/item_text_options.h"
#include "core/ui_integration.h"
#include "window/window_peer_menu.h"
#include "storage/storage_shared_media.h"
#include "mtproto/mtproto_config.h"
#include "data/notify/data_notify_settings.h"
@ -112,14 +113,15 @@ QString GetErrorTextForSending(
not_null<PeerData*> peer,
const HistoryItemsList &items,
const TextWithTags &comment,
bool ignoreSlowmodeCountdown) {
bool ignoreSlowmodeCountdown,
bool unquoted) {
if (!peer->canWrite()) {
return tr::lng_forward_cant(tr::now);
}
for (const auto &item : items) {
if (const auto media = item->media()) {
const auto error = media->errorTextForForward(peer);
const auto error = media->errorTextForForward(peer, unquoted);
if (!error.isEmpty() && error != qstr("skip")) {
return error;
}
@ -235,8 +237,9 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
QString GetErrorTextForSending(
not_null<PeerData*> peer,
const HistoryItemsList &items,
bool ignoreSlowmodeCountdown) {
return GetErrorTextForSending(peer, items, {}, ignoreSlowmodeCountdown);
bool ignoreSlowmodeCountdown,
bool unquoted) {
return GetErrorTextForSending(peer, items, {}, ignoreSlowmodeCountdown, unquoted);
}
TextWithEntities DropCustomEmoji(TextWithEntities text) {
@ -531,7 +534,8 @@ HistoryMessage::HistoryMessage(
const QString &postAuthor,
not_null<DocumentData*> document,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup)
HistoryMessageMarkupData &&markup,
uint64 newGroupId)
: HistoryItem(
history,
id,
@ -551,6 +555,11 @@ HistoryMessage::HistoryMessage(
document,
skipPremiumEffect);
setText(caption);
if (newGroupId) {
setGroupId(
MessageGroupId::FromRaw(history->peer->id, newGroupId));
}
}
HistoryMessage::HistoryMessage(
@ -564,7 +573,8 @@ HistoryMessage::HistoryMessage(
const QString &postAuthor,
not_null<PhotoData*> photo,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup)
HistoryMessageMarkupData &&markup,
uint64 newGroupId)
: HistoryItem(
history,
id,
@ -580,6 +590,11 @@ HistoryMessage::HistoryMessage(
_media = std::make_unique<Data::MediaPhoto>(this, photo);
setText(caption);
if (newGroupId) {
setGroupId(
MessageGroupId::FromRaw(history->peer->id, newGroupId));
}
}
HistoryMessage::HistoryMessage(

View file

@ -43,12 +43,14 @@ void RequestDependentMessageData(
[[nodiscard]] QString GetErrorTextForSending(
not_null<PeerData*> peer,
const HistoryItemsList &items,
bool ignoreSlowmodeCountdown = false);
bool ignoreSlowmodeCountdown = false,
bool unquoted = false);
[[nodiscard]] QString GetErrorTextForSending(
not_null<PeerData*> peer,
const HistoryItemsList &items,
const TextWithTags &comment,
bool ignoreSlowmodeCountdown = false);
bool ignoreSlowmodeCountdown = false,
bool unquoted = false);
[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text);
class HistoryMessage final : public HistoryItem {
@ -95,7 +97,8 @@ public:
const QString &postAuthor,
not_null<DocumentData*> document,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup); // local document
HistoryMessageMarkupData &&markup,
uint64 newGroupId = 0); // local document
HistoryMessage(
not_null<History*> history,
MsgId id,
@ -107,7 +110,8 @@ public:
const QString &postAuthor,
not_null<PhotoData*> photo,
const TextWithEntities &caption,
HistoryMessageMarkupData &&markup); // local photo
HistoryMessageMarkupData &&markup,
uint64 newGroupId = 0); // local photo
HistoryMessage(
not_null<History*> history,
MsgId id,

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_widget.h"
#include "kotato/kotato_settings.h"
#include "kotato/kotato_lang.h"
#include "api/api_editing.h"
#include "api/api_bot.h"
#include "api/api_chat_participants.h"
@ -40,7 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/message_sending_animation_controller.h"
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
#include "ui/text/format_values.h"
#include "ui/chat/forward_options_box.h"
//#include "ui/chat/forward_options_box.h"
#include "ui/chat/message_bar.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "ui/chat/choose_send_as.h"
@ -6087,12 +6088,61 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
st::historyReplyHeight).contains(e->pos());
if (_replyForwardPressed && !_fieldBarCancel->isHidden()) {
updateField();
} else if (_inReplyEditForward) {
if (readyToForward()) {
if (_toForward.items.empty() || e->button() != Qt::LeftButton) {
return;
}
const auto draft = std::move(_toForward);
session().data().cancelForwarding(_history);
auto list = session().data().itemsToIds(draft.items);
Window::ShowForwardMessagesBox(controller(), {
.ids = session().data().itemsToIds(draft.items),
.options = draft.options,
.groupOptions = draft.groupOptions,
});
} else {
Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId());
}
}
}
void HistoryWidget::contextMenuEvent(QContextMenuEvent *e) {
if (_menu) {
return;
}
const auto hasSecondLayer = (_editMsgId
|| _replyToId
|| readyToForward()
|| _kbReplyTo);
_replyForwardPressed = hasSecondLayer && QRect(
0,
_field->y() - st::historySendPadding - st::historyReplyHeight,
st::historyReplySkip,
st::historyReplyHeight).contains(e->pos());
if (_replyForwardPressed && !_fieldBarCancel->isHidden()) {
return;
} else if (_inReplyEditForward) {
if (readyToForward()) {
using Options = Data::ForwardOptions;
const auto now = _toForward.options;
using GroupingOptions = Data::GroupingOptions;
const auto count = _toForward.items.size();
const auto dropNames = (now != Options::PreserveInfo);
const auto hasMediaToGroup = [&] {
if (count > 1) {
auto grouppableMediaCount = 0;
for (const auto item : _toForward.items) {
if (item->media() && item->media()->canBeGrouped()) {
grouppableMediaCount++;
} else {
grouppableMediaCount = 0;
}
if (grouppableMediaCount > 1) {
return true;
}
}
}
return false;
}();
const auto hasCaptions = [&] {
for (const auto item : _toForward.items) {
if (const auto media = item->media()) {
@ -6104,71 +6154,76 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
}
return false;
}();
const auto hasOnlyForcedForwardedInfo = [&] {
if (hasCaptions) {
return false;
}
for (const auto item : _toForward.items) {
if (const auto media = item->media()) {
if (!media->forceForwardedInfo()) {
return false;
}
} else {
return false;
}
}
return true;
}();
const auto dropCaptions = (now == Options::NoNamesAndCaptions);
const auto weak = Ui::MakeWeak(this);
const auto changeRecipient = crl::guard(weak, [=] {
if (_toForward.items.empty()) {
return;
}
const auto draft = std::move(_toForward);
session().data().cancelForwarding(_history);
auto list = session().data().itemsToIds(draft.items);
Window::ShowForwardMessagesBox(controller(), {
.ids = session().data().itemsToIds(draft.items),
.options = draft.options,
});
});
if (hasOnlyForcedForwardedInfo) {
changeRecipient();
return;
}
const auto optionsChanged = crl::guard(weak, [=](
Ui::ForwardOptions options) {
const auto newOptions = (options.hasCaptions
&& options.dropCaptions)
? Options::NoNamesAndCaptions
: options.dropNames
? Options::NoSenderNames
: Options::PreserveInfo;
const auto addForwardOption = [=](
Options newOptions,
const QString &langKey,
int settingsKey) {
if (_history && _toForward.options != newOptions) {
_toForward.options = newOptions;
_history->setForwardDraft({
.ids = session().data().itemsToIds(_toForward.items),
.options = newOptions,
_menu->addAction(ktr(langKey), [=] {
const auto error = GetErrorTextForSending(
_history->peer,
_toForward.items,
true,
newOptions != Options::PreserveInfo);
if (!error.isEmpty()) {
Ui::ShowMultilineToast({
.text = { error }
});
return;
}
_toForward.options = newOptions;
_history->setForwardDraft({
.ids = session().data().itemsToIds(_toForward.items),
.options = newOptions,
.groupOptions = _toForward.groupOptions,
});
updateField();
if (::Kotato::JsonSettings::GetBool("forward_remember_mode")) {
::Kotato::JsonSettings::Set("forward_mode", settingsKey);
::Kotato::JsonSettings::Write();
}
});
updateField();
}
});
controller()->show(Box(
Ui::ForwardOptionsBox,
count,
Ui::ForwardOptions{
.dropNames = dropNames,
.hasCaptions = hasCaptions,
.dropCaptions = dropCaptions,
},
optionsChanged,
changeRecipient));
} else {
controller()->showPeerHistory(
_peer,
Window::SectionShow::Way::Forward,
_editMsgId ? _editMsgId : replyToId());
};
_menu = base::make_unique_q<Ui::PopupMenu>(this);
addForwardOption(Options::PreserveInfo, "ktg_forward_menu_quoted", 0);
addForwardOption(Options::NoSenderNames, "ktg_forward_menu_unquoted", 1);
if (hasCaptions) {
addForwardOption(Options::NoNamesAndCaptions, "ktg_forward_menu_uncaptioned", 2);
}
if (hasMediaToGroup && count > 1) {
const auto addGroupingOption = [=](
GroupingOptions newOptions,
const QString &langKey,
int settingsKey) {
if (_history && _toForward.groupOptions != newOptions) {
_menu->addAction(ktr(langKey), [=] {
_toForward.groupOptions = newOptions;
_history->setForwardDraft({
.ids = session().data().itemsToIds(_toForward.items),
.options = _toForward.options,
.groupOptions = newOptions,
});
updateForwardingTexts();
updateField();
if (::Kotato::JsonSettings::GetBool("forward_remember_mode")) {
::Kotato::JsonSettings::Set("forward_grouping_mode", settingsKey);
::Kotato::JsonSettings::Write();
}
});
}
};
_menu->addSeparator();
addGroupingOption(GroupingOptions::GroupAsIs, "ktg_forward_menu_default_albums", 0);
addGroupingOption(GroupingOptions::RegroupAll, "ktg_forward_menu_group_all_media", 1);
addGroupingOption(GroupingOptions::Separate, "ktg_forward_menu_separate_messages", 2);
}
_menu->popup(QCursor::pos());
}
}
}
@ -6780,10 +6835,17 @@ bool HistoryWidget::sendExistingDocument(
return false;
}
Api::SendExistingDocument(
Api::MessageToSend(prepareSendAction(options)),
document,
localId);
if (document->hasRemoteLocation()) {
Api::SendExistingDocument(
Api::MessageToSend(prepareSendAction(options)),
document,
localId);
} else {
Api::SendWebDocument(
Api::MessageToSend(prepareSendAction(options)),
document,
localId);
}
if (_fieldAutocomplete->stickersShown()) {
clearFieldText();
@ -7588,6 +7650,8 @@ void HistoryWidget::updateForwardingTexts() {
auto insertedPeers = base::flat_set<not_null<PeerData*>>();
auto insertedNames = base::flat_set<QString>();
auto fullname = QString();
auto hasMediaToGroup = false;
auto grouppableMediaCount = 0;
auto names = std::vector<QString>();
names.reserve(_toForward.items.size());
for (const auto item : _toForward.items) {
@ -7608,8 +7672,20 @@ void HistoryWidget::updateForwardingTexts() {
} else {
Unexpected("Corrupt forwarded information in message.");
}
if (!hasMediaToGroup) {
if (item->media() && item->media()->canBeGrouped()) {
grouppableMediaCount++;
} else {
grouppableMediaCount = 0;
}
if (grouppableMediaCount > 1) {
hasMediaToGroup = true;
}
}
}
if (!keepNames) {
if (!keepCaptions) {
from = ktr("ktg_forward_sender_names_and_captions_removed");
} else if (!keepNames) {
from = tr::lng_forward_sender_names_removed(tr::now);
} else if (names.size() > 2) {
from = tr::lng_forwarding_from(tr::now, lt_count, names.size() - 1, lt_user, names[0]);
@ -7634,8 +7710,19 @@ void HistoryWidget::updateForwardingTexts() {
text = DropCustomEmoji(std::move(text));
}
} else {
text = Ui::Text::PlainLink(
tr::lng_forward_messages(tr::now, lt_count, count));
auto forwardText = tr::lng_forward_messages(tr::now, lt_count, count);
switch (_toForward.groupOptions) {
case Data::GroupingOptions::RegroupAll:
forwardText += ", " + ktr("ktg_forward_subtitle_group_all_media");
break;
case Data::GroupingOptions::Separate:
forwardText += ", " + ktr("ktg_forward_subtitle_separate_messages");
break;
}
text = Ui::Text::PlainLink(forwardText);
}
}
_toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions());

View file

@ -286,6 +286,7 @@ protected:
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
@ -793,6 +794,7 @@ private:
int _topDelta = 0;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<> _cancelRequests;
};

View file

@ -406,6 +406,12 @@ const std::map<QString, Definition, std::greater<QString>> DefinitionMap {
{ "profile_top_mute", {
.type = SettingType::BoolSetting,
.defaultValue = false, }},
{ "forward_retain_selection", {
.type = SettingType::BoolSetting,
.defaultValue = false, }},
{ "forward_on_click", {
.type = SettingType::BoolSetting,
.defaultValue = false, }},
{ "auto_scroll_unfocused", {
.type = SettingType::BoolSetting,
.defaultValue = false, }},
@ -415,6 +421,20 @@ const std::map<QString, Definition, std::greater<QString>> DefinitionMap {
{ "telegram_sites_autologin", {
.type = SettingType::BoolSetting,
.defaultValue = true, }},
{ "forward_remember_mode", {
.type = SettingType::BoolSetting,
.defaultValue = true, }},
{ "forward_mode", {
.type = SettingType::IntSetting,
.defaultValue = 0,
.limitHandler = IntLimit(0, 2), }},
{ "forward_grouping_mode", {
.type = SettingType::IntSetting,
.defaultValue = 0,
.limitHandler = IntLimit(0, 2), }},
{ "forward_force_old_unquoted", {
.type = SettingType::BoolSetting,
.defaultValue = false, }},
};
using OldOptionKey = QString;

View file

@ -45,6 +45,54 @@ namespace Settings {
namespace {
QString ForwardModeLabel(int mode) {
switch (mode) {
case 0:
return ktr("ktg_forward_mode_quoted");
case 1:
return ktr("ktg_forward_mode_unquoted");
case 2:
return ktr("ktg_forward_mode_uncaptioned");
default:
Unexpected("Boost in Settings::ForwardModeLabel.");
}
return QString();
}
QString GroupingModeLabel(int mode) {
switch (mode) {
case 0:
return ktr("ktg_forward_grouping_mode_preserve_albums");
case 1:
return ktr("ktg_forward_grouping_mode_regroup");
case 2:
return ktr("ktg_forward_grouping_mode_separate");
default:
Unexpected("Boost in Settings::GroupingModeLabel.");
}
return QString();
}
QString GroupingModeDescription(int mode) {
switch (mode) {
case 0:
case 2:
return QString();
case 1:
return ktr("ktg_forward_grouping_mode_regroup_desc");
default:
Unexpected("Boost in Settings::GroupingModeLabel.");
}
return QString();
}
QString NetBoostLabel(int boost) {
switch (boost) {
@ -382,6 +430,71 @@ void SetupKotatoForward(not_null<Ui::VerticalLayout*> container) {
AddSkip(container);
AddSubsectionTitle(container, rktr("ktg_settings_forward"));
SettingsMenuJsonSwitch(ktg_forward_remember_mode, forward_remember_mode);
auto forwardModeText = rpl::single(
ForwardModeLabel(::Kotato::JsonSettings::GetInt("forward_mode"))
) | rpl::then(
::Kotato::JsonSettings::Events(
"forward_mode"
) | rpl::map([] {
return ForwardModeLabel(::Kotato::JsonSettings::GetInt("forward_mode"));
})
);
AddButtonWithLabel(
container,
rktr("ktg_forward_mode"),
forwardModeText,
st::settingsButtonNoIcon
)->addClickHandler([=] {
Ui::show(Box<::Kotato::RadioBox>(
ktr("ktg_forward_mode"),
::Kotato::JsonSettings::GetInt("forward_mode"),
3,
ForwardModeLabel,
[=] (int value) {
::Kotato::JsonSettings::Set("forward_mode", value);
::Kotato::JsonSettings::Write();
}, false));
});
auto forwardGroupingModeText = rpl::single(
GroupingModeLabel(::Kotato::JsonSettings::GetInt("forward_grouping_mode"))
) | rpl::then(
::Kotato::JsonSettings::Events(
"forward_grouping_mode"
) | rpl::map([] {
return GroupingModeLabel(::Kotato::JsonSettings::GetInt("forward_grouping_mode"));
})
);
AddButtonWithLabel(
container,
rktr("ktg_forward_grouping_mode"),
forwardGroupingModeText,
st::settingsButtonNoIcon
)->addClickHandler([=] {
Ui::show(Box<::Kotato::RadioBox>(
ktr("ktg_forward_grouping_mode"),
::Kotato::JsonSettings::GetInt("forward_grouping_mode"),
3,
GroupingModeLabel,
GroupingModeDescription,
[=] (int value) {
::Kotato::JsonSettings::Set("forward_grouping_mode", value);
::Kotato::JsonSettings::Write();
}, false));
});
SettingsMenuJsonSwitch(ktg_forward_force_old_unquoted, forward_force_old_unquoted);
AddSkip(container);
AddDividerText(container, rktr("ktg_forward_force_old_unquoted_desc"));
AddSkip(container);
SettingsMenuJsonSwitch(ktg_settings_forward_retain_selection, forward_retain_selection);
SettingsMenuJsonSwitch(ktg_settings_forward_chat_on_click, forward_on_click);
AddSkip(container);
AddDividerText(container, rktr("ktg_settings_forward_chat_on_click_description"));

View file

@ -506,7 +506,8 @@ bool MainWidget::setForwardDraft(PeerId peerId, Data::ForwardDraft &&draft) {
const auto error = GetErrorTextForSending(
peer,
session().data().idsToItems(draft.ids),
true);
true,
draft.options != Data::ForwardOptions::PreserveInfo);
if (!error.isEmpty()) {
Ui::show(Ui::MakeInformBox(error), Ui::LayerOption::KeepOther);
return false;

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/add_bot_to_chat_box.h"
#include "boxes/peers/add_participants_box.h"
#include "boxes/peers/edit_contact_box.h"
#include "boxes/share_box.h"
#include "ui/boxes/report_box.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h"
@ -43,11 +44,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_blocked_peers.h"
#include "api/api_chat_filters.h"
#include "api/api_polls.h"
#include "api/api_sending.h"
#include "api/api_updates.h"
#include "api/api_text_entities.h"
#include "mtproto/mtproto_config.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_message.h" // GetErrorTextForSending.
#include "history/history_widget.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_context_menu.h"
#include "window/window_adaptive.h" // Adaptive::isThreeColumn
#include "window/window_session_controller.h"
@ -65,9 +70,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_drafts.h"
#include "data/data_user.h"
#include "data/data_game.h"
#include "data/data_web_page.h"
#include "data/data_scheduled_messages.h"
#include "data/data_histories.h"
#include "data/data_chat_filters.h"
#include "data/data_file_origin.h"
#include "dialogs/dialogs_key.h"
#include "core/application.h"
#include "export/export_manager.h"
@ -77,6 +85,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_window.h" // st::windowMinWidth
#include "styles/style_menu_icons.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
#include <QAction>
namespace Window {
@ -1215,6 +1225,7 @@ void BlockSenderFromRepliesBox(
Window::ClearReply{ id });
}
/*
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
Data::ForwardDraft &&draft,
@ -1267,15 +1278,244 @@ QPointer<Ui::BoxContent> ShowForwardMessagesBox(
std::move(initBox)), Ui::LayerOption::KeepOther);
return weak->data();
}
*/
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
Data::ForwardDraft &&draft,
FnMut<void()> &&successCallback) {
struct ShareData {
ShareData(not_null<PeerData*> peer, Data::ForwardDraft &&fwdDraft, FnMut<void()> &&callback)
: peer(peer)
, draft(std::move(fwdDraft))
, submitCallback(std::move(callback)) {
}
not_null<PeerData*> peer;
Data::ForwardDraft draft;
int requestsLeft = 0;
FnMut<void()> submitCallback;
};
const auto weak = std::make_shared<QPointer<ShareBox>>();
const auto firstItem = navigation->session().data().message(draft.ids[0]);
const auto history = firstItem->history();
const auto owner = &history->owner();
const auto session = &history->session();
const auto isGame = firstItem->getMessageBot()
&& firstItem->media()
&& (firstItem->media()->game() != nullptr);
const auto items = history->owner().idsToItems(draft.ids);
const auto hasCaptions = ranges::any_of(items, [](auto item) {
return item->media()
&& !item->originalText().text.isEmpty()
&& item->media()->allowsEditCaption();
});
const auto hasOnlyForcedForwardedInfo = hasCaptions
? false
: ranges::all_of(items, [](auto item) {
return item->media() && item->media()->forceForwardedInfo();
});
const auto canCopyLink = [=] {
if (draft.ids.size() > 10) {
return false;
}
const auto groupId = firstItem->groupId();
for (const auto item : items) {
if (groupId != item->groupId()) {
return false;
}
}
return (firstItem->hasDirectLink() || isGame);
}();
const auto hasMediaForGrouping = [=] {
if (draft.ids.size() > 1) {
auto grouppableMediaCount = 0;
for (const auto item : items) {
if (item->media() && item->media()->canBeGrouped()) {
grouppableMediaCount++;
} else {
grouppableMediaCount = 0;
}
if (grouppableMediaCount > 1) {
return true;
}
}
}
return false;
}();
const auto data = std::make_shared<ShareData>(history->peer, std::move(draft), std::move(successCallback));
auto copyCallback = [=]() {
if (const auto item = owner->message(data->draft.ids[0])) {
if (item->hasDirectLink()) {
HistoryView::CopyPostLink(
navigation->parentController(),
item->fullId(),
HistoryView::Context::History);
} else if (const auto bot = item->getMessageBot()) {
if (const auto media = item->media()) {
if (const auto game = media->game()) {
const auto link = session->createInternalLinkFull(
bot->username
+ qsl("?game=")
+ game->shortName);
QGuiApplication::clipboard()->setText(link);
Ui::Toast::Show(tr::lng_share_game_link_copied(tr::now));
}
}
}
}
};
auto submitCallback = [=](
std::vector<not_null<PeerData*>> &&result,
TextWithTags &&comment,
Api::SendOptions options,
Data::ForwardOptions forwardOptions,
Data::GroupingOptions groupOptions) {
if (data->requestsLeft > 0) {
return; // Share clicked already.
}
auto items = history->owner().idsToItems(data->draft.ids);
if (items.empty() || result.empty()) {
return;
}
const auto error = [&] {
for (const auto peer : result) {
const auto error = GetErrorTextForSending(
peer,
items,
comment,
false, /* ignoreSlowmodeCountdown */
forwardOptions != Data::ForwardOptions::PreserveInfo);
if (!error.isEmpty()) {
return std::make_pair(error, peer);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->name())
).append("\n\n");
}
text.append(error.first);
Ui::show(
Ui::MakeInformBox(text),
Ui::LayerOption::KeepOther);
return;
}
const auto checkAndClose = [=] {
data->requestsLeft--;
if (!data->requestsLeft) {
Ui::Toast::Show(tr::lng_share_done(tr::now));
Ui::hideLayer();
}
};
auto &api = owner->session().api();
data->draft.options = forwardOptions;
data->draft.groupOptions = groupOptions;
for (const auto peer : result) {
const auto history = owner->history(peer);
auto action = Api::SendAction(history);
action.options = options;
action.clearDraft = false;
if (!comment.text.isEmpty()) {
auto message = ApiWrap::MessageToSend(action);
message.textWithTags = comment;
api.sendMessage(std::move(message));
}
data->requestsLeft++;
auto resolved = history->resolveForwardDraft(data->draft);
api.forwardMessages(std::move(resolved), action, [=] {
checkAndClose();
});
}
if (data->submitCallback
&& !::Kotato::JsonSettings::GetBool("forward_retain_selection")) {
data->submitCallback();
}
};
auto filterCallback = [](PeerData *peer) {
return peer->canWrite();
};
auto copyLinkCallback = canCopyLink
? Fn<void()>(std::move(copyCallback))
: Fn<void()>();
auto goToChatCallback = [navigation, data](
PeerData *peer,
Data::ForwardOptions forwardOptions,
Data::GroupingOptions groupOptions) {
if (data->submitCallback
&& !::Kotato::JsonSettings::GetBool("forward_retain_selection")) {
data->submitCallback();
}
data->draft.options = forwardOptions;
data->draft.groupOptions = groupOptions;
navigation->parentController()->content()->setForwardDraft(peer->id, std::move(data->draft));
};
*weak = Ui::show(
Box<ShareBox>(ShareBox::Descriptor{
.session = session,
.copyCallback = std::move(copyLinkCallback),
.submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback),
.goToChatCallback = std::move(goToChatCallback),
.forwardOptions = {
.messagesCount = int(draft.ids.size()),
.show = !hasOnlyForcedForwardedInfo,
.hasCaptions = hasCaptions,
.hasMedia = hasMediaForGrouping,
.isShare = false,
},
}),
Ui::LayerOption::KeepOther);
return weak->data();
}
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
not_null<Window::SessionNavigation*> navigation,
MessageIdsList &&items,
FnMut<void()> &&successCallback) {
const auto options = [] {
switch (::Kotato::JsonSettings::GetInt("forward_mode")) {
case 1: return Data::ForwardOptions::NoSenderNames;
case 2: return Data::ForwardOptions::NoNamesAndCaptions;
default: return Data::ForwardOptions::PreserveInfo;
}
}();
const auto groupOptions = [] {
switch (::Kotato::JsonSettings::GetInt("forward_grouping_mode")) {
case 1: return Data::GroupingOptions::RegroupAll;
case 2: return Data::GroupingOptions::Separate;
default: return Data::GroupingOptions::GroupAsIs;
}
}();
return ShowForwardMessagesBox(
navigation,
Data::ForwardDraft{ .ids = std::move(items) },
std::move(successCallback));
Data::ForwardDraft{
.ids = std::move(items),
.options = options,
.groupOptions = groupOptions,
}, std::move(successCallback));
}
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(

View file

@ -190,8 +190,8 @@ PRIVATE
ui/chat/chat_theme.h
ui/chat/continuous_scroll.cpp
ui/chat/continuous_scroll.h
ui/chat/forward_options_box.cpp
ui/chat/forward_options_box.h
#ui/chat/forward_options_box.cpp
#ui/chat/forward_options_box.h
ui/chat/group_call_bar.cpp
ui/chat/group_call_bar.h
ui/chat/group_call_userpics.cpp