feat: aesthetic bottom bar, a bunch of fixes

This commit is contained in:
bleizix 2025-07-01 03:32:21 +05:00
parent f16fb42283
commit 0d13f4710d
15 changed files with 623 additions and 384 deletions

View file

@ -3279,6 +3279,21 @@ void ApiWrap::forwardMessages(
FnMut<void()> &&successCallback) { FnMut<void()> &&successCallback) {
Expects(!draft.items.empty()); Expects(!draft.items.empty());
const auto fullAyuForward = AyuForward::isFullAyuForwardNeeded(draft.items.front());
if (fullAyuForward) {
crl::async([=] {
AyuForward::forwardMessages(_session, action, false, draft);
});
return;
}
const auto ayuIntelligentForwardNeeded = AyuForward::isAyuForwardNeeded(draft.items);
if (ayuIntelligentForwardNeeded) {
crl::async([=] {
AyuForward::intelligentForward(_session, action, draft);
});
return;
}
auto &histories = _session->data().histories(); auto &histories = _session->data().histories();
struct SharedCallback { struct SharedCallback {
@ -3338,20 +3353,6 @@ void ApiWrap::forwardMessages(
sendFlags |= SendFlag::f_top_msg_id; sendFlags |= SendFlag::f_top_msg_id;
} }
const auto fullAyuForward = AyuForward::isFullAyuForwardNeeded(draft.items.front()->from(), draft.items.front()->history());
if (fullAyuForward) {
crl::async([=] {
AyuForward::forwardMessages(peer, draft.items, _session, history, action, false);
});
return;
}
const auto ayuIntelligentForwardNeeded = AyuForward::isAyuForwardNeeded(draft.items);
if (ayuIntelligentForwardNeeded) {
crl::async([=] {
AyuForward::intelligentForward(peer, draft.items, _session, history, action);
});
return;
}
auto forwardFrom = draft.items.front()->history()->peer; auto forwardFrom = draft.items.front()->history()->peer;
auto ids = QVector<MTPint>(); auto ids = QVector<MTPint>();
auto randomIds = QVector<MTPlong>(); auto randomIds = QVector<MTPlong>();
@ -3780,11 +3781,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
? replyTo->topicRootId() ? replyTo->topicRootId()
: Data::ForumTopic::kGeneralId; : Data::ForumTopic::kGeneralId;
const auto topic = peer->forumTopicFor(topicRootId); const auto topic = peer->forumTopicFor(topicRootId);
if (!(topic
? Data::CanSendTexts(topic)
: Data::CanSendTexts(peer) || AyuForward::isForwarding(peer->id)
)
const bool canTexts = topic
? Data::CanSendTexts(topic)
: Data::CanSendTexts(peer);
if (!(canTexts || AyuForward::isForwarding((peer->id)))
|| Api::SendDice(message)) { || Api::SendDice(message)) {
return; return;
} }

View file

@ -1,14 +1,12 @@
#include "ayu_forward.h" #include "ayu_forward.h"
#include <lang_auto.h> #include <lang_auto.h>
#include <base/random.h> #include <base/random.h>
#include <data/data_peer.h> #include <data/data_peer.h>
#include <history/history_item.h> #include <history/history_item.h>
#include <styles/style_boxes.h> #include <styles/style_boxes.h>
#include "apiwrap.h" #include "apiwrap.h"
#include "ayu_sync.h" #include "ayu_sync.h"
#include "ayu/utils/telegram_helpers.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "core/application.h" #include "core/application.h"
#include "data/data_changes.h" #include "data/data_changes.h"
@ -26,76 +24,97 @@
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
namespace AyuForward { namespace AyuForward {
std::unordered_map<PeerId, ForwardState> forwardStates;
bool isForwarding(const PeerId &id) { std::unordered_map<PeerId, std::shared_ptr<ForwardState>> forwardStates;
const auto state = forwardStates.find(id);
return id.value bool isForwarding(const PeerId &id) {
&& state != forwardStates.end() const auto fwState = forwardStates.find(id);
&& state->second.state != ForwardState::State::Finished if (id.value && fwState != forwardStates.end()) {
&& state->second.currentChunk < state->second.totalChunks const auto state = *fwState->second;
&& !state->second.stopRequested
&& state->second.totalChunks return state.state != ForwardState::State::Finished
&& state->second.totalMessages; && state.currentChunk < state.totalChunks
&& !state.stopRequested
&& state.totalChunks
&& state.totalMessages;
} }
return false;
}
QString stateName(const PeerId &id) { void cancelForward(const PeerId &id, const Main::Session &session) {
const auto fwState = forwardStates.find(id);
if (fwState != forwardStates.end()) {
fwState->second->stopRequested = true;
fwState->second->updateBottomBar(session, &id, ForwardState::State::Finished);
}
}
std::pair<QString, QString> stateName(const PeerId &id) {
const auto fwState = forwardStates.find(id); const auto fwState = forwardStates.find(id);
if (fwState == forwardStates.end()) { if (fwState == forwardStates.end()) {
return QString(); return std::make_pair(QString(), QString());
} }
const auto state = fwState->second; const auto state = fwState->second;
QString messagesString = tr::ayu_AyuForwardStatusChunkCount(tr::now, QString messagesString = tr::ayu_AyuForwardStatusSentCount(tr::now,
lt_count1, lt_count1,
QString::number(state.sentMessages), QString::number(state->sentMessages),
lt_count2, lt_count2,
QString::number(state.totalMessages) QString::number(state->totalMessages)
); );
QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now, QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now,
lt_count1, lt_count1,
QString::number(state.currentChunk + 1), QString::number(state->currentChunk + 1),
lt_count2, lt_count2,
QString::number(state.totalChunks) QString::number(state->totalChunks)
); );
const auto partString = state.totalChunks <= 1 ? messagesString : (messagesString + "" + chunkString); const auto partString = state->totalChunks <= 1 ? messagesString : (messagesString + "" + chunkString);
QString status; QString status;
if (state.state == ForwardState::State::Preparing) {
if (state->state == ForwardState::State::Preparing) {
status = tr::ayu_AyuForwardStatusPreparing(tr::now); status = tr::ayu_AyuForwardStatusPreparing(tr::now);
} else if (state.state == ForwardState::State::Downloading) { } else if (state->state == ForwardState::State::Downloading) {
status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now); status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now);
} else if (state.state == ForwardState::State::Sending) { } else if (state->state == ForwardState::State::Sending) {
status = tr::ayu_AyuForwardStatusForwarding(tr::now); status = tr::ayu_AyuForwardStatusForwarding(tr::now);
} else { // ForwardState::State::Finished } else {
// ForwardState::State::Finished
status = tr::ayu_AyuForwardStatusFinished(tr::now); status = tr::ayu_AyuForwardStatusFinished(tr::now);
} }
return status.append("\n").append(partString);// + "\n" + partString; return std::make_pair(status, partString);
} }
void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) {
void ForwardState::updateBottomBar(not_null<Main::Session *> session, const PeerData *peer, const State &st) {
state = st; state = st;
forwardStates[peer->id] = *this;
session->changes().peerUpdated(session->data().peer(peer->id), Data::PeerUpdate::Flag::Rights); session.changes().peerUpdated(session.data().peer(*peer), Data::PeerUpdate::Flag::Rights);
} }
static Ui::PreparedList prepareMedia(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items, int &i) { static Ui::PreparedList prepareMedia(not_null<Main::Session*> session,
const auto prepare = [&] (not_null<Data::Media*> media){ const std::vector<not_null<HistoryItem*>> &items,
int &i) {
const auto prepare = [&](not_null<Data::Media*> media)
{
auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media)); auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media));
Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, 1280); Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, 1280);
return prepared; return prepared;
}; };
Ui::PreparedList list;
const auto startItem = items[i]; const auto startItem = items[i];
const auto media = startItem->media(); const auto media = startItem->media();
const auto groupId = startItem->groupId(); const auto groupId = startItem->groupId();
Ui::PreparedList list;
list.files.emplace_back(prepare(media)); list.files.emplace_back(prepare(media));
if (!groupId.value) { if (!groupId.value) {
@ -107,32 +126,26 @@ namespace AyuForward {
if (nextItem->groupId() != groupId) { if (nextItem->groupId() != groupId) {
break; break;
} }
if (const auto nextMedia = nextItem->media()) { if (const auto nextMedia = nextItem->media()) {
list.files.emplace_back(prepare(nextMedia)); list.files.emplace_back(prepare(nextMedia));
i = k; i = k;
} }
} }
return list; return list;
} }
void sendMedia( void sendMedia(
not_null<Main::Session*> session, not_null<Main::Session*> session,
Ui::PreparedList &&list, std::shared_ptr<Ui::PreparedBundle> bundle,
not_null<Data::Media*> primaryMedia, not_null<Data::Media*> primaryMedia,
Api::MessageToSend &&message, Api::MessageToSend &&message) {
uint64 newGroupId) { if (const auto document = primaryMedia->document(); document && document->sticker()) {
AyuSync::sendStickerSync(session, message, document);
if (const auto document = primaryMedia->document()) {
if (document->isGifv() || document->sticker()) {
AyuSync::sendGifOrStickSync(session, message, document);
return; return;
} }
}
const auto mediaType = [&] { auto mediaType = [&]
{
if (const auto document = primaryMedia->document()) { if (const auto document = primaryMedia->document()) {
if (document->isVoiceMessage()) { if (document->isVoiceMessage()) {
return SendMediaType::Audio; return SendMediaType::Audio;
@ -145,51 +158,92 @@ namespace AyuForward {
return SendMediaType::Photo; return SendMediaType::Photo;
}(); }();
if (mediaType == SendMediaType::Round || mediaType == SendMediaType::Audio) {
const auto path = bundle->groups.front().list.files.front().path;
auto group = std::make_shared<SendingAlbum>(); QFile file(path);
group->groupId = newGroupId; auto failed = false;
if (!file.open(QIODevice::ReadOnly)) {
LOG(("failed to open file for forward with reason: %1").arg(file.errorString()));
failed = true;
}
auto data = file.readAll();
AyuSync::sendDocumentSync( if (!failed && data.size()) {
session, file.close();
std::move(list), AyuSync::sendVoiceSync(session,
mediaType, data,
std::move(message.textWithTags), primaryMedia->document()->duration(),
group, mediaType == SendMediaType::Round,
message.action); message.action);
return;
}
// at least try to send it as squared-video
} }
// workaround
auto isTherePhotos = false;
for (auto &group : bundle->groups) {
for (Ui::PreparedFile &file : group.list.files) {
if (file.type == Ui::PreparedFile::Type::Photo) {
isTherePhotos = true;
break;
}
}
}
if (mediaType == SendMediaType::File && isTherePhotos) {
mediaType = SendMediaType::Photo;
}
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem *>> &items) { for (auto &group : bundle->groups) {
AyuSync::sendDocumentSync(
session,
group,
mediaType,
std::move(message.textWithTags),
message.action);
}
}
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem*>> &items) {
for (const auto &item : items) { for (const auto &item : items) {
if (isAyuForwardNeeded(item)) { if (isAyuForwardNeeded(item)) {
return true; return true;
} }
} }
return false; return false;
} }
bool isAyuForwardNeeded(not_null<HistoryItem *> item) { bool isAyuForwardNeeded(not_null<HistoryItem*> item) {
if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) { if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) {
return true; return true;
} }
return false; return false;
} }
bool isFullAyuForwardNeeded(not_null<PeerData *> peer, not_null<History*> history) { bool isFullAyuForwardNeeded(not_null<HistoryItem*> item) {
return peer->isAyuNoForwards() || history->peer->isAyuNoForwards(); return item->from()->isAyuNoForwards() || item->history()->peer->isAyuNoForwards();
} }
struct ForwardChunk { struct ForwardChunk
{
bool isAyuForwardNeeded; bool isAyuForwardNeeded;
std::vector<not_null<HistoryItem *>> items; std::vector<not_null<HistoryItem*>> items;
}; };
void intelligentForward(const PeerData* peer, const std::vector<not_null<HistoryItem *>> &items, not_null<Main::Session *> session, not_null<History *> history, const Api::SendAction &action) { void intelligentForward(
not_null<Main::Session*> session,
const Api::SendAction &action,
Data::ResolvedForwardDraft draft) {
const auto history = action.history;
history->setForwardDraft(action.replyTo.topicRootId, {}); history->setForwardDraft(action.replyTo.topicRootId, {});
auto chunks = std::vector<ForwardChunk>(); const auto items = draft.items;
const auto peer = history->peer;
auto chunks = std::vector<ForwardChunk>();
auto currentArray = std::vector<not_null<HistoryItem*>>();
auto currentArray = std::vector<not_null<HistoryItem *>>();
auto currentChunk = ForwardChunk({ auto currentChunk = ForwardChunk({
.isAyuForwardNeeded = isAyuForwardNeeded(items[0]), .isAyuForwardNeeded = isAyuForwardNeeded(items[0]),
.items = currentArray .items = currentArray
@ -200,7 +254,8 @@ namespace AyuForward {
currentChunk.items = currentArray; currentChunk.items = currentArray;
chunks.push_back(currentChunk); chunks.push_back(currentChunk);
currentArray = std::vector<not_null<HistoryItem *>>(); currentArray = std::vector<not_null<HistoryItem*>>();
currentChunk = ForwardChunk({ currentChunk = ForwardChunk({
.isAyuForwardNeeded = isAyuForwardNeeded(item), .isAyuForwardNeeded = isAyuForwardNeeded(item),
.items = currentArray .items = currentArray
@ -213,97 +268,132 @@ namespace AyuForward {
chunks.push_back(currentChunk); chunks.push_back(currentChunk);
auto state = std::make_shared<ForwardState>(chunks.size()); auto state = std::make_shared<ForwardState>(chunks.size());
forwardStates[peer->id] = *state; forwardStates[peer->id] = state;
for (const auto &chunk : chunks) { for (const auto &chunk : chunks) {
if (chunk.isAyuForwardNeeded) { if (chunk.isAyuForwardNeeded) {
forwardMessages(peer, chunk.items, session, history, action, true); forwardMessages(session, action, true, Data::ResolvedForwardDraft(chunk.items));
} else { } else {
state->totalMessages = chunk.items.size(); state->totalMessages = chunk.items.size();
state->sentMessages = 0; state->sentMessages = 0;
state->updateBottomBar(session, peer, ForwardState::State::Sending); state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
AyuSync::forwardMessagesSync(session, chunk.items, action); AyuSync::forwardMessagesSync(session, chunk.items, action, draft.options);
state->sentMessages = state->totalMessages; state->sentMessages = state->totalMessages;
state->updateBottomBar(session, peer, ForwardState::State::Finished);
}
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
}
state->currentChunk++; state->currentChunk++;
} }
state->updateBottomBar(session, peer, ForwardState::State::Finished);
} state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
}
void forwardMessages(
not_null<Main::Session*> session,
const Api::SendAction &action,
bool forwardState,
Data::ResolvedForwardDraft draft) {
const auto items = draft.items;
const auto history = action.history;
const auto peer = history->peer;
void forwardMessages(const PeerData *peer, std::vector<not_null<HistoryItem *> > items,
not_null<Main::Session *> session,
not_null<History *> history, const Api::SendAction &action, bool forwardState) {
history->setForwardDraft(action.replyTo.topicRootId, {}); history->setForwardDraft(action.replyTo.topicRootId, {});
ForwardState state; std::shared_ptr<ForwardState> state;
if (forwardState) { if (forwardState) {
state = forwardStates[peer->id]; state = std::make_shared<ForwardState>(*forwardStates[peer->id]);
} else { } else {
state = ForwardState(1); state = std::make_shared<ForwardState>(1);
} }
forwardStates[peer->id] = state; forwardStates[peer->id] = state;
std::unordered_map<uint64, uint64> groupIds; std::unordered_map<uint64, uint64> groupIds;
std::vector<not_null<HistoryItem*>> toBeDownloaded;
std::vector<not_null<HistoryItem *> > toBeDownloaded;
for (const auto item: items) { for (const auto item : items) {
if (item->media()) { if (mediaDownloadable(item->media())) {
toBeDownloaded.push_back(item); toBeDownloaded.push_back(item);
} }
if (item->groupId()) { if (item->groupId()) {
const auto currentId = groupIds.find(item->groupId().value); const auto currentId = groupIds.find(item->groupId().value);
if (currentId == groupIds.end()) { if (currentId == groupIds.end()) {
groupIds[item->groupId().value] = base::RandomValue<uint64>(); groupIds[item->groupId().value] = base::RandomValue<uint64>();
} }
} }
} }
state->totalMessages = items.size();
if (toBeDownloaded.size()) { if (toBeDownloaded.size()) {
state.updateBottomBar(session, peer, ForwardState::State::Downloading); state->state = ForwardState::State::Downloading;
state->updateBottomBar(*session, &peer->id, ForwardState::State::Downloading);
AyuSync::loadDocuments(session, toBeDownloaded); AyuSync::loadDocuments(session, toBeDownloaded);
} }
state.totalMessages = items.size();
state.sentMessages = 0; state->sentMessages = 0;
state.updateBottomBar(session, peer, ForwardState::State::Sending); state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending);
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
const auto item = items[i]; const auto item = items[i];
if (state->stopRequested) {
if (state.stopRequested) { state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
state.updateBottomBar(session, peer, ForwardState::State::Finished);
return; return;
} }
auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id))); auto extractedText = extractText(item);
if (extractedText.empty() && !mediaDownloadable(item->media())) {
message.textWithTags.text = item->originalText().text;
message.textWithTags.tags = TextUtilities::ConvertEntitiesToTextTags(item->originalText().entities);
const auto groupId = item->groupId().value;
const auto newGroupId = groupIds.contains(groupId) ? groupIds.find(groupId)->second : 0;
if (const auto media = item->media()) {
if (const auto poll = media->poll()) {
// need to implement extract of the text
continue; continue;
} }
sendMedia(session, prepareMedia(session, items, i), media, std::move(message), newGroupId);
} else { auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id)));
AyuSync::sendMessageSync(session, message); message.action.replyTo = action.replyTo;
}
state.sentMessages += 1; if (draft.options != Data::ForwardOptions::NoNamesAndCaptions) {
} message.textWithTags = extractedText;
state.updateBottomBar(session, peer, ForwardState::State::Finished);
} }
if (!mediaDownloadable(item->media())) {
AyuSync::sendMessageSync(session, message);
} else if (const auto media = item->media()) {
if (media->poll()) {
AyuSync::sendMessageSync(session, message);
continue;
}
auto preparedMedia = prepareMedia(session, items, i);
Ui::SendFilesWay way;
way.setGroupFiles(true);
way.setSendImagesAsPhotos(true);
auto groups = Ui::DivideByGroups(
std::move(preparedMedia),
way,
peer->slowmodeApplied());
auto bundle = Ui::PrepareFilesBundle(
std::move(groups),
way,
message.textWithTags,
false);
sendMedia(session, bundle, media, std::move(message));
}
// if there are grouped messages
// "i" is incremented in prepareMedia
state->sentMessages = i + 1;
}
state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished);
}
} // namespace AyuFeatures::AyuForward } // namespace AyuFeatures::AyuForward

View file

@ -1,26 +1,25 @@
#pragma once #pragma once
#include <main/main_session.h> #include <main/main_session.h>
#include "history/history.h" #include "history/history.h"
namespace AyuForward { namespace AyuForward {
bool isForwarding(const PeerId &id); bool isForwarding(const PeerId &id);
QString stateName(const PeerId &id); void cancelForward(const PeerId &id, const Main::Session &session);
std::pair<QString, QString> stateName(const PeerId &id);
class ForwardState { class ForwardState
{
public: public:
enum class State { enum class State
{
Preparing, Preparing,
Downloading, Downloading,
Sending, Sending,
Finished Finished
}; };
void updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st);
void updateBottomBar(not_null<Main::Session *> session, const PeerData *peer, const State &st);
int totalChunks; int totalChunks;
int currentChunk; int currentChunk;
@ -32,13 +31,17 @@ public:
}; };
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem*>> &items);
bool isAyuForwardNeeded(const std::vector<not_null<HistoryItem *>> &items); bool isAyuForwardNeeded(not_null<HistoryItem*> item);
bool isAyuForwardNeeded(not_null<HistoryItem *> item); bool isFullAyuForwardNeeded(not_null<HistoryItem*> item);
bool isFullAyuForwardNeeded(not_null<PeerData *> peer, not_null<History*> history); void intelligentForward(
void intelligentForward(const PeerData* peer, not_null<Main::Session*> session,
const std::vector<not_null<HistoryItem *>> &item, not_null<Main::Session *> session, not_null<History *> history, const Api::SendAction &action); const Api::SendAction &action,
void forwardMessages(const PeerData* peer, std::vector<not_null<HistoryItem*>> item, not_null<Main::Session *> session, not_null<History *> history, const Api::SendAction &action, bool forwardState); Data::ResolvedForwardDraft draft);
void forwardMessages(
not_null<Main::Session*> session,
const Api::SendAction &action,
bool forwardState,
Data::ResolvedForwardDraft draft);
} }

View file

@ -1,4 +1,3 @@
#include "ayu_sync.h" #include "ayu_sync.h"
#include "api/api_sending.h" #include "api/api_sending.h"
@ -13,11 +12,12 @@
#include "main/main_session.h" #include "main/main_session.h"
#include "storage/file_download_mtproto.h" #include "storage/file_download_mtproto.h"
class TimedCountDownLatch class TimedCountDownLatch
{ {
public: public:
explicit TimedCountDownLatch(int count) : count_(count) {} explicit TimedCountDownLatch(int count)
: count_(count) {
}
void countDown() { void countDown() {
std::unique_lock lock(mutex_); std::unique_lock lock(mutex_);
@ -45,7 +45,7 @@ private:
namespace AyuSync { namespace AyuSync {
QString pathForSave(not_null<Main::Session *> session) { QString pathForSave(not_null<Main::Session*> session) {
const auto path = Core::App().settings().downloadPath(); const auto path = Core::App().settings().downloadPath();
if (path.isEmpty()) { if (path.isEmpty()) {
return File::DefaultDownloadPath(session); return File::DefaultDownloadPath(session);
@ -56,8 +56,7 @@ QString pathForSave(not_null<Main::Session *> session) {
return path; return path;
} }
QString filePath(not_null<Main::Session*> session, const Data::Media *media) {
QString filePath(not_null<Main::Session *> session, const Data::Media *media) {
if (const auto document = media->document()) { if (const auto document = media->document()) {
if (!document->filename().isEmpty()) { if (!document->filename().isEmpty()) {
return pathForSave(session) + media->document()->filename(); return pathForSave(session) + media->document()->filename();
@ -79,25 +78,21 @@ QString filePath(not_null<Main::Session *> session, const Data::Media *media) {
return QString(); return QString();
} }
qint64 fileSize(not_null<HistoryItem *> item) {
qint64 fileSize(not_null<HistoryItem*> item) {
if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) { if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) {
QFile file(path); QFile file(path);
if (file.exists()) { if (file.exists()) {
return file.size(); auto size = file.size();
return size;
} }
} }
return 0; return 0;
} }
void loadDocuments(not_null<Main::Session *> session, const std::vector<not_null<HistoryItem *>> &items) { void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items) {
for (const auto &item : items) { for (const auto &item : items) {
if (const auto data = item->media()->document()) { if (const auto data = item->media()->document()) {
if (data->isGifv()) {
// no need to download it
continue;
}
const auto size = fileSize(item); const auto size = fileSize(item);
if (size == data->size) { if (size == data->size) {
@ -109,9 +104,7 @@ void loadDocuments(not_null<Main::Session *> session, const std::vector<not_null
file.remove(); file.remove();
} }
loadDocumentSync(session, data, item); loadDocumentSync(session, data, item);
} else if (auto photo = item->media()->photo()) { } else if (auto photo = item->media()->photo()) {
if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) { if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) {
continue; continue;
@ -121,31 +114,40 @@ void loadDocuments(not_null<Main::Session *> session, const std::vector<not_null
} }
} }
} }
void loadDocumentSync(not_null<Main::Session *> session, DocumentData *data, not_null<HistoryItem *> item) {
void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_null<HistoryItem*> item) {
auto latch = std::make_shared<TimedCountDownLatch>(1); auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>(); auto lifetime = std::make_shared<rpl::lifetime>();
data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media())); data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media()));
rpl::single() | rpl::then(
session->downloaderTaskFinished() session->downloaderTaskFinished()
| rpl::filter([&]{ ) | rpl::filter([&]
return item->media()->document()->status == FileDownloadFailed || fileSize(item) == data->size; {
}) | rpl::start_with_next([&]() mutable{ return data->status == FileDownloadFailed || fileSize(item) == data->size;
}) | rpl::start_with_next([&]() mutable
{
latch->countDown(); latch->countDown();
base::take(lifetime)->destroy(); base::take(lifetime)->destroy();
}, *lifetime); },
*lifetime);
latch->await(std::chrono::minutes(5)); latch->await(std::chrono::minutes(5));
} }
void forwardMessagesSync(not_null<Main::Session *> session, void forwardMessagesSync(not_null<Main::Session*> session,
const std::vector<not_null<HistoryItem *>> &items, const std::vector<not_null<HistoryItem*>> &items,
const ApiWrap::SendAction &action) { const ApiWrap::SendAction &action,
Data::ForwardOptions options) {
auto latch = std::make_shared<TimedCountDownLatch>(1); auto latch = std::make_shared<TimedCountDownLatch>(1);
crl::on_main([=, &latch]{ crl::on_main([=, &latch]
session->api().forwardMessages(Data::ResolvedForwardDraft(items), action, [&]{ {
session->api().forwardMessages(Data::ResolvedForwardDraft(items, options),
action,
[&]
{
latch->countDown(); latch->countDown();
}); });
}); });
@ -154,8 +156,7 @@ void forwardMessagesSync(not_null<Main::Session *> session,
latch->await(std::chrono::minutes(1)); latch->await(std::chrono::minutes(1));
} }
void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photo) {
void loadPhotoSync(not_null<Main::Session *> session, const std::pair<not_null<PhotoData *>, FullMsgId> &photo) {
const auto folderPath = pathForSave(session); const auto folderPath = pathForSave(session);
const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath; const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath;
@ -177,15 +178,18 @@ void loadPhotoSync(not_null<Main::Session *> session, const std::pair<not_null<P
} }
view->wanted(Data::PhotoSize::Large, photo.second); view->wanted(Data::PhotoSize::Large, photo.second);
const auto finalCheck = [=]{ const auto finalCheck = [=]
{
return !photo.first->loading(); return !photo.first->loading();
}; };
const auto saveToFiles = [=]{ const auto saveToFiles = [=]
{
QDir directory(path); QDir directory(path);
const auto dir = directory.absolutePath(); const auto dir = directory.absolutePath();
const auto nameBase = dir.endsWith('/') ? dir : dir + '/'; const auto nameBase = dir.endsWith('/') ? dir : dir + '/';
const auto fullPath = nameBase + QString::number(photo.first->getDC()) + "_" + QString::number(photo.first->id) + ".jpg"; const auto fullPath = nameBase + QString::number(photo.first->getDC()) + "_" + QString::number(photo.first->id)
+ ".jpg";
view->saveToFile(fullPath); view->saveToFile(fullPath);
}; };
@ -195,20 +199,24 @@ void loadPhotoSync(not_null<Main::Session *> session, const std::pair<not_null<P
if (finalCheck()) { if (finalCheck()) {
saveToFiles(); saveToFiles();
} else { } else {
session->downloaderTaskFinished() | rpl::filter([&]{ session->downloaderTaskFinished() | rpl::filter([&]
{
return finalCheck(); return finalCheck();
}) | rpl::start_with_next([&]() mutable{ }) | rpl::start_with_next([&]() mutable
{
saveToFiles(); saveToFiles();
latch->countDown(); latch->countDown();
base::take(lifetime)->destroy(); base::take(lifetime)->destroy();
},*lifetime); },
*lifetime);
} }
latch->await(std::chrono::minutes(5)); latch->await(std::chrono::minutes(5));
} }
void sendMessageSync(not_null<Main::Session *> session, Api::MessageToSend &message) { void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message) {
crl::on_main([=, &message] { crl::on_main([=, &message]
{
// we cannot send events to objects // we cannot send events to objects
// owned by a different thread // owned by a different thread
// because sendMessage updates UI too // because sendMessage updates UI too
@ -220,35 +228,40 @@ void sendMessageSync(not_null<Main::Session *> session, Api::MessageToSend &mess
waitForMsgSync(session, message.action); waitForMsgSync(session, message.action);
} }
void waitForMsgSync(not_null<Main::Session *> session, const Api::SendAction &action) { void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action) {
auto latch = std::make_shared<TimedCountDownLatch>(1); auto latch = std::make_shared<TimedCountDownLatch>(1);
auto lifetime = std::make_shared<rpl::lifetime>(); auto lifetime = std::make_shared<rpl::lifetime>();
session->data().itemIdChanged() session->data().itemIdChanged()
| rpl::filter([&](const Data::Session::IdChange &update){ | rpl::filter([&](const Data::Session::IdChange &update)
{
return action.history->peer->id == update.newId.peer; return action.history->peer->id == update.newId.peer;
}) | rpl::start_with_next([&]{ }) | rpl::start_with_next([&]
{
latch->countDown(); latch->countDown();
base::take(lifetime)->destroy(); base::take(lifetime)->destroy();
},*lifetime); },
*lifetime);
latch->await(std::chrono::minutes(2)); latch->await(std::chrono::minutes(2));
} }
void sendDocumentSync(not_null<Main::Session *> session, void sendDocumentSync(not_null<Main::Session*> session,
Ui::PreparedList &&list, Ui::PreparedGroup &group,
SendMediaType type, SendMediaType type,
TextWithTags &&caption, TextWithTags &&caption,
const std::shared_ptr<SendingAlbum> &album,
const Api::SendAction &action) { const Api::SendAction &action) {
const auto size = group.list.files.size();
const auto size = list.files.size();
auto latch = std::make_shared<TimedCountDownLatch>(size); auto latch = std::make_shared<TimedCountDownLatch>(size);
auto lifetime = std::make_shared<rpl::lifetime>(); auto lifetime = std::make_shared<rpl::lifetime>();
crl::on_main([=, list = std::move(list), caption = std::move(caption)]() mutable{ auto groupId = std::make_shared<SendingAlbum>();
session->api().sendFiles(std::move(list), type, std::move(caption), album, action); groupId->groupId = base::RandomValue<uint64>();
crl::on_main([=, lst = std::move(group.list), caption = std::move(caption)]() mutable
{
session->api().sendFiles(std::move(lst), type, std::move(caption), groupId, action);
}); });
@ -261,27 +274,45 @@ void sendDocumentSync(not_null<Main::Session *> session,
rpl::merge( rpl::merge(
session->uploader().documentReady(), session->uploader().documentReady(),
session->uploader().photoReady() session->uploader().photoReady()
) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto){ ) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto)
{
return docOrPhoto.fullId.peer == action.history->peer->id; return docOrPhoto.fullId.peer == action.history->peer->id;
}) | rpl::start_with_next([&]{ }) | rpl::start_with_next([&]
{
latch->countDown(); latch->countDown();
}, *lifetime); },
*lifetime);
latch->await(std::chrono::minutes(5 * size)); latch->await(std::chrono::minutes(5 * size));
base::take(lifetime)->destroy(); base::take(lifetime)->destroy();
} }
void sendGifOrStickSync(not_null<Main::Session *> session, void sendStickerSync(not_null<Main::Session*> session,
Api::MessageToSend &message, Api::MessageToSend &message,
not_null<DocumentData *> document) { not_null<DocumentData*> document) {
auto &action = message.action; auto &action = message.action;
crl::on_main([&] { crl::on_main([&]
{
Api::SendExistingDocument(std::move(message), document, std::nullopt); Api::SendExistingDocument(std::move(message), document, std::nullopt);
}); });
waitForMsgSync(session, action); waitForMsgSync(session, action);
} }
void sendVoiceSync(not_null<Main::Session*> session,
const QByteArray &data,
int64_t duration,
bool video,
const Api::SendAction &action) {
crl::on_main([&]
{
session->api().sendVoiceMessage(data,
QVector<signed char>(),
duration,
video,
action);
});
waitForMsgSync(session, action);
}
} // namespace AyuSync } // namespace AyuSync

View file

@ -24,28 +24,33 @@
#include "storage/file_download.h" #include "storage/file_download.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
namespace AyuSync { namespace AyuSync {
QString pathForSave(not_null<Main::Session *> session); QString pathForSave(not_null<Main::Session*> session);
QString filePath(not_null<Main::Session *> session, const Data::Media *media); QString filePath(not_null<Main::Session*> session, const Data::Media *media);
void loadDocuments(not_null<Main::Session *> session, const std::vector<not_null<HistoryItem *> > &items); void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items);
bool isMediaDownloadable(Data::Media *media); bool isMediaDownloadable(Data::Media *media);
void sendMessageSync(not_null<Main::Session *> session, Api::MessageToSend &message); void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message);
void sendDocumentSync(not_null<Main::Session *> session, void sendDocumentSync(not_null<Main::Session*> session,
Ui::PreparedList &&list, Ui::PreparedGroup &group,
SendMediaType type, SendMediaType type,
TextWithTags &&caption, TextWithTags &&caption,
const std::shared_ptr<SendingAlbum> &album,
const Api::SendAction &action); const Api::SendAction &action);
void sendGifOrStickSync(not_null<Main::Session *> session, void sendStickerSync(not_null<Main::Session*> session,
Api::MessageToSend &message, Api::MessageToSend &message,
not_null<DocumentData *> document); not_null<DocumentData*> document);
void waitForMsgSync(not_null<Main::Session *> session, const Api::SendAction &action); void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action);
void loadPhotoSync(not_null<Main::Session *> session, const std::pair<not_null<PhotoData *>, FullMsgId> &photos); void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photos);
void loadDocumentSync(not_null<Main::Session *> session, DocumentData *data, not_null<HistoryItem *> item); void loadDocumentSync(not_null<Main::Session*> session, DocumentData *data, not_null<HistoryItem*> item);
void forwardMessagesSync(not_null<Main::Session *> session, const std::vector<not_null<HistoryItem *> > &items, const ApiWrap::SendAction &action); void forwardMessagesSync(not_null<Main::Session*> session,
const std::vector<not_null<HistoryItem*>> &items,
const ApiWrap::SendAction &action,
Data::ForwardOptions options);
void sendVoiceSync(not_null<Main::Session*> session,
const QByteArray &data,
int64_t duration,
bool video,
const Api::SendAction &action);
} }

View file

@ -742,3 +742,34 @@ ID getUserIdFromPackId(uint64 id) {
return ownerId; return ownerId;
} }
TextWithTags extractText(not_null<HistoryItem*> item) {
TextWithTags result;
QString text;
if (const auto media = item->media()) {
if (const auto poll = media->poll()) {
text.append("\xF0\x9F\x93\x8A ") // 📊
.append(poll->question.text).append("\n");
for (const auto answer : poll->answers) {
text.append("").append(answer.text.text).append("\n");
}
}
}
result.tags = TextUtilities::ConvertEntitiesToTextTags(item->originalText().entities);
result.text = text.isEmpty() ? item->originalText().text : text;
return result;
}
bool mediaDownloadable(Data::Media *media) {
if (!media
|| media->webpage() || media->poll() || media->game()
|| media->invoice() || media->location() || media->paper()
|| media->giveawayStart() || media->giveawayResults()
|| media->sharedContact() || media->call()
) {
return false;
}
return true;
}

View file

@ -12,6 +12,8 @@
#include "dialogs/dialogs_main_list.h" #include "dialogs/dialogs_main_list.h"
#include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_badge.h"
#include "main/main_domain.h" #include "main/main_domain.h"
#include "data/data_poll.h"
#include "data/data_media_types.h"
using UsernameResolverCallback = Fn<void(const QString &, UserData *)>; using UsernameResolverCallback = Fn<void(const QString &, UserData *)>;
@ -55,3 +57,6 @@ void searchById(ID userId, Main::Session *session, bool retry, const UsernameRes
void searchById(ID userId, Main::Session *session, const UsernameResolverCallback &callback); void searchById(ID userId, Main::Session *session, const UsernameResolverCallback &callback);
ID getUserIdFromPackId(uint64 id); ID getUserIdFromPackId(uint64 id);
TextWithTags extractText(not_null<HistoryItem*> item);
bool mediaDownloadable(Data::Media* media);

View file

@ -63,6 +63,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include "ayu/features/forward/ayu_forward.h"
namespace { namespace {
using namespace Ui::Text; using namespace Ui::Text;
@ -1280,6 +1282,69 @@ std::unique_ptr<Ui::AbstractButton> BoostsToLiftWriteRestriction(
return result; return result;
} }
std::unique_ptr<Ui::AbstractButton> AyuForwardWriteRestriction(
not_null<QWidget *> parent,
const PeerId &peer,
const Main::Session &session) {
using namespace Ui;
// status and part
const auto pair = AyuForward::stateName(peer);
auto result = std::make_unique<FlatButton>(
parent,
QString(),
st::historyComposeButton);
const auto raw = result.get();
const auto title = CreateChild<FlatLabel>(
raw,
pair.first,
st::frozenRestrictionTitle);
title->setTextColorOverride(st::historyComposeButton.color->c);
title->setAttribute(Qt::WA_TransparentForMouseEvents);
title->show();
const auto subtitle = CreateChild<FlatLabel>(
raw,
pair.second,
st::frozenRestrictionSubtitle);
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
subtitle->show();
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
const auto toggle = [&](auto &&widget, bool shown) {
if (widget->isHidden() == shown) {
widget->setVisible(shown);
}
};
const auto small = 2 * st::defaultDialogRow.photoSize;
const auto shown = (size.width() > small);
toggle(title, shown);
toggle(subtitle, shown);
const auto skip = st::defaultDialogRow.padding.left();
const auto available = size.width() - skip * 2;
title->resizeToWidth(available);
subtitle->resizeToWidth(available);
const auto height = title->height() + subtitle->height();
const auto top = (size.height() - height) / 2;
title->moveToLeft(skip, top, size.width());
subtitle->moveToLeft(skip, top + title->height(), size.width());
}, title->lifetime());
raw->setClickedCallback([&] {
AyuForward::cancelForward(peer, session);
});
return result;
}
std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction( std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction(
not_null<QWidget*> parent, not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,

View file

@ -202,7 +202,10 @@ enum class FrozenWriteRestrictionType {
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
FrozenWriteRestrictionType type, FrozenWriteRestrictionType type,
FreezeInfoStyleOverride st = {}); FreezeInfoStyleOverride st = {});
std::unique_ptr<Ui::AbstractButton> AyuForwardWriteRestriction(
not_null<QWidget *> parent,
const PeerId &peer,
const Main::Session &session);
void SelectTextInFieldWithMargins( void SelectTextInFieldWithMargins(
not_null<Ui::InputField*> field, not_null<Ui::InputField*> field,
const TextSelection &selection); const TextSelection &selection);

View file

@ -75,7 +75,7 @@ enum class ChannelDataFlag : uint64 {
PaidMessagesAvailable = (1ULL << 37), PaidMessagesAvailable = (1ULL << 37),
AutoTranslation = (1ULL << 38), AutoTranslation = (1ULL << 38),
AyuNoForwards = (1ULL << 60), AyuNoForwards = (1ULL << 63),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;

View file

@ -24,7 +24,7 @@ enum class ChatDataFlag {
CanSetUsername = (1 << 7), CanSetUsername = (1 << 7),
NoForwards = (1 << 8), NoForwards = (1 << 8),
AyuNoForwards = (1 << 20), AyuNoForwards = (1 << 31),
}; };
inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; };
using ChatDataFlags = base::flags<ChatDataFlag>; using ChatDataFlags = base::flags<ChatDataFlag>;

View file

@ -186,7 +186,7 @@ SendError RestrictionError(
ChatRestriction restriction) { ChatRestriction restriction) {
if (AyuForward::isForwarding(peer->id)) { if (AyuForward::isForwarding(peer->id)) {
return SendError({ return SendError({
.text = AyuForward::stateName(peer->id) .text = AyuForward::stateName(peer->id).first + "\n" + AyuForward::stateName(peer->id).second,
}); });
} }
using Flag = ChatRestriction; using Flag = ChatRestriction;

View file

@ -353,7 +353,7 @@ enum class MessageFlag : uint64 {
HideDisplayDate = (1ULL << 51), HideDisplayDate = (1ULL << 51),
AyuNoForwards = (1ULL << 60), AyuNoForwards = (1ULL << 63),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;

View file

@ -6758,7 +6758,9 @@ void HistoryWidget::updateSendRestriction() {
return; return;
} }
_sendRestrictionKey = restriction.text; _sendRestrictionKey = restriction.text;
if (!restriction) { if (AyuForward::isForwarding(_peer->id)) {
_sendRestriction = AyuForwardWriteRestriction(this, _peer->id, session());
} else if (!restriction) {
_sendRestriction = nullptr; _sendRestriction = nullptr;
} else if (restriction.frozen) { } else if (restriction.frozen) {
const auto show = controller()->uiShow(); const auto show = controller()->uiShow();

View file

@ -563,7 +563,9 @@ bool MainWidget::setForwardDraft(
.forward = &items, .forward = &items,
.ignoreSlowmodeCountdown = true, .ignoreSlowmodeCountdown = true,
}); });
if (error) { // allow opening chat that
// already have some forward task
if (error && !AyuForward::isForwarding(history->peer->id)) {
Data::ShowSendErrorToast(_controller, history->peer, error); Data::ShowSendErrorToast(_controller, history->peer, error);
return false; return false;
} }