Show additional information in userpic suggest / accept.

This commit is contained in:
John Preston 2022-12-22 16:41:17 +04:00
parent 076f0e0800
commit 2364b0ad4e
12 changed files with 256 additions and 117 deletions

View file

@ -31,11 +31,25 @@ photoEditorButtonIconFgOver: mediaviewPipControlsFgOver;
photoEditorButtonIconFgActive: lightButtonFg;
photoEditorButtonIconFgInactive: mediaviewPipPlaybackInactive;
photoEditorButtonBarHeight: 50px;
photoEditorButtonBarWidth: windowMinWidth;
photoEditorButtonBarHeight: 48px;
photoEditorButtonBarWidth: 422px;
photoEditorButtonBarPadding: margins(2px, 0px, 2px, 0px);
photoEditorTextButtonPadding: margins(22px, 0px, 22px, 0px);
photoEditorButtonStyle: TextStyle(semiboldTextStyle) {
font: font(14px semibold);
linkFont: font(14px semibold);
linkFontOver: font(14px semibold underline);
}
photoEditorButtonTextTop: 15px;
photoEditorAbout: FlatLabel(defaultFlatLabel) {
textFg: mediaviewCaptionFg;
minWidth: 240px;
align: align(top);
}
photoEditorAboutMargin: margins(10px, 22px, 10px, 0px);
photoEditorRotateButton: IconButton(defaultIconButton) {
width: photoEditorButtonBarHeight;
height: photoEditorButtonBarHeight;
@ -43,7 +57,7 @@ photoEditorRotateButton: IconButton(defaultIconButton) {
icon: icon {{ "photo_editor/rotate", photoEditorButtonIconFg }};
iconOver: icon {{ "photo_editor/rotate", photoEditorButtonIconFgOver }};
rippleAreaPosition: point(5px, 5px);
rippleAreaPosition: point(4px, 4px);
rippleAreaSize: 40px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: shadowFg;

View file

@ -67,11 +67,12 @@ PhotoEditor::PhotoEditor(
photo,
_modifications,
_controllers,
std::move(data)))
data))
, _controls(base::make_unique_q<PhotoEditorControls>(
this,
_controllers,
_modifications))
_modifications,
data))
, _colorPicker(std::make_unique<ColorPicker>(
this,
Deserialize(Core::App().settings().photoEditorBrush()))) {
@ -81,12 +82,18 @@ PhotoEditor::PhotoEditor(
if (size.isEmpty()) {
return;
}
const auto geometry = QRect(QPoint(), size);
const auto contentRect = geometry - st::photoEditorContentMargins;
_content->setGeometry(contentRect);
const auto contentBottom = contentRect.top() + contentRect.height();
const auto controlsRect = geometry
- style::margins(0, contentBottom, 0, 0);
_content->setGeometry(rect() - st::photoEditorContentMargins);
}, lifetime());
_content->innerRect(
) | rpl::start_with_next([=](QRect inner) {
if (inner.isEmpty()) {
return;
}
const auto innerTop = _content->y() + inner.top();
const auto skip = st::photoEditorCropPointSize;
const auto controlsRect = rect()
- style::margins(0, innerTop + inner.height() + skip, 0, 0);
_controls->setGeometry(controlsRect);
}, lifetime());

View file

@ -30,6 +30,8 @@ struct EditorData {
RoundedRect,
};
TextWithEntities about;
QString confirm;
CropType cropType = CropType::Rect;
bool keepAspectRatio = false;
};

View file

@ -80,6 +80,8 @@ PhotoEditorContent::PhotoEditorContent(
mods.angle,
mods.flipped, imageSizeF);
_paint->applyTransform(geometry, mods.angle, mods.flipped);
_innerRect = geometry;
}, lifetime());
paintRequest(

View file

@ -37,6 +37,10 @@ public:
void setupDragArea();
[[nodiscard]] rpl::producer<QRect> innerRect() const {
return _innerRect.value();
}
private:
const QSize _photoSize;
@ -44,6 +48,7 @@ private:
const base::unique_qptr<Crop> _crop;
const std::shared_ptr<Image> _photo;
rpl::variable<QRect> _innerRect;
rpl::variable<PhotoModifications> _modifications;
rpl::event_stream<int> _keyPresses;

View file

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/image/image_prepare.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/painter.h"
#include "styles/style_editor.h"
@ -55,7 +57,7 @@ EdgeButton::EdgeButton(
const style::RippleAnimation &st)
: Ui::RippleButton(parent, st)
, _fg(fg)
, _text(st::semiboldTextStyle, text)
, _text(st::photoEditorButtonStyle, text)
, _width(_text.maxWidth()
+ st::photoEditorTextButtonPadding.left()
+ st::photoEditorTextButtonPadding.right())
@ -78,7 +80,7 @@ void EdgeButton::init() {
paintRipple(p, _rippleRect.x(), _rippleRect.y());
p.setPen(_fg);
const auto textTop = (height() - _text.minHeight()) / 2;
const auto textTop = st::photoEditorButtonTextTop;
_text.draw(p, 0, textTop, width(), style::al_center);
}, lifetime());
}
@ -122,25 +124,49 @@ ButtonBar::ButtonBar(
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
const auto children = RpWidget::children();
if (children.empty()) {
return;
}
const auto widgets = ranges::views::all(
children
) | ranges::views::filter([](not_null<const QObject*> object) {
return object->isWidgetType();
}) | ranges::views::transform([](not_null<QObject*> object) {
return static_cast<Ui::RpWidget*>(object.get());
return static_cast<QWidget*>(object.get());
}) | ranges::to_vector;
if (widgets.size() < 2) {
return;
}
const auto residualWidth = size.width()
- ranges::accumulate(widgets, 0, ranges::plus(), &QWidget::width);
const auto step = residualWidth / float(widgets.size() - 1);
const auto layout = [&](bool symmetrical) {
auto widths = widgets | ranges::views::transform(
&QWidget::width
) | ranges::to_vector;
const auto count = int(widths.size());
const auto middle = count / 2;
if (symmetrical) {
for (auto i = 0; i != middle; ++i) {
const auto j = count - i - 1;
widths[i] = widths[j] = std::max(widths[i], widths[j]);
}
}
const auto residualWidth = size.width()
- ranges::accumulate(widths, 0);
if (symmetrical && residualWidth < 0) {
return false;
}
const auto step = residualWidth / float(count - 1);
auto left = 0.;
for (const auto &widget : widgets) {
widget->moveToLeft(int(left), 0);
left += widget->width() + step;
auto left = 0.;
auto &&ints = ranges::views::ints(0, ranges::unreachable);
auto &&list = ranges::views::zip(widgets, widths, ints);
for (const auto &[widget, width, index] : list) {
widget->move(int((index >= middle)
? (left + width - widget->width())
: left), 0);
left += width + step;
}
return true;
};
if (!layout(true)) {
layout(false);
}
auto result = QImage(
@ -165,37 +191,45 @@ PhotoEditorControls::PhotoEditorControls(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<Controllers> controllers,
const PhotoModifications modifications,
bool doneControls)
const EditorData &data)
: RpWidget(parent)
, _bg(st::roundedBg)
, _buttonHeight(st::photoEditorButtonBarHeight)
, _transformButtons(base::make_unique_q<ButtonBar>(this, _bg))
, _paintTopButtons(base::make_unique_q<ButtonBar>(this, _bg))
, _paintBottomButtons(base::make_unique_q<ButtonBar>(this, _bg))
, _about(data.about.empty()
? nullptr
: base::make_unique_q<Ui::FadeWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
rpl::single(data.about),
st::photoEditorAbout)))
, _transformCancel(base::make_unique_q<EdgeButton>(
_transformButtons,
tr::lng_cancel(tr::now),
_buttonHeight,
true,
_bg,
st::activeButtonFg,
st::mediaviewCaptionFg,
st::photoEditorRotateButton.ripple))
, _rotateButton(base::make_unique_q<Ui::IconButton>(
_transformButtons,
st::photoEditorRotateButton))
, _flipButton(base::make_unique_q<Ui::IconButton>(
_transformButtons,
st::photoEditorFlipButton))
, _rotateButton(base::make_unique_q<Ui::IconButton>(
_transformButtons,
st::photoEditorRotateButton))
, _paintModeButton(base::make_unique_q<Ui::IconButton>(
_transformButtons,
st::photoEditorPaintModeButton))
, _transformDone(base::make_unique_q<EdgeButton>(
_transformButtons,
tr::lng_box_done(tr::now),
(data.confirm.isEmpty() ? tr::lng_box_done(tr::now) : data.confirm),
_buttonHeight,
false,
_bg,
st::lightButtonFg,
st::mediaviewTextLinkFg,
st::photoEditorRotateButton.ripple))
, _paintCancel(base::make_unique_q<EdgeButton>(
_paintBottomButtons,
@ -203,7 +237,7 @@ PhotoEditorControls::PhotoEditorControls(
_buttonHeight,
true,
_bg,
st::activeButtonFg,
st::mediaviewCaptionFg,
st::photoEditorRotateButton.ripple))
, _undoButton(base::make_unique_q<Ui::IconButton>(
_paintTopButtons,
@ -225,19 +259,9 @@ PhotoEditorControls::PhotoEditorControls(
_buttonHeight,
false,
_bg,
st::lightButtonFg,
st::mediaviewTextLinkFg,
st::photoEditorRotateButton.ripple)) {
{
const auto &padding = st::photoEditorButtonBarPadding;
const auto w = st::photoEditorButtonBarWidth
- padding.left()
- padding.right();
_transformButtons->resize(w, _buttonHeight);
_paintBottomButtons->resize(w, _buttonHeight);
_paintTopButtons->resize(w, _buttonHeight);
}
{
const auto icon = &st::photoEditorPaintIconActive;
_paintModeButtonActive->setIconOverride(icon, icon);
@ -250,6 +274,14 @@ PhotoEditorControls::PhotoEditorControls(
return;
}
const auto &padding = st::photoEditorButtonBarPadding;
const auto w = std::min(st::photoEditorButtonBarWidth, size.width())
- padding.left()
- padding.right();
_transformButtons->resize(w, _buttonHeight);
_paintBottomButtons->resize(w, _buttonHeight);
_paintTopButtons->resize(w, _buttonHeight);
const auto buttonsTop = bottomButtonsTop();
const auto &current = _transformButtons->isHidden()
@ -259,6 +291,16 @@ PhotoEditorControls::PhotoEditorControls(
current->moveToLeft(
(size.width() - current->width()) / 2,
buttonsTop);
if (_about) {
const auto &margin = st::photoEditorAboutMargin;
const auto skip = st::photoEditorCropPointSize;
_about->resizeToWidth(
size.width() - margin.left() - margin.right());
_about->moveToLeft(
(size.width() - _about->width()) / 2,
margin.top() - skip);
}
}, lifetime());
_mode.changes(
@ -413,6 +455,9 @@ void PhotoEditorControls::showAnimated(
const auto duration = st::photoEditorBarAnimationDuration;
const auto isTransform = (mode == Mode::Transform);
if (_about) {
_about->toggle(isTransform, animated);
}
const auto buttonsLeft = (width() - _transformButtons->width()) / 2;
const auto buttonsTop = bottomButtonsTop();

View file

@ -15,6 +15,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class IconButton;
class FlatLabel;
template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace Editor {
@ -22,6 +25,7 @@ namespace Editor {
class EdgeButton;
class ButtonBar;
struct Controllers;
struct EditorData;
class PhotoEditorControls final : public Ui::RpWidget {
public:
@ -29,7 +33,7 @@ public:
not_null<Ui::RpWidget*> parent,
std::shared_ptr<Controllers> controllers,
const PhotoModifications modifications,
bool doneControls = true);
const EditorData &data);
[[nodiscard]] rpl::producer<int> rotateRequests() const;
[[nodiscard]] rpl::producer<> flipRequests() const;
@ -58,9 +62,11 @@ private:
const base::unique_qptr<ButtonBar> _paintTopButtons;
const base::unique_qptr<ButtonBar> _paintBottomButtons;
const base::unique_qptr<Ui::FadeWrap<Ui::FlatLabel>> _about;
const base::unique_qptr<EdgeButton> _transformCancel;
const base::unique_qptr<Ui::IconButton> _rotateButton;
const base::unique_qptr<Ui::IconButton> _flipButton;
const base::unique_qptr<Ui::IconButton> _rotateButton;
const base::unique_qptr<Ui::IconButton> _paintModeButton;
const base::unique_qptr<EdgeButton> _transformDone;

View file

@ -71,7 +71,7 @@ void OpenWithPreparedFile(
void PrepareProfilePhoto(
not_null<QWidget*> parent,
not_null<Window::Controller*> controller,
ImageRoundRadius radius,
EditorData data,
Fn<void(QImage &&image)> &&doneCallback,
QImage &&image) {
const auto resizeToMinSize = [=](
@ -121,12 +121,7 @@ void PrepareProfilePhoto(
controller,
fileImage,
PhotoModifications{ .crop = std::move(crop) },
EditorData{
.cropType = (radius == ImageRoundRadius::Ellipse
? EditorData::CropType::Ellipse
: EditorData::CropType::RoundedRect),
.keepAspectRatio = true,
});
data);
const auto raw = editor.get();
auto layer = std::make_unique<LayerWidget>(parent, std::move(editor));
InitEditorLayer(layer.get(), raw, std::move(applyModifications));
@ -136,7 +131,7 @@ void PrepareProfilePhoto(
void PrepareProfilePhotoFromFile(
not_null<QWidget*> parent,
not_null<Window::Controller*> controller,
ImageRoundRadius radius,
EditorData data,
Fn<void(QImage &&image)> &&doneCallback) {
const auto callback = [=, done = std::move(doneCallback)](
const FileDialog::OpenResult &result) mutable {
@ -152,7 +147,7 @@ void PrepareProfilePhotoFromFile(
PrepareProfilePhoto(
parent,
controller,
radius,
data,
std::move(done),
std::move(image));
};

View file

@ -25,6 +25,8 @@ class SessionController;
namespace Editor {
struct EditorData;
void OpenWithPreparedFile(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
@ -35,14 +37,14 @@ void OpenWithPreparedFile(
void PrepareProfilePhoto(
not_null<QWidget*> parent,
not_null<Window::Controller*> controller,
ImageRoundRadius radius,
EditorData data,
Fn<void(QImage &&image)> &&doneCallback,
QImage &&image);
void PrepareProfilePhotoFromFile(
not_null<QWidget*> parent,
not_null<Window::Controller*> controller,
ImageRoundRadius radius,
EditorData data,
Fn<void(QImage &&image)> &&doneCallback);
} // namespace Editor

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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"
@ -31,7 +32,71 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
void ShowUserpicSuggestion(
not_null<Window::SessionController*> controller,
const std::shared_ptr<Data::PhotoMedia> &media,
const FullMsgId itemId,
not_null<PeerData*> peer) {
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 = [=] {
using namespace Settings;
const auto session = &photo->session();
auto &peerPhotos = session->api().peerPhoto();
peerPhotos.updateSelf(photo, itemId);
controller->showSettings(Information::Id());
};
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);
} else {
peerPhotos.upload(user, std::move(image));
}
controller->showSettings(Information::Id());
};
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));
}
}
} // namespace
UserpicSuggestion::UserpicSuggestion(
not_null<Element*> parent,
not_null<PeerData*> chat,
@ -84,50 +149,8 @@ ClickHandlerPtr UserpicSuggestion::createViewLink() {
if (out) {
PhotoOpenClickHandler(photo, show, itemId).onClick(
context);
} else if (photo->hasVideo()) {
const auto user = peer->asUser();
const auto name = (user && !user->firstName.isEmpty())
? user->firstName
: peer->name();
const auto done = [=] {
using namespace Settings;
const auto session = &photo->session();
auto &peerPhotos = session->api().peerPhoto();
peerPhotos.updateSelf(photo, itemId);
controller->showSettings(Information::Id());
};
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);
} else {
peerPhotos.upload(user, std::move(image));
}
controller->showSettings(Information::Id());
};
Editor::PrepareProfilePhoto(
controller->content(),
&controller->window(),
ImageRoundRadius::Ellipse,
callback,
base::duplicate(*original));
ShowUserpicSuggestion(controller, media, itemId, peer);
}
} else if (!photo->loading()) {
PhotoSaveClickHandler(photo, itemId).onClick(context);

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_user_photos.h" // UserPhotosViewer.
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_layer_widget.h"
#include "history/admin_log/history_admin_log_item.h"
#include "history/history.h"
@ -1134,10 +1135,15 @@ object_ptr<Ui::RpWidget> ProfilePhotoPrivacyController::setupBelowWidget(
base::call_delayed(
st::settingsButton.ripple.hideDuration,
crl::guard(container, [=] {
Editor::PrepareProfilePhotoFromFile(
using namespace Editor;
PrepareProfilePhotoFromFile(
container,
&controller->window(),
ImageRoundRadius::Ellipse,
{
.confirm = tr::lng_profile_set_photo_button(tr::now),
.cropType = EditorData::CropType::Ellipse,
.keepAspectRatio = true,
},
[=](QImage &&image) {
state->updatePhoto(std::move(image), true);
state->hiddenByUser = false;

View file

@ -22,7 +22,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_instance.h"
#include "core/application.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_layer_widget.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
@ -70,22 +72,29 @@ void CameraBox(
}
}, box->lifetime());
auto done = [=, done = std::move(doneCallback)](QImage &&image) {
auto done = [=, done = std::move(doneCallback)]() mutable {
using namespace Editor;
auto callback = [=, done = std::move(done)](QImage &&image) {
box->closeBox();
done(std::move(image));
};
PrepareProfilePhoto(
box,
controller,
{
.confirm = tr::lng_profile_set_photo_button(tr::now),
.cropType = ((peer && peer->isForum())
? EditorData::CropType::RoundedRect
: EditorData::CropType::Ellipse),
.keepAspectRatio = true,
},
std::move(callback),
track->frame(FrameRequest()).mirrored(true, false));
box->closeBox();
done(std::move(image));
};
box->setTitle(tr::lng_profile_camera_title());
box->addButton(tr::lng_continue(), [=, done = std::move(done)]() mutable {
Editor::PrepareProfilePhoto(
box,
controller,
((peer && peer->isForum())
? ImageRoundRadius::Large
: ImageRoundRadius::Ellipse),
std::move(done),
track->frame(FrameRequest()).mirrored(true, false));
});
box->addButton(tr::lng_continue(), std::move(done));
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
@ -260,12 +269,35 @@ void UserpicButton::choosePhotoLocally() {
base::call_delayed(
_st.changeButton.ripple.hideDuration,
crl::guard(this, [=] {
Editor::PrepareProfilePhotoFromFile(
using namespace Editor;
const auto user = _peer ? _peer->asUser() : nullptr;
const auto name = (user && !user->firstName.isEmpty())
? user->firstName
: _peer->name();
const auto phrase = (type == ChosenType::Suggest)
? &tr::lng_profile_suggest_sure
: (_peer->isUser() && !_peer->isSelf())
? &tr::lng_profile_set_personal_sure
: nullptr;
PrepareProfilePhotoFromFile(
this,
_window,
((_peer && _peer->isForum())
? ImageRoundRadius::Large
: ImageRoundRadius::Ellipse),
{
.about = (phrase
? (*phrase)(
tr::now,
lt_user,
Ui::Text::Bold(name),
Ui::Text::WithEntities)
: TextWithEntities()),
.confirm = ((type == ChosenType::Suggest)
? tr::lng_profile_suggest_button(tr::now)
: tr::lng_profile_set_photo_button(tr::now)),
.cropType = ((_peer && _peer->isForum())
? EditorData::CropType::RoundedRect
: EditorData::CropType::Ellipse),
.keepAspectRatio = true,
},
callback(type));
}));
};