mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Edit price on send, send single paid media.
This commit is contained in:
parent
3ece9b1566
commit
a9bd7803e6
30 changed files with 382 additions and 37 deletions
|
@ -3311,6 +3311,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
|
@ -3322,6 +3324,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
|
||||
"lng_factcheck_links" = "Only **t.me/** links are allowed.";
|
||||
|
||||
"lng_paid_title" = "Paid Content";
|
||||
"lng_paid_enter_cost" = "Enter Unlock Cost";
|
||||
"lng_paid_cost_placeholder" = "Stars to Unlock";
|
||||
"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}";
|
||||
"lng_paid_about_link" = "More about stars >";
|
||||
"lng_paid_price" = "Unlock for {price}";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Api {
|
|||
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
|
||||
|
||||
struct SendOptions {
|
||||
uint64 price = 0;
|
||||
PeerData *sendAs = nullptr;
|
||||
TimeId scheduled = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
|
|
|
@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
|||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
||||
void ViewsManager::pollExtendedMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
bool force) {
|
||||
if (!item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
|
@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
|||
const auto peer = item->history()->peer;
|
||||
auto &request = _pollRequests[peer];
|
||||
if (request.ids.contains(id) || request.sent.contains(id)) {
|
||||
return;
|
||||
if (!force || request.forced) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
request.ids.emplace(id);
|
||||
if (!request.id && !request.when) {
|
||||
request.when = crl::now() + kPollExtendedMediaPeriod;
|
||||
if (force) {
|
||||
request.forced = true;
|
||||
}
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
const auto delay = force ? 1 : kPollExtendedMediaPeriod;
|
||||
if (!request.id && (!request.when || force)) {
|
||||
request.when = crl::now() + delay;
|
||||
}
|
||||
if (!_pollTimer.isActive() || force) {
|
||||
_pollTimer.callOnce(delay);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests(
|
|||
if (i->second.ids.empty()) {
|
||||
i = _pollRequests.erase(i);
|
||||
} else {
|
||||
i->second.when = now + kPollExtendedMediaPeriod;
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
const auto delay = i->second.forced
|
||||
? 1
|
||||
: kPollExtendedMediaPeriod;
|
||||
i->second.when = now + delay;
|
||||
if (!_pollTimer.isActive() || i->second.forced) {
|
||||
_pollTimer.callOnce(delay);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ public:
|
|||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item);
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item, bool force = false);
|
||||
|
||||
private:
|
||||
struct PollExtendedMediaRequest {
|
||||
|
@ -34,6 +34,7 @@ private:
|
|||
mtpRequestId id = 0;
|
||||
base::flat_set<MsgId> ids;
|
||||
base::flat_set<MsgId> sent;
|
||||
bool forced = false;
|
||||
};
|
||||
|
||||
void viewsIncrement();
|
||||
|
|
|
@ -4188,7 +4188,11 @@ void ApiWrap::sendMediaWithRandomId(
|
|||
MTP_flags(flags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
media,
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
|
|
|
@ -463,6 +463,7 @@ void EditCaptionBox::rebuildPreview() {
|
|||
st::defaultComposeControls,
|
||||
gifPaused,
|
||||
file,
|
||||
[] { return true; },
|
||||
Ui::AttachControls::Type::EditOnly);
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
|
@ -36,9 +37,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/chat/attach/attach_single_file_preview.h"
|
||||
#include "ui/chat/attach/attach_single_media_preview.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -58,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace {
|
||||
|
||||
constexpr auto kMaxMessageLength = 4096;
|
||||
constexpr auto kMaxPrice = 1000ULL;
|
||||
|
||||
using Ui::SendFilesWay;
|
||||
|
||||
|
@ -103,6 +107,74 @@ rpl::producer<QString> FieldPlaceholder(
|
|||
: tr::lng_photos_comment();
|
||||
}
|
||||
|
||||
void EditPriceBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
uint64 price,
|
||||
Fn<void(uint64)> apply) {
|
||||
const auto owner = &session->data();
|
||||
box->setTitle(tr::lng_paid_title());
|
||||
AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_paid_enter_cost(),
|
||||
(st::boxRowPadding - QMargins(
|
||||
st::defaultSubsectionTitlePadding.left(),
|
||||
0,
|
||||
st::defaultSubsectionTitlePadding.right(),
|
||||
0)));
|
||||
const auto field = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::editTagField,
|
||||
tr::lng_paid_cost_placeholder(),
|
||||
price ? QString::number(price) : QString()));
|
||||
field->selectAll();
|
||||
field->setMaxLength(QString::number(kMaxPrice).size());
|
||||
box->setFocusCallback([=] {
|
||||
field->setFocusFast();
|
||||
});
|
||||
const auto about = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_paid_about(
|
||||
lt_link,
|
||||
tr::lng_paid_about_link() | Ui::Text::ToLink(),
|
||||
Ui::Text::WithEntities),
|
||||
st::paidAmountAbout),
|
||||
st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
Core::App().iv().openWithIvPreferred(
|
||||
session,
|
||||
u"https://telegram.org/blog/telegram-stars"_q);
|
||||
return false;
|
||||
});
|
||||
|
||||
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(field);
|
||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
||||
}, field->lifetime());
|
||||
|
||||
const auto save = [=] {
|
||||
const auto now = field->getLastText().toULongLong();
|
||||
if (now > kMaxPrice) {
|
||||
field->showError();
|
||||
return;
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
apply(now);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
|
||||
field->submits(
|
||||
) | rpl::start_with_next(save, field->lifetime());
|
||||
|
||||
box->addButton(tr::lng_settings_save(), save);
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
|
||||
|
@ -153,7 +225,8 @@ SendFilesBox::Block::Block(
|
|||
int from,
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way)
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
|
@ -170,14 +243,16 @@ SendFilesBox::Block::Block(
|
|||
parent.get(),
|
||||
st,
|
||||
my,
|
||||
way);
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
parent,
|
||||
st,
|
||||
gifPaused,
|
||||
first);
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
|
@ -385,6 +460,9 @@ Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
|
|||
: _invertCaption
|
||||
? SendMenu::CaptionState::Above
|
||||
: SendMenu::CaptionState::Below;
|
||||
result.price = canChangePrice()
|
||||
? _price.current()
|
||||
: std::optional<uint64>();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
@ -398,6 +476,7 @@ auto SendFilesBox::prepareSendMenuCallback()
|
|||
case Type::CaptionUp: _invertCaption = true; break;
|
||||
case Type::SpoilerOn: toggleSpoilers(true); break;
|
||||
case Type::SpoilerOff: toggleSpoilers(false); break;
|
||||
case Type::ChangePrice: changePrice(); break;
|
||||
default:
|
||||
SendMenu::DefaultCallback(
|
||||
_show,
|
||||
|
@ -588,14 +667,22 @@ void SendFilesBox::refreshButtons() {
|
|||
addMenuButton();
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
|
||||
bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
|
||||
return (details.type != SendMenu::Type::Disabled)
|
||||
|| (details.spoiler != SendMenu::SpoilerState::None)
|
||||
|| (details.caption != SendMenu::CaptionState::None);
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSpoilerMenu() const {
|
||||
return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
|
||||
return !hasPrice()
|
||||
&& _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
bool SendFilesBox::canChangePrice() const {
|
||||
const auto way = _sendWay.current();
|
||||
return _list.canChangePrice(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
void SendFilesBox::applyBlockChanges() {
|
||||
|
@ -618,6 +705,71 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
|
|||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::changePrice() {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto session = &_show->session();
|
||||
const auto now = _price.current();
|
||||
_show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
|
||||
if (weak && price != now) {
|
||||
_price = price;
|
||||
refreshPriceTag();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasPrice() const {
|
||||
return canChangePrice() && _price.current() > 0;
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshPriceTag() {
|
||||
const auto resetSpoilers = hasPrice() || _priceTag;
|
||||
if (resetSpoilers) {
|
||||
for (auto &file : _list.files) {
|
||||
file.spoiler = false;
|
||||
}
|
||||
for (auto &block : _blocks) {
|
||||
block.toggleSpoilers(hasPrice());
|
||||
}
|
||||
}
|
||||
if (!hasPrice()) {
|
||||
_priceTag = nullptr;
|
||||
} else if (!_priceTag) {
|
||||
_priceTag = std::make_unique<Ui::RpWidget>(_inner.data());
|
||||
const auto raw = _priceTag.get();
|
||||
|
||||
raw->show();
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(st::toastBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto radius = std::min(raw->width(), raw->height()) / 2.;
|
||||
p.drawRoundedRect(raw->rect(), radius, radius);
|
||||
}, raw->lifetime());
|
||||
|
||||
auto price = _price.value() | rpl::map([=](uint64 amount) {
|
||||
return QChar(0x2B50) + Lang::FormatCountDecimal(amount);
|
||||
});
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
tr::lng_paid_price(lt_price, std::move(price)),
|
||||
st::paidTagLabel);
|
||||
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
const auto inner = QRect(QPoint(), size);
|
||||
const auto rect = inner.marginsAdded(st::paidTagPadding);
|
||||
raw->resize(rect.size());
|
||||
label->move(-rect.topLeft());
|
||||
}, label->lifetime());
|
||||
_inner->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
raw->move(
|
||||
(size.width() - raw->width()) / 2,
|
||||
(size.height() - raw->height()) / 2);
|
||||
}, raw->lifetime());
|
||||
} else {
|
||||
_priceTag->raise();
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::addMenuButton() {
|
||||
const auto details = _sendMenuDetails();
|
||||
if (!hasSendMenu(details)) {
|
||||
|
@ -766,7 +918,8 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
from,
|
||||
till,
|
||||
gifPaused,
|
||||
_sendWay.current());
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
|
@ -893,6 +1046,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
|
||||
void SendFilesBox::refreshControls(bool initial) {
|
||||
refreshButtons();
|
||||
refreshPriceTag();
|
||||
refreshTitleText();
|
||||
updateSendWayControls();
|
||||
updateCaptionPlaceholder();
|
||||
|
@ -1447,6 +1601,7 @@ void SendFilesBox::send(
|
|||
auto child = _sendMenuDetails();
|
||||
child.spoiler = SendMenu::SpoilerState::None;
|
||||
child.caption = SendMenu::CaptionState::None;
|
||||
child.price = std::nullopt;
|
||||
return SendMenu::DefaultCallback(_show, sendCallback())(
|
||||
{ .type = SendMenu::ActionType::Schedule },
|
||||
child);
|
||||
|
@ -1475,6 +1630,7 @@ void SendFilesBox::send(
|
|||
? _caption->getTextWithAppliedMarkdown()
|
||||
: TextWithTags();
|
||||
options.invertCaption = _invertCaption;
|
||||
options.price = hasPrice() ? _price.current() : 0;
|
||||
if (!validateLength(caption.text)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -149,7 +149,8 @@ private:
|
|||
int from,
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way);
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
|
@ -190,6 +191,11 @@ private:
|
|||
void addMenuButton();
|
||||
void applyBlockChanges();
|
||||
void toggleSpoilers(bool enabled);
|
||||
void changePrice();
|
||||
|
||||
[[nodiscard]] bool canChangePrice() const;
|
||||
[[nodiscard]] bool hasPrice() const;
|
||||
void refreshPriceTag();
|
||||
|
||||
bool validateLength(const QString &text) const;
|
||||
void refreshButtons();
|
||||
|
@ -251,6 +257,8 @@ private:
|
|||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
Fn<void()> _cancelledCallback;
|
||||
rpl::variable<uint64> _price = 0;
|
||||
std::unique_ptr<Ui::RpWidget> _priceTag;
|
||||
bool _confirmed = false;
|
||||
bool _invertCaption = false;
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ ComposeIcons {
|
|||
menuSpoilerOff: icon;
|
||||
menuBelow: icon;
|
||||
menuAbove: icon;
|
||||
menuPrice: icon;
|
||||
|
||||
stripBubble: icon;
|
||||
stripExpandPanel: icon;
|
||||
|
@ -610,6 +611,7 @@ defaultComposeIcons: ComposeIcons {
|
|||
menuSpoilerOff: menuIconSpoilerOff;
|
||||
menuBelow: menuIconBelow;
|
||||
menuAbove: menuIconAbove;
|
||||
menuPrice: menuIconEarn;
|
||||
|
||||
stripBubble: icon{
|
||||
{ "chat/reactions_bubble_shadow", windowShadowFg },
|
||||
|
@ -1406,3 +1408,15 @@ editTagField: InputField(defaultInputField) {
|
|||
editTagLimit: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
|
||||
paidStarIconTop: 7px;
|
||||
paidAmountAbout: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 256px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
paidTagLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: radialFg;
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
paidTagPadding: margins(16px, 6px, 16px, 6px);
|
||||
|
|
|
@ -170,6 +170,12 @@ void UpdateCloudFile(
|
|||
if (data.progressivePartSize && !file.location.valid()) {
|
||||
file.progressivePartSize = data.progressivePartSize;
|
||||
}
|
||||
if (data.location.width()
|
||||
&& data.location.height()
|
||||
&& !file.location.valid()
|
||||
&& !file.location.width()) {
|
||||
file.location = data.location;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -323,7 +323,7 @@ bool UpdateExtendedMedia(
|
|||
auto changed = false;
|
||||
const auto count = int(media.size());
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
if (i < invoice.extendedMedia.size()) {
|
||||
if (i <= invoice.extendedMedia.size()) {
|
||||
invoice.extendedMedia.emplace_back();
|
||||
changed = true;
|
||||
}
|
||||
|
@ -388,6 +388,7 @@ Invoice ComputeInvoiceData(
|
|||
auto result = Invoice{
|
||||
.amount = data.vstars_amount().v,
|
||||
.currency = Ui::kCreditsCurrency,
|
||||
.isPaidMedia = true,
|
||||
};
|
||||
UpdateExtendedMedia(result, item, data.vextended_media().v);
|
||||
return result;
|
||||
|
@ -1908,6 +1909,7 @@ MediaInvoice::MediaInvoice(
|
|||
.title = data.title,
|
||||
.description = data.description,
|
||||
.photo = data.photo,
|
||||
.isPaidMedia = data.isPaidMedia,
|
||||
.isTest = data.isTest,
|
||||
} {
|
||||
_invoice.extendedMedia.reserve(data.extendedMedia.size());
|
||||
|
|
|
@ -94,6 +94,7 @@ struct Invoice {
|
|||
TextWithEntities description;
|
||||
std::vector<std::unique_ptr<Media>> extendedMedia;
|
||||
PhotoData *photo = nullptr;
|
||||
bool isPaidMedia = false;
|
||||
bool isTest = false;
|
||||
};
|
||||
[[nodiscard]] bool HasExtendedMedia(const Invoice &invoice);
|
||||
|
|
|
@ -7,9 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "history/view/media/history_view_media_common.h"
|
||||
|
||||
#include "api/api_views.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/painter.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_wall_paper.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
|
@ -19,7 +23,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/media/history_view_document.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwindow.h"
|
||||
#include "media/streaming/media_streaming_utility.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
|
@ -180,4 +191,34 @@ QSize CountPhotoMediaSize(
|
|||
media.scaled(media.width(), newWidth, Qt::KeepAspectRatio));
|
||||
}
|
||||
|
||||
ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item) {
|
||||
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
const auto controller = my.sessionWindow.get();
|
||||
const auto itemId = item->fullId();
|
||||
const auto session = &item->history()->session();
|
||||
using Result = Payments::CheckoutResult;
|
||||
const auto done = crl::guard(session, [=](Result result) {
|
||||
if (result != Result::Paid) {
|
||||
return;
|
||||
} else if (const auto item = session->data().message(itemId)) {
|
||||
session->api().views().pollExtendedMedia(item, true);
|
||||
}
|
||||
});
|
||||
Payments::CheckoutProcess::Start(
|
||||
item,
|
||||
Payments::Mode::Payment,
|
||||
(controller
|
||||
? crl::guard(
|
||||
controller,
|
||||
[=](auto) { controller->widget()->activate(); })
|
||||
: Fn<void(Payments::CheckoutResult)>()),
|
||||
((controller && Payments::IsCreditsInvoice(item))
|
||||
? Payments::ProcessNonPanelPaymentFormFactory(
|
||||
controller,
|
||||
done)
|
||||
: nullptr));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -75,4 +75,6 @@ void PaintInterpolatedIcon(
|
|||
int newWidth,
|
||||
int maxWidth);
|
||||
|
||||
[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/media/history_view_media_common.h"
|
||||
#include "history/view/media/history_view_media_spoiler.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
#include "media/streaming/media_streaming_player.h"
|
||||
#include "media/streaming/media_streaming_document.h"
|
||||
|
@ -38,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_web_page.h"
|
||||
#include "core/application.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
@ -140,7 +142,8 @@ void Photo::dataMediaCreated() const {
|
|||
|
||||
if (_data->inlineThumbnailBytes().isEmpty()
|
||||
&& !_dataMedia->image(PhotoSize::Large)
|
||||
&& !_dataMedia->image(PhotoSize::Thumbnail)) {
|
||||
&& !_dataMedia->image(PhotoSize::Thumbnail)
|
||||
&& !_data->extendedMediaPreview()) {
|
||||
_dataMedia->wanted(PhotoSize::Small, _realParent->fullId());
|
||||
}
|
||||
history()->owner().registerHeavyViewPart(_parent);
|
||||
|
@ -277,8 +280,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
|||
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
|
||||
const auto st = context.st;
|
||||
const auto sti = context.imageStyle();
|
||||
auto loaded = _dataMedia->loaded();
|
||||
auto displayLoading = _data->displayLoading();
|
||||
const auto preview = _data->extendedMediaPreview();
|
||||
auto loaded = preview || _dataMedia->loaded();
|
||||
auto displayLoading = !preview && _data->displayLoading();
|
||||
|
||||
auto inWebPage = (_parent->media() != this);
|
||||
auto paintx = 0, painty = 0, paintw = width(), painth = height();
|
||||
|
@ -365,6 +369,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
|||
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
|
||||
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
|
||||
}
|
||||
} else if (preview) {
|
||||
paintPriceTag(p, rthumb);
|
||||
}
|
||||
if (showEnlarge) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
@ -397,6 +403,43 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
|||
}
|
||||
}
|
||||
|
||||
void Photo::paintPriceTag(Painter &p, QRect rthumb) const {
|
||||
const auto media = parent()->data()->media();
|
||||
const auto invoice = media ? media->invoice() : nullptr;
|
||||
const auto price = invoice->isPaidMedia ? invoice->amount : 0;
|
||||
if (!price) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto text = Ui::Text::String();
|
||||
text.setText(
|
||||
st::semiboldTextStyle,
|
||||
tr::lng_paid_price(
|
||||
tr::now,
|
||||
lt_price,
|
||||
QChar(0x2B50) + Lang::FormatCountDecimal(invoice->amount)));
|
||||
const auto width = text.maxWidth();
|
||||
const auto inner = QRect(0, 0, width, text.minHeight());
|
||||
const auto outer = inner.marginsAdded(st::paidTagPadding);
|
||||
const auto size = outer.size();
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(st::toastBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
const auto radius = std::min(size.width(), size.height()) / 2.;
|
||||
const auto rect = QRect(
|
||||
rthumb.x() + (rthumb.width() - size.width()) / 2,
|
||||
rthumb.y() + (rthumb.height() - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height());
|
||||
|
||||
p.drawRoundedRect(rect, radius, radius);
|
||||
p.setPen(st::toastFg);
|
||||
|
||||
text.draw(p, rect.x() - outer.x(), rect.y() - outer.y(), width);
|
||||
}
|
||||
|
||||
void Photo::validateUserpicImageCache(QSize size, bool forum) const {
|
||||
const auto forumValue = forum ? 1 : 0;
|
||||
const auto large = _dataMedia->image(PhotoSize::Large);
|
||||
|
@ -604,6 +647,14 @@ QRect Photo::enlargeRect() const {
|
|||
};
|
||||
}
|
||||
|
||||
ClickHandlerPtr Photo::ensureExtendedMediaLink() const {
|
||||
const auto item = parent()->data();
|
||||
if (!_extendedMediaLink && item->isRegular()) {
|
||||
_extendedMediaLink = MakePaidMediaLink(item);
|
||||
}
|
||||
return _extendedMediaLink;
|
||||
}
|
||||
|
||||
TextState Photo::textState(QPoint point, StateRequest request) const {
|
||||
auto result = TextState(_parent);
|
||||
|
||||
|
@ -617,7 +668,9 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
|
|||
|
||||
if (QRect(paintx, painty, paintw, painth).contains(point)) {
|
||||
ensureDataMediaCreated();
|
||||
result.link = (_spoiler && !_spoiler->revealed)
|
||||
result.link = _data->extendedMediaPreview()
|
||||
? ensureExtendedMediaLink()
|
||||
: (_spoiler && !_spoiler->revealed)
|
||||
? _spoiler->link
|
||||
: _data->uploading()
|
||||
? _cancell
|
||||
|
|
|
@ -147,10 +147,13 @@ private:
|
|||
[[nodiscard]] QSize photoSize() const;
|
||||
[[nodiscard]] QRect enlargeRect() const;
|
||||
|
||||
void paintPriceTag(Painter &p, QRect rthumb) const;
|
||||
[[nodiscard]] ClickHandlerPtr ensureExtendedMediaLink() const;
|
||||
void togglePollingStory(bool enabled) const;
|
||||
|
||||
const not_null<PhotoData*> _data;
|
||||
const FullStoryId _storyId;
|
||||
mutable ClickHandlerPtr _extendedMediaLink;
|
||||
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
|
||||
mutable std::unique_ptr<Streamed> _streamed;
|
||||
const std::unique_ptr<MediaSpoiler> _spoiler;
|
||||
|
|
|
@ -638,6 +638,7 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) {
|
|||
menuSpoilerOff: icon {{ "menu/spoiler_off", storiesComposeWhiteText }};
|
||||
menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }};
|
||||
menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }};
|
||||
menuPrice: icon {{ "menu/earn", storiesComposeWhiteText }};
|
||||
|
||||
stripBubble: icon{
|
||||
{ "chat/reactions_bubble_shadow", windowShadowFg },
|
||||
|
|
|
@ -678,6 +678,14 @@ FillMenuResult FillSendMenu(
|
|||
}, details); },
|
||||
above ? &icons.menuBelow : &icons.menuAbove);
|
||||
}
|
||||
if (details.price) {
|
||||
menu->addAction(
|
||||
((*details.price > 0)
|
||||
? tr::lng_context_change_price(tr::now)
|
||||
: tr::lng_context_make_paid(tr::now)),
|
||||
[=] { action({ .type = ActionType::ChangePrice }, details); },
|
||||
&icons.menuPrice);
|
||||
}
|
||||
|
||||
using namespace HistoryView::Reactions;
|
||||
const auto effect = std::make_shared<QPointer<EffectPreview>>();
|
||||
|
|
|
@ -53,6 +53,7 @@ struct Details {
|
|||
Type type = Type::Disabled;
|
||||
SpoilerState spoiler = SpoilerState::None;
|
||||
CaptionState caption = CaptionState::None;
|
||||
std::optional<uint64> price;
|
||||
bool effectAllowed = false;
|
||||
};
|
||||
|
||||
|
@ -69,6 +70,7 @@ enum class ActionType : uchar {
|
|||
SpoilerOff,
|
||||
CaptionUp,
|
||||
CaptionDown,
|
||||
ChangePrice,
|
||||
};
|
||||
struct Action {
|
||||
using Type = ActionType;
|
||||
|
|
|
@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Payments {
|
||||
namespace {
|
||||
|
||||
bool IsCreditsInvoice(not_null<HistoryItem*> item) {
|
||||
if (const auto payment = item->Get<HistoryServicePayment>()) {
|
||||
|
@ -38,8 +37,6 @@ bool IsCreditsInvoice(not_null<HistoryItem*> item) {
|
|||
return invoice && (invoice->currency == Ui::kCreditsCurrency);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace Payments {
|
|||
enum class CheckoutResult;
|
||||
struct NonPanelPaymentForm;
|
||||
|
||||
[[nodiscard]] bool IsCreditsInvoice(not_null<HistoryItem*> item);
|
||||
|
||||
Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void(Payments::CheckoutResult)> maybeReturnToBot = nullptr);
|
||||
|
|
|
@ -32,9 +32,11 @@ constexpr auto kMinPreviewWidth = 20;
|
|||
AbstractSingleMediaPreview::AbstractSingleMediaPreview(
|
||||
QWidget *parent,
|
||||
const style::ComposeControls &st,
|
||||
AttachControls::Type type)
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: AbstractSinglePreview(parent)
|
||||
, _st(st)
|
||||
, _canToggleSpoiler(std::move(canToggleSpoiler))
|
||||
, _minThumbH(st::sendBoxAlbumGroupSize.height()
|
||||
+ st::sendBoxAlbumGroupSkipTop * 2)
|
||||
, _controls(base::make_unique_q<AttachControlsWidget>(this, type)) {
|
||||
|
@ -266,7 +268,9 @@ void AbstractSingleMediaPreview::applyCursor(style::cursor cursor) {
|
|||
}
|
||||
|
||||
void AbstractSingleMediaPreview::showContextMenu(QPoint position) {
|
||||
if (!_sendWay.sendImagesAsPhotos() || !supportsSpoilers()) {
|
||||
if (!_canToggleSpoiler()
|
||||
|| !_sendWay.sendImagesAsPhotos()
|
||||
|| !supportsSpoilers()) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
|
|
|
@ -26,7 +26,8 @@ public:
|
|||
AbstractSingleMediaPreview(
|
||||
QWidget *parent,
|
||||
const style::ComposeControls &st,
|
||||
AttachControls::Type type);
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
~AbstractSingleMediaPreview();
|
||||
|
||||
void setSendWay(SendFilesWay way);
|
||||
|
@ -71,6 +72,7 @@ private:
|
|||
|
||||
const style::ComposeControls &_st;
|
||||
SendFilesWay _sendWay;
|
||||
Fn<bool()> _canToggleSpoiler;
|
||||
bool _animated = false;
|
||||
QPixmap _preview;
|
||||
QPixmap _previewBlurred;
|
||||
|
|
|
@ -31,10 +31,12 @@ AlbumPreview::AlbumPreview(
|
|||
QWidget *parent,
|
||||
const style::ComposeControls &st,
|
||||
gsl::span<Ui::PreparedFile> items,
|
||||
SendFilesWay way)
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _sendWay(way)
|
||||
, _canToggleSpoiler(std::move(canToggleSpoiler))
|
||||
, _dragTimer([=] { switchToDrag(); }) {
|
||||
setMouseTracking(true);
|
||||
prepareThumbs(items);
|
||||
|
@ -573,7 +575,7 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
|
|||
void AlbumPreview::showContextMenu(
|
||||
not_null<AlbumThumbnail*> thumb,
|
||||
QPoint position) {
|
||||
if (!_sendWay.sendImagesAsPhotos()) {
|
||||
if (!_canToggleSpoiler() || !_sendWay.sendImagesAsPhotos()) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
|
|
|
@ -28,7 +28,8 @@ public:
|
|||
QWidget *parent,
|
||||
const style::ComposeControls &st,
|
||||
gsl::span<Ui::PreparedFile> items,
|
||||
SendFilesWay way);
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
~AlbumPreview();
|
||||
|
||||
void setSendWay(SendFilesWay way);
|
||||
|
@ -92,6 +93,7 @@ private:
|
|||
|
||||
const style::ComposeControls &_st;
|
||||
SendFilesWay _sendWay;
|
||||
Fn<bool()> _canToggleSpoiler;
|
||||
style::cursor _cursor = style::cur_default;
|
||||
std::vector<int> _order;
|
||||
std::vector<QSize> _itemsShownDimensions;
|
||||
|
|
|
@ -36,7 +36,7 @@ ItemSingleMediaPreview::ItemSingleMediaPreview(
|
|||
Fn<bool()> gifPaused,
|
||||
not_null<HistoryItem*> item,
|
||||
AttachControls::Type type)
|
||||
: AbstractSingleMediaPreview(parent, st, type)
|
||||
: AbstractSingleMediaPreview(parent, st, type, [] { return true; })
|
||||
, _gifPaused(std::move(gifPaused))
|
||||
, _fullId(item->fullId()) {
|
||||
const auto media = item->media();
|
||||
|
|
|
@ -195,6 +195,10 @@ bool PreparedList::canMoveCaption(bool sendingAlbum, bool compress) const {
|
|||
|| (file.type == PreparedFile::Type::Photo && compress);
|
||||
}
|
||||
|
||||
bool PreparedList::canChangePrice(bool sendingAlbum, bool compress) const {
|
||||
return canMoveCaption(sendingAlbum, compress);
|
||||
}
|
||||
|
||||
bool PreparedList::hasGroupOption(bool slowmode) const {
|
||||
if (slowmode || files.size() < 2) {
|
||||
return false;
|
||||
|
|
|
@ -115,6 +115,9 @@ struct PreparedList {
|
|||
[[nodiscard]] bool canMoveCaption(
|
||||
bool sendingAlbum,
|
||||
bool compress) const;
|
||||
[[nodiscard]] bool canChangePrice(
|
||||
bool sendingAlbum,
|
||||
bool compress) const;
|
||||
[[nodiscard]] bool canBeSentInSlowmode() const;
|
||||
[[nodiscard]] bool canBeSentInSlowmodeWith(
|
||||
const PreparedList &other) const;
|
||||
|
|
|
@ -19,6 +19,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
|||
const style::ComposeControls &st,
|
||||
Fn<bool()> gifPaused,
|
||||
const PreparedFile &file,
|
||||
Fn<bool()> canToggleSpoiler,
|
||||
AttachControls::Type type) {
|
||||
auto preview = QImage();
|
||||
auto animated = false;
|
||||
|
@ -51,7 +52,8 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
|||
Core::IsMimeSticker(file.information->filemime),
|
||||
file.spoiler,
|
||||
animationPreview ? file.path : QString(),
|
||||
type);
|
||||
type,
|
||||
std::move(canToggleSpoiler));
|
||||
}
|
||||
|
||||
SingleMediaPreview::SingleMediaPreview(
|
||||
|
@ -63,8 +65,9 @@ SingleMediaPreview::SingleMediaPreview(
|
|||
bool sticker,
|
||||
bool spoiler,
|
||||
const QString &animatedPreviewPath,
|
||||
AttachControls::Type type)
|
||||
: AbstractSingleMediaPreview(parent, st, type)
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: AbstractSingleMediaPreview(parent, st, type, std::move(canToggleSpoiler))
|
||||
, _gifPaused(std::move(gifPaused))
|
||||
, _sticker(sticker) {
|
||||
Expects(!preview.isNull());
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
const style::ComposeControls &st,
|
||||
Fn<bool()> gifPaused,
|
||||
const PreparedFile &file,
|
||||
Fn<bool()> canToggleSpoiler,
|
||||
AttachControls::Type type = AttachControls::Type::Full);
|
||||
|
||||
SingleMediaPreview(
|
||||
|
@ -36,7 +37,8 @@ public:
|
|||
bool sticker,
|
||||
bool spoiler,
|
||||
const QString &animatedPreviewPath,
|
||||
AttachControls::Type type);
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
|
||||
protected:
|
||||
bool supportsSpoilers() const override;
|
||||
|
|
Loading…
Add table
Reference in a new issue