Implement simple UI for single-type likes in stories.

This commit is contained in:
John Preston 2023-08-02 21:41:58 +02:00
parent 3adb0c1856
commit 4bd925ac2c
27 changed files with 235 additions and 64 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -272,6 +272,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_noforwards_channel" = "Sorry, forwarding from this channel is disabled by admins.";
"lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins.";
"lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins.";
"lng_error_nocopy_story" = "Sorry, copying of this story is disabled by the author.";
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
"lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?";
@ -3876,6 +3877,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_archive_done" = "This story is hidden from your profile.";
"lng_stories_archive_done_many#one" = "{count} story is hidden from your profile.";
"lng_stories_archive_done_many#other" = "{count} stories are hidden from your profile.";
"lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk.";
"lng_stealth_mode_menu_item" = "Stealth Mode";
"lng_stealth_mode_title" = "Stealth Mode";

View File

@ -196,6 +196,8 @@ ComposeControls {
send: SendButton;
attach: IconButton;
emoji: EmojiButton;
like: IconButton;
liked: icon;
suggestions: EmojiSuggestions;
tabbed: EmojiPan;
tabbedHeightMin: pixels;

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace ChatHelpers {
struct ComposeFeatures {
bool likes = false;
bool sendAs = true;
bool ttlInfo = true;
bool botCommandSend = true;

View File

@ -276,8 +276,13 @@ bool Story::edited() const {
return _edited;
}
bool Story::canDownload() const {
return /*!forbidsForward() || */_peer->isSelf();
bool Story::canDownloadIfPremium() const {
return !forbidsForward() || _peer->isSelf();
}
bool Story::canDownloadChecked() const {
return _peer->isSelf()
|| (canDownloadIfPremium() && _peer->session().premium());
}
bool Story::canShare() const {

View File

@ -100,7 +100,8 @@ public:
[[nodiscard]] bool forbidsForward() const;
[[nodiscard]] bool edited() const;
[[nodiscard]] bool canDownload() const;
[[nodiscard]] bool canDownloadIfPremium() const;
[[nodiscard]] bool canDownloadChecked() const;
[[nodiscard]] bool canShare() const;
[[nodiscard]] bool canDelete() const;
[[nodiscard]] bool canReport() const;

View File

@ -41,6 +41,7 @@ struct SetHistoryArgs {
Fn<Api::SendAction()> sendActionFactory;
rpl::producer<int> slowmodeSecondsLeft;
rpl::producer<bool> sendDisabledBySlowmode;
rpl::producer<bool> liked;
rpl::producer<std::optional<QString>> writeRestriction;
};

View File

@ -1069,9 +1069,10 @@ ComposeControls::ComposeControls(
, _wrap(std::make_unique<Ui::RpWidget>(parent))
, _writeRestricted(std::make_unique<Ui::RpWidget>(parent))
, _send(std::make_shared<Ui::SendButton>(_wrap.get(), _st.send))
, _attachToggle(Ui::CreateChild<Ui::IconButton>(
_wrap.get(),
_st.attach))
, _like(_features.likes
? Ui::CreateChild<Ui::IconButton>(_wrap.get(), _st.like)
: nullptr)
, _attachToggle(Ui::CreateChild<Ui::IconButton>(_wrap.get(), _st.attach))
, _tabbedSelectorToggle(Ui::CreateChild<Ui::EmojiButton>(
_wrap.get(),
_st.emoji))
@ -1138,6 +1139,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
| rpl::then(std::move(args.slowmodeSecondsLeft));
_sendDisabledBySlowmode = rpl::single(false)
| rpl::then(std::move(args.sendDisabledBySlowmode));
_liked = args.liked ? std::move(args.liked) : rpl::single(false);
_writeRestriction = rpl::single(std::optional<QString>())
| rpl::then(std::move(args.writeRestriction));
const auto history = *args.history;
@ -1153,6 +1155,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
initWebpageProcess();
initForwardProcess();
updateBotCommandShown();
updateLikeShown();
updateMessagesTTLShown();
updateControlsGeometry(_wrap->size());
updateControlsVisibility();
@ -1559,6 +1562,15 @@ void ComposeControls::init() {
_botCommandStart->setClickedCallback([=] { setText({ "/" }); });
}
if (_like) {
_like->setClickedCallback([=] { _likeToggled.fire({}); });
_liked.value(
) | rpl::start_with_next([=](bool liked) {
const auto icon = liked ? &_st.liked : nullptr;
_like->setIconOverride(icon, icon);
}, _like->lifetime());
}
_wrap->sizeValue(
) | rpl::start_with_next([=](QSize size) {
updateControlsGeometry(size);
@ -1980,7 +1992,7 @@ void ComposeControls::fieldChanged() {
if (!_hasSendText.current() && _preview) {
_preview->setState(Data::PreviewState::Allowed);
}
if (updateBotCommandShown()) {
if (updateBotCommandShown() || updateLikeShown()) {
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
}
@ -2521,6 +2533,7 @@ void ComposeControls::updateControlsGeometry(QSize size) {
- st::historySendRight
- _send->width()
- _tabbedSelectorToggle->width()
- (_likeShown ? _like->width() : 0)
- (_botCommandShown ? _botCommandStart->width() : 0)
- (_silent ? _silent->width() : 0)
- (_ttlInfo ? _ttlInfo->width() : 0);
@ -2560,6 +2573,12 @@ void ComposeControls::updateControlsGeometry(QSize size) {
right += _send->width();
_tabbedSelectorToggle->moveToRight(right, buttonsTop);
right += _tabbedSelectorToggle->width();
if (_like) {
_like->moveToRight(right, buttonsTop);
if (_likeShown) {
right += _like->width();
}
}
if (_botCommandStart) {
_botCommandStart->moveToRight(right, buttonsTop);
if (_botCommandShown) {
@ -2584,6 +2603,9 @@ void ComposeControls::updateControlsVisibility() {
if (_botCommandStart) {
_botCommandStart->setVisible(_botCommandShown);
}
if (_like) {
_like->setVisible(_likeShown);
}
if (_ttlInfo) {
_ttlInfo->show();
}
@ -2598,6 +2620,15 @@ void ComposeControls::updateControlsVisibility() {
}
}
bool ComposeControls::updateLikeShown() {
auto shown = _like && !HasSendText(_field);
if (_likeShown != shown) {
_likeShown = shown;
return true;
}
return false;
}
bool ComposeControls::updateBotCommandShown() {
auto shown = false;
const auto peer = _history ? _history->peer.get() : nullptr;
@ -3100,6 +3131,10 @@ rpl::producer<not_null<QEvent*>> ComposeControls::viewportEvents() const {
return _voiceRecordBar->lockViewportEvents();
}
rpl::producer<> ComposeControls::likeToggled() const {
return _likeToggled.events();
}
bool ComposeControls::isRecording() const {
return _voiceRecordBar->isRecording();
}
@ -3123,6 +3158,12 @@ rpl::producer<bool> ComposeControls::fieldMenuShownValue() const {
return _field->menuShownValue();
}
not_null<QWidget*> ComposeControls::likeAnimationTarget() const {
Expects(_like != nullptr);
return _like;
}
bool ComposeControls::preventsClose(Fn<void()> &&continueCallback) const {
if (_voiceRecordBar->isActive()) {
_voiceRecordBar->showDiscardBox(std::move(continueCallback));

View File

@ -161,6 +161,7 @@ public:
[[nodiscard]] rpl::producer<InlineChosen> inlineResultChosen() const;
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
[[nodiscard]] rpl::producer<not_null<QEvent*>> viewportEvents() const;
[[nodiscard]] rpl::producer<> likeToggled() const;
[[nodiscard]] auto scrollKeyEvents() const
-> rpl::producer<not_null<QKeyEvent*>>;
[[nodiscard]] auto editLastMessageRequests() const
@ -221,6 +222,7 @@ public:
[[nodiscard]] rpl::producer<bool> recordingActiveValue() const;
[[nodiscard]] rpl::producer<bool> hasSendTextValue() const;
[[nodiscard]] rpl::producer<bool> fieldMenuShownValue() const;
[[nodiscard]] not_null<QWidget*> likeAnimationTarget() const;
void applyCloudDraft();
void applyDraft(
@ -292,6 +294,7 @@ private:
bool showRecordButton() const;
void drawRestrictedWrite(QPainter &p, const QString &error);
bool updateBotCommandShown();
bool updateLikeShown();
void cancelInlineBot();
void clearInlineBot();
@ -344,6 +347,7 @@ private:
Fn<Api::SendAction()> _sendActionFactory;
rpl::variable<int> _slowmodeSecondsLeft;
rpl::variable<bool> _sendDisabledBySlowmode;
rpl::variable<bool> _liked;
rpl::variable<std::optional<QString>> _writeRestriction;
rpl::variable<bool> _hidden;
Mode _mode = Mode::Normal;
@ -354,6 +358,7 @@ private:
std::optional<Ui::RoundRect> _backgroundRect;
const std::shared_ptr<Ui::SendButton> _send;
Ui::IconButton * const _like = nullptr;
const not_null<Ui::IconButton*> _attachToggle;
std::unique_ptr<Ui::IconButton> _replaceMedia;
const not_null<Ui::EmojiButton*> _tabbedSelectorToggle;
@ -386,6 +391,7 @@ private:
rpl::event_stream<not_null<QKeyEvent*>> _scrollKeyEvents;
rpl::event_stream<not_null<QKeyEvent*>> _editLastMessageRequests;
rpl::event_stream<std::optional<bool>> _attachRequests;
rpl::event_stream<> _likeToggled;
rpl::event_stream<ReplyNextRequest> _replyNextRequests;
rpl::event_stream<> _focusRequests;
rpl::variable<bool> _recording;
@ -407,6 +413,7 @@ private:
mtpRequestId _inlineBotResolveRequestId = 0;
bool _isInlineBot = false;
bool _botCommandShown = false;
bool _likeShown = false;
FullMsgId _editingId;
std::shared_ptr<Data::PhotoMedia> _photoEditMedia;

View File

@ -313,7 +313,7 @@ Controller::Controller(not_null<Delegate*> delegate)
.type = Ui::MessageSendingAnimationFrom::Type::Emoji,
.globalStartGeometry = id.globalGeometry,
.frame = id.icon,
});
}, _wrap.get());
_replyArea->sendReaction(id.id);
unfocusReply();
}, _lifetime);
@ -587,6 +587,18 @@ bool Controller::skipCaption() const {
return _captionFullView != nullptr;
}
bool Controller::liked() const {
return _liked.current();
}
rpl::producer<bool> Controller::likedValue() const {
return _liked.value();
}
void Controller::toggleLiked(bool liked) {
_liked = liked;
}
void Controller::showFullCaption() {
if (_captionText.empty()) {
return;
@ -892,6 +904,7 @@ bool Controller::changeShown(Data::Story *story) {
story,
Data::Stories::Polling::Viewer);
}
_liked = false;
return true;
}
@ -1551,7 +1564,8 @@ void Controller::updatePowerSaveBlocker(const Player::TrackState &state) {
void Controller::startReactionAnimation(
Data::ReactionId id,
Ui::MessageSendingAnimationFrom from) {
Ui::MessageSendingAnimationFrom from,
not_null<QWidget*> target) {
Expects(shown());
auto args = Ui::ReactionFlyAnimationArgs{
@ -1568,7 +1582,7 @@ void Controller::startReactionAnimation(
Data::CustomEmojiSizeTag::Isolated);
const auto layer = _reactionAnimation->layer();
_wrap->paintRequest() | rpl::start_with_next([=] {
if (!_reactionAnimation->paintBadgeFrame(_wrap.get())) {
if (!_reactionAnimation->paintBadgeFrame(target)) {
InvokeQueued(layer, [=] {
_reactionAnimation = nullptr;
_wrap->update();

View File

@ -123,6 +123,9 @@ public:
[[nodiscard]] Data::FileOrigin fileOrigin() const;
[[nodiscard]] TextWithEntities captionText() const;
[[nodiscard]] bool skipCaption() const;
[[nodiscard]] bool liked() const;
[[nodiscard]] rpl::producer<bool> likedValue() const;
void toggleLiked(bool liked);
void showFullCaption();
void captionClosing();
void captionClosed();
@ -236,7 +239,8 @@ private:
void startReactionAnimation(
Data::ReactionId id,
Ui::MessageSendingAnimationFrom from);
Ui::MessageSendingAnimationFrom from,
not_null<QWidget*> target);
const not_null<Delegate*> _delegate;
@ -269,6 +273,7 @@ private:
Data::StoriesContext _context;
std::optional<Data::StoriesSource> _source;
std::optional<StoriesList> _list;
rpl::variable<bool> _liked;
FullStoryId _waitingForId;
int _waitingForDelta = 0;
int _index = 0;

View File

@ -121,6 +121,7 @@ ReplyArea::ReplyArea(not_null<Controller*> controller)
.voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now),
.voiceLockFromBottom = true,
.features = {
.likes = true,
.sendAs = false,
.ttlInfo = false,
.botCommandSend = false,
@ -620,6 +621,11 @@ void ReplyArea::initActions() {
sendInlineResult(chosen.result, chosen.bot, chosen.options, localId);
}, _lifetime);
_controls->likeToggled(
) | rpl::start_with_next([=] {
_controller->toggleLiked(!_controller->liked());
}, _lifetime);
_controls->setMimeDataHook([=](
not_null<const QMimeData*> data,
Ui::InputField::MimeAction action) {
@ -660,6 +666,7 @@ void ReplyArea::show(ReplyAreaData data) {
const auto history = user ? user->owner().history(user).get() : nullptr;
_controls->setHistory({
.history = history,
.liked = _controller->likedValue(),
});
_controls->clear();
const auto hidden = user && user->isSelf();
@ -718,6 +725,10 @@ void ReplyArea::tryProcessKeyInput(not_null<QKeyEvent*> e) {
_controls->tryProcessKeyInput(e);
}
not_null<QWidget*> ReplyArea::likeAnimationTarget() const {
return _controls->likeAnimationTarget();
}
void ReplyArea::showPremiumToast(not_null<DocumentData*> emoji) {
// #TODO stories
}

View File

@ -70,6 +70,8 @@ public:
[[nodiscard]] bool ignoreWindowMove(QPoint position) const;
void tryProcessKeyInput(not_null<QKeyEvent*> e);
[[nodiscard]] not_null<QWidget*> likeAnimationTarget() const;
private:
class Cant;

View File

@ -353,7 +353,7 @@ struct Feature {
if (const auto window = show->resolveWindow(usage)) {
Settings::ShowPremium(
window,
u"stories_stealth_mode"_q);
u"stories__stealth_mode"_q);
window->window().activate();
}
} else if (now.mode.cooldownTill > now.now) {

View File

@ -109,6 +109,7 @@ mediaviewRight: icon {
{ "mediaview/next", mediaviewControlFg }
};
mediaviewSave: icon {{ "mediaview/download", mediaviewControlFg }};
mediaviewSaveLocked: icon {{ "mediaview/download_locked", mediaviewControlFg }};
mediaviewShare: icon {{ "mediaview/viewer_share", mediaviewControlFg }};
mediaviewRotate: icon {{ "mediaview/rotate", mediaviewControlFg }};
mediaviewMore: icon {{ "title_menu_dots", mediaviewControlFg }};
@ -466,6 +467,10 @@ storiesAttach: IconButton(historyAttach) {
iconOver: icon {{ "chat/input_attach", storiesComposeGrayIcon }};
ripple: storiesComposeRippleLight;
}
storiesLike: IconButton(storiesAttach) {
icon: icon {{ "chat/input_like", storiesComposeGrayIcon }};
iconOver: icon {{ "chat/input_like", storiesComposeGrayIcon }};
}
storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }};
storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }};
storiesRemoveSet: IconButton(stickerPanRemoveSet) {
@ -676,6 +681,8 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
}
attach: storiesAttach;
emoji: storiesAttachEmoji;
like: storiesLike;
liked: icon{{ "chat/input_liked", settingsIconBg1 }};
suggestions: EmojiSuggestions(defaultEmojiSuggestions) {
dropdown: InnerDropdown(emojiSuggestionsDropdown) {
animation: PanelAnimation(defaultPanelAnimation) {

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/view/media_view_overlay_opengl.h"
#include "data/data_peer_values.h" // AmPremiumValue.
#include "ui/gl/gl_shader.h"
#include "ui/painter.h"
#include "media/stories/media_stories_view.h"
@ -122,7 +123,16 @@ OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
crl::on_main(this, [=] {
_owner->_storiesChanged.events(
) | rpl::start_with_next([=] {
invalidateControls();
if (_owner->_storiesSession) {
Data::AmPremiumValue(
_owner->_storiesSession
) | rpl::start_with_next([=] {
invalidateControls();
}, _storiesLifetime);
} else {
_storiesLifetime.destroy();
invalidateControls();
}
}, _lifetime);
});
}
@ -648,8 +658,7 @@ void OverlayWidget::RendererGL::paintControl(
QRect inner,
float64 innerOpacity,
const style::icon &icon) {
const auto stories = (_owner->_stories != nullptr);
const auto meta = ControlMeta(control, stories);
const auto meta = controlMeta(control);
Assert(meta.icon == &icon);
const auto overAlpha = overOpacity * kOverBackgroundOpacity;
@ -707,18 +716,25 @@ void OverlayWidget::RendererGL::paintControl(
FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
}
auto OverlayWidget::RendererGL::ControlMeta(Over control, bool stories)
-> Control {
auto OverlayWidget::RendererGL::controlMeta(Over control) const -> Control {
const auto stories = [&] {
return (_owner->_stories != nullptr);
};
switch (control) {
case Over::Left: return {
0,
stories ? &st::storiesLeft : &st::mediaviewLeft
stories() ? &st::storiesLeft : &st::mediaviewLeft
};
case Over::Right: return {
1,
stories ? &st::storiesRight : &st::mediaviewRight
stories() ? &st::storiesRight : &st::mediaviewRight
};
case Over::Save: return {
2,
(_owner->saveControlLocked()
? &st::mediaviewSaveLocked
: &st::mediaviewSave)
};
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 };
@ -730,14 +746,13 @@ void OverlayWidget::RendererGL::validateControls() {
if (!_controlsImage.image().isNull()) {
return;
}
const auto stories = (_owner->_stories != nullptr);
const auto metas = {
ControlMeta(Over::Left, stories),
ControlMeta(Over::Right, stories),
ControlMeta(Over::Save, stories),
ControlMeta(Over::Share, stories),
ControlMeta(Over::Rotate, stories),
ControlMeta(Over::More, stories),
controlMeta(Over::Left),
controlMeta(Over::Right),
controlMeta(Over::Save),
controlMeta(Over::Share),
controlMeta(Over::Rotate),
controlMeta(Over::More),
};
auto maxWidth = 0;
auto fullHeight = 0;

View File

@ -149,7 +149,7 @@ private:
Ui::GL::Image _storiesSiblingParts[kStoriesSiblingPartsCount];
static constexpr auto kControlsCount = 6;
[[nodiscard]] static Control ControlMeta(Over control, bool stories);
[[nodiscard]] Control controlMeta(Over control) const;
// Last one is for the over circle image.
std::array<QRect, kControlsCount + 1> _controlsTextures;
@ -158,6 +158,7 @@ private:
bool _shadowsForStories = false;
bool _blendingEnabled = false;
rpl::lifetime _storiesLifetime;
rpl::lifetime _lifetime;
};

View File

@ -86,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session_settings.h"
#include "layout/layout_document_generic_preview.h"
#include "platform/platform_overlay_widget.h"
#include "settings/settings_premium.h"
#include "storage/file_download.h"
#include "storage/storage_account.h"
#include "calls/calls_instance.h"
@ -130,6 +131,7 @@ constexpr auto kIdsPreloadAfter = 28;
constexpr auto kLeftSiblingTextureIndex = 1;
constexpr auto kRightSiblingTextureIndex = 2;
constexpr auto kStoriesControlsOpacity = 1.;
constexpr auto kStorySavePromoDuration = 3 * crl::time(1000);
class PipDelegate final : public Pip::Delegate {
public:
@ -326,16 +328,18 @@ public:
return _widget->_body;
}
bool valid() const override {
return _widget->_storiesSession != nullptr;
return _widget->_session || _widget->_storiesSession;
}
operator bool() const override {
return valid();
}
Main::Session &session() const override {
Expects(_widget->_storiesSession != nullptr);
Expects(_widget->_session || _widget->_storiesSession);
return *_widget->_storiesSession;
return _widget->_session
? *_widget->_session
: *_widget->_storiesSession;
}
bool paused(ChatHelpers::PauseReason reason) const override {
if (_widget->isHidden()
@ -1024,22 +1028,26 @@ QSize OverlayWidget::flipSizeByRotation(QSize size) const {
return FlipSizeByRotation(size, _rotation);
}
bool OverlayWidget::hasCopyMediaRestriction() const {
const auto story = _stories ? _stories->story() : nullptr;
return (story && !story->canDownload())
|| (_history && !_history->peer->allowsForwarding())
bool OverlayWidget::hasCopyMediaRestriction(bool skipPremiumCheck) const {
if (const auto story = _stories ? _stories->story() : nullptr) {
return skipPremiumCheck
? story->canDownloadIfPremium()
: story->canDownloadChecked();
}
return (_history && !_history->peer->allowsForwarding())
|| (_message && _message->forbidsSaving());
}
bool OverlayWidget::showCopyMediaRestriction() {
if (!hasCopyMediaRestriction()) {
bool OverlayWidget::showCopyMediaRestriction(bool skipPRemiumCheck) {
if (!hasCopyMediaRestriction(skipPRemiumCheck)) {
return false;
} else if (!_history) {
return true;
} else if (_stories) {
uiShow()->showToast(tr::lng_error_nocopy_story(tr::now));
} else if (_history) {
uiShow()->showToast(_history->peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
: tr::lng_error_nocopy_group(tr::now));
}
Ui::Toast::Show(_widget, _history->peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
: tr::lng_error_nocopy_group(tr::now));
return true;
}
@ -1210,8 +1218,8 @@ void OverlayWidget::refreshNavVisibility() {
}
}
bool OverlayWidget::contentCanBeSaved() const {
if (hasCopyMediaRestriction()) {
bool OverlayWidget::computeSaveButtonVisible() const {
if (hasCopyMediaRestriction(true)) {
return false;
} else if (_photo) {
return _photo->hasVideo() || _photoMedia->loaded();
@ -1240,6 +1248,26 @@ void OverlayWidget::checkForSaveLoaded() {
}
}
void OverlayWidget::showPremiumDownloadPromo() {
const auto filter = [=](const auto &...) {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto window = uiShow()->resolveWindow(usage)) {
const auto ref = u"stories__save_stories_to_gallery"_q;
Settings::ShowPremium(window, ref);
}
return false;
};
uiShow()->showToast({
.text = tr::lng_stories_save_promo(
tr::now,
lt_link,
Ui::Text::Bold(tr::lng_send_as_premium_required_link(tr::now)),
Ui::Text::WithEntities),
.duration = kStorySavePromoDuration,
.filter = filter,
});
}
void OverlayWidget::updateControls() {
if (_document && documentBubbleShown()) {
_docRect = QRect(
@ -1290,7 +1318,7 @@ void OverlayWidget::updateControls() {
const auto overRect = QRect(
QPoint(),
QSize(st::mediaviewIconOver, st::mediaviewIconOver));
_saveVisible = contentCanBeSaved();
_saveVisible = computeSaveButtonVisible();
_shareVisible = story && story->canShare();
_rotateVisible = !_themePreviewShown && !story;
const auto navRect = [&](int i) {
@ -1320,6 +1348,7 @@ void OverlayWidget::updateControls() {
_saveNav = navRect(index);
_saveNavOver = style::centerrect(_saveNav, overRect);
_saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave);
Assert(st::mediaviewSave.size() == st::mediaviewSaveLocked.size());
const auto dNow = QDateTime::currentDateTime();
const auto d = [&] {
@ -1471,7 +1500,7 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
? &st::mediaMenuIconArchiveStory
: &st::mediaMenuIconSaveStory);
}
if ((!story || story->canDownload())
if ((!story || story->canDownloadChecked())
&& _document
&& !_document->filepath(true).isEmpty()) {
const auto text = Platform::IsMac()
@ -1533,11 +1562,13 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
[=] { deleteMedia(); },
&st::mediaMenuIconDelete);
}
if (!hasCopyMediaRestriction()) {
if (!hasCopyMediaRestriction(true)) {
addAction(
tr::lng_mediaview_save_as(tr::now),
[=] { saveAs(); },
&st::mediaMenuIconDownload);
(saveControlLocked()
? &st::mediaMenuIconDownloadLocked
: &st::mediaMenuIconDownload));
}
if (const auto overviewType = computeOverviewType()) {
@ -2285,7 +2316,11 @@ void OverlayWidget::notifyFileDialogShown(bool shown) {
}
void OverlayWidget::saveAs() {
if (showCopyMediaRestriction()) {
if (showCopyMediaRestriction(true)) {
return;
} else if (hasCopyMediaRestriction()) {
Assert(_stories != nullptr);
showPremiumDownloadPromo();
return;
}
QString file;
@ -2421,9 +2456,13 @@ void OverlayWidget::handleDocumentClick() {
void OverlayWidget::downloadMedia() {
if (!_photo && !_document) {
return;
}
if (Core::App().settings().askDownloadPath()) {
} else if (Core::App().settings().askDownloadPath()) {
return saveAs();
} else if (hasCopyMediaRestriction()) {
if (_stories && !hasCopyMediaRestriction(true)) {
showPremiumDownloadPromo();
}
return;
}
QString path;
@ -2478,7 +2517,7 @@ void OverlayWidget::downloadMedia() {
}
}
} else {
_saveVisible = contentCanBeSaved();
_saveVisible = computeSaveButtonVisible();
update(_saveNavOver);
}
updateOver(_lastMouseMovePos);
@ -2498,7 +2537,7 @@ void OverlayWidget::downloadMedia() {
}
} else {
if (!_photo || !_photoMedia->loaded()) {
_saveVisible = contentCanBeSaved();
_saveVisible = computeSaveButtonVisible();
update(_saveNavOver);
} else {
if (!QDir().exists(path)) {
@ -3899,8 +3938,7 @@ void OverlayWidget::initThemePreview() {
_themeShare->setClickedCallback([=] {
QGuiApplication::clipboard()->setText(
session->createInternalLinkFull("addtheme/" + slug));
Ui::Toast::Show(
_body,
uiShow()->showToast(
tr::lng_background_link_copied(tr::now));
});
} else {
@ -4198,6 +4236,10 @@ not_null<Ui::RpWidget*> OverlayWidget::storiesWrap() {
}
std::shared_ptr<ChatHelpers::Show> OverlayWidget::storiesShow() {
return uiShow();
}
std::shared_ptr<ChatHelpers::Show> OverlayWidget::uiShow() {
if (!_cachedShow) {
_cachedShow = std::make_shared<Show>(this);
}
@ -4778,6 +4820,13 @@ void OverlayWidget::paintSaveMsgContent(
p.setOpacity(1);
}
bool OverlayWidget::saveControlLocked() const {
const auto story = _stories ? _stories->story() : nullptr;
return story
&& story->canDownloadIfPremium()
&& !story->canDownloadChecked();
}
void OverlayWidget::paintControls(
not_null<Renderer*> renderer,
float64 opacity) {
@ -4811,7 +4860,9 @@ void OverlayWidget::paintControls(
_saveVisible,
_saveNavOver,
_saveNavIcon,
st::mediaviewSave },
(saveControlLocked()
? st::mediaviewSaveLocked
: st::mediaviewSave) },
{
Over::Share,
_shareVisible,

View File

@ -245,6 +245,7 @@ private:
void playbackPauseMusic();
void switchToPip();
[[nodiscard]] int topNotchSkip() const;
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow();
not_null<Ui::RpWidget*> storiesWrap() override;
std::shared_ptr<ChatHelpers::Show> storiesShow() override;
@ -310,8 +311,9 @@ private:
void handleScreenChanged(QScreen *screen);
bool contentCanBeSaved() const;
[[nodiscard]] bool computeSaveButtonVisible() const;
void checkForSaveLoaded();
void showPremiumDownloadPromo();
Entity entityForUserPhotos(int index) const;
Entity entityForSharedMedia(int index) const;
@ -495,8 +497,10 @@ private:
void validatePhotoImage(Image *image, bool blurred);
void validatePhotoCurrentImage();
[[nodiscard]] bool hasCopyMediaRestriction() const;
[[nodiscard]] bool showCopyMediaRestriction();
[[nodiscard]] bool hasCopyMediaRestriction(
bool skipPremiumCheck = false) const;
[[nodiscard]] bool showCopyMediaRestriction(
bool skipPRemiumCheck = false);
[[nodiscard]] QSize flipSizeByRotation(QSize size) const;
@ -518,7 +522,8 @@ private:
[[nodiscard]] bool contentShown() const;
[[nodiscard]] bool opaqueContentShown() const;
void clearStreaming(bool savePosition = true);
bool canInitStreaming() const;
[[nodiscard]] bool canInitStreaming() const;
[[nodiscard]] bool saveControlLocked() const;
[[nodiscard]] bool topShadowOnTheRight() const;
void applyHideWindowWorkaround();

View File

@ -95,8 +95,7 @@ void EmojiFlyAnimation::repaint() {
}
}
bool EmojiFlyAnimation::paintBadgeFrame(
not_null<Ui::RpWidget*> widget) {
bool EmojiFlyAnimation::paintBadgeFrame(not_null<QWidget*> widget) {
_target = widget;
return !_fly.finished();
}

View File

@ -25,7 +25,7 @@ public:
[[nodiscard]] bool finished() const;
void repaint();
bool paintBadgeFrame(not_null<Ui::RpWidget*> widget);
bool paintBadgeFrame(not_null<QWidget*> widget);
private:
const int _flySize = 0;
@ -33,7 +33,7 @@ private:
Ui::RpWidget _layer;
QRect _area;
bool _areaUpdated = false;
QPointer<Ui::RpWidget> _target;
QPointer<QWidget> _target;
};

View File

@ -126,6 +126,7 @@ mediaMenuIconCancel: icon {{ "menu/cancel", mediaviewMenuFg }};
mediaMenuIconShowInChat: icon {{ "menu/show_in_chat", mediaviewMenuFg }};
mediaMenuIconShowInFolder: icon {{ "menu/show_in_folder", mediaviewMenuFg }};
mediaMenuIconDownload: icon {{ "menu/download", mediaviewMenuFg }};
mediaMenuIconDownloadLocked: icon {{ "menu/download_locked", mediaviewMenuFg }};
mediaMenuIconCopy: icon {{ "menu/copy", mediaviewMenuFg }};
mediaMenuIconForward: icon {{ "menu/forward", mediaviewMenuFg }};
mediaMenuIconDelete: icon {{ "menu/delete", mediaviewMenuFg }};