mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 07:07:08 +02:00
Implement simple UI for single-type likes in stories.
This commit is contained in:
parent
3adb0c1856
commit
4bd925ac2c
27 changed files with 235 additions and 64 deletions
Telegram
Resources
icons/chat
langs
SourceFiles
chat_helpers
data
history/view/controls
media
stories
media_stories_controller.cppmedia_stories_controller.hmedia_stories_reply.cppmedia_stories_reply.hmedia_stories_stealth.cpp
view
ui
BIN
Telegram/Resources/icons/chat/input_like.png
Normal file
BIN
Telegram/Resources/icons/chat/input_like.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 699 B |
BIN
Telegram/Resources/icons/chat/input_like@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/input_like@2x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.3 KiB |
BIN
Telegram/Resources/icons/chat/input_like@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/input_like@3x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2 KiB |
BIN
Telegram/Resources/icons/chat/input_liked.png
Normal file
BIN
Telegram/Resources/icons/chat/input_liked.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 559 B |
BIN
Telegram/Resources/icons/chat/input_liked@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/input_liked@2x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 942 B |
BIN
Telegram/Resources/icons/chat/input_liked@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/input_liked@3x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.3 KiB |
|
@ -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";
|
||||
|
|
|
@ -196,6 +196,8 @@ ComposeControls {
|
|||
send: SendButton;
|
||||
attach: IconButton;
|
||||
emoji: EmojiButton;
|
||||
like: IconButton;
|
||||
liked: icon;
|
||||
suggestions: EmojiSuggestions;
|
||||
tabbed: EmojiPan;
|
||||
tabbedHeightMin: pixels;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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 }};
|
||||
|
|
Loading…
Add table
Reference in a new issue