Added ability to set favorite reaction from settings.
This commit is contained in:
		
							parent
							
								
									4304071d18
								
							
						
					
					
						commit
						4216d72c67
					
				
					 6 changed files with 583 additions and 0 deletions
				
			
		| 
						 | 
					@ -256,6 +256,8 @@ PRIVATE
 | 
				
			||||||
    boxes/phone_banned_box.h
 | 
					    boxes/phone_banned_box.h
 | 
				
			||||||
    boxes/pin_messages_box.cpp
 | 
					    boxes/pin_messages_box.cpp
 | 
				
			||||||
    boxes/pin_messages_box.h
 | 
					    boxes/pin_messages_box.h
 | 
				
			||||||
 | 
					    boxes/reactions_settings_box.cpp
 | 
				
			||||||
 | 
					    boxes/reactions_settings_box.h
 | 
				
			||||||
    boxes/ringtones_box.cpp
 | 
					    boxes/ringtones_box.cpp
 | 
				
			||||||
    boxes/ringtones_box.h
 | 
					    boxes/ringtones_box.h
 | 
				
			||||||
    boxes/self_destruction_box.cpp
 | 
					    boxes/self_destruction_box.cpp
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -440,6 +440,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
"lng_settings_chat_quick_action_reply" = "Reply with double click";
 | 
					"lng_settings_chat_quick_action_reply" = "Reply with double click";
 | 
				
			||||||
"lng_settings_chat_quick_action_react" = "Send reaction with double click";
 | 
					"lng_settings_chat_quick_action_react" = "Send reaction with double click";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"lng_settings_chat_reactions_title" = "Quick Reaction";
 | 
				
			||||||
 | 
					"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
 | 
				
			||||||
 | 
					"lng_settings_chat_message_reply_from" = "Bob Harris";
 | 
				
			||||||
 | 
					"lng_settings_chat_message_reply" = "Good morning";
 | 
				
			||||||
 | 
					"lng_settings_chat_message" = "Do you know what time it is?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"lng_settings_section_filters" = "Folders";
 | 
					"lng_settings_section_filters" = "Folders";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"lng_settings_section_background" = "Chat background";
 | 
					"lng_settings_section_background" = "Chat background";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										519
									
								
								Telegram/SourceFiles/boxes/reactions_settings_box.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								Telegram/SourceFiles/boxes/reactions_settings_box.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,519 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 "boxes/reactions_settings_box.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "base/unixtime.h"
 | 
				
			||||||
 | 
					#include "data/data_document.h"
 | 
				
			||||||
 | 
					#include "data/data_document_media.h"
 | 
				
			||||||
 | 
					#include "data/data_message_reactions.h"
 | 
				
			||||||
 | 
					#include "data/data_session.h"
 | 
				
			||||||
 | 
					#include "history/admin_log/history_admin_log_item.h"
 | 
				
			||||||
 | 
					#include "history/history.h"
 | 
				
			||||||
 | 
					#include "history/history_message.h"
 | 
				
			||||||
 | 
					#include "history/view/history_view_element.h"
 | 
				
			||||||
 | 
					#include "history/view/history_view_react_button.h" // DefaultIconFactory
 | 
				
			||||||
 | 
					#include "lang/lang_keys.h"
 | 
				
			||||||
 | 
					#include "lottie/lottie_icon.h"
 | 
				
			||||||
 | 
					#include "main/main_session.h"
 | 
				
			||||||
 | 
					#include "settings/settings_common.h"
 | 
				
			||||||
 | 
					#include "ui/chat/chat_style.h"
 | 
				
			||||||
 | 
					#include "ui/chat/chat_theme.h"
 | 
				
			||||||
 | 
					#include "ui/layers/generic_box.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/buttons.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/labels.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/scroll_area.h"
 | 
				
			||||||
 | 
					#include "ui/wrap/fade_wrap.h"
 | 
				
			||||||
 | 
					#include "ui/wrap/vertical_layout.h"
 | 
				
			||||||
 | 
					#include "window/section_widget.h"
 | 
				
			||||||
 | 
					#include "window/window_session_controller.h"
 | 
				
			||||||
 | 
					#include "styles/style_boxes.h"
 | 
				
			||||||
 | 
					#include "styles/style_chat.h"
 | 
				
			||||||
 | 
					#include "styles/style_layers.h"
 | 
				
			||||||
 | 
					#include "styles/style_media_player.h" // mediaPlayerMenuCheck
 | 
				
			||||||
 | 
					#include "styles/style_settings.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr auto kVisibleButtonsCount = 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AddIcon(
 | 
				
			||||||
 | 
							not_null<Ui::RpWidget*> parent,
 | 
				
			||||||
 | 
							rpl::producer<QPoint> iconPositionValue,
 | 
				
			||||||
 | 
							int iconSize,
 | 
				
			||||||
 | 
							not_null<Main::Session*> session,
 | 
				
			||||||
 | 
							const Data::Reaction &reaction,
 | 
				
			||||||
 | 
							rpl::producer<> &&selects,
 | 
				
			||||||
 | 
							rpl::producer<> &&destroys,
 | 
				
			||||||
 | 
							not_null<rpl::lifetime*> stateLifetime) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct State {
 | 
				
			||||||
 | 
							struct Entry {
 | 
				
			||||||
 | 
								std::shared_ptr<Data::DocumentMedia> media;
 | 
				
			||||||
 | 
								std::shared_ptr<Lottie::Icon> icon;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							Entry appear;
 | 
				
			||||||
 | 
							Entry select;
 | 
				
			||||||
 | 
							bool appearAnimated = false;
 | 
				
			||||||
 | 
							rpl::lifetime loadingLifetime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							base::unique_qptr<Ui::RpWidget> widget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Ui::Animations::Simple finalAnimation;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto state = stateLifetime->make_state<State>();
 | 
				
			||||||
 | 
						state->widget = base::make_unique_q<Ui::RpWidget>(parent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						state->appear.media = reaction.appearAnimation->createMediaView();
 | 
				
			||||||
 | 
						state->select.media = reaction.selectAnimation->createMediaView();
 | 
				
			||||||
 | 
						state->appear.media->checkStickerLarge();
 | 
				
			||||||
 | 
						state->select.media->checkStickerLarge();
 | 
				
			||||||
 | 
						rpl::single() | rpl::then(
 | 
				
			||||||
 | 
							session->downloaderTaskFinished()
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
							const auto check = [&](State::Entry &entry) {
 | 
				
			||||||
 | 
								if (!entry.media) {
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								} else if (!entry.media->loaded()) {
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								entry.icon = HistoryView::Reactions::DefaultIconFactory(
 | 
				
			||||||
 | 
									entry.media.get(),
 | 
				
			||||||
 | 
									iconSize);
 | 
				
			||||||
 | 
								entry.media = nullptr;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							if (check(state->select) && check(state->appear)) {
 | 
				
			||||||
 | 
								state->loadingLifetime.destroy();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}, state->loadingLifetime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto widget = state->widget.get();
 | 
				
			||||||
 | 
						widget->resize(iconSize, iconSize);
 | 
				
			||||||
 | 
						widget->setAttribute(Qt::WA_TransparentForMouseEvents);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::move(
 | 
				
			||||||
 | 
							iconPositionValue
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](const QPoint &point) {
 | 
				
			||||||
 | 
							widget->moveToLeft(point.x(), point.y());
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto update = crl::guard(widget, [=] { widget->update(); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget->paintRequest(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=,
 | 
				
			||||||
 | 
								frameSize = (iconSize / style::DevicePixelRatio())] {
 | 
				
			||||||
 | 
							Painter p(widget);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (state->finalAnimation.animating()) {
 | 
				
			||||||
 | 
								const auto progress = 1. - state->finalAnimation.value(0.);
 | 
				
			||||||
 | 
								const auto size = widget->size();
 | 
				
			||||||
 | 
								const auto scaledSize = size * progress;
 | 
				
			||||||
 | 
								const auto scaledCenter = QPoint(
 | 
				
			||||||
 | 
									(size.width() - scaledSize.width()) / 2.,
 | 
				
			||||||
 | 
									(size.height() - scaledSize.height()) / 2.);
 | 
				
			||||||
 | 
								p.setOpacity(progress);
 | 
				
			||||||
 | 
								p.translate(scaledCenter);
 | 
				
			||||||
 | 
								p.scale(progress, progress);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto paintFrame = [&](not_null<Lottie::Icon*> animation) {
 | 
				
			||||||
 | 
								const auto frame = animation->frame();
 | 
				
			||||||
 | 
								p.drawImage(
 | 
				
			||||||
 | 
									QRect(
 | 
				
			||||||
 | 
										(widget->width() - frameSize) / 2,
 | 
				
			||||||
 | 
										(widget->height() - frameSize) / 2,
 | 
				
			||||||
 | 
										frameSize,
 | 
				
			||||||
 | 
										frameSize),
 | 
				
			||||||
 | 
									frame);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto appear = state->appear.icon.get();
 | 
				
			||||||
 | 
							if (appear && !state->appearAnimated) {
 | 
				
			||||||
 | 
								state->appearAnimated = true;
 | 
				
			||||||
 | 
								appear->animate(update, 0, appear->framesCount() - 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (appear && appear->animating()) {
 | 
				
			||||||
 | 
								paintFrame(appear);
 | 
				
			||||||
 | 
							} else if (const auto select = state->select.icon.get()) {
 | 
				
			||||||
 | 
								paintFrame(select);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::move(
 | 
				
			||||||
 | 
							selects
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
							const auto select = state->select.icon.get();
 | 
				
			||||||
 | 
							if (select && !select->animating()) {
 | 
				
			||||||
 | 
								select->animate(update, 0, select->framesCount() - 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::move(
 | 
				
			||||||
 | 
							destroys
 | 
				
			||||||
 | 
						) | rpl::take(1) | rpl::start_with_next([=, from = 0., to = 1.] {
 | 
				
			||||||
 | 
							state->finalAnimation.start(
 | 
				
			||||||
 | 
								[=](float64 value) {
 | 
				
			||||||
 | 
									update();
 | 
				
			||||||
 | 
									if (value == to) {
 | 
				
			||||||
 | 
										stateLifetime->destroy();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								from,
 | 
				
			||||||
 | 
								to,
 | 
				
			||||||
 | 
								st::defaultPopupMenu.showDuration);
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget->raise();
 | 
				
			||||||
 | 
						widget->show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PeerId GenerateUser(not_null<History*> history, const QString &name) {
 | 
				
			||||||
 | 
						Expects(history->peer->isUser());
 | 
				
			||||||
 | 
						const auto peerId = Data::FakePeerIdForJustName(name);
 | 
				
			||||||
 | 
						history->owner().processUser(MTP_user(
 | 
				
			||||||
 | 
							MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min),
 | 
				
			||||||
 | 
							peerToBareMTPInt(peerId),
 | 
				
			||||||
 | 
							MTP_long(0),
 | 
				
			||||||
 | 
							MTP_string(tr::lng_settings_chat_message_reply_from(tr::now)),
 | 
				
			||||||
 | 
							MTPstring(), // last name
 | 
				
			||||||
 | 
							MTPstring(), // username
 | 
				
			||||||
 | 
							MTPstring(), // phone
 | 
				
			||||||
 | 
							MTPUserProfilePhoto(), // profile photo
 | 
				
			||||||
 | 
							MTPUserStatus(), // status
 | 
				
			||||||
 | 
							MTP_int(0), // bot info version
 | 
				
			||||||
 | 
							MTPVector<MTPRestrictionReason>(), // restrictions
 | 
				
			||||||
 | 
							MTPstring(), // bot placeholder
 | 
				
			||||||
 | 
							MTPstring())); // lang code
 | 
				
			||||||
 | 
						return peerId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AdminLog::OwnedItem GenerateItem(
 | 
				
			||||||
 | 
							not_null<HistoryView::ElementDelegate*> delegate,
 | 
				
			||||||
 | 
							not_null<History*> history,
 | 
				
			||||||
 | 
							PeerId from,
 | 
				
			||||||
 | 
							MsgId replyTo,
 | 
				
			||||||
 | 
							const QString &text) {
 | 
				
			||||||
 | 
						Expects(history->peer->isUser());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto item = history->addNewLocalMessage(
 | 
				
			||||||
 | 
							history->nextNonHistoryEntryId(),
 | 
				
			||||||
 | 
							MessageFlag::FakeHistoryItem
 | 
				
			||||||
 | 
								| MessageFlag::HasFromId
 | 
				
			||||||
 | 
								| MessageFlag::HasReplyInfo,
 | 
				
			||||||
 | 
							UserId(), // via
 | 
				
			||||||
 | 
							replyTo,
 | 
				
			||||||
 | 
							base::unixtime::now(), // date
 | 
				
			||||||
 | 
							from,
 | 
				
			||||||
 | 
							QString(), // postAuthor
 | 
				
			||||||
 | 
							TextWithEntities{ .text = text },
 | 
				
			||||||
 | 
							MTP_messageMediaEmpty(),
 | 
				
			||||||
 | 
							HistoryMessageMarkupData(),
 | 
				
			||||||
 | 
							uint64(0)); // groupedId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return AdminLog::OwnedItem(delegate, item);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AddMessage(
 | 
				
			||||||
 | 
							not_null<Ui::GenericBox*> box,
 | 
				
			||||||
 | 
							not_null<Window::SessionController*> controller,
 | 
				
			||||||
 | 
							rpl::producer<QString> &&emojiValue) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto widget = box->addRow(
 | 
				
			||||||
 | 
							object_ptr<Ui::RpWidget>(box),
 | 
				
			||||||
 | 
							style::margins(
 | 
				
			||||||
 | 
								0,
 | 
				
			||||||
 | 
								st::settingsSectionSkip,
 | 
				
			||||||
 | 
								0,
 | 
				
			||||||
 | 
								st::settingsPrivacySkipTop));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						class Delegate final : public HistoryView::SimpleElementDelegate {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							using HistoryView::SimpleElementDelegate::SimpleElementDelegate;
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							HistoryView::Context elementContext() override {
 | 
				
			||||||
 | 
								return HistoryView::Context::ContactPreview;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct State {
 | 
				
			||||||
 | 
							AdminLog::OwnedItem reply;
 | 
				
			||||||
 | 
							AdminLog::OwnedItem item;
 | 
				
			||||||
 | 
							std::unique_ptr<Delegate> delegate;
 | 
				
			||||||
 | 
							std::unique_ptr<Ui::ChatStyle> style;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct {
 | 
				
			||||||
 | 
								std::vector<rpl::lifetime> lifetimes;
 | 
				
			||||||
 | 
								bool flag = false;
 | 
				
			||||||
 | 
							} icons;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto state = box->lifetime().make_state<State>();
 | 
				
			||||||
 | 
						state->delegate = std::make_unique<Delegate>(
 | 
				
			||||||
 | 
							controller,
 | 
				
			||||||
 | 
							crl::guard(widget, [=] { widget->update(); }));
 | 
				
			||||||
 | 
						state->style = std::make_unique<Ui::ChatStyle>();
 | 
				
			||||||
 | 
						state->icons.lifetimes = std::vector<rpl::lifetime>(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto history = controller->session().data().history(
 | 
				
			||||||
 | 
							PeerData::kServiceNotificationsId);
 | 
				
			||||||
 | 
						state->reply = GenerateItem(
 | 
				
			||||||
 | 
							state->delegate.get(),
 | 
				
			||||||
 | 
							history,
 | 
				
			||||||
 | 
							GenerateUser(
 | 
				
			||||||
 | 
								history,
 | 
				
			||||||
 | 
								tr::lng_settings_chat_message_reply_from(tr::now)),
 | 
				
			||||||
 | 
							0,
 | 
				
			||||||
 | 
							tr::lng_settings_chat_message_reply(tr::now));
 | 
				
			||||||
 | 
						auto message = GenerateItem(
 | 
				
			||||||
 | 
							state->delegate.get(),
 | 
				
			||||||
 | 
							history,
 | 
				
			||||||
 | 
							history->peer->id,
 | 
				
			||||||
 | 
							state->reply->data()->fullId().msg,
 | 
				
			||||||
 | 
							tr::lng_settings_chat_message(tr::now));
 | 
				
			||||||
 | 
						const auto view = message.get();
 | 
				
			||||||
 | 
						state->item = std::move(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto padding = st::settingsForwardPrivacyPadding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget->widthValue(
 | 
				
			||||||
 | 
						) | rpl::filter(
 | 
				
			||||||
 | 
							rpl::mappers::_1 >= (st::historyMinimalWidth / 2)
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](int width) {
 | 
				
			||||||
 | 
							const auto height = view->resizeGetHeight(width);
 | 
				
			||||||
 | 
							const auto top = view->marginTop();
 | 
				
			||||||
 | 
							const auto bottom = view->marginBottom();
 | 
				
			||||||
 | 
							const auto full = padding + top + height + bottom + padding;
 | 
				
			||||||
 | 
							widget->resize(width, full);
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto rightSize = st::settingsReactionCornerSize;
 | 
				
			||||||
 | 
						const auto rightRect = [=] {
 | 
				
			||||||
 | 
							const auto viewInner = view->innerGeometry();
 | 
				
			||||||
 | 
							return QRect(
 | 
				
			||||||
 | 
								viewInner.x() + viewInner.width(),
 | 
				
			||||||
 | 
								padding
 | 
				
			||||||
 | 
									+ view->marginTop()
 | 
				
			||||||
 | 
									+ view->resizeGetHeight(widget->width())
 | 
				
			||||||
 | 
									- rightSize.height(),
 | 
				
			||||||
 | 
								rightSize.width(),
 | 
				
			||||||
 | 
								rightSize.height()).translated(st::settingsReactionCornerSkip);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget->paintRequest(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](const QRect &rect) {
 | 
				
			||||||
 | 
							Window::SectionWidget::PaintBackground(
 | 
				
			||||||
 | 
								controller,
 | 
				
			||||||
 | 
								controller->defaultChatTheme().get(), // #TODO themes
 | 
				
			||||||
 | 
								widget,
 | 
				
			||||||
 | 
								rect);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Painter p(widget);
 | 
				
			||||||
 | 
							auto hq = PainterHighQualityEnabler(p);
 | 
				
			||||||
 | 
							const auto theme = controller->defaultChatTheme().get();
 | 
				
			||||||
 | 
							auto context = theme->preparePaintContext(
 | 
				
			||||||
 | 
								state->style.get(),
 | 
				
			||||||
 | 
								widget->rect(),
 | 
				
			||||||
 | 
								widget->rect());
 | 
				
			||||||
 | 
							context.outbg = view->hasOutLayout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const auto radius = rightSize.height() / 2;
 | 
				
			||||||
 | 
								const auto r = rightRect();
 | 
				
			||||||
 | 
								const auto &st = context.st->messageStyle(
 | 
				
			||||||
 | 
									context.outbg,
 | 
				
			||||||
 | 
									context.selected());
 | 
				
			||||||
 | 
								p.setPen(Qt::NoPen);
 | 
				
			||||||
 | 
								p.setBrush(st.msgShadow);
 | 
				
			||||||
 | 
								p.drawRoundedRect(r.translated(0, st::msgShadow), radius, radius);
 | 
				
			||||||
 | 
								p.setBrush(st.msgBg);
 | 
				
			||||||
 | 
								p.drawRoundedRect(r, radius, radius);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							p.translate(padding / 2, padding + view->marginBottom());
 | 
				
			||||||
 | 
							view->draw(p, context);
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rpl::duplicate(
 | 
				
			||||||
 | 
							emojiValue
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=,
 | 
				
			||||||
 | 
									emojiValue = std::move(emojiValue),
 | 
				
			||||||
 | 
									iconSize = st::settingsReactionMessageSize](
 | 
				
			||||||
 | 
								const QString &emoji) {
 | 
				
			||||||
 | 
							const auto &reactions = controller->session().data().reactions();
 | 
				
			||||||
 | 
							for (const auto &r : reactions.list(Data::Reactions::Type::All)) {
 | 
				
			||||||
 | 
								if (emoji != r.emoji) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								const auto index = state->icons.flag ? 1 : 0;
 | 
				
			||||||
 | 
								state->icons.lifetimes[index] = rpl::lifetime();
 | 
				
			||||||
 | 
								AddIcon(
 | 
				
			||||||
 | 
									box->verticalLayout(),
 | 
				
			||||||
 | 
									widget->geometryValue(
 | 
				
			||||||
 | 
									) | rpl::map([=](const QRect &r) {
 | 
				
			||||||
 | 
										return widget->pos()
 | 
				
			||||||
 | 
											+ rightRect().topLeft()
 | 
				
			||||||
 | 
											+ QPoint(
 | 
				
			||||||
 | 
												(rightSize.width() - iconSize) / 2,
 | 
				
			||||||
 | 
												(rightSize.height() - iconSize) / 2);
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
									iconSize,
 | 
				
			||||||
 | 
									&controller->session(),
 | 
				
			||||||
 | 
									r,
 | 
				
			||||||
 | 
									rpl::never<>(),
 | 
				
			||||||
 | 
									rpl::duplicate(emojiValue) | rpl::skip(1) | rpl::to_empty,
 | 
				
			||||||
 | 
									&state->icons.lifetimes[index]);
 | 
				
			||||||
 | 
								state->icons.flag = !state->icons.flag;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}, widget->lifetime());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SetupShadows(
 | 
				
			||||||
 | 
							not_null<Ui::RpWidget*> parent,
 | 
				
			||||||
 | 
							not_null<Ui::ScrollArea*> scroll,
 | 
				
			||||||
 | 
							not_null<Ui::RpWidget*> inner) {
 | 
				
			||||||
 | 
						using namespace rpl::mappers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto topShadow = Ui::CreateChild<Ui::FadeShadow>(parent.get());
 | 
				
			||||||
 | 
						const auto bottomShadow = Ui::CreateChild<Ui::FadeShadow>(parent.get());
 | 
				
			||||||
 | 
						scroll->geometryValue(
 | 
				
			||||||
 | 
						) | rpl::start_with_next_done([=](const QRect &geometry) {
 | 
				
			||||||
 | 
							topShadow->resizeToWidth(geometry.width());
 | 
				
			||||||
 | 
							topShadow->move(
 | 
				
			||||||
 | 
								geometry.x(),
 | 
				
			||||||
 | 
								geometry.y());
 | 
				
			||||||
 | 
							bottomShadow->resizeToWidth(geometry.width());
 | 
				
			||||||
 | 
							bottomShadow->move(
 | 
				
			||||||
 | 
								geometry.x(),
 | 
				
			||||||
 | 
								geometry.y() + geometry.height() - st::lineWidth);
 | 
				
			||||||
 | 
						}, [t = Ui::MakeWeak(topShadow), b = Ui::MakeWeak(bottomShadow)] {
 | 
				
			||||||
 | 
							Ui::DestroyChild(t.data());
 | 
				
			||||||
 | 
							Ui::DestroyChild(b.data());
 | 
				
			||||||
 | 
						}, topShadow->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						topShadow->toggleOn(scroll->scrollTopValue() | rpl::map(_1 > 0));
 | 
				
			||||||
 | 
						bottomShadow->toggleOn(rpl::combine(
 | 
				
			||||||
 | 
							scroll->scrollTopValue(),
 | 
				
			||||||
 | 
							scroll->heightValue(),
 | 
				
			||||||
 | 
							inner->heightValue(),
 | 
				
			||||||
 | 
							_1 + _2 < _3));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReactionsSettingsBox(
 | 
				
			||||||
 | 
							not_null<Ui::GenericBox*> box,
 | 
				
			||||||
 | 
							not_null<Window::SessionController*> controller) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct State {
 | 
				
			||||||
 | 
							rpl::variable<QString> selectedEmoji;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto &reactions = controller->session().data().reactions();
 | 
				
			||||||
 | 
						const auto state = box->lifetime().make_state<State>();
 | 
				
			||||||
 | 
						state->selectedEmoji = reactions.favorite();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AddMessage(box, controller, state->selectedEmoji.value());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto container = box->verticalLayout();
 | 
				
			||||||
 | 
						Settings::AddSubsectionTitle(
 | 
				
			||||||
 | 
							container,
 | 
				
			||||||
 | 
							tr::lng_settings_chat_reactions_subtitle());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto &stButton = st::settingsButton;
 | 
				
			||||||
 | 
						const auto scrollContainer = box->addRow(
 | 
				
			||||||
 | 
							object_ptr<Ui::FixedHeightWidget>(
 | 
				
			||||||
 | 
								box,
 | 
				
			||||||
 | 
								kVisibleButtonsCount
 | 
				
			||||||
 | 
									* (stButton.height
 | 
				
			||||||
 | 
										+ stButton.padding.top()
 | 
				
			||||||
 | 
										+ stButton.padding.bottom())),
 | 
				
			||||||
 | 
							style::margins());
 | 
				
			||||||
 | 
						const auto scroll = Ui::CreateChild<Ui::ScrollArea>(
 | 
				
			||||||
 | 
							scrollContainer,
 | 
				
			||||||
 | 
							st::boxScroll);
 | 
				
			||||||
 | 
						const auto buttonsContainer = scroll->setOwnedWidget(
 | 
				
			||||||
 | 
							object_ptr<Ui::VerticalLayout>(scroll));
 | 
				
			||||||
 | 
						scrollContainer->sizeValue(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](const QSize &s) {
 | 
				
			||||||
 | 
							scroll->resize(s.width(), s.height());
 | 
				
			||||||
 | 
							buttonsContainer->resizeToWidth(s.width());
 | 
				
			||||||
 | 
						}, scroll->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto check = Ui::CreateChild<Ui::RpWidget>(buttonsContainer.get());
 | 
				
			||||||
 | 
						check->resize(st::settingsReactionCornerSize);
 | 
				
			||||||
 | 
						check->setAttribute(Qt::WA_TransparentForMouseEvents);
 | 
				
			||||||
 | 
						check->paintRequest(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
							Painter p(check);
 | 
				
			||||||
 | 
							st::mediaPlayerMenuCheck.paintInCenter(p, check->rect());
 | 
				
			||||||
 | 
						}, check->lifetime());
 | 
				
			||||||
 | 
						const auto checkButton = [=](not_null<const Ui::RpWidget*> button) {
 | 
				
			||||||
 | 
							check->moveToRight(
 | 
				
			||||||
 | 
								st::settingsButtonRightSkip,
 | 
				
			||||||
 | 
								button->y() + (button->height() - check->height()) / 2);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto firstCheckedButton = (Ui::RpWidget*)(nullptr);
 | 
				
			||||||
 | 
						for (const auto &r : reactions.list(Data::Reactions::Type::All)) {
 | 
				
			||||||
 | 
							const auto button = Settings::AddButton(
 | 
				
			||||||
 | 
								buttonsContainer,
 | 
				
			||||||
 | 
								rpl::single<QString>(base::duplicate(r.title)),
 | 
				
			||||||
 | 
								stButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto iconSize = st::settingsReactionSize;
 | 
				
			||||||
 | 
							AddIcon(
 | 
				
			||||||
 | 
								button,
 | 
				
			||||||
 | 
								button->sizeValue(
 | 
				
			||||||
 | 
								) | rpl::map([=, left = button->st().iconLeft](const QSize &s) {
 | 
				
			||||||
 | 
									return QPoint(
 | 
				
			||||||
 | 
										left + st::settingsReactionRightSkip,
 | 
				
			||||||
 | 
										(s.height() - iconSize) / 2);
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								iconSize,
 | 
				
			||||||
 | 
								&controller->session(),
 | 
				
			||||||
 | 
								r,
 | 
				
			||||||
 | 
								button->events(
 | 
				
			||||||
 | 
								) | rpl::filter([=](not_null<QEvent*> event) {
 | 
				
			||||||
 | 
									return event->type() == QEvent::Enter;
 | 
				
			||||||
 | 
								}) | rpl::to_empty,
 | 
				
			||||||
 | 
								rpl::never<>(),
 | 
				
			||||||
 | 
								&button->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							button->setClickedCallback([=, emoji = r.emoji] {
 | 
				
			||||||
 | 
								checkButton(button);
 | 
				
			||||||
 | 
								state->selectedEmoji = emoji;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							if (r.emoji == state->selectedEmoji.current()) {
 | 
				
			||||||
 | 
								firstCheckedButton = button;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (firstCheckedButton) {
 | 
				
			||||||
 | 
							firstCheckedButton->geometryValue(
 | 
				
			||||||
 | 
							) | rpl::filter([=](const QRect &r) {
 | 
				
			||||||
 | 
								return r.isValid();
 | 
				
			||||||
 | 
							}) | rpl::take(1) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
								checkButton(firstCheckedButton);
 | 
				
			||||||
 | 
							}, firstCheckedButton->lifetime());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						check->raise();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SetupShadows(scrollContainer, scroll, buttonsContainer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						box->setTitle(tr::lng_settings_chat_reactions_title());
 | 
				
			||||||
 | 
						box->setWidth(st::boxWideWidth);
 | 
				
			||||||
 | 
						box->addButton(tr::lng_settings_save(), [=] {
 | 
				
			||||||
 | 
							const auto &data = controller->session().data();
 | 
				
			||||||
 | 
							const auto selectedEmoji = state->selectedEmoji.current();
 | 
				
			||||||
 | 
							if (data.reactions().favorite() != selectedEmoji) {
 | 
				
			||||||
 | 
								data.reactions().setFavorite(selectedEmoji);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							box->closeBox();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								Telegram/SourceFiles/boxes/reactions_settings_box.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Telegram/SourceFiles/boxes/reactions_settings_box.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Ui {
 | 
				
			||||||
 | 
					class GenericBox;
 | 
				
			||||||
 | 
					} // namespace Ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Window {
 | 
				
			||||||
 | 
					class SessionController;
 | 
				
			||||||
 | 
					} // namespace Window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReactionsSettingsBox(
 | 
				
			||||||
 | 
						not_null<Ui::GenericBox*> box,
 | 
				
			||||||
 | 
						not_null<Window::SessionController*> controller);
 | 
				
			||||||
| 
						 | 
					@ -370,6 +370,12 @@ settingsPeerToPeerSkip: 9px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
settingsIconRadius: 6px;
 | 
					settingsIconRadius: 6px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					settingsReactionSize: 50px;
 | 
				
			||||||
 | 
					settingsReactionRightSkip: -10px;
 | 
				
			||||||
 | 
					settingsReactionCornerSize: size(28px, 22px);
 | 
				
			||||||
 | 
					settingsReactionCornerSkip: point(11px, -6px);
 | 
				
			||||||
 | 
					settingsReactionMessageSize: 36px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
notifyPreviewMargins: margins(40px, 20px, 40px, 58px);
 | 
					notifyPreviewMargins: margins(40px, 20px, 40px, 58px);
 | 
				
			||||||
notifyPreviewUserpicSize: 36px;
 | 
					notifyPreviewUserpicSize: 36px;
 | 
				
			||||||
notifyPreviewUserpicPosition: point(14px, 11px);
 | 
					notifyPreviewUserpicPosition: point(14px, 11px);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "settings/settings_advanced.h"
 | 
					#include "settings/settings_advanced.h"
 | 
				
			||||||
#include "boxes/connection_box.h"
 | 
					#include "boxes/connection_box.h"
 | 
				
			||||||
#include "boxes/auto_download_box.h"
 | 
					#include "boxes/auto_download_box.h"
 | 
				
			||||||
 | 
					#include "boxes/reactions_settings_box.h"
 | 
				
			||||||
#include "boxes/stickers_box.h"
 | 
					#include "boxes/stickers_box.h"
 | 
				
			||||||
#include "ui/boxes/confirm_box.h"
 | 
					#include "ui/boxes/confirm_box.h"
 | 
				
			||||||
#include "boxes/background_box.h"
 | 
					#include "boxes/background_box.h"
 | 
				
			||||||
| 
						 | 
					@ -62,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "mainwidget.h"
 | 
					#include "mainwidget.h"
 | 
				
			||||||
#include "mainwindow.h"
 | 
					#include "mainwindow.h"
 | 
				
			||||||
#include "facades.h"
 | 
					#include "facades.h"
 | 
				
			||||||
 | 
					#include "styles/style_chat_helpers.h" // stickersRemove
 | 
				
			||||||
#include "styles/style_settings.h"
 | 
					#include "styles/style_settings.h"
 | 
				
			||||||
#include "styles/style_layers.h"
 | 
					#include "styles/style_layers.h"
 | 
				
			||||||
#include "styles/style_window.h"
 | 
					#include "styles/style_window.h"
 | 
				
			||||||
| 
						 | 
					@ -836,6 +838,25 @@ void SetupMessages(
 | 
				
			||||||
	const auto react = addQuick(
 | 
						const auto react = addQuick(
 | 
				
			||||||
		Quick::React,
 | 
							Quick::React,
 | 
				
			||||||
		tr::lng_settings_chat_quick_action_react(tr::now));
 | 
							tr::lng_settings_chat_quick_action_react(tr::now));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						class EmptyButton final : public Ui::IconButton {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							EmptyButton(not_null<Ui::RpWidget*> p, const style::IconButton &st)
 | 
				
			||||||
 | 
							: Ui::IconButton(p, st)
 | 
				
			||||||
 | 
							, _rippleAreaPosition(st.rippleAreaPosition) {
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						protected:
 | 
				
			||||||
 | 
							void paintEvent(QPaintEvent *e) override {
 | 
				
			||||||
 | 
								Painter p(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								paintRipple(p, _rippleAreaPosition, nullptr);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							const QPoint _rippleAreaPosition;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto buttonRight = Ui::CreateChild<EmptyButton>(
 | 
				
			||||||
 | 
							inner,
 | 
				
			||||||
 | 
							st::stickersRemove);
 | 
				
			||||||
	const auto reactRight = Ui::CreateChild<Ui::RpWidget>(inner);
 | 
						const auto reactRight = Ui::CreateChild<Ui::RpWidget>(inner);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct State {
 | 
						struct State {
 | 
				
			||||||
| 
						 | 
					@ -872,6 +893,11 @@ void SetupMessages(
 | 
				
			||||||
		reactRight->moveToRight(
 | 
							reactRight->moveToRight(
 | 
				
			||||||
			st::settingsButtonRightSkip,
 | 
								st::settingsButtonRightSkip,
 | 
				
			||||||
			r.y() + (r.height() - rightSize.height()) / 2);
 | 
								r.y() + (r.height() - rightSize.height()) / 2);
 | 
				
			||||||
 | 
							buttonRight->moveToLeft(
 | 
				
			||||||
 | 
								reactRight->x()
 | 
				
			||||||
 | 
									+ (rightSize.width() - buttonRight->width()) / 2,
 | 
				
			||||||
 | 
								reactRight->y()
 | 
				
			||||||
 | 
									+ (rightSize.height() - buttonRight->height()) / 2);
 | 
				
			||||||
	}, reactRight->lifetime());
 | 
						}, reactRight->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	groupQuick->setChangedCallback([=](Quick value) {
 | 
						groupQuick->setChangedCallback([=](Quick value) {
 | 
				
			||||||
| 
						 | 
					@ -879,6 +905,10 @@ void SetupMessages(
 | 
				
			||||||
		Core::App().saveSettingsDelayed();
 | 
							Core::App().saveSettingsDelayed();
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						buttonRight->setClickedCallback([=, show = Window::Show(controller)] {
 | 
				
			||||||
 | 
							show.showBox(Box(ReactionsSettingsBox, controller));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	AddSkip(inner, st::settingsCheckboxesSkip);
 | 
						AddSkip(inner, st::settingsCheckboxesSkip);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue