tdesktop/Telegram/SourceFiles/history/view/media/history_view_userpic_suggestion.cpp
2023-07-20 07:20:32 +04:00

281 lines
7.9 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_userpic_suggestion.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_user.h"
#include "data/data_photo_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_session.h"
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_layer_widget.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "history/view/history_view_element.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/painter.h"
#include "mainwidget.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "settings/settings_information.h" // UpdatePhotoLocally
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
constexpr auto kToastDuration = 5 * crl::time(1000);
void ShowUserpicSuggestion(
not_null<Window::SessionController*> controller,
const std::shared_ptr<Data::PhotoMedia> &media,
const FullMsgId itemId,
not_null<PeerData*> peer,
Fn<void()> setDone) {
const auto photo = media->owner();
const auto from = peer->asUser();
const auto name = (from && !from->firstName.isEmpty())
? from->firstName
: peer->name();
if (photo->hasVideo()) {
const auto done = [=](Fn<void()> close) {
using namespace Settings;
const auto session = &photo->session();
auto &peerPhotos = session->api().peerPhoto();
peerPhotos.updateSelf(photo, itemId, setDone);
close();
};
controller->show(Ui::MakeConfirmBox({
.text = tr::lng_profile_accept_video_sure(
tr::now,
lt_user,
name),
.confirmed = done,
.confirmText = tr::lng_profile_set_video_button(
tr::now),
}));
} else {
const auto original = std::make_shared<QImage>(
media->image(Data::PhotoSize::Large)->original());
const auto callback = [=](QImage &&image) {
using namespace Settings;
const auto session = &photo->session();
const auto user = session->user();
UpdatePhotoLocally(user, image);
auto &peerPhotos = session->api().peerPhoto();
if (original->size() == image.size()
&& original->constBits() == image.constBits()) {
peerPhotos.updateSelf(photo, itemId, setDone);
} else {
peerPhotos.upload(user, { std::move(image) }, setDone);
}
};
using namespace Editor;
PrepareProfilePhoto(
controller->content(),
&controller->window(),
{
.about = { tr::lng_profile_accept_photo_sure(
tr::now,
lt_user,
name) },
.confirm = tr::lng_profile_set_photo_button(tr::now),
.cropType = EditorData::CropType::Ellipse,
.keepAspectRatio = true,
},
callback,
base::duplicate(*original));
}
}
[[nodiscard]] QImage GrabUserpicFrame(base::weak_ptr<Photo> photo) {
const auto strong = photo.get();
if (!strong || !strong->width() || !strong->height()) {
return {};
}
const auto ratio = style::DevicePixelRatio();
auto frame = QImage(
QSize(strong->width(), strong->height()) * ratio,
QImage::Format_ARGB32_Premultiplied);
frame.fill(Qt::transparent);
frame.setDevicePixelRatio(ratio);
auto p = Painter(&frame);
strong->paintUserpicFrame(p, QPoint(0, 0), false);
p.end();
return frame;
}
void ShowSetToast(
not_null<Window::SessionController*> controller,
const QImage &frame) {
const auto text = Ui::Text::Bold(
tr::lng_profile_changed_photo_title(tr::now)
).append('\n').append(
tr::lng_profile_changed_photo_about(
tr::now,
lt_link,
Ui::Text::Link(
tr::lng_profile_changed_photo_link(tr::now),
u"tg://settings/edit_profile"_q),
Ui::Text::WithEntities)
);
auto st = std::make_shared<style::Toast>(st::historyPremiumToast);
const auto skip = st->padding.top();
const auto size = st->style.font->height * 2;
const auto ratio = style::DevicePixelRatio();
auto copy = frame.scaled(
QSize(size, size) * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
copy.setDevicePixelRatio(ratio);
st->padding.setLeft(skip + size + skip);
st->palette.linkFg = st->palette.selectLinkFg = st::mediaviewTextLinkFg;
const auto weak = controller->showToast({
.text = text,
.st = st.get(),
.duration = kToastDuration,
.multiline = true,
.dark = true,
.slideSide = RectPart::Bottom,
});
if (const auto strong = weak.get()) {
const auto widget = strong->widget();
widget->lifetime().add([st = std::move(st)] {});
const auto preview = Ui::CreateChild<Ui::RpWidget>(widget.get());
preview->moveToLeft(skip, skip);
preview->resize(size, size);
preview->show();
preview->setAttribute(Qt::WA_TransparentForMouseEvents);
preview->paintRequest(
) | rpl::start_with_next([=] {
QPainter(preview).drawImage(0, 0, copy);
}, preview->lifetime());
}
}
[[nodiscard]] Fn<void()> ShowSetToastCallback(
base::weak_ptr<Window::SessionController> weak,
QImage frame) {
return [weak = std::move(weak), frame = std::move(frame)] {
if (const auto strong = weak.get()) {
ShowSetToast(strong, frame);
}
};
}
} // namespace
UserpicSuggestion::UserpicSuggestion(
not_null<Element*> parent,
not_null<PeerData*> chat,
not_null<PhotoData*> photo,
int width)
: _photo(parent, chat, photo, width) {
_photo.initDimensions();
_photo.resizeGetHeight(_photo.maxWidth());
}
UserpicSuggestion::~UserpicSuggestion() = default;
int UserpicSuggestion::top() {
return st::msgServiceGiftBoxButtonMargins.top();
}
QSize UserpicSuggestion::size() {
return { _photo.maxWidth(), _photo.minHeight() };
}
QString UserpicSuggestion::title() {
return QString();
}
QString UserpicSuggestion::button() {
return _photo.getPhoto()->hasVideo()
? (_photo.parent()->data()->out()
? tr::lng_action_suggested_video_button(tr::now)
: tr::lng_profile_set_video_button(tr::now))
: tr::lng_action_suggested_photo_button(tr::now);
}
TextWithEntities UserpicSuggestion::subtitle() {
return _photo.parent()->data()->notificationText();
}
ClickHandlerPtr UserpicSuggestion::createViewLink() {
const auto out = _photo.parent()->data()->out();
const auto photo = _photo.getPhoto();
const auto itemId = _photo.parent()->data()->fullId();
const auto peer = _photo.parent()->data()->history()->peer;
const auto weak = base::make_weak(&_photo);
const auto show = crl::guard(weak, [=](FullMsgId id) {
_photo.showPhoto(id);
});
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
auto frame = GrabUserpicFrame(weak);
if (frame.isNull()) {
return;
}
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
const auto media = photo->activeMediaView();
if (media->loaded()) {
if (out) {
PhotoOpenClickHandler(photo, show, itemId).onClick(
context);
} else {
ShowUserpicSuggestion(
controller,
media,
itemId,
peer,
ShowSetToastCallback(controller, std::move(frame)));
}
} else if (!photo->loading()) {
PhotoSaveClickHandler(photo, itemId).onClick(context);
}
}
});
}
void UserpicSuggestion::draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) {
p.translate(geometry.topLeft());
_photo.draw(p, context);
p.translate(-geometry.topLeft());
}
void UserpicSuggestion::stickerClearLoopPlayed() {
}
std::unique_ptr<StickerPlayer> UserpicSuggestion::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return nullptr;
}
bool UserpicSuggestion::hasHeavyPart() {
return _photo.hasHeavyPart();
}
void UserpicSuggestion::unloadHeavyPart() {
_photo.unloadHeavyPart();
}
} // namespace HistoryView