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(