mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Support replies to stories layout in messages.
This commit is contained in:
parent
b195ec4fd5
commit
2e6790c45c
15 changed files with 522 additions and 39 deletions
|
@ -285,6 +285,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_edit_message_text" = "New message text...";
|
||||
"lng_deleted" = "Deleted Account";
|
||||
"lng_deleted_message" = "Deleted message";
|
||||
"lng_deleted_story" = "Deleted story";
|
||||
"lng_pinned_message" = "Pinned message";
|
||||
"lng_pinned_previous" = "Previous message";
|
||||
"lng_pinned_unpin_sure" = "Would you like to unpin this message?";
|
||||
|
|
|
@ -272,6 +272,38 @@ void Changes::entryRemoved(not_null<Dialogs::Entry*> entry) {
|
|||
_entryChanges.drop(entry);
|
||||
}
|
||||
|
||||
void Changes::storyUpdated(
|
||||
not_null<Story*> story,
|
||||
StoryUpdate::Flags flags) {
|
||||
const auto drop = (flags & StoryUpdate::Flag::Destroyed);
|
||||
_storyChanges.updated(story, flags, drop);
|
||||
if (!drop) {
|
||||
scheduleNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<StoryUpdate> Changes::storyUpdates(
|
||||
StoryUpdate::Flags flags) const {
|
||||
return _storyChanges.updates(flags);
|
||||
}
|
||||
|
||||
rpl::producer<StoryUpdate> Changes::storyUpdates(
|
||||
not_null<Story*> story,
|
||||
StoryUpdate::Flags flags) const {
|
||||
return _storyChanges.updates(story, flags);
|
||||
}
|
||||
|
||||
rpl::producer<StoryUpdate> Changes::storyFlagsValue(
|
||||
not_null<Story*> story,
|
||||
StoryUpdate::Flags flags) const {
|
||||
return _storyChanges.flagsValue(story, flags);
|
||||
}
|
||||
|
||||
rpl::producer<StoryUpdate> Changes::realtimeStoryUpdates(
|
||||
StoryUpdate::Flag flag) const {
|
||||
return _storyChanges.realtimeUpdates(flag);
|
||||
}
|
||||
|
||||
void Changes::scheduleNotifications() {
|
||||
if (!_notify) {
|
||||
_notify = true;
|
||||
|
@ -291,6 +323,7 @@ void Changes::sendNotifications() {
|
|||
_messageChanges.sendNotifications();
|
||||
_entryChanges.sendNotifications();
|
||||
_topicChanges.sendNotifications();
|
||||
_storyChanges.sendNotifications();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -38,6 +38,7 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
|
|||
namespace Data {
|
||||
|
||||
class ForumTopic;
|
||||
class Story;
|
||||
|
||||
struct NameUpdate {
|
||||
NameUpdate(
|
||||
|
@ -215,6 +216,24 @@ struct EntryUpdate {
|
|||
|
||||
};
|
||||
|
||||
struct StoryUpdate {
|
||||
enum class Flag : uint32 {
|
||||
None = 0,
|
||||
|
||||
Edited = (1U << 0),
|
||||
Destroyed = (1U << 1),
|
||||
NewAdded = (1U << 2),
|
||||
|
||||
LastUsedBit = (1U << 2),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
not_null<Story*> story;
|
||||
Flags flags = 0;
|
||||
|
||||
};
|
||||
|
||||
class Changes final {
|
||||
public:
|
||||
explicit Changes(not_null<Main::Session*> session);
|
||||
|
@ -298,6 +317,20 @@ public:
|
|||
EntryUpdate::Flag flag) const;
|
||||
void entryRemoved(not_null<Dialogs::Entry*> entry);
|
||||
|
||||
void storyUpdated(
|
||||
not_null<Story*> story,
|
||||
StoryUpdate::Flags flags);
|
||||
[[nodiscard]] rpl::producer<StoryUpdate> storyUpdates(
|
||||
StoryUpdate::Flags flags) const;
|
||||
[[nodiscard]] rpl::producer<StoryUpdate> storyUpdates(
|
||||
not_null<Story*> story,
|
||||
StoryUpdate::Flags flags) const;
|
||||
[[nodiscard]] rpl::producer<StoryUpdate> storyFlagsValue(
|
||||
not_null<Story*> story,
|
||||
StoryUpdate::Flags flags) const;
|
||||
[[nodiscard]] rpl::producer<StoryUpdate> realtimeStoryUpdates(
|
||||
StoryUpdate::Flag flag) const;
|
||||
|
||||
void sendNotifications();
|
||||
|
||||
private:
|
||||
|
@ -348,6 +381,7 @@ private:
|
|||
Manager<ForumTopic, TopicUpdate> _topicChanges;
|
||||
Manager<HistoryItem, MessageUpdate> _messageChanges;
|
||||
Manager<Dialogs::Entry, EntryUpdate> _entryChanges;
|
||||
Manager<Story, StoryUpdate> _storyChanges;
|
||||
|
||||
bool _notify = false;
|
||||
|
||||
|
|
|
@ -8,11 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_stories.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
// #TODO stories testing
|
||||
#include "data/data_user.h"
|
||||
|
@ -23,6 +27,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxResolveTogether = 100;
|
||||
|
||||
using UpdateFlag = StoryUpdate::Flag;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool StoriesList::unread() const {
|
||||
|
@ -56,6 +64,10 @@ StoryId Story::id() const {
|
|||
return _id;
|
||||
}
|
||||
|
||||
FullStoryId Story::fullId() const {
|
||||
return { _peer->id, _id };
|
||||
}
|
||||
|
||||
TimeId Story::date() const {
|
||||
return _date;
|
||||
}
|
||||
|
@ -74,6 +86,45 @@ DocumentData *Story::document() const {
|
|||
return result ? result->get() : nullptr;
|
||||
}
|
||||
|
||||
bool Story::hasReplyPreview() const {
|
||||
return v::match(_media.data, [](not_null<PhotoData*> photo) {
|
||||
return !photo->isNull();
|
||||
}, [](not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail();
|
||||
});
|
||||
}
|
||||
|
||||
Image *Story::replyPreview() const {
|
||||
return v::match(_media.data, [&](not_null<PhotoData*> photo) {
|
||||
return photo->getReplyPreview(
|
||||
Data::FileOriginStory(_peer->id, _id),
|
||||
_peer,
|
||||
false);
|
||||
}, [&](not_null<DocumentData*> document) {
|
||||
return document->getReplyPreview(
|
||||
Data::FileOriginStory(_peer->id, _id),
|
||||
_peer,
|
||||
false);
|
||||
});
|
||||
}
|
||||
|
||||
TextWithEntities Story::inReplyText() const {
|
||||
const auto type = u"Story"_q;
|
||||
return _caption.text.isEmpty()
|
||||
? Ui::Text::PlainLink(type)
|
||||
: tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
Ui::Text::PlainLink(type),
|
||||
Ui::Text::WithEntities),
|
||||
lt_caption,
|
||||
_caption,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
void Story::setPinned(bool pinned) {
|
||||
_pinned = pinned;
|
||||
}
|
||||
|
@ -110,6 +161,10 @@ Session &Stories::owner() const {
|
|||
return *_owner;
|
||||
}
|
||||
|
||||
Main::Session &Stories::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
||||
void Stories::apply(const MTPDupdateStories &data) {
|
||||
pushToFront(parse(data.vstories()));
|
||||
_allChanged.fire({});
|
||||
|
@ -137,10 +192,7 @@ StoriesList Stories::parse(const MTPUserStories &stories) {
|
|||
}, [&](const MTPDstoryItemSkipped &data) {
|
||||
result.ids.push_back(data.vid().v);
|
||||
}, [&](const MTPDstoryItemDeleted &data) {
|
||||
_deleted.emplace(FullStoryId{
|
||||
.peer = peerFromUser(userId),
|
||||
.story = data.vid().v,
|
||||
});
|
||||
applyDeleted({ peerFromUser(userId), data.vid().v });
|
||||
--result.total;
|
||||
});
|
||||
}
|
||||
|
@ -189,6 +241,35 @@ Story *Stories::parse(not_null<PeerData*> peer, const MTPDstoryItem &data) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void Stories::updateDependentMessages(not_null<Data::Story*> story) {
|
||||
const auto i = _dependentMessages.find(story);
|
||||
if (i != end(_dependentMessages)) {
|
||||
for (const auto &dependent : i->second) {
|
||||
dependent->updateDependencyItem();
|
||||
}
|
||||
}
|
||||
session().changes().storyUpdated(
|
||||
story,
|
||||
Data::StoryUpdate::Flag::Edited);
|
||||
}
|
||||
|
||||
void Stories::registerDependentMessage(
|
||||
not_null<HistoryItem*> dependent,
|
||||
not_null<Data::Story*> dependency) {
|
||||
_dependentMessages[dependency].emplace(dependent);
|
||||
}
|
||||
|
||||
void Stories::unregisterDependentMessage(
|
||||
not_null<HistoryItem*> dependent,
|
||||
not_null<Data::Story*> dependency) {
|
||||
const auto i = _dependentMessages.find(dependency);
|
||||
if (i != end(_dependentMessages)) {
|
||||
if (i->second.remove(dependent) && i->second.empty()) {
|
||||
_dependentMessages.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::loadMore() {
|
||||
if (_loadMoreRequestId || _allLoaded) {
|
||||
return;
|
||||
|
@ -216,6 +297,119 @@ void Stories::loadMore() {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void Stories::sendResolveRequests() {
|
||||
if (!_resolveRequests.empty()) {
|
||||
return;
|
||||
}
|
||||
struct Prepared {
|
||||
QVector<MTPint> ids;
|
||||
std::vector<Fn<void()>> callbacks;
|
||||
};
|
||||
auto leftToSend = kMaxResolveTogether;
|
||||
auto byPeer = base::flat_map<PeerId, Prepared>();
|
||||
for (auto i = begin(_resolves); i != end(_resolves);) {
|
||||
auto &[peerId, ids] = *i;
|
||||
auto &prepared = byPeer[peerId];
|
||||
for (auto &[storyId, callbacks] : ids) {
|
||||
prepared.ids.push_back(MTP_int(storyId));
|
||||
prepared.callbacks.insert(
|
||||
end(prepared.callbacks),
|
||||
std::make_move_iterator(begin(callbacks)),
|
||||
std::make_move_iterator(end(callbacks)));
|
||||
if (!--leftToSend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto sending = int(prepared.ids.size());
|
||||
if (sending == ids.size()) {
|
||||
i = _resolves.erase(i);
|
||||
if (!leftToSend) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ids.erase(begin(ids), begin(ids) + sending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto api = &_owner->session().api();
|
||||
for (auto &entry : byPeer) {
|
||||
const auto peerId = entry.first;
|
||||
auto &prepared = entry.second;
|
||||
const auto finish = [=, ids = prepared.ids](mtpRequestId id) {
|
||||
for (const auto &id : ids) {
|
||||
finalizeResolve({ peerId, id.v });
|
||||
}
|
||||
if (auto callbacks = _resolveRequests.take(id)) {
|
||||
for (const auto &callback : *callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
if (_resolveRequests.empty() && !_resolves.empty()) {
|
||||
crl::on_main(&session(), [=] { sendResolveRequests(); });
|
||||
}
|
||||
};
|
||||
const auto user = _owner->session().data().peer(peerId)->asUser();
|
||||
if (!user) {
|
||||
_resolveRequests[0] = std::move(prepared.callbacks);
|
||||
finish(0);
|
||||
continue;
|
||||
}
|
||||
const auto requestId = api->request(MTPstories_GetStoriesByID(
|
||||
user->inputUser,
|
||||
MTP_vector<MTPint>(std::move(prepared.ids))
|
||||
)).done([=](const MTPstories_Stories &result, mtpRequestId id) {
|
||||
owner().processUsers(result.data().vusers());
|
||||
processResolvedStories(user, result.data().vstories().v);
|
||||
finish(id);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId id) {
|
||||
finish(id);
|
||||
}).send();
|
||||
_resolveRequests.emplace(requestId, std::move(prepared.callbacks));
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::processResolvedStories(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPStoryItem> &list) {
|
||||
for (const auto &item : list) {
|
||||
item.match([&](const MTPDstoryItem &data) {
|
||||
[[maybe_unused]] const auto story = parse(peer, data);
|
||||
}, [&](const MTPDstoryItemSkipped &data) {
|
||||
LOG(("API Error: Unexpected storyItemSkipped in resolve."));
|
||||
}, [&](const MTPDstoryItemDeleted &data) {
|
||||
applyDeleted({ peer->id, data.vid().v });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::finalizeResolve(FullStoryId id) {
|
||||
const auto already = lookup(id);
|
||||
if (!already.has_value() && already.error() == NoStory::Unknown) {
|
||||
LOG(("API Error: Could not resolve story %1_%2"
|
||||
).arg(id.peer.value
|
||||
).arg(id.story));
|
||||
applyDeleted(id);
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::applyDeleted(FullStoryId id) {
|
||||
const auto i = _stories.find(id.peer);
|
||||
if (i != end(_stories)) {
|
||||
const auto j = i->second.find(id.story);
|
||||
if (j != end(i->second)) {
|
||||
auto story = std::move(j->second);
|
||||
i->second.erase(j);
|
||||
session().changes().storyUpdated(
|
||||
story.get(),
|
||||
UpdateFlag::Destroyed);
|
||||
if (i->second.empty()) {
|
||||
_stories.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
_deleted.emplace(id);
|
||||
}
|
||||
|
||||
const std::vector<StoriesList> &Stories::all() {
|
||||
return _all;
|
||||
}
|
||||
|
@ -242,6 +436,21 @@ base::expected<not_null<Story*>, NoStory> Stories::lookup(
|
|||
}
|
||||
|
||||
void Stories::resolve(FullStoryId id, Fn<void()> done) {
|
||||
const auto already = lookup(id);
|
||||
if (already.has_value() || already.error() != NoStory::Unknown) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
auto &ids = _resolves[id.peer];
|
||||
if (ids.empty()) {
|
||||
crl::on_main(&session(), [=] {
|
||||
sendResolveRequests();
|
||||
});
|
||||
}
|
||||
auto &callbacks = ids[id.story];
|
||||
if (done) {
|
||||
callbacks.push_back(std::move(done));
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::pushToBack(StoriesList &&list) {
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "base/expected.h"
|
||||
|
||||
class Image;
|
||||
class PhotoData;
|
||||
class DocumentData;
|
||||
|
||||
|
@ -39,11 +40,16 @@ public:
|
|||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
|
||||
[[nodiscard]] StoryId id() const;
|
||||
[[nodiscard]] FullStoryId fullId() const;
|
||||
[[nodiscard]] TimeId date() const;
|
||||
[[nodiscard]] const StoryMedia &media() const;
|
||||
[[nodiscard]] PhotoData *photo() const;
|
||||
[[nodiscard]] DocumentData *document() const;
|
||||
|
||||
[[nodiscard]] bool hasReplyPreview() const;
|
||||
[[nodiscard]] Image *replyPreview() const;
|
||||
[[nodiscard]] TextWithEntities inReplyText() const;
|
||||
|
||||
void setPinned(bool pinned);
|
||||
[[nodiscard]] bool pinned() const;
|
||||
|
||||
|
@ -55,7 +61,7 @@ public:
|
|||
private:
|
||||
const StoryId _id = 0;
|
||||
const not_null<PeerData*> _peer;
|
||||
const StoryMedia _media;
|
||||
StoryMedia _media;
|
||||
TextWithEntities _caption;
|
||||
const TimeId _date = 0;
|
||||
bool _pinned = false;
|
||||
|
@ -84,6 +90,15 @@ public:
|
|||
~Stories();
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
void updateDependentMessages(not_null<Data::Story*> story);
|
||||
void registerDependentMessage(
|
||||
not_null<HistoryItem*> dependent,
|
||||
not_null<Data::Story*> dependency);
|
||||
void unregisterDependentMessage(
|
||||
not_null<HistoryItem*> dependent,
|
||||
not_null<Data::Story*> dependency);
|
||||
|
||||
void loadMore();
|
||||
void apply(const MTPDupdateStories &data);
|
||||
|
@ -101,9 +116,15 @@ private:
|
|||
[[nodiscard]] Story *parse(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPDstoryItem &data);
|
||||
void processResolvedStories(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPStoryItem> &list);
|
||||
void sendResolveRequests();
|
||||
void finalizeResolve(FullStoryId id);
|
||||
|
||||
void pushToBack(StoriesList &&list);
|
||||
void pushToFront(StoriesList &&list);
|
||||
void applyDeleted(FullStoryId id);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
base::flat_map<
|
||||
|
@ -111,6 +132,15 @@ private:
|
|||
base::flat_map<StoryId, std::unique_ptr<Story>>> _stories;
|
||||
base::flat_set<FullStoryId> _deleted;
|
||||
|
||||
base::flat_map<
|
||||
PeerId,
|
||||
base::flat_map<StoryId, std::vector<Fn<void()>>>> _resolves;
|
||||
base::flat_map<mtpRequestId, std::vector<Fn<void()>>> _resolveRequests;
|
||||
|
||||
std::map<
|
||||
not_null<Data::Story*>,
|
||||
base::flat_set<not_null<HistoryItem*>>> _dependentMessages;
|
||||
|
||||
std::vector<StoriesList> _all;
|
||||
rpl::event_stream<> _allChanged;
|
||||
QString _state;
|
||||
|
|
|
@ -2544,7 +2544,7 @@ void HistoryItem::setReplyFields(
|
|||
&& !IsServerMsgId(reply->replyToMsgId)) {
|
||||
reply->replyToMsgId = replyTo;
|
||||
if (!reply->updateData(this)) {
|
||||
RequestDependentMessageData(
|
||||
RequestDependentMessageItem(
|
||||
this,
|
||||
reply->replyToPeerId,
|
||||
reply->replyToMsgId);
|
||||
|
@ -2966,12 +2966,21 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
|||
reply->replyToPeerId = config.replyToPeer;
|
||||
reply->replyToMsgId = config.replyTo;
|
||||
reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
|
||||
reply->replyToStoryId = config.replyToStory;
|
||||
reply->storyReply = (config.replyToStory != 0);
|
||||
reply->topicPost = config.replyIsTopicPost;
|
||||
if (!reply->updateData(this)) {
|
||||
RequestDependentMessageData(
|
||||
this,
|
||||
reply->replyToPeerId,
|
||||
reply->replyToMsgId);
|
||||
if (reply->replyToMsgId) {
|
||||
RequestDependentMessageItem(
|
||||
this,
|
||||
reply->replyToPeerId,
|
||||
reply->replyToMsgId);
|
||||
} else if (reply->replyToStoryId) {
|
||||
RequestDependentMessageStory(
|
||||
this,
|
||||
reply->replyToPeerId,
|
||||
reply->replyToStoryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto via = Get<HistoryMessageVia>()) {
|
||||
|
@ -3518,7 +3527,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
|||
dependent->topicPost = data.is_forum_topic()
|
||||
|| Has<HistoryServiceTopicInfo>();
|
||||
if (!updateServiceDependent()) {
|
||||
RequestDependentMessageData(
|
||||
RequestDependentMessageItem(
|
||||
this,
|
||||
(dependent->peerId
|
||||
? dependent->peerId
|
||||
|
|
|
@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_document.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "api/api_bot.h"
|
||||
|
@ -257,15 +258,17 @@ bool HistoryMessageReply::updateData(
|
|||
bool force) {
|
||||
const auto guard = gsl::finally([&] { refreshReplyToMedia(); });
|
||||
if (!force) {
|
||||
if (replyToMsg || !replyToMsgId) {
|
||||
if ((replyToMsg || !replyToMsgId)
|
||||
&& (replyToStory || !replyToStoryId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!replyToMsg) {
|
||||
const auto peerId = replyToPeerId
|
||||
? replyToPeerId
|
||||
: holder->history()->peer->id;
|
||||
if (!replyToMsg && replyToMsgId) {
|
||||
replyToMsg = holder->history()->owner().message(
|
||||
(replyToPeerId
|
||||
? replyToPeerId
|
||||
: holder->history()->peer->id),
|
||||
peerId,
|
||||
replyToMsgId);
|
||||
if (replyToMsg) {
|
||||
if (replyToMsg->isEmpty()) {
|
||||
|
@ -279,8 +282,22 @@ bool HistoryMessageReply::updateData(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!replyToStory && replyToStoryId) {
|
||||
const auto maybe = holder->history()->owner().stories().lookup({
|
||||
peerId,
|
||||
replyToStoryId,
|
||||
});
|
||||
if (maybe) {
|
||||
replyToStory = *maybe;
|
||||
holder->history()->owner().stories().registerDependentMessage(
|
||||
holder,
|
||||
replyToStory.get());
|
||||
} else if (maybe.error() == Data::NoStory::Deleted) {
|
||||
force = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (replyToMsg) {
|
||||
if (replyToMsg || replyToStory) {
|
||||
const auto repaint = [=] { holder->customEmojiRepaint(); };
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &holder->history()->session(),
|
||||
|
@ -288,14 +305,16 @@ bool HistoryMessageReply::updateData(
|
|||
};
|
||||
replyToText.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
replyToMsg->inReplyText(),
|
||||
(replyToMsg
|
||||
? replyToMsg->inReplyText()
|
||||
: replyToStory->inReplyText()),
|
||||
Ui::DialogTextOptions(),
|
||||
context);
|
||||
|
||||
updateName(holder);
|
||||
|
||||
setReplyToLinkFrom(holder);
|
||||
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
|
||||
if (replyToMsg && !replyToMsg->Has<HistoryMessageForwarded>()) {
|
||||
if (auto bot = replyToMsg->viaBot()) {
|
||||
replyToVia = std::make_unique<HistoryMessageVia>();
|
||||
replyToVia->create(
|
||||
|
@ -304,15 +323,17 @@ bool HistoryMessageReply::updateData(
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (replyToMsg) {
|
||||
const auto peer = replyToMsg->history()->peer;
|
||||
replyToColorKey = (!holder->out()
|
||||
&& (peer->isMegagroup() || peer->isChat()))
|
||||
? replyToMsg->from()->id
|
||||
: PeerId(0);
|
||||
} else {
|
||||
replyToColorKey = PeerId(0);
|
||||
}
|
||||
|
||||
const auto media = replyToMsg->media();
|
||||
const auto media = replyToMsg ? replyToMsg->media() : nullptr;
|
||||
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
|
||||
spoiler = nullptr;
|
||||
} else if (!spoiler) {
|
||||
|
@ -320,19 +341,23 @@ bool HistoryMessageReply::updateData(
|
|||
}
|
||||
} else if (force) {
|
||||
replyToMsgId = 0;
|
||||
replyToStoryId = 0;
|
||||
replyToColorKey = PeerId(0);
|
||||
spoiler = nullptr;
|
||||
}
|
||||
if (force) {
|
||||
holder->history()->owner().requestItemResize(holder);
|
||||
}
|
||||
return (replyToMsg || !replyToMsgId);
|
||||
return (replyToMsg || !replyToMsgId)
|
||||
&& (replyToStory || !replyToStoryId);
|
||||
}
|
||||
|
||||
void HistoryMessageReply::setReplyToLinkFrom(
|
||||
not_null<HistoryItem*> holder) {
|
||||
replyToLnk = replyToMsg
|
||||
? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId())
|
||||
: replyToStory
|
||||
? JumpToStoryClickHandler(replyToStory.get())
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
|
@ -365,7 +390,9 @@ PeerData *HistoryMessageReply::replyToFrom(
|
|||
|
||||
QString HistoryMessageReply::replyToFromName(
|
||||
not_null<HistoryItem*> holder) const {
|
||||
if (!replyToMsg) {
|
||||
if (replyToStory) {
|
||||
return replyToFromName(replyToStory->peer());
|
||||
} else if (!replyToMsg) {
|
||||
return QString();
|
||||
} else if (holder->Has<HistoryMessageForwarded>()) {
|
||||
if (const auto fwd = replyToMsg->Get<HistoryMessageForwarded>()) {
|
||||
|
@ -405,10 +432,15 @@ void HistoryMessageReply::updateName(
|
|||
replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
|
||||
if (const auto from = replyToFrom(holder)) {
|
||||
replyToVersion = from->nameVersion();
|
||||
} else {
|
||||
} else if (replyToMsg) {
|
||||
replyToVersion = replyToMsg->author()->nameVersion();
|
||||
} else {
|
||||
replyToVersion = replyToStory->peer()->nameVersion();
|
||||
}
|
||||
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
||||
bool hasPreview = (replyToStory && replyToStory->hasReplyPreview())
|
||||
|| (replyToMsg
|
||||
&& replyToMsg->media()
|
||||
&& replyToMsg->media()->hasReplyPreview());
|
||||
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
int32 w = replyToName.maxWidth();
|
||||
if (replyToVia) {
|
||||
|
@ -417,14 +449,17 @@ void HistoryMessageReply::updateName(
|
|||
|
||||
maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize)));
|
||||
} else {
|
||||
maxReplyWidth = st::msgDateFont->width(replyToMsgId ? tr::lng_profile_loading(tr::now) : tr::lng_deleted_message(tr::now));
|
||||
maxReplyWidth = st::msgDateFont->width(statePhrase());
|
||||
}
|
||||
maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right();
|
||||
}
|
||||
|
||||
void HistoryMessageReply::resize(int width) const {
|
||||
if (replyToVia) {
|
||||
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
||||
bool hasPreview = (replyToStory && replyToStory->hasReplyPreview())
|
||||
|| (replyToMsg
|
||||
&& replyToMsg->media()
|
||||
&& replyToMsg->media()->hasReplyPreview());
|
||||
int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew);
|
||||
}
|
||||
|
@ -471,16 +506,19 @@ void HistoryMessageReply::paint(
|
|||
const auto pausedSpoiler = context.paused
|
||||
|| On(PowerSaving::kChatSpoiler);
|
||||
if (w > st::msgReplyBarSkip) {
|
||||
if (replyToMsg) {
|
||||
const auto media = replyToMsg->media();
|
||||
auto hasPreview = media && media->hasReplyPreview();
|
||||
if (replyToMsg || replyToStory) {
|
||||
const auto media = replyToMsg ? replyToMsg->media() : nullptr;
|
||||
auto hasPreview = (replyToStory && replyToStory->hasReplyPreview()) || (media && media->hasReplyPreview());
|
||||
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
|
||||
hasPreview = false;
|
||||
}
|
||||
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
|
||||
if (hasPreview) {
|
||||
if (const auto image = media->replyPreview()) {
|
||||
const auto image = media
|
||||
? media->replyPreview()
|
||||
: replyToStory->replyPreview();
|
||||
if (image) {
|
||||
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
|
||||
const auto preview = image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
|
@ -542,11 +580,19 @@ void HistoryMessageReply::paint(
|
|||
p.setPen(inBubble
|
||||
? stm->msgDateFg
|
||||
: st->msgDateImgFg());
|
||||
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(replyToMsgId ? tr::lng_profile_loading(tr::now) : tr::lng_deleted_message(tr::now), w - st::msgReplyBarSkip));
|
||||
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(statePhrase(), w - st::msgReplyBarSkip));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString HistoryMessageReply::statePhrase() const {
|
||||
return (replyToMsgId || replyToStoryId)
|
||||
? tr::lng_profile_loading(tr::now)
|
||||
: storyReply
|
||||
? tr::lng_deleted_story(tr::now)
|
||||
: tr::lng_deleted_message(tr::now);
|
||||
}
|
||||
|
||||
void HistoryMessageReply::refreshReplyToMedia() {
|
||||
replyToDocumentId = 0;
|
||||
replyToWebPageId = 0;
|
||||
|
|
|
@ -25,6 +25,7 @@ struct PeerUserpicView;
|
|||
|
||||
namespace Data {
|
||||
class Session;
|
||||
class Story;
|
||||
} // namespace Data
|
||||
|
||||
namespace Media::Player {
|
||||
|
@ -182,6 +183,44 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class ReplyToStoryPointer final {
|
||||
public:
|
||||
ReplyToStoryPointer(Data::Story *story = nullptr) : _data(story) {
|
||||
}
|
||||
ReplyToStoryPointer(ReplyToStoryPointer &&other)
|
||||
: _data(base::take(other._data)) {
|
||||
}
|
||||
ReplyToStoryPointer &operator=(ReplyToStoryPointer &&other) {
|
||||
_data = base::take(other._data);
|
||||
return *this;
|
||||
}
|
||||
ReplyToStoryPointer &operator=(Data::Story *item) {
|
||||
_data = item;
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return !_data;
|
||||
}
|
||||
[[nodiscard]] Data::Story *get() const {
|
||||
return _data;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::Story *operator->() const {
|
||||
return _data;
|
||||
}
|
||||
[[nodiscard]] Data::Story &operator*() const {
|
||||
return *_data;
|
||||
}
|
||||
|
||||
private:
|
||||
Data::Story *_data = nullptr;
|
||||
|
||||
};
|
||||
|
||||
struct HistoryMessageReply
|
||||
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
|
||||
HistoryMessageReply() = default;
|
||||
|
@ -236,6 +275,7 @@ struct HistoryMessageReply
|
|||
[[nodiscard]] ClickHandlerPtr replyToLink() const {
|
||||
return replyToLnk;
|
||||
}
|
||||
[[nodiscard]] QString statePhrase() const;
|
||||
void setReplyToLinkFrom(not_null<HistoryItem*> holder);
|
||||
|
||||
void refreshReplyToMedia();
|
||||
|
@ -249,6 +289,7 @@ struct HistoryMessageReply
|
|||
DocumentId replyToDocumentId = 0;
|
||||
WebPageId replyToWebPageId = 0;
|
||||
ReplyToMessagePointer replyToMsg;
|
||||
ReplyToStoryPointer replyToStory;
|
||||
std::unique_ptr<HistoryMessageVia> replyToVia;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
|
||||
ClickHandlerPtr replyToLnk;
|
||||
|
@ -257,6 +298,7 @@ struct HistoryMessageReply
|
|||
mutable int maxReplyWidth = 0;
|
||||
int toWidth = 0;
|
||||
bool topicPost = false;
|
||||
bool storyReply = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_media_types.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
@ -132,7 +133,7 @@ QString GetErrorTextForSending(
|
|||
return GetErrorTextForSending(thread->peer(), std::move(request));
|
||||
}
|
||||
|
||||
void RequestDependentMessageData(
|
||||
void RequestDependentMessageItem(
|
||||
not_null<HistoryItem*> item,
|
||||
PeerId peerId,
|
||||
MsgId msgId) {
|
||||
|
@ -153,6 +154,23 @@ void RequestDependentMessageData(
|
|||
done);
|
||||
}
|
||||
|
||||
void RequestDependentMessageStory(
|
||||
not_null<HistoryItem*> item,
|
||||
PeerId peerId,
|
||||
StoryId storyId) {
|
||||
const auto fullId = item->fullId();
|
||||
const auto history = item->history();
|
||||
const auto session = &history->session();
|
||||
const auto done = [=] {
|
||||
if (const auto item = session->data().message(fullId)) {
|
||||
item->updateDependencyItem();
|
||||
}
|
||||
};
|
||||
history->owner().stories().resolve(
|
||||
{ peerId ? peerId : history->peer->id, storyId },
|
||||
done);
|
||||
}
|
||||
|
||||
MessageFlags NewMessageFlags(not_null<PeerData*> peer) {
|
||||
return MessageFlag::BeingSent
|
||||
| (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing);
|
||||
|
@ -266,6 +284,24 @@ ClickHandlerPtr JumpToMessageClickHandler(
|
|||
});
|
||||
}
|
||||
|
||||
ClickHandlerPtr JumpToStoryClickHandler(not_null<Data::Story*> story) {
|
||||
return JumpToStoryClickHandler(story->peer(), story->id());
|
||||
}
|
||||
|
||||
ClickHandlerPtr JumpToStoryClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
StoryId storyId) {
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
const auto separate = Core::App().separateWindowForPeer(peer);
|
||||
const auto controller = separate
|
||||
? separate->sessionController()
|
||||
: peer->session().tryResolveWindow();
|
||||
if (controller) {
|
||||
controller->openPeerStory(peer, storyId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MessageFlags FlagsFromMTP(
|
||||
MsgId id,
|
||||
MTPDmessage::Flags flags,
|
||||
|
|
|
@ -15,6 +15,7 @@ struct SendAction;
|
|||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class Story;
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
|
@ -69,10 +70,14 @@ void CheckReactionNotificationSchedule(
|
|||
const TextWithEntities &text = TextWithEntities());
|
||||
[[nodiscard]] TextWithEntities UnsupportedMessageText();
|
||||
|
||||
void RequestDependentMessageData(
|
||||
void RequestDependentMessageItem(
|
||||
not_null<HistoryItem*> item,
|
||||
PeerId peerId,
|
||||
MsgId msgId);
|
||||
void RequestDependentMessageStory(
|
||||
not_null<HistoryItem*> item,
|
||||
PeerId peerId,
|
||||
StoryId storyId);
|
||||
[[nodiscard]] MessageFlags NewMessageFlags(not_null<PeerData*> peer);
|
||||
[[nodiscard]] bool ShouldSendSilent(
|
||||
not_null<PeerData*> peer,
|
||||
|
@ -115,6 +120,11 @@ struct SendingErrorRequest {
|
|||
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
||||
not_null<HistoryItem*> item,
|
||||
FullMsgId returnToId = FullMsgId());
|
||||
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
|
||||
not_null<Data::Story*> story);
|
||||
ClickHandlerPtr JumpToStoryClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
StoryId storyId);
|
||||
|
||||
[[nodiscard]] not_null<HistoryItem*> GenerateJoinedMessage(
|
||||
not_null<History*> history,
|
||||
|
|
|
@ -2268,7 +2268,8 @@ bool Message::getStateReplyInfo(
|
|||
if (auto reply = displayedReply()) {
|
||||
int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
|
||||
if (point.y() >= trect.top() && point.y() < trect.top() + h) {
|
||||
if (reply->replyToMsg && QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) {
|
||||
if ((reply->replyToMsg || reply->replyToStory)
|
||||
&& QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) {
|
||||
outResult->link = reply->replyToLink();
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -10,10 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/timer.h"
|
||||
#include "base/power_save_blocker.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/stories/media_stories_caption_full_view.h"
|
||||
#include "media/stories/media_stories_delegate.h"
|
||||
#include "media/stories/media_stories_header.h"
|
||||
|
@ -394,6 +396,18 @@ void Controller::show(
|
|||
_slider->show({ .index = _index, .total = list.total });
|
||||
_replyArea->show({ .user = list.user, .id = id });
|
||||
|
||||
const auto session = &list.user->session();
|
||||
if (_session != session) {
|
||||
_session = session;
|
||||
_sessionLifetime = session->changes().storyUpdates(
|
||||
Data::StoryUpdate::Flag::Destroyed
|
||||
) | rpl::start_with_next([=](Data::StoryUpdate update) {
|
||||
if (update.story->fullId() == _shown) {
|
||||
_delegate->storiesClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (_contentFaded) {
|
||||
togglePaused(true);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ namespace Ui {
|
|||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Media::Player {
|
||||
struct TrackState;
|
||||
} // namespace Media::Player
|
||||
|
@ -152,6 +156,9 @@ private:
|
|||
|
||||
std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
|
||||
|
||||
Main::Session *_session = nullptr;
|
||||
rpl::lifetime _sessionLifetime;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
|
|
@ -2464,6 +2464,18 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
|
|||
};
|
||||
}
|
||||
|
||||
void SessionController::openPeerStory(
|
||||
not_null<PeerData*> peer,
|
||||
StoryId storyId) {
|
||||
using namespace Media::View;
|
||||
using namespace Data;
|
||||
|
||||
auto &stories = session().data().stories();
|
||||
if (const auto from = stories.lookup({ peer->id, storyId })) {
|
||||
window().openInMediaView(OpenRequest(this, *from));
|
||||
}
|
||||
}
|
||||
|
||||
void SessionController::openPeerStories(PeerId peerId) {
|
||||
using namespace Media::View;
|
||||
using namespace Data;
|
||||
|
@ -2474,9 +2486,7 @@ void SessionController::openPeerStories(PeerId peerId) {
|
|||
return list.user->id;
|
||||
});
|
||||
if (i != end(all) && !i->ids.empty()) {
|
||||
if (const auto from = stories.lookup({ peerId, i->ids.front() })) {
|
||||
window().openInMediaView(OpenRequest(this, *from));
|
||||
}
|
||||
openPeerStory(i->user, i->ids.front());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -564,6 +564,7 @@ public:
|
|||
return _peerThemeOverride.value();
|
||||
}
|
||||
|
||||
void openPeerStory(not_null<PeerData*> peer, StoryId storyId);
|
||||
void openPeerStories(PeerId peerId);
|
||||
|
||||
struct PaintContextArgs {
|
||||
|
|
Loading…
Add table
Reference in a new issue