diff --git a/Telegram/Resources/animations/camera_outline.tgs b/Telegram/Resources/animations/camera_outline.tgs
new file mode 100644
index 0000000000..f63db249de
Binary files /dev/null and b/Telegram/Resources/animations/camera_outline.tgs differ
diff --git a/Telegram/Resources/animations/photo_suggest_icon.tgs b/Telegram/Resources/animations/photo_suggest_icon.tgs
new file mode 100644
index 0000000000..d05f5648e1
Binary files /dev/null and b/Telegram/Resources/animations/photo_suggest_icon.tgs differ
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index 7b120093d0..94365b758f 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -41,6 +41,8 @@
../../animations/no_chats.tgs
../../animations/transcribe_loading.tgs
../../animations/cake.tgs
+ ../../animations/camera_outline.tgs
+ ../../animations/photo_suggest_icon.tgs
../../animations/dice/dice_idle.tgs
../../animations/dice/dart_idle.tgs
diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp
index 2a7ed69029..f80ca5db54 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_peer_photo.h"
#include "api/api_text_entities.h"
#include "apiwrap.h"
+#include "base/call_delayed.h"
#include "boxes/peers/edit_peer_common.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/tabbed_panel.h"
@@ -31,8 +32,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/userpic/info_userpic_emoji_builder_common.h"
#include "info/userpic/info_userpic_emoji_builder_menu_item.h"
#include "lang/lang_keys.h"
+#include "lottie/lottie_common.h"
+#include "lottie/lottie_frame_generator.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
+#include "ui/animated_icon.h"
#include "ui/controls/emoji_button_factory.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/userpic_button.h"
@@ -48,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
+#include "ui/painter.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
@@ -56,12 +61,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
+#include "styles/style_widgets.h"
#include
#include
namespace {
+constexpr auto kAnimationStartFrame = 0;
+constexpr auto kAnimationEndFrame = 21;
+
QString UserPhone(not_null user) {
const auto phone = user->phone();
return phone.isEmpty()
@@ -144,6 +153,11 @@ private:
void processChosenPhotoWithMarkup(
UserpicBuilder::Result &&data,
bool suggest);
+ void executeWithDelay(
+ Fn callback,
+ bool suggest,
+ bool startAnimation = true);
+ void finishIconAnimation(bool suggest);
not_null _box;
not_null _window;
@@ -153,6 +167,10 @@ private:
Ui::InputField *_firstNameField = nullptr;
base::unique_qptr _emojiPanel;
base::unique_qptr _photoMenu;
+ std::unique_ptr _suggestIcon;
+ std::unique_ptr _cameraIcon;
+ Ui::RpWidget *_suggestIconWidget = nullptr;
+ Ui::RpWidget *_cameraIconWidget = nullptr;
QString _phone;
Fn _focus;
Fn _save;
@@ -457,6 +475,7 @@ void Controller::setupNotesField() {
}
void Controller::setupPhotoButtons() {
+ const auto iconSize = st::restoreUserpicIcon.size;
auto nameValue = _firstNameField
? rpl::merge(
rpl::single(_firstNameField->getLastText().trimmed()),
@@ -489,12 +508,58 @@ void Controller::setupPhotoButtons() {
});
suggestBirthdayWrap->toggleOn(rpl::single(!_user->birthday().valid()));
+ _suggestIcon = Ui::MakeAnimatedIcon({
+ .generator = [] {
+ return std::make_unique(
+ Lottie::ReadContent(
+ QByteArray(),
+ u":/animations/photo_suggest_icon.tgs"_q));
+ },
+ .sizeOverride = iconSize * style::DevicePixelRatio(),
+ .colorized = true,
+ });
+
+ _cameraIcon = Ui::MakeAnimatedIcon({
+ .generator = [] {
+ return std::make_unique(
+ Lottie::ReadContent(
+ QByteArray(),
+ u":/animations/camera_outline.tgs"_q));
+ },
+ .sizeOverride = iconSize * style::DevicePixelRatio(),
+ .colorized = true,
+ });
+
const auto suggestButton = Settings::AddButtonWithIcon(
inner,
tr::lng_suggest_photo_for(lt_user, rpl::duplicate(nameValue)),
st::settingsButtonLight,
- { &st::menuBlueIconPhotoSuggest });
+ { nullptr });
+
+ _suggestIconWidget = Ui::CreateChild(suggestButton);
+ _suggestIconWidget->resize(iconSize * style::DevicePixelRatio());
+ _suggestIconWidget->paintRequest() | rpl::start_with_next([=] {
+ if (_suggestIcon && _suggestIcon->valid()) {
+ auto p = QPainter(_suggestIconWidget);
+ const auto frame = _suggestIcon->frame(st::lightButtonFg->c);
+ const auto rect = _suggestIconWidget->rect();
+ p.drawImage(rect, frame);
+ }
+ }, _suggestIconWidget->lifetime());
+
+ suggestButton->sizeValue() | rpl::start_with_next([=](QSize size) {
+ _suggestIconWidget->move(
+ st::settingsButtonLight.iconLeft - iconSize.width() / 2,
+ (size.height() - _suggestIconWidget->height()) / 2);
+ }, _suggestIconWidget->lifetime());
+
suggestButton->setClickedCallback([=] {
+ if (_suggestIcon && _suggestIcon->valid()) {
+ _suggestIcon->setCustomStartFrame(kAnimationStartFrame);
+ _suggestIcon->setCustomEndFrame(kAnimationEndFrame);
+ _suggestIcon->jumpToStart([=] { _suggestIconWidget->update(); });
+ _suggestIcon->animate([=] { _suggestIconWidget->update(); });
+ }
showPhotoMenu(true);
});
@@ -502,8 +567,32 @@ void Controller::setupPhotoButtons() {
inner,
tr::lng_set_photo_for_user(lt_user, rpl::duplicate(nameValue)),
st::settingsButtonLight,
- { &st::menuBlueIconPhotoSet });
+ { nullptr });
+
+ _cameraIconWidget = Ui::CreateChild(setButton);
+ _cameraIconWidget->resize(iconSize * style::DevicePixelRatio());
+ _cameraIconWidget->paintRequest() | rpl::start_with_next([=] {
+ if (_cameraIcon && _cameraIcon->valid()) {
+ auto p = QPainter(_cameraIconWidget);
+ const auto frame = _cameraIcon->frame(st::lightButtonFg->c);
+ const auto rect = _cameraIconWidget->rect();
+ p.drawImage(rect, frame);
+ }
+ }, _cameraIconWidget->lifetime());
+
+ setButton->sizeValue() | rpl::start_with_next([=](QSize size) {
+ _cameraIconWidget->move(
+ st::settingsButtonLight.iconLeft - iconSize.width() / 2,
+ (size.height() - _cameraIconWidget->height()) / 2);
+ }, _cameraIconWidget->lifetime());
+
setButton->setClickedCallback([=] {
+ if (_cameraIcon && _cameraIcon->valid()) {
+ _cameraIcon->setCustomStartFrame(kAnimationStartFrame);
+ _cameraIcon->setCustomEndFrame(kAnimationEndFrame);
+ _cameraIcon->jumpToStart([=] { _cameraIconWidget->update(); });
+ _cameraIcon->animate([=] { _cameraIconWidget->update(); });
+ }
showPhotoMenu(false);
});
@@ -623,9 +712,13 @@ void Controller::showPhotoMenu(bool suggest) {
_box,
st::popupMenuWithIcons);
+ QObject::connect(_photoMenu.get(), &QObject::destroyed, [=] {
+ finishIconAnimation(suggest);
+ });
+
_photoMenu->addAction(
tr::lng_attach_photo(tr::now),
- [=] { choosePhotoFile(suggest); },
+ [=] { executeWithDelay([=] { choosePhotoFile(suggest); }, suggest); },
&st::menuIconPhoto);
if (const auto data = QGuiApplication::clipboard()->mimeData()) {
@@ -659,7 +752,7 @@ void Controller::showPhotoMenu(bool suggest) {
};
_photoMenu->addAction(
tr::lng_profile_photo_from_clipboard(tr::now),
- std::move(callback),
+ [=] { executeWithDelay(callback, suggest); },
&st::menuIconPhoto);
}
}
@@ -708,6 +801,11 @@ void Controller::processChosenPhoto(QImage &&image, bool suggest) {
Api::PeerPhoto::UserPhoto photo{
.image = base::duplicate(image),
};
+ if (suggest && _suggestIcon && _suggestIcon->valid()) {
+ _suggestIcon->animate([=] { _suggestIconWidget->update(); });
+ } else if (!suggest && _cameraIcon && _cameraIcon->valid()) {
+ _cameraIcon->animate([=] { _cameraIconWidget->update(); });
+ }
if (suggest) {
_window->session().api().peerPhoto().suggest(_user, std::move(photo));
_window->showPeerHistory(_user->id);
@@ -724,6 +822,11 @@ void Controller::processChosenPhotoWithMarkup(
.markupDocumentId = data.id,
.markupColors = std::move(data.colors),
};
+ if (suggest && _suggestIcon && _suggestIcon->valid()) {
+ _suggestIcon->animate([=] { _suggestIconWidget->update(); });
+ } else if (!suggest && _cameraIcon && _cameraIcon->valid()) {
+ _cameraIcon->animate([=] { _cameraIconWidget->update(); });
+ }
if (suggest) {
_window->session().api().peerPhoto().suggest(_user, std::move(photo));
_window->showPeerHistory(_user->id);
@@ -732,6 +835,38 @@ void Controller::processChosenPhotoWithMarkup(
}
}
+void Controller::finishIconAnimation(bool suggest) {
+ const auto icon = suggest ? _suggestIcon.get() : _cameraIcon.get();
+ const auto widget = suggest ? _suggestIconWidget : _cameraIconWidget;
+ if (icon && icon->valid()) {
+ icon->setCustomStartFrame(icon->frameIndex());
+ icon->setCustomEndFrame(-1);
+ icon->animate([=] { widget->update(); });
+ }
+}
+
+void Controller::executeWithDelay(
+ Fn callback,
+ bool suggest,
+ bool startAnimation) {
+ const auto icon = suggest ? _suggestIcon.get() : _cameraIcon.get();
+ const auto widget = suggest ? _suggestIconWidget : _cameraIconWidget;
+
+ if (startAnimation && icon && icon->valid()) {
+ icon->setCustomStartFrame(icon->frameIndex());
+ icon->setCustomEndFrame(-1);
+ icon->animate([=] { widget->update(); });
+ }
+
+ if (icon && icon->valid() && icon->animating()) {
+ base::call_delayed(50, [=] {
+ executeWithDelay(callback, suggest, false);
+ });
+ } else {
+ callback();
+ }
+}
+
} // namespace
void EditContactBox(