Allow sharing stories and copying a link.

This commit is contained in:
John Preston 2023-06-12 20:41:26 +04:00
parent 3ac7725111
commit a933168ef7
26 changed files with 587 additions and 152 deletions

View File

@ -987,6 +987,8 @@ PRIVATE
media/stories/media_stories_recent_views.h
media/stories/media_stories_reply.cpp
media/stories/media_stories_reply.h
media/stories/media_stories_share.cpp
media/stories/media_stories_share.h
media/stories/media_stories_sibling.cpp
media/stories/media_stories_sibling.h
media/stories/media_stories_slider.cpp

View File

@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_scheduled_messages.h"
#include "data/data_channel_admins.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@ -763,9 +764,7 @@ QString ApiWrap::exportDirectMessageLink(
channel->inputChannel,
MTP_int(item->id)
)).done([=](const MTPExportedMessageLink &result) {
const auto link = result.match([&](const auto &data) {
return qs(data.vlink());
});
const auto link = qs(result.data().vlink());
if (current != link) {
_unlikelyMessageLinks.emplace_or_assign(itemId, link);
}
@ -773,6 +772,32 @@ QString ApiWrap::exportDirectMessageLink(
return current;
}
QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
const auto storyId = story->fullId();
const auto user = story->peer()->asUser();
Assert(user != nullptr);
const auto fallback = [&] {
const auto base = user->username();
const auto story = QString::number(storyId.story);
const auto query = base + "?story=" + story;
return session().createInternalLinkFull(query);
};
const auto i = _unlikelyStoryLinks.find(storyId);
const auto current = (i != end(_unlikelyStoryLinks))
? i->second
: fallback();
request(MTPstories_ExportStoryLink(
story->peer()->asUser()->inputUser,
MTP_int(story->id())
)).done([=](const MTPExportedStoryLink &result) {
const auto link = qs(result.data().vlink());
if (current != link) {
_unlikelyStoryLinks.emplace_or_assign(storyId, link);
}
}).send();
return current;
}
void ApiWrap::requestContacts() {
if (_session->data().contactsLoaded().current() || _contactsRequestId) {
return;

View File

@ -35,6 +35,7 @@ enum class StickersType : uchar;
class Forum;
class ForumTopic;
class Thread;
class Story;
} // namespace Data
namespace InlineBots {
@ -160,6 +161,7 @@ public:
QString exportDirectMessageLink(
not_null<HistoryItem*> item,
bool inRepliesContext);
QString exportDirectStoryLink(not_null<Data::Story*> item);
void requestContacts();
void requestDialogs(Data::Folder *folder = nullptr);
@ -707,5 +709,6 @@ private:
base::flat_map<not_null<UserData*>, Fn<void()>> _botCommonGroupsRequests;
base::flat_map<FullMsgId, QString> _unlikelyMessageLinks;
base::flat_map<FullStoryId, QString> _unlikelyStoryLinks;
};

View File

@ -1414,15 +1414,16 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
? MsgId(0)
: topicRootId;
const auto peer = thread->peer();
histories.sendRequest(history, requestType, [=](
const auto threadHistory = thread->owningHistory();
histories.sendRequest(threadHistory, requestType, [=](
Fn<void()> finish) {
auto &api = history->session().api();
auto &api = threadHistory->session().api();
const auto sendFlags = commonSendFlags
| (topMsgId ? Flag::f_top_msg_id : Flag(0))
| (ShouldSendSilent(peer, options)
? Flag::f_silent
: Flag(0));
history->sendRequestId = api.request(
threadHistory->sendRequestId = api.request(
MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
history->peer->input,
@ -1433,7 +1434,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_int(options.scheduled),
MTP_inputPeerEmpty() // send_as
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
history->session().api().applyUpdates(updates);
threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId);
if (state->requests.empty()) {
if (show->valid()) {
@ -1451,10 +1452,10 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
peer->name()));
}
finish();
}).afterRequest(history->sendRequestId).send();
return history->sendRequestId;
}).afterRequest(threadHistory->sendRequestId).send();
return threadHistory->sendRequestId;
});
state->requests.insert(history->sendRequestId);
state->requests.insert(threadHistory->sendRequestId);
}
};
}

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_chat_participant_status.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_photo.h"
@ -196,6 +197,51 @@ bool Story::pinned() const {
return _pinned;
}
void Story::setIsPublic(bool isPublic) {
_isPublic = isPublic;
}
bool Story::isPublic() const {
return _isPublic;
}
void Story::setCloseFriends(bool closeFriends) {
_closeFriends = closeFriends;
}
bool Story::closeFriends() const {
return _closeFriends;
}
bool Story::hasDirectLink() const {
if (!_isPublic || (!_pinned && expired())) {
return false;
}
const auto user = _peer->asUser();
return user && !user->username().isEmpty();
}
std::optional<QString> Story::errorTextForForward(
not_null<Thread*> to) const {
const auto peer = to->peer();
const auto holdsPhoto = v::is<not_null<PhotoData*>>(_media.data);
const auto first = holdsPhoto
? ChatRestriction::SendPhotos
: ChatRestriction::SendVideos;
const auto second = holdsPhoto
? ChatRestriction::SendVideos
: ChatRestriction::SendPhotos;
if (const auto error = Data::RestrictionError(peer, first)) {
return *error;
} else if (const auto error = Data::RestrictionError(peer, second)) {
return *error;
} else if (!Data::CanSend(to, first, false)
|| !Data::CanSend(to, second, false)) {
return tr::lng_forward_cant(tr::now);
}
return {};
}
void Story::setCaption(TextWithEntities &&caption) {
_caption = std::move(caption);
}
@ -259,6 +305,8 @@ void Story::applyViewsSlice(
bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
const auto pinned = data.is_pinned();
const auto isPublic = data.is_public();
const auto closeFriends = data.is_close_friends();
auto caption = TextWithEntities{
data.vcaption().value_or_empty(),
Api::EntitiesFromMTP(
@ -280,6 +328,8 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
const auto changed = (_media != media)
|| (_pinned != pinned)
|| (_isPublic != isPublic)
|| (_closeFriends != closeFriends)
|| (_caption != caption)
|| (_views != views)
|| (_recentViewers != recent);
@ -288,6 +338,8 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
}
_media = std::move(media);
_pinned = pinned;
_isPublic = isPublic;
_closeFriends = closeFriends;
_caption = std::move(caption);
_views = views;
_recentViewers = std::move(recent);

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image;
class PhotoData;
class DocumentData;
enum class ChatRestriction;
namespace Main {
class Session;
@ -23,6 +24,7 @@ class Session;
namespace Data {
class Session;
class Thread;
struct StoryIdDates {
StoryId id = 0;
@ -91,6 +93,14 @@ public:
void setPinned(bool pinned);
[[nodiscard]] bool pinned() const;
void setIsPublic(bool isPublic);
[[nodiscard]] bool isPublic() const;
void setCloseFriends(bool closeFriends);
[[nodiscard]] bool closeFriends() const;
[[nodiscard]] bool hasDirectLink() const;
[[nodiscard]] std::optional<QString> errorTextForForward(
not_null<Thread*> to) const;
void setCaption(TextWithEntities &&caption);
[[nodiscard]] const TextWithEntities &caption() const;
@ -117,7 +127,9 @@ private:
int _views = 0;
const TimeId _date = 0;
const TimeId _expires = 0;
bool _pinned = false;
bool _pinned : 1 = false;
bool _isPublic : 1 = false;
bool _closeFriends : 1 = false;
};

View File

@ -113,6 +113,18 @@ void UserData::setCommonChatsCount(int count) {
}
}
bool UserData::hasPrivateForwardName() const {
return !_privateForwardName.isEmpty();
}
QString UserData::privateForwardName() const {
return _privateForwardName;
}
void UserData::setPrivateForwardName(const QString &name) {
_privateForwardName = name;
}
void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) {
bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty();
@ -449,6 +461,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->checkFolder(update.vfolder_id().value_or_empty());
user->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
user->setTranslationDisabled(update.is_translations_disabled());
user->setPrivateForwardName(
update.vprivate_forward_name().value_or_empty());
if (const auto info = user->botInfo.get()) {
const auto group = update.vbot_group_admin_rights()

View File

@ -170,6 +170,10 @@ public:
int commonChatsCount() const;
void setCommonChatsCount(int count);
[[nodiscard]] bool hasPrivateForwardName() const;
[[nodiscard]] QString privateForwardName() const;
void setPrivateForwardName(const QString &name);
private:
auto unavailableReasons() const
-> const std::vector<Data::UnavailableReason> & override;
@ -180,6 +184,7 @@ private:
std::vector<Data::UnavailableReason> _unavailableReasons;
QString _phone;
QString _privateForwardName;
ContactStatus _contactStatus = ContactStatus::Unknown;
CallsStatus _callsStatus = CallsStatus::Unknown;
int _commonChatsCount = 0;

View File

@ -64,6 +64,11 @@ QString GetErrorTextForSending(
const auto thread = topic
? not_null<Data::Thread*>(topic)
: peer->owner().history(peer);
if (request.story) {
if (const auto error = request.story->errorTextForForward(thread)) {
return *error;
}
}
if (request.forward) {
for (const auto &item : *request.forward) {
if (const auto error = item->errorTextForForward(thread)) {
@ -84,6 +89,7 @@ QString GetErrorTextForSending(
}
if (peer->slowmodeApplied()) {
const auto count = (hasText ? 1 : 0)
+ (request.story ? 1 : 0)
+ (request.forward ? int(request.forward->size()) : 0);
if (const auto history = peer->owner().historyLoaded(peer)) {
if (!request.ignoreSlowmodeCountdown
@ -94,7 +100,7 @@ QString GetErrorTextForSending(
}
if (request.text && request.text->text.size() > MaxMessageSize) {
return tr::lng_slowmode_too_long(tr::now);
} else if (hasText && count > 1) {
} else if ((hasText || request.story) && count > 1) {
return tr::lng_slowmode_no_many(tr::now);
} else if (count > 1) {
const auto albumForward = [&] {

View File

@ -91,6 +91,7 @@ void RequestDependentMessageStory(
struct SendingErrorRequest {
MsgId topicRootId = 0;
const HistoryItemsList *forward = nullptr;
const Data::Story *story = nullptr;
const TextWithTags *text = nullptr;
bool ignoreSlowmodeCountdown = false;
};

View File

@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_groups.h"
#include "data/data_channel.h"
#include "data/data_file_click_handler.h"
@ -1131,6 +1132,20 @@ void CopyPostLink(
: tr::lng_context_about_private_link(tr::now));
}
void CopyStoryLink(
std::shared_ptr<Main::SessionShow> show,
FullStoryId storyId) {
const auto session = &show->session();
const auto maybeStory = session->data().stories().lookup(storyId);
if (!maybeStory) {
return;
}
const auto story = *maybeStory;
QGuiApplication::clipboard()->setText(
session->api().exportDirectStoryLink(story));
show->showToast(tr::lng_channel_public_link_copied(tr::now));
}
void AddPollActions(
not_null<Ui::PopupMenu*> menu,
not_null<PollData*> poll,

View File

@ -15,6 +15,7 @@ struct ReactionId;
namespace Main {
class Session;
class SessionShow;
} // namespace Main
namespace Ui {
@ -58,6 +59,9 @@ void CopyPostLink(
not_null<Window::SessionController*> controller,
FullMsgId itemId,
Context context);
void CopyStoryLink(
std::shared_ptr<Main::SessionShow> show,
FullStoryId storyId);
void AddPollActions(
not_null<Ui::PopupMenu*> menu,
not_null<PollData*> poll,

View File

@ -25,9 +25,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/stories/media_stories_reactions.h"
#include "media/stories/media_stories_recent_views.h"
#include "media/stories/media_stories_reply.h"
#include "media/stories/media_stories_share.h"
#include "media/stories/media_stories_view.h"
#include "media/audio/media_audio.h"
#include "ui/rp_widget.h"
#include "ui/layers/box_content.h"
#include "styles/style_media_view.h"
#include "styles/style_widgets.h"
#include "styles/style_boxes.h" // UserpicButton
@ -758,8 +760,23 @@ void Controller::setMenuShown(bool shown) {
}
}
bool Controller::canShare() const {
if (const auto maybeStory = _session->data().stories().lookup(_shown)) {
const auto story = *maybeStory;
const auto user = story->peer()->asUser();
return story->isPublic()
&& (story->pinned() || !story->expired())
&& (!user->username().isEmpty()
|| !user->hasPrivateForwardName());
}
return false;
}
bool Controller::canDownload() const {
return _source && _source->user->isSelf();
if (const auto maybeStory = _session->data().stories().lookup(_shown)) {
return (*maybeStory)->peer()->isSelf();
}
return false;
}
void Controller::repaintSibling(not_null<Sibling*> sibling) {
@ -876,6 +893,13 @@ void Controller::unfocusReply() {
_wrap->setFocus();
}
void Controller::share() {
const auto show = _delegate->storiesShow();
if (auto box = PrepareShareBox(show, _shown)) {
show->show(std::move(box));
}
}
rpl::lifetime &Controller::lifetime() {
return _lifetime;
}

View File

@ -120,6 +120,7 @@ public:
void contentPressed(bool pressed);
void setMenuShown(bool shown);
[[nodiscard]] bool canShare() const;
[[nodiscard]] bool canDownload() const;
void repaintSibling(not_null<Sibling*> sibling);
@ -129,6 +130,7 @@ public:
[[nodiscard]] rpl::producer<> moreViewsLoaded() const;
void unfocusReply();
void share();
[[nodiscard]] rpl::lifetime &lifetime();

View File

@ -0,0 +1,202 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/stories/media_stories_share.h"
#include "api/api_common.h"
#include "apiwrap.h"
#include "base/random.h"
#include "boxes/share_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_chat_participant_status.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_thread.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "history/view/history_view_context_menu.h" // CopyStoryLink.
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "styles/style_calls.h"
namespace Media::Stories {
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShareBox(
std::shared_ptr<ChatHelpers::Show> show,
FullStoryId id) {
const auto session = &show->session();
const auto resolve = [=] {
const auto maybeStory = session->data().stories().lookup(id);
return maybeStory ? maybeStory->get() : nullptr;
};
const auto story = resolve();
if (!story) {
return { nullptr };
}
const auto canCopyLink = story->hasDirectLink();
auto copyCallback = [=] {
const auto story = resolve();
if (!story) {
return;
}
if (story->hasDirectLink()) {
using namespace HistoryView;
CopyStoryLink(show, story->fullId());
}
};
struct State {
int requests = 0;
};
const auto state = std::make_shared<State>();
auto filterCallback = [=](not_null<Data::Thread*> thread) {
return Data::CanSend(thread, ChatRestriction::SendPhotos)
&& Data::CanSend(thread, ChatRestriction::SendVideos);
};
auto copyLinkCallback = canCopyLink
? Fn<void()>(std::move(copyCallback))
: Fn<void()>();
auto submitCallback = [=](
std::vector<not_null<Data::Thread*>> &&result,
TextWithTags &&comment,
Api::SendOptions options,
Data::ForwardOptions forwardOptions) {
if (state->requests) {
return; // Share clicked already.
}
const auto story = resolve();
if (!story) {
return;
}
const auto user = story->peer()->asUser();
Assert(user != nullptr);
const auto error = [&] {
for (const auto thread : result) {
const auto error = GetErrorTextForSending(
thread,
{ .story = story, .text = &comment });
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
show->showBox(Ui::MakeInformBox(text));
return;
}
const auto generateRandom = [&] {
return base::RandomValue<MTPlong>();
};
const auto api = &story->owner().session().api();
auto &histories = story->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
for (const auto thread : result) {
const auto action = Api::SendAction(thread, options);
if (!comment.text.isEmpty()) {
auto message = Api::MessageToSend(action);
message.textWithTags = comment;
message.action.clearDraft = false;
api->sendMessage(std::move(message));
}
const auto topicRootId = thread->topicRootId();
const auto kGeneralId = Data::ForumTopic::kGeneralId;
const auto topMsgId = (topicRootId == kGeneralId)
? MsgId(0)
: topicRootId;
const auto peer = thread->peer();
const auto threadHistory = thread->owningHistory();
const auto randomId = base::RandomValue<uint64>();
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto done = [=] {
if (!--state->requests) {
if (show->valid()) {
show->showToast(tr::lng_share_done(tr::now));
show->hideLayer();
}
}
};
histories.sendPreparedMessage(
threadHistory,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
MTP_inputMediaStory(
user->inputUser,
MTP_int(id.story)),
MTPstring(),
MTP_long(randomId),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
MTP_inputPeerEmpty()
), [=](const MTPUpdates &result, const MTP::Response &response) {
done();
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId);
done();
});
++state->requests;
}
};
const auto scheduleStyle = [&] {
auto date = Ui::ChooseDateTimeStyleArgs();
date.labelStyle = &st::groupCallBoxLabel;
date.dateFieldStyle = &st::groupCallScheduleDateField;
date.timeFieldStyle = &st::groupCallScheduleTimeField;
date.separatorStyle = &st::callMuteButtonLabel;
date.atStyle = &st::callMuteButtonLabel;
date.calendarStyle = &st::groupCallCalendarColors;
auto st = HistoryView::ScheduleBoxStyleArgs();
st.topButtonStyle = &st::groupCallMenuToggle;
st.popupMenuStyle = &st::groupCallPopupMenu;
st.chooseDateTimeArgs = std::move(date);
return st;
};
return Box<ShareBox>(ShareBox::Descriptor{
.session = session,
.copyCallback = std::move(copyLinkCallback),
.submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback),
.stMultiSelect = &st::groupCallMultiSelect,
.stComment = &st::groupCallShareBoxComment,
.st = &st::groupCallShareBoxList,
.stLabel = &st::groupCallField,
.scheduleBoxStyle = scheduleStyle(),
});
}
} // namespace Media::Stories

View File

@ -0,0 +1,26 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/object_ptr.h"
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui {
class BoxContent;
} // namespace Ui
namespace Media::Stories {
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShareBox(
std::shared_ptr<ChatHelpers::Show> show,
FullStoryId id);
} // namespace Media::Stories

View File

@ -32,6 +32,10 @@ void View::ready() {
_controller->ready();
}
bool View::canShare() const {
return _controller->canShare();
}
bool View::canDownload() const {
return _controller->canDownload();
}
@ -79,6 +83,10 @@ void View::contentPressed(bool pressed) {
_controller->contentPressed(pressed);
}
void View::share() {
_controller->share();
}
SiblingView View::sibling(SiblingType type) const {
return _controller->sibling(type);
}

View File

@ -56,6 +56,7 @@ public:
void show(not_null<Data::Story*> story, Data::StoriesContext context);
void ready();
[[nodiscard]] bool canShare() const;
[[nodiscard]] bool canDownload() const;
[[nodiscard]] QRect finalShownGeometry() const;
[[nodiscard]] rpl::producer<QRect> finalShownGeometryValue() const;
@ -74,6 +75,7 @@ public:
[[nodiscard]] bool paused() const;
void togglePaused(bool paused);
void contentPressed(bool pressed);
void share();
[[nodiscard]] rpl::lifetime &lifetime();

View File

@ -109,6 +109,7 @@ mediaviewRight: icon {
{ "mediaview/next", mediaviewControlFg }
};
mediaviewSave: icon {{ "mediaview/download", mediaviewControlFg }};
mediaviewShare: icon {{ "mediaview/stories_next", mediaviewControlFg }};
mediaviewRotate: icon {{ "mediaview/rotate", mediaviewControlFg }};
mediaviewMore: icon {{ "title_menu_dots", mediaviewControlFg }};

View File

@ -611,7 +611,7 @@ void OverlayWidget::RendererGL::paintControlsStart() {
}
void OverlayWidget::RendererGL::paintControl(
OverState control,
Over control,
QRect over,
float64 overOpacity,
QRect inner,
@ -676,20 +676,21 @@ void OverlayWidget::RendererGL::paintControl(
FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
}
auto OverlayWidget::RendererGL::ControlMeta(OverState control, bool stories)
auto OverlayWidget::RendererGL::ControlMeta(Over control, bool stories)
-> Control {
switch (control) {
case OverLeftNav: return {
case Over::Left: return {
0,
stories ? &st::storiesLeft : &st::mediaviewLeft
};
case OverRightNav: return {
case Over::Right: return {
1,
stories ? &st::storiesRight : &st::mediaviewRight
};
case OverSave: return { 2, &st::mediaviewSave };
case OverRotate: return { 3, &st::mediaviewRotate };
case OverMore: return { 4, &st::mediaviewMore };
case Over::Save: return { 2, &st::mediaviewSave };
case Over::Share: return { 3, &st::mediaviewShare };
case Over::Rotate: return { 4, &st::mediaviewRotate };
case Over::More: return { 5, &st::mediaviewMore };
}
Unexpected("Control value in OverlayWidget::RendererGL::ControlIndex.");
}
@ -700,11 +701,12 @@ void OverlayWidget::RendererGL::validateControls() {
}
const auto stories = (_owner->_stories != nullptr);
const auto metas = {
ControlMeta(OverLeftNav, stories),
ControlMeta(OverRightNav, stories),
ControlMeta(OverSave, stories),
ControlMeta(OverRotate, stories),
ControlMeta(OverMore, stories),
ControlMeta(Over::Left, stories),
ControlMeta(Over::Right, stories),
ControlMeta(Over::Save, stories),
ControlMeta(Over::Share, stories),
ControlMeta(Over::Rotate, stories),
ControlMeta(Over::More, stories),
};
auto maxWidth = 0;
auto fullHeight = 0;

View File

@ -63,7 +63,7 @@ private:
void paintSaveMsg(QRect outer) override;
void paintControlsStart() override;
void paintControl(
OverState control,
Over control,
QRect over,
float64 overOpacity,
QRect inner,
@ -145,10 +145,8 @@ private:
static constexpr auto kStoriesSiblingPartsCount = 4;
Ui::GL::Image _storiesSiblingParts[kStoriesSiblingPartsCount];
static constexpr auto kControlsCount = 5;
[[nodiscard]] static Control ControlMeta(
OverState control,
bool stories);
static constexpr auto kControlsCount = 6;
[[nodiscard]] static Control ControlMeta(Over control, bool stories);
// Last one is for the over circle image.
std::array<QRect, kControlsCount + 1> _controlsTextures;

View File

@ -215,7 +215,7 @@ void OverlayWidget::RendererSW::paintControlsStart() {
}
void OverlayWidget::RendererSW::paintControl(
OverState control,
Over control,
QRect over,
float64 overOpacity,
QRect inner,

View File

@ -43,7 +43,7 @@ private:
void paintSaveMsg(QRect outer) override;
void paintControlsStart() override;
void paintControl(
OverState control,
Over control,
QRect over,
float64 overOpacity,
QRect inner,

View File

@ -34,7 +34,7 @@ public:
virtual void paintSaveMsg(QRect outer) = 0;
virtual void paintControlsStart() = 0;
virtual void paintControl(
OverState control,
Over control,
QRect over,
float64 overOpacity,
QRect inner,

View File

@ -536,8 +536,8 @@ OverlayWidget::OverlayWidget()
base::install_event_filter(_widget, [=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Leave) {
if (_over != OverNone) {
updateOverState(OverNone);
if (_over != Over::None) {
updateOverState(Over::None);
}
} else if (type == QEvent::MouseButtonPress) {
handleMousePress(mousePosition(e), mouseButton(e));
@ -675,7 +675,7 @@ void OverlayWidget::setupWindow() {
|| _helper->skipTitleHitTest(widgetPoint)) {
return Flag::None | Flag(0);
}
const auto inControls = (_over != OverNone) && (_over != OverVideo);
const auto inControls = (_over != Over::None) && (_over != Over::Video);
if (inControls
|| (_streamed
&& _streamed->controls
@ -970,13 +970,16 @@ QSize OverlayWidget::flipSizeByRotation(QSize size) const {
}
bool OverlayWidget::hasCopyMediaRestriction() const {
return (_history && !_history->peer->allowsForwarding())
return (_stories && !_stories->canDownload())
|| (_history && !_history->peer->allowsForwarding())
|| (_message && _message->forbidsSaving());
}
bool OverlayWidget::showCopyMediaRestriction() {
if (!hasCopyMediaRestriction()) {
return false;
} else if (!_history) {
return true;
}
Ui::Toast::Show(_widget, _history->peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
@ -1145,8 +1148,7 @@ void OverlayWidget::refreshNavVisibility() {
}
bool OverlayWidget::contentCanBeSaved() const {
if ((_stories && !_stories->canDownload())
|| hasCopyMediaRestriction()) {
if (hasCopyMediaRestriction()) {
return false;
} else if (_photo) {
return _photo->hasVideo() || _photoMedia->loaded();
@ -1225,6 +1227,7 @@ void OverlayWidget::updateControls() {
QPoint(),
QSize(st::mediaviewIconOver, st::mediaviewIconOver));
_saveVisible = contentCanBeSaved();
_shareVisible = _stories && _stories->canShare();
_rotateVisible = !_themePreviewShown && !_stories;
const auto navRect = [&](int i) {
return QRect(width() - st::mediaviewIconSize.width() * i,
@ -1232,15 +1235,26 @@ void OverlayWidget::updateControls() {
st::mediaviewIconSize.width(),
st::mediaviewIconSize.height());
};
_saveNav = navRect(_rotateVisible ? 3 : 2);
_saveNavOver = style::centerrect(_saveNav, overRect);
_saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave);
_rotateNav = navRect(2);
_rotateNavOver = style::centerrect(_rotateNav, overRect);
_rotateNavIcon = style::centerrect(_rotateNav, st::mediaviewRotate);
_moreNav = navRect(1);
auto index = 1;
_moreNav = navRect(index);
_moreNavOver = style::centerrect(_moreNav, overRect);
_moreNavIcon = style::centerrect(_moreNav, st::mediaviewMore);
++index;
_rotateNav = navRect(index);
_rotateNavOver = style::centerrect(_rotateNav, overRect);
_rotateNavIcon = style::centerrect(_rotateNav, st::mediaviewRotate);
if (_rotateVisible) {
++index;
}
_shareNav = navRect(index);
_shareNavOver = style::centerrect(_shareNav, overRect);
_shareNavIcon = style::centerrect(_shareNav, st::mediaviewSave);
if (_shareVisible) {
++index;
}
_saveNav = navRect(index);
_saveNavOver = style::centerrect(_saveNav, overRect);
_saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave);
const auto dNow = QDateTime::currentDateTime();
const auto d = [&] {
@ -1571,17 +1585,18 @@ bool OverlayWidget::updateControlsAnimation(crl::time now) {
}
_helper->setControlsOpacity(_controlsOpacity.current());
const auto content = finalContentRect();
const auto siblingType = (_over == OverLeftStories)
const auto siblingType = (_over == Over::LeftStories)
? Stories::SiblingType::Left
: Stories::SiblingType::Right;
const auto toUpdate = QRegion()
+ (_over == OverLeftNav ? _leftNavOver : _leftNavIcon)
+ (_over == OverRightNav ? _rightNavOver : _rightNavIcon)
+ (_over == OverSave ? _saveNavOver : _saveNavIcon)
+ (_over == OverRotate ? _rotateNavOver : _rotateNavIcon)
+ (_over == OverMore ? _moreNavOver : _moreNavIcon)
+ (_over == Over::Left ? _leftNavOver : _leftNavIcon)
+ (_over == Over::Right ? _rightNavOver : _rightNavIcon)
+ (_over == Over::Save ? _saveNavOver : _saveNavIcon)
+ (_over == Over::Share ? _shareNavOver : _shareNavIcon)
+ (_over == Over::Rotate ? _rotateNavOver : _rotateNavIcon)
+ (_over == Over::More ? _moreNavOver : _moreNavIcon)
+ ((_stories
&& (_over == OverLeftStories || _over == OverRightStories))
&& (_over == Over::LeftStories || _over == Over::RightStories))
? _stories->sibling(siblingType).layout.geometry
: QRect())
+ _headerNav
@ -1604,7 +1619,7 @@ void OverlayWidget::waitingAnimationCallback() {
void OverlayWidget::updateCursor() {
setCursor((_controlsState == ControlsHidden)
? Qt::BlankCursor
: (_over == OverNone || (_over == OverVideo && _stories))
: (_over == Over::None || (_over == Over::Video && _stories))
? style::cur_default
: style::cur_pointer);
}
@ -2971,7 +2986,7 @@ void OverlayWidget::clearControlsState() {
_saveMsgAnimation.stop();
_saveMsgTimer.cancel();
_loadRequest = 0;
_over = _down = OverNone;
_over = _down = Over::None;
_pressed = false;
_dragging = 0;
setCursor(style::cur_default);
@ -3158,7 +3173,7 @@ void OverlayWidget::displayPhoto(
refreshCaption();
_blurred = true;
_down = OverNone;
_down = Over::None;
if (!_staticContent.isNull()) {
// Video thumbnail.
const auto size = style::ConvertScale(
@ -4117,8 +4132,8 @@ void OverlayWidget::storiesTogglePaused(bool paused) {
float64 OverlayWidget::storiesSiblingOver(Stories::SiblingType type) {
return (type == Stories::SiblingType::Left)
? overLevel(OverLeftStories)
: overLevel(OverRightStories);
? overLevel(Over::LeftStories)
: overLevel(Over::RightStories);
}
void OverlayWidget::storiesRepaint() {
update();
@ -4408,7 +4423,7 @@ void OverlayWidget::paintRadialLoadingContent(
if (_photo) {
paintBg(radialOpacity, st::radialBg);
} else {
const auto o = overLevel(OverIcon);
const auto o = overLevel(Over::Icon);
paintBg(
_documentMedia->loaded() ? radialOpacity : 1.,
anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, o));
@ -4584,7 +4599,7 @@ void OverlayWidget::paintControls(
not_null<Renderer*> renderer,
float64 opacity) {
struct Control {
OverState state = OverNone;
Over state = Over::None;
bool visible = false;
const QRect &over;
const QRect &inner;
@ -4595,33 +4610,39 @@ void OverlayWidget::paintControls(
// When adding / removing controls please update RendererGL.
const Control controls[] = {
{
OverLeftNav,
Over::Left,
_leftNavVisible,
_leftNavOver,
_leftNavIcon,
_stories ? st::storiesLeft : st::mediaviewLeft,
true },
{
OverRightNav,
Over::Right,
_rightNavVisible,
_rightNavOver,
_rightNavIcon,
_stories ? st::storiesRight : st::mediaviewRight,
true },
{
OverSave,
Over::Save,
_saveVisible,
_saveNavOver,
_saveNavIcon,
st::mediaviewSave },
{
OverRotate,
Over::Share,
_shareVisible,
_shareNavOver,
_shareNavIcon,
st::mediaviewShare },
{
Over::Rotate,
_rotateVisible,
_rotateNavOver,
_rotateNavIcon,
st::mediaviewRotate },
{
OverMore,
Over::More,
true,
_moreNavOver,
_moreNavIcon,
@ -4673,7 +4694,7 @@ void OverlayWidget::paintFooterContent(
const auto name = _nameNav.translated(shift);
const auto date = _dateNav.translated(shift);
if (header.intersects(clip)) {
auto o = _headerHasLink ? overLevel(OverHeader) : 0;
auto o = _headerHasLink ? overLevel(Over::Header) : 0;
p.setOpacity(controlOpacity(o) * opacity);
p.drawText(header.left(), header.top() + st::mediaviewThickFont->ascent, _headerText);
@ -4687,7 +4708,7 @@ void OverlayWidget::paintFooterContent(
// name
if (_nameNav.isValid() && name.intersects(clip)) {
float64 o = _from ? overLevel(OverName) : 0.;
float64 o = _from ? overLevel(Over::Name) : 0.;
p.setOpacity(controlOpacity(o) * opacity);
_fromNameLabel.drawElided(p, name.left(), name.top(), name.width());
@ -4699,7 +4720,7 @@ void OverlayWidget::paintFooterContent(
// date
if (date.intersects(clip)) {
float64 o = overLevel(OverDate);
float64 o = overLevel(Over::Date);
p.setOpacity(controlOpacity(o) * opacity);
p.drawText(date.left(), date.top() + st::mediaviewFont->ascent, _dateText);
@ -4768,7 +4789,7 @@ void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
const auto key = e->key();
const auto modifiers = e->modifiers();
const auto ctrl = modifiers.testFlag(Qt::ControlModifier);
if (_stories && key == Qt::Key_Space && _down != OverVideo) {
if (_stories && key == Qt::Key_Space && _down != Over::Video) {
_stories->togglePaused(!_stories->paused());
return;
}
@ -5180,27 +5201,28 @@ void OverlayWidget::handleMousePress(
ClickHandler::pressed();
if (button == Qt::LeftButton) {
_down = OverNone;
_down = Over::None;
if (!ClickHandler::getPressed()) {
if ((_over == OverLeftNav && moveToNext(-1))
|| (_over == OverRightNav && moveToNext(1))
if ((_over == Over::Left && moveToNext(-1))
|| (_over == Over::Right && moveToNext(1))
|| (_stories
&& _over == OverLeftStories
&& _over == Over::LeftStories
&& _stories->jumpFor(-1))
|| (_stories
&& _over == OverRightStories
&& _over == Over::RightStories
&& _stories->jumpFor(1))) {
_lastAction = position;
} else if (_over == OverName
|| _over == OverDate
|| _over == OverHeader
|| _over == OverSave
|| _over == OverRotate
|| _over == OverIcon
|| _over == OverMore
|| _over == OverVideo) {
} else if (_over == Over::Name
|| _over == Over::Date
|| _over == Over::Header
|| _over == Over::Save
|| _over == Over::Share
|| _over == Over::Rotate
|| _over == Over::Icon
|| _over == Over::More
|| _over == Over::Video) {
_down = _over;
if (_over == OverVideo && _stories) {
if (_over == Over::Video && _stories) {
_stories->contentPressed(true);
}
} else if (!_saveMsg.contains(position) || !isSaveMsgShown()) {
@ -5223,7 +5245,7 @@ bool OverlayWidget::handleDoubleClick(
Qt::MouseButton button) {
updateOver(position);
if (_over != OverVideo || !_streamed || button != Qt::LeftButton) {
if (_over != Over::Video || !_streamed || button != Qt::LeftButton) {
return false;
} else if (_stories) {
toggleFullScreen(_windowed);
@ -5276,46 +5298,47 @@ void OverlayWidget::handleMouseMove(QPoint position) {
}
}
void OverlayWidget::updateOverRect(OverState state) {
void OverlayWidget::updateOverRect(Over state) {
using Type = Stories::SiblingType;
switch (state) {
case OverLeftNav:
case Over::Left:
update(_stories ? _leftNavIcon : _leftNavOver);
break;
case OverRightNav:
case Over::Right:
update(_stories ? _rightNavIcon : _rightNavOver);
break;
case OverLeftStories:
case Over::LeftStories:
update(_stories
? _stories->sibling(Type::Left).layout.geometry :
QRect());
break;
case OverRightStories:
case Over::RightStories:
update(_stories
? _stories->sibling(Type::Right).layout.geometry
: QRect());
break;
case OverName: update(_nameNav); break;
case OverDate: update(_dateNav); break;
case OverSave: update(_saveNavOver); break;
case OverRotate: update(_rotateNavOver); break;
case OverIcon: update(_docIconRect); break;
case OverHeader: update(_headerNav); break;
case OverMore: update(_moreNavOver); break;
case Over::Name: update(_nameNav); break;
case Over::Date: update(_dateNav); break;
case Over::Save: update(_saveNavOver); break;
case Over::Share: update(_shareNavOver); break;
case Over::Rotate: update(_rotateNavOver); break;
case Over::Icon: update(_docIconRect); break;
case Over::Header: update(_headerNav); break;
case Over::More: update(_moreNavOver); break;
}
}
bool OverlayWidget::updateOverState(OverState newState) {
bool OverlayWidget::updateOverState(Over newState) {
bool result = true;
if (_over != newState) {
if (newState == OverMore && !_ignoringDropdown) {
if (newState == Over::More && !_ignoringDropdown) {
_dropdownShowTimer.callOnce(0);
} else {
_dropdownShowTimer.cancel();
}
updateOverRect(_over);
updateOverRect(newState);
if (_over != OverNone) {
if (_over != Over::None) {
_animations[_over] = crl::now();
const auto i = _animationOpacities.find(_over);
if (i != end(_animationOpacities)) {
@ -5330,7 +5353,7 @@ bool OverlayWidget::updateOverState(OverState newState) {
result = false;
}
_over = newState;
if (newState != OverNone) {
if (newState != Over::None) {
_animations[_over] = crl::now();
const auto i = _animationOpacities.find(_over);
if (i != end(_animationOpacities)) {
@ -5382,52 +5405,54 @@ void OverlayWidget::updateOver(QPoint pos) {
using SiblingType = Stories::SiblingType;
if (_fullScreenVideo) {
updateOverState(OverVideo);
updateOverState(Over::Video);
} else if (_leftNavVisible && _leftNav.contains(pos)) {
updateOverState(OverLeftNav);
updateOverState(Over::Left);
} else if (_rightNavVisible && _rightNav.contains(pos)) {
updateOverState(OverRightNav);
updateOverState(Over::Right);
} else if (_stories
&& _stories->sibling(
SiblingType::Left).layout.geometry.contains(pos)) {
updateOverState(OverLeftStories);
updateOverState(Over::LeftStories);
} else if (_stories
&& _stories->sibling(
SiblingType::Right).layout.geometry.contains(pos)) {
updateOverState(OverRightStories);
updateOverState(Over::RightStories);
} else if (!_stories && _from && _nameNav.contains(pos)) {
updateOverState(OverName);
updateOverState(Over::Name);
} else if (!_stories
&& _message
&& _message->isRegular()
&& _dateNav.contains(pos)) {
updateOverState(OverDate);
updateOverState(Over::Date);
} else if (!_stories && _headerHasLink && _headerNav.contains(pos)) {
updateOverState(OverHeader);
updateOverState(Over::Header);
} else if (_saveVisible && _saveNav.contains(pos)) {
updateOverState(OverSave);
updateOverState(Over::Save);
} else if (_shareVisible && _shareNav.contains(pos)) {
updateOverState(Over::Share);
} else if (_rotateVisible && _rotateNav.contains(pos)) {
updateOverState(OverRotate);
updateOverState(Over::Rotate);
} else if (_document
&& documentBubbleShown()
&& _docIconRect.contains(pos)) {
updateOverState(OverIcon);
updateOverState(Over::Icon);
} else if (_moreNav.contains(pos)) {
updateOverState(OverMore);
updateOverState(Over::More);
} else if (contentShown() && finalContentRect().contains(pos)) {
if (_stories) {
updateOverState(OverVideo);
updateOverState(Over::Video);
} else if (_streamed
&& _document
&& (_document->isVideoFile() || _document->isVideoMessage())) {
updateOverState(OverVideo);
updateOverState(Over::Video);
} else if (!_streamed && _document && !_documentMedia->loaded()) {
updateOverState(OverIcon);
} else if (_over != OverNone) {
updateOverState(OverNone);
updateOverState(Over::Icon);
} else if (_over != Over::None) {
updateOverState(Over::None);
}
} else if (_over != OverNone) {
updateOverState(OverNone);
} else if (_over != Over::None) {
updateOverState(Over::None);
}
}
@ -5476,7 +5501,7 @@ void OverlayWidget::handleMouseRelease(
return;
}
if (_over == OverName && _down == OverName) {
if (_over == Over::Name && _down == Over::Name) {
if (_from) {
if (!_windowed) {
close();
@ -5486,19 +5511,21 @@ void OverlayWidget::handleMouseRelease(
window->window().activate();
}
}
} else if (_over == OverDate && _down == OverDate) {
} else if (_over == Over::Date && _down == Over::Date) {
toMessage();
} else if (_over == OverHeader && _down == OverHeader) {
} else if (_over == Over::Header && _down == Over::Header) {
showMediaOverview();
} else if (_over == OverSave && _down == OverSave) {
} else if (_over == Over::Save && _down == Over::Save) {
downloadMedia();
} else if (_over == OverRotate && _down == OverRotate) {
} else if (_over == Over::Share && _down == Over::Share && _stories) {
_stories->share();
} else if (_over == Over::Rotate && _down == Over::Rotate) {
playbackControlsRotate();
} else if (_over == OverIcon && _down == OverIcon) {
} else if (_over == Over::Icon && _down == Over::Icon) {
handleDocumentClick();
} else if (_over == OverMore && _down == OverMore) {
} else if (_over == Over::More && _down == Over::More) {
InvokeQueued(_widget, [=] { showDropdown(); });
} else if (_over == OverVideo && _down == OverVideo) {
} else if (_over == Over::Video && _down == Over::Video) {
if (_stories) {
_stories->contentPressed(false);
} else if (_streamed) {
@ -5530,7 +5557,7 @@ void OverlayWidget::handleMouseRelease(
}
_pressed = false;
}
_down = OverNone;
_down = Over::None;
if (!isHidden()) {
activateControls();
}
@ -5921,7 +5948,7 @@ void OverlayWidget::updateHeader() {
_headerNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewHeaderTop, hwidth, st::mediaviewThickFont->height);
}
float64 OverlayWidget::overLevel(OverState control) const {
float64 OverlayWidget::overLevel(Over control) const {
auto i = _animationOpacities.find(control);
return (i == end(_animationOpacities))
? (_over == control ? 1. : 0.)

View File

@ -109,7 +109,7 @@ public:
//void leaveToChildEvent(QEvent *e, QWidget *child) override {
// // e -- from enterEvent() of child TWidget
// updateOverState(OverNone);
// updateOverState(Over::None);
//}
//void enterFromChildEvent(QEvent *e, QWidget *child) override {
// // e -- from leaveEvent() of child TWidget
@ -143,21 +143,22 @@ private:
class RendererGL;
// If changing, see paintControls()!
enum OverState {
OverNone,
OverLeftNav,
OverRightNav,
OverLeftStories,
OverRightStories,
OverHeader,
OverName,
OverDate,
OverSave,
OverRotate,
OverMore,
OverIcon,
OverVideo,
OverCaption,
enum class Over {
None,
Left,
Right,
LeftStories,
RightStories,
Header,
Name,
Date,
Save,
Share,
Rotate,
More,
Icon,
Video,
Caption,
};
struct Entity {
std::variant<
@ -471,9 +472,9 @@ private:
bool nonbright = false) const;
[[nodiscard]] bool isSaveMsgShown() const;
void updateOverRect(OverState state);
bool updateOverState(OverState newState);
float64 overLevel(OverState control) const;
void updateOverRect(Over state);
bool updateOverState(Over newState);
float64 overLevel(Over control) const;
void checkGroupThumbsAnimation();
void initGroupThumbs();
@ -549,11 +550,13 @@ private:
QRect _rightNav, _rightNavOver, _rightNavIcon;
QRect _headerNav, _nameNav, _dateNav;
QRect _rotateNav, _rotateNavOver, _rotateNavIcon;
QRect _shareNav, _shareNavOver, _shareNavIcon;
QRect _saveNav, _saveNavOver, _saveNavIcon;
QRect _moreNav, _moreNavOver, _moreNavIcon;
bool _leftNavVisible = false;
bool _rightNavVisible = false;
bool _saveVisible = false;
bool _shareVisible = false;
bool _rotateVisible = false;
bool _headerHasLink = false;
QString _dateText;
@ -653,8 +656,8 @@ private:
mtpRequestId _loadRequest = 0;
OverState _over = OverNone;
OverState _down = OverNone;
Over _over = Over::None;
Over _down = Over::None;
QPoint _lastAction, _lastMouseMovePos;
bool _ignoringDropdown = false;
@ -693,8 +696,8 @@ private:
Ui::Animations::Simple _saveMsgAnimation;
base::Timer _saveMsgTimer;
base::flat_map<OverState, crl::time> _animations;
base::flat_map<OverState, anim::value> _animationOpacities;
base::flat_map<Over, crl::time> _animations;
base::flat_map<Over, anim::value> _animationOpacities;
rpl::event_stream<Media::Player::TrackState> _touchbarTrackState;
rpl::event_stream<TouchBarItemType> _touchbarDisplay;