From 0d13f4710df88e4e64b5fe5fb74c7fb4061edc68 Mon Sep 17 00:00:00 2001 From: bleizix Date: Tue, 1 Jul 2025 03:32:21 +0500 Subject: [PATCH] feat: aesthetic bottom bar, a bunch of fixes --- Telegram/SourceFiles/apiwrap.cpp | 38 +- .../ayu/features/forward/ayu_forward.cpp | 568 ++++++++++-------- .../ayu/features/forward/ayu_forward.h | 49 +- .../ayu/features/forward/ayu_sync.cpp | 187 +++--- .../ayu/features/forward/ayu_sync.h | 43 +- .../ayu/utils/telegram_helpers.cpp | 31 + .../SourceFiles/ayu/utils/telegram_helpers.h | 5 + .../chat_helpers/message_field.cpp | 65 ++ .../SourceFiles/chat_helpers/message_field.h | 5 +- Telegram/SourceFiles/data/data_channel.h | 2 +- Telegram/SourceFiles/data/data_chat.h | 2 +- .../data/data_chat_participant_status.cpp | 2 +- Telegram/SourceFiles/data/data_types.h | 2 +- .../SourceFiles/history/history_widget.cpp | 4 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- 15 files changed, 623 insertions(+), 384 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index fb1368e991..7e3c6af205 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3279,6 +3279,21 @@ void ApiWrap::forwardMessages( FnMut &&successCallback) { 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(); struct SharedCallback { @@ -3338,20 +3353,6 @@ void ApiWrap::forwardMessages( 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 ids = QVector(); auto randomIds = QVector(); @@ -3780,11 +3781,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) { ? replyTo->topicRootId() : Data::ForumTopic::kGeneralId; 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)) { return; } diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp index 97899a6f37..4651ff298d 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp @@ -1,14 +1,12 @@ - #include "ayu_forward.h" - #include #include #include #include #include - #include "apiwrap.h" #include "ayu_sync.h" +#include "ayu/utils/telegram_helpers.h" #include "base/unixtime.h" #include "core/application.h" #include "data/data_changes.h" @@ -26,284 +24,376 @@ #include "ui/text/text_utilities.h" namespace AyuForward { - std::unordered_map forwardStates; - bool isForwarding(const PeerId &id) { - const auto state = forwardStates.find(id); - return id.value - && state != forwardStates.end() - && state->second.state != ForwardState::State::Finished - && state->second.currentChunk < state->second.totalChunks - && !state->second.stopRequested - && state->second.totalChunks - && state->second.totalMessages; - } +std::unordered_map> forwardStates; - QString stateName(const PeerId &id) { - const auto fwState = forwardStates.find(id); +bool isForwarding(const PeerId &id) { + const auto fwState = forwardStates.find(id); + if (id.value && fwState != forwardStates.end()) { + const auto state = *fwState->second; - if (fwState == forwardStates.end()) { - return QString(); - } - const auto state = fwState->second; + return state.state != ForwardState::State::Finished + && state.currentChunk < state.totalChunks + && !state.stopRequested + && state.totalChunks + && state.totalMessages; + } + return false; +} - QString messagesString = tr::ayu_AyuForwardStatusChunkCount(tr::now, - lt_count1, - QString::number(state.sentMessages), - lt_count2, - QString::number(state.totalMessages) - ); - QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now, - lt_count1, - QString::number(state.currentChunk + 1), - lt_count2, - QString::number(state.totalChunks) - ); +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); + } +} - const auto partString = state.totalChunks <= 1 ? messagesString : (messagesString + " • " + chunkString); - - QString status; - if (state.state == ForwardState::State::Preparing) { - status = tr::ayu_AyuForwardStatusPreparing(tr::now); - } else if (state.state == ForwardState::State::Downloading) { - status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now); - } else if (state.state == ForwardState::State::Sending) { - status = tr::ayu_AyuForwardStatusForwarding(tr::now); - } else { // ForwardState::State::Finished - status = tr::ayu_AyuForwardStatusFinished(tr::now); - } +std::pair stateName(const PeerId &id) { + const auto fwState = forwardStates.find(id); - return status.append("\n").append(partString);// + "\n" + partString; - } + if (fwState == forwardStates.end()) { + return std::make_pair(QString(), QString()); + } - void ForwardState::updateBottomBar(not_null session, const PeerData *peer, const State &st) { - state = st; - forwardStates[peer->id] = *this; - session->changes().peerUpdated(session->data().peer(peer->id), Data::PeerUpdate::Flag::Rights); - } + const auto state = fwState->second; + + QString messagesString = tr::ayu_AyuForwardStatusSentCount(tr::now, + lt_count1, + QString::number(state->sentMessages), + lt_count2, + QString::number(state->totalMessages) + + ); + + QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now, + lt_count1, + QString::number(state->currentChunk + 1), + lt_count2, + QString::number(state->totalChunks) + + ); + + const auto partString = state->totalChunks <= 1 ? messagesString : (messagesString + " • " + chunkString); + + QString status; + + if (state->state == ForwardState::State::Preparing) { + status = tr::ayu_AyuForwardStatusPreparing(tr::now); + } else if (state->state == ForwardState::State::Downloading) { + status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now); + } else if (state->state == ForwardState::State::Sending) { + status = tr::ayu_AyuForwardStatusForwarding(tr::now); + } else { + // ForwardState::State::Finished + status = tr::ayu_AyuForwardStatusFinished(tr::now); + } - static Ui::PreparedList prepareMedia(not_null session, const std::vector> &items, int &i) { - const auto prepare = [&] (not_null media){ - auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media)); - Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, 1280); - return prepared; - }; + return std::make_pair(status, partString); +} +void ForwardState::updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st) { + state = st; - Ui::PreparedList list; - const auto startItem = items[i]; - const auto media = startItem->media(); - const auto groupId = startItem->groupId(); - - list.files.emplace_back(prepare(media)); - - if (!groupId.value) { - return list; - } - - for (int k = i + 1; k < items.size(); ++k) { - const auto nextItem = items[k]; - if (nextItem->groupId() != groupId) { - break; - } - - if (const auto nextMedia = nextItem->media()) { - list.files.emplace_back(prepare(nextMedia)); - i = k; - } - } - - return list; - } - - void sendMedia( - not_null session, - Ui::PreparedList &&list, - not_null primaryMedia, - Api::MessageToSend &&message, - uint64 newGroupId) { + session.changes().peerUpdated(session.data().peer(*peer), Data::PeerUpdate::Flag::Rights); +} - if (const auto document = primaryMedia->document()) { - if (document->isGifv() || document->sticker()) { - AyuSync::sendGifOrStickSync(session, message, document); - return; - } - } +static Ui::PreparedList prepareMedia(not_null session, + const std::vector> &items, + int &i) { + const auto prepare = [&](not_null media) + { + auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media)); + Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, 1280); + return prepared; + }; - const auto mediaType = [&] { - if (const auto document = primaryMedia->document()) { - if (document->isVoiceMessage()) { - return SendMediaType::Audio; - } else if (document->isVideoMessage()) { - return SendMediaType::Round; - } else { - return SendMediaType::File; - } + const auto startItem = items[i]; + const auto media = startItem->media(); + const auto groupId = startItem->groupId(); + + Ui::PreparedList list; + list.files.emplace_back(prepare(media)); + + if (!groupId.value) { + return list; + } + + for (int k = i + 1; k < items.size(); ++k) { + const auto nextItem = items[k]; + if (nextItem->groupId() != groupId) { + break; + } + if (const auto nextMedia = nextItem->media()) { + list.files.emplace_back(prepare(nextMedia)); + i = k; + } + } + return list; +} + +void sendMedia( + not_null session, + std::shared_ptr bundle, + not_null primaryMedia, + Api::MessageToSend &&message) { + if (const auto document = primaryMedia->document(); document && document->sticker()) { + AyuSync::sendStickerSync(session, message, document); + return; + } + + auto mediaType = [&] + { + if (const auto document = primaryMedia->document()) { + if (document->isVoiceMessage()) { + return SendMediaType::Audio; + } else if (document->isVideoMessage()) { + return SendMediaType::Round; + } else { + return SendMediaType::File; } - 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(); - group->groupId = newGroupId; + QFile file(path); + 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()) { + file.close(); + AyuSync::sendVoiceSync(session, + data, + primaryMedia->document()->duration(), + mediaType == SendMediaType::Round, + 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; + } + + for (auto &group : bundle->groups) { + AyuSync::sendDocumentSync( session, - std::move(list), + group, mediaType, std::move(message.textWithTags), - group, message.action); - } + } +} + +bool isAyuForwardNeeded(const std::vector> &items) { + for (const auto &item : items) { + if (isAyuForwardNeeded(item)) { + return true; + } + } + return false; +} + +bool isAyuForwardNeeded(not_null item) { + if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) { + return true; + } + return false; +} + +bool isFullAyuForwardNeeded(not_null item) { + return item->from()->isAyuNoForwards() || item->history()->peer->isAyuNoForwards(); +} + +struct ForwardChunk +{ + bool isAyuForwardNeeded; + std::vector> items; +}; + +void intelligentForward( + not_null session, + const Api::SendAction &action, + Data::ResolvedForwardDraft draft) { + const auto history = action.history; + history->setForwardDraft(action.replyTo.topicRootId, {}); + + const auto items = draft.items; + const auto peer = history->peer; + + auto chunks = std::vector(); + auto currentArray = std::vector>(); + + auto currentChunk = ForwardChunk({ + .isAyuForwardNeeded = isAyuForwardNeeded(items[0]), + .items = currentArray + }); + + for (const auto &item : items) { + if (isAyuForwardNeeded(item) != currentChunk.isAyuForwardNeeded) { + currentChunk.items = currentArray; + chunks.push_back(currentChunk); + + currentArray = std::vector>(); + + currentChunk = ForwardChunk({ + .isAyuForwardNeeded = isAyuForwardNeeded(item), + .items = currentArray + }); + } + currentArray.push_back(item); + } + + currentChunk.items = currentArray; + chunks.push_back(currentChunk); + + auto state = std::make_shared(chunks.size()); + forwardStates[peer->id] = state; - bool isAyuForwardNeeded(const std::vector> &items) { - for (const auto &item : items) { - if (isAyuForwardNeeded(item)) { - return true; - } - } - return false; - } + for (const auto &chunk : chunks) { + if (chunk.isAyuForwardNeeded) { + forwardMessages(session, action, true, Data::ResolvedForwardDraft(chunk.items)); + } else { + state->totalMessages = chunk.items.size(); + state->sentMessages = 0; + state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending); - bool isAyuForwardNeeded(not_null item) { - if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) { - return true; - } - return false; - } + AyuSync::forwardMessagesSync(session, chunk.items, action, draft.options); - bool isFullAyuForwardNeeded(not_null peer, not_null history) { - return peer->isAyuNoForwards() || history->peer->isAyuNoForwards(); - } + state->sentMessages = state->totalMessages; - struct ForwardChunk { - bool isAyuForwardNeeded; - std::vector> items; - }; + state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished); + } + state->currentChunk++; + } - void intelligentForward(const PeerData* peer, const std::vector> &items, not_null session, not_null history, const Api::SendAction &action) { - history->setForwardDraft(action.replyTo.topicRootId, {}); + state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished); +} - auto chunks = std::vector(); +void forwardMessages( + not_null 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; - auto currentArray = std::vector>(); - auto currentChunk = ForwardChunk({ - .isAyuForwardNeeded = isAyuForwardNeeded(items[0]), - .items = currentArray - }); + history->setForwardDraft(action.replyTo.topicRootId, {}); - for (const auto &item : items) { - if (isAyuForwardNeeded(item) != currentChunk.isAyuForwardNeeded) { - currentChunk.items = currentArray; - chunks.push_back(currentChunk); + std::shared_ptr state; - currentArray = std::vector>(); - currentChunk = ForwardChunk({ - .isAyuForwardNeeded = isAyuForwardNeeded(item), - .items = currentArray - }); - } - currentArray.push_back(item); - } + if (forwardState) { + state = std::make_shared(*forwardStates[peer->id]); + } else { + state = std::make_shared(1); + } - currentChunk.items = currentArray; - chunks.push_back(currentChunk); + forwardStates[peer->id] = state; - auto state = std::make_shared(chunks.size()); - forwardStates[peer->id] = *state; + std::unordered_map groupIds; - for (const auto &chunk : chunks) { - if (chunk.isAyuForwardNeeded) { - forwardMessages(peer, chunk.items, session, history, action, true); - } else { - state->totalMessages = chunk.items.size(); - state->sentMessages = 0; - state->updateBottomBar(session, peer, ForwardState::State::Sending); - - AyuSync::forwardMessagesSync(session, chunk.items, action); - - state->sentMessages = state->totalMessages; - state->updateBottomBar(session, peer, ForwardState::State::Finished); - } - - state->currentChunk++; - } - state->updateBottomBar(session, peer, ForwardState::State::Finished); - - } - - void forwardMessages(const PeerData *peer, std::vector > items, - not_null session, - not_null history, const Api::SendAction &action, bool forwardState) { - history->setForwardDraft(action.replyTo.topicRootId, {}); - - ForwardState state; - if (forwardState) { - state = forwardStates[peer->id]; - } else { - state = ForwardState(1); - } - - forwardStates[peer->id] = state; - - std::unordered_map groupIds; + std::vector> toBeDownloaded; - std::vector > toBeDownloaded; + for (const auto item : items) { + if (mediaDownloadable(item->media())) { + toBeDownloaded.push_back(item); + } - for (const auto item: items) { - if (item->media()) { - toBeDownloaded.push_back(item); - } - if (item->groupId()) { - const auto currentId = groupIds.find(item->groupId().value); - if (currentId == groupIds.end()) { - groupIds[item->groupId().value] = base::RandomValue(); - } - } - } - if (toBeDownloaded.size()) { - state.updateBottomBar(session, peer, ForwardState::State::Downloading); - AyuSync::loadDocuments(session, toBeDownloaded); - } + if (item->groupId()) { + const auto currentId = groupIds.find(item->groupId().value); - state.totalMessages = items.size(); - state.sentMessages = 0; - state.updateBottomBar(session, peer, ForwardState::State::Sending); - - for (int i = 0; i < items.size(); i++) { - const auto item = items[i]; + if (currentId == groupIds.end()) { + groupIds[item->groupId().value] = base::RandomValue(); + } + } + } + state->totalMessages = items.size(); + if (toBeDownloaded.size()) { + state->state = ForwardState::State::Downloading; + state->updateBottomBar(*session, &peer->id, ForwardState::State::Downloading); + AyuSync::loadDocuments(session, toBeDownloaded); + } - if (state.stopRequested) { - state.updateBottomBar(session, peer, ForwardState::State::Finished); - return; - } - - auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id))); - - 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; + state->sentMessages = 0; + state->updateBottomBar(*session, &peer->id, ForwardState::State::Sending); - if (const auto media = item->media()) { - if (const auto poll = media->poll()) { - // need to implement extract of the text - continue; - } - sendMedia(session, prepareMedia(session, items, i), media, std::move(message), newGroupId); - } else { - AyuSync::sendMessageSync(session, message); - } - state.sentMessages += 1; - } - state.updateBottomBar(session, peer, ForwardState::State::Finished); - } + for (int i = 0; i < items.size(); i++) { + const auto item = items[i]; + + if (state->stopRequested) { + state->updateBottomBar(*session, &peer->id, ForwardState::State::Finished); + return; + } + + auto extractedText = extractText(item); + if (extractedText.empty() && !mediaDownloadable(item->media())) { + continue; + } + + auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id))); + message.action.replyTo = action.replyTo; + + if (draft.options != Data::ForwardOptions::NoNamesAndCaptions) { + message.textWithTags = extractedText; + } + + 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 diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h index 2f9537671c..f63df19eec 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h @@ -1,44 +1,47 @@ - #pragma once - #include
#include "history/history.h" - namespace AyuForward { bool isForwarding(const PeerId &id); -QString stateName(const PeerId &id); +void cancelForward(const PeerId &id, const Main::Session &session); +std::pair stateName(const PeerId &id); -class ForwardState { +class ForwardState +{ public: - enum class State { + enum class State + { Preparing, Downloading, Sending, Finished }; + void updateBottomBar(const Main::Session &session, const PeerId *peer, const State &st); - void updateBottomBar(not_null session, const PeerData *peer, const State &st); + int totalChunks; + int currentChunk; + int totalMessages; + int sentMessages; - int totalChunks; - int currentChunk; - int totalMessages; - int sentMessages; - - State state = State::Preparing; - bool stopRequested = false; + State state = State::Preparing; + bool stopRequested = false; }; +bool isAyuForwardNeeded(const std::vector> &items); +bool isAyuForwardNeeded(not_null item); +bool isFullAyuForwardNeeded(not_null item); +void intelligentForward( + not_null session, + const Api::SendAction &action, + Data::ResolvedForwardDraft draft); +void forwardMessages( + not_null session, + const Api::SendAction &action, + bool forwardState, + Data::ResolvedForwardDraft draft); -bool isAyuForwardNeeded(const std::vector> &items); -bool isAyuForwardNeeded(not_null item); -bool isFullAyuForwardNeeded(not_null peer, not_null history); -void intelligentForward(const PeerData* peer, - const std::vector> &item, not_null session, not_null history, const Api::SendAction &action); -void forwardMessages(const PeerData* peer, std::vector> item, not_null session, not_null history, const Api::SendAction &action, bool forwardState); - - -} \ No newline at end of file +} diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp index cf2ccacd8e..f71148c3a4 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp @@ -1,4 +1,3 @@ - #include "ayu_sync.h" #include "api/api_sending.h" @@ -13,11 +12,12 @@ #include "main/main_session.h" #include "storage/file_download_mtproto.h" - class TimedCountDownLatch { public: - explicit TimedCountDownLatch(int count) : count_(count) {} + explicit TimedCountDownLatch(int count) + : count_(count) { + } void countDown() { std::unique_lock lock(mutex_); @@ -45,7 +45,7 @@ private: namespace AyuSync { -QString pathForSave(not_null session) { +QString pathForSave(not_null session) { const auto path = Core::App().settings().downloadPath(); if (path.isEmpty()) { return File::DefaultDownloadPath(session); @@ -56,8 +56,7 @@ QString pathForSave(not_null session) { return path; } - -QString filePath(not_null session, const Data::Media *media) { +QString filePath(not_null session, const Data::Media *media) { if (const auto document = media->document()) { if (!document->filename().isEmpty()) { return pathForSave(session) + media->document()->filename(); @@ -79,25 +78,21 @@ QString filePath(not_null session, const Data::Media *media) { return QString(); } -qint64 fileSize(not_null item) { + +qint64 fileSize(not_null item) { if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) { QFile file(path); if (file.exists()) { - return file.size(); + auto size = file.size(); + return size; } } return 0; } -void loadDocuments(not_null session, const std::vector> &items) { +void loadDocuments(not_null session, const std::vector> &items) { for (const auto &item : items) { if (const auto data = item->media()->document()) { - - if (data->isGifv()) { - // no need to download it - continue; - } - const auto size = fileSize(item); if (size == data->size) { @@ -109,9 +104,7 @@ void loadDocuments(not_null session, const std::vectormedia()->photo()) { if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) { continue; @@ -121,49 +114,57 @@ void loadDocuments(not_null session, const std::vector session, DocumentData *data, not_null item) { +void loadDocumentSync(not_null session, DocumentData *data, not_null item) { auto latch = std::make_shared(1); auto lifetime = std::make_shared(); data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media())); - session->downloaderTaskFinished() - | rpl::filter([&]{ - return item->media()->document()->status == FileDownloadFailed || fileSize(item) == data->size; - }) | rpl::start_with_next([&]() mutable{ - latch->countDown(); - base::take(lifetime)->destroy(); - }, *lifetime); + rpl::single() | rpl::then( + session->downloaderTaskFinished() + ) | rpl::filter([&] + { + return data->status == FileDownloadFailed || fileSize(item) == data->size; + }) | rpl::start_with_next([&]() mutable + { + latch->countDown(); + base::take(lifetime)->destroy(); + }, + *lifetime); latch->await(std::chrono::minutes(5)); } -void forwardMessagesSync(not_null session, - const std::vector> &items, - const ApiWrap::SendAction &action) { +void forwardMessagesSync(not_null session, + const std::vector> &items, + const ApiWrap::SendAction &action, + Data::ForwardOptions options) { auto latch = std::make_shared(1); - crl::on_main([=, &latch]{ - session->api().forwardMessages(Data::ResolvedForwardDraft(items), action, [&]{ - latch->countDown(); - }); + crl::on_main([=, &latch] + { + session->api().forwardMessages(Data::ResolvedForwardDraft(items, options), + action, + [&] + { + latch->countDown(); + }); }); latch->await(std::chrono::minutes(1)); } - -void loadPhotoSync(not_null session, const std::pair, FullMsgId> &photo) { +void loadPhotoSync(not_null session, const std::pair, FullMsgId> &photo) { const auto folderPath = pathForSave(session); const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath; const auto path = downloadPath.isEmpty() - ? File::DefaultDownloadPath(session) - : downloadPath == FileDialog::Tmp() - ? session->local().tempDirectory() - : downloadPath; + ? File::DefaultDownloadPath(session) + : downloadPath == FileDialog::Tmp() + ? session->local().tempDirectory() + : downloadPath; if (path.isEmpty()) { return; } @@ -177,15 +178,18 @@ void loadPhotoSync(not_null session, const std::pairwanted(Data::PhotoSize::Large, photo.second); - const auto finalCheck = [=]{ + const auto finalCheck = [=] + { return !photo.first->loading(); }; - const auto saveToFiles = [=]{ + const auto saveToFiles = [=] + { QDir directory(path); const auto dir = directory.absolutePath(); 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); }; @@ -195,60 +199,69 @@ void loadPhotoSync(not_null session, const std::pairdownloaderTaskFinished() | rpl::filter([&]{ + session->downloaderTaskFinished() | rpl::filter([&] + { return finalCheck(); - }) | rpl::start_with_next([&]() mutable{ - saveToFiles(); - latch->countDown(); - base::take(lifetime)->destroy(); - },*lifetime); + }) | rpl::start_with_next([&]() mutable + { + saveToFiles(); + latch->countDown(); + base::take(lifetime)->destroy(); + }, + *lifetime); } latch->await(std::chrono::minutes(5)); } -void sendMessageSync(not_null session, Api::MessageToSend &message) { - crl::on_main([=, &message] { - // we cannot send events to objects - // owned by a different thread - // because sendMessage updates UI too +void sendMessageSync(not_null session, Api::MessageToSend &message) { + crl::on_main([=, &message] + { + // we cannot send events to objects + // owned by a different thread + // because sendMessage updates UI too - session->api().sendMessage(std::move(message)); - }); + session->api().sendMessage(std::move(message)); + }); waitForMsgSync(session, message.action); } -void waitForMsgSync(not_null session, const Api::SendAction &action) { +void waitForMsgSync(not_null session, const Api::SendAction &action) { auto latch = std::make_shared(1); auto lifetime = std::make_shared(); 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; - }) | rpl::start_with_next([&]{ - latch->countDown(); - base::take(lifetime)->destroy(); - },*lifetime); + }) | rpl::start_with_next([&] + { + latch->countDown(); + base::take(lifetime)->destroy(); + }, + *lifetime); latch->await(std::chrono::minutes(2)); } -void sendDocumentSync(not_null session, - Ui::PreparedList &&list, +void sendDocumentSync(not_null session, + Ui::PreparedGroup &group, SendMediaType type, TextWithTags &&caption, - const std::shared_ptr &album, const Api::SendAction &action) { - - const auto size = list.files.size(); + const auto size = group.list.files.size(); auto latch = std::make_shared(size); auto lifetime = std::make_shared(); - crl::on_main([=, list = std::move(list), caption = std::move(caption)]() mutable{ - session->api().sendFiles(std::move(list), type, std::move(caption), album, action); + auto groupId = std::make_shared(); + groupId->groupId = base::RandomValue(); + + 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 session, rpl::merge( session->uploader().documentReady(), session->uploader().photoReady() - ) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto){ - return docOrPhoto.fullId.peer == action.history->peer->id; - }) | rpl::start_with_next([&]{ - latch->countDown(); - }, *lifetime); + ) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto) + { + return docOrPhoto.fullId.peer == action.history->peer->id; + }) | rpl::start_with_next([&] + { + latch->countDown(); + }, + *lifetime); latch->await(std::chrono::minutes(5 * size)); base::take(lifetime)->destroy(); } -void sendGifOrStickSync(not_null session, - Api::MessageToSend &message, - not_null document) { +void sendStickerSync(not_null session, + Api::MessageToSend &message, + not_null document) { auto &action = message.action; - crl::on_main([&] { - Api::SendExistingDocument(std::move(message), document, std::nullopt); + crl::on_main([&] + { + Api::SendExistingDocument(std::move(message), document, std::nullopt); }); - waitForMsgSync(session, action); } +void sendVoiceSync(not_null session, + const QByteArray &data, + int64_t duration, + bool video, + const Api::SendAction &action) { + crl::on_main([&] + { + session->api().sendVoiceMessage(data, + QVector(), + duration, + video, + action); + }); + waitForMsgSync(session, action); +} } // namespace AyuSync diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h index 3a6e14816a..51e8d66740 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h @@ -24,28 +24,33 @@ #include "storage/file_download.h" #include "storage/storage_account.h" - namespace AyuSync { -QString pathForSave(not_null session); -QString filePath(not_null session, const Data::Media *media); -void loadDocuments(not_null session, const std::vector > &items); +QString pathForSave(not_null session); +QString filePath(not_null session, const Data::Media *media); +void loadDocuments(not_null session, const std::vector> &items); bool isMediaDownloadable(Data::Media *media); -void sendMessageSync(not_null session, Api::MessageToSend &message); +void sendMessageSync(not_null session, Api::MessageToSend &message); -void sendDocumentSync(not_null session, - Ui::PreparedList &&list, - SendMediaType type, - TextWithTags &&caption, - const std::shared_ptr &album, - const Api::SendAction &action); - -void sendGifOrStickSync(not_null session, - Api::MessageToSend &message, - not_null document); -void waitForMsgSync(not_null session, const Api::SendAction &action); -void loadPhotoSync(not_null session, const std::pair, FullMsgId> &photos); -void loadDocumentSync(not_null session, DocumentData *data, not_null item); -void forwardMessagesSync(not_null session, const std::vector > &items, const ApiWrap::SendAction &action); +void sendDocumentSync(not_null session, + Ui::PreparedGroup &group, + SendMediaType type, + TextWithTags &&caption, + const Api::SendAction &action); +void sendStickerSync(not_null session, + Api::MessageToSend &message, + not_null document); +void waitForMsgSync(not_null session, const Api::SendAction &action); +void loadPhotoSync(not_null session, const std::pair, FullMsgId> &photos); +void loadDocumentSync(not_null session, DocumentData *data, not_null item); +void forwardMessagesSync(not_null session, + const std::vector> &items, + const ApiWrap::SendAction &action, + Data::ForwardOptions options); +void sendVoiceSync(not_null session, + const QByteArray &data, + int64_t duration, + bool video, + const Api::SendAction &action); } diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp index b2c3dd3817..80f88f46d2 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp @@ -742,3 +742,34 @@ ID getUserIdFromPackId(uint64 id) { return ownerId; } + +TextWithTags extractText(not_null 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; +} diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h index 7fc169cde8..9c000466c0 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h @@ -12,6 +12,8 @@ #include "dialogs/dialogs_main_list.h" #include "info/profile/info_profile_badge.h" #include "main/main_domain.h" +#include "data/data_poll.h" +#include "data/data_media_types.h" using UsernameResolverCallback = Fn; @@ -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); ID getUserIdFromPackId(uint64 id); + +TextWithTags extractText(not_null item); +bool mediaDownloadable(Data::Media* media); \ No newline at end of file diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index fb82472a0b..abf2fb7195 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -63,6 +63,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include "ayu/features/forward/ayu_forward.h" + namespace { using namespace Ui::Text; @@ -1280,6 +1282,69 @@ std::unique_ptr BoostsToLiftWriteRestriction( return result; } +std::unique_ptr AyuForwardWriteRestriction( + not_null 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( + parent, + QString(), + st::historyComposeButton); + const auto raw = result.get(); + + const auto title = CreateChild( + raw, + pair.first, + st::frozenRestrictionTitle); + title->setTextColorOverride(st::historyComposeButton.color->c); + + + title->setAttribute(Qt::WA_TransparentForMouseEvents); + title->show(); + const auto subtitle = CreateChild( + 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 FrozenWriteRestriction( not_null parent, std::shared_ptr show, diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 4d7ef52b48..fc8aaea19c 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -202,7 +202,10 @@ enum class FrozenWriteRestrictionType { std::shared_ptr show, FrozenWriteRestrictionType type, FreezeInfoStyleOverride st = {}); - +std::unique_ptr AyuForwardWriteRestriction( + not_null parent, + const PeerId &peer, + const Main::Session &session); void SelectTextInFieldWithMargins( not_null field, const TextSelection &selection); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 7cc5c39e0a..2ad833a8fd 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -75,7 +75,7 @@ enum class ChannelDataFlag : uint64 { PaidMessagesAvailable = (1ULL << 37), AutoTranslation = (1ULL << 38), - AyuNoForwards = (1ULL << 60), + AyuNoForwards = (1ULL << 63), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 15114fc098..f7384cb6dc 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -24,7 +24,7 @@ enum class ChatDataFlag { CanSetUsername = (1 << 7), NoForwards = (1 << 8), - AyuNoForwards = (1 << 20), + AyuNoForwards = (1 << 31), }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; using ChatDataFlags = base::flags; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 4bce0f1e63..f337c74136 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -186,7 +186,7 @@ SendError RestrictionError( ChatRestriction restriction) { if (AyuForward::isForwarding(peer->id)) { return SendError({ - .text = AyuForward::stateName(peer->id) + .text = AyuForward::stateName(peer->id).first + "\n" + AyuForward::stateName(peer->id).second, }); } using Flag = ChatRestriction; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 0a154a7e2c..14a99b78d9 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -353,7 +353,7 @@ enum class MessageFlag : uint64 { HideDisplayDate = (1ULL << 51), - AyuNoForwards = (1ULL << 60), + AyuNoForwards = (1ULL << 63), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b2113c07aa..f976be3924 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6758,7 +6758,9 @@ void HistoryWidget::updateSendRestriction() { return; } _sendRestrictionKey = restriction.text; - if (!restriction) { + if (AyuForward::isForwarding(_peer->id)) { + _sendRestriction = AyuForwardWriteRestriction(this, _peer->id, session()); + } else if (!restriction) { _sendRestriction = nullptr; } else if (restriction.frozen) { const auto show = controller()->uiShow(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 2a458c24e9..6c1649770b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -563,7 +563,9 @@ bool MainWidget::setForwardDraft( .forward = &items, .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); return false; }