290 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
	
		
			8.5 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 "boxes/peers/edit_peer_reactions.h"
 | |
| 
 | |
| #include "boxes/reactions_settings_box.h" // AddReactionAnimatedIcon
 | |
| #include "data/data_message_reactions.h"
 | |
| #include "data/data_peer.h"
 | |
| #include "data/data_chat.h"
 | |
| #include "data/data_channel.h"
 | |
| #include "data/data_session.h"
 | |
| #include "main/main_session.h"
 | |
| #include "apiwrap.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "ui/layers/generic_box.h"
 | |
| #include "ui/widgets/buttons.h"
 | |
| #include "ui/widgets/checkbox.h"
 | |
| #include "ui/wrap/slide_wrap.h"
 | |
| #include "settings/settings_common.h"
 | |
| #include "window/window_session_controller.h"
 | |
| #include "styles/style_settings.h"
 | |
| #include "styles/style_info.h"
 | |
| 
 | |
| void EditAllowedReactionsBox(
 | |
| 		not_null<Ui::GenericBox*> box,
 | |
| 		not_null<Window::SessionNavigation*> navigation,
 | |
| 		bool isGroup,
 | |
| 		const std::vector<Data::Reaction> &list,
 | |
| 		const Data::AllowedReactions &allowed,
 | |
| 		Fn<void(const Data::AllowedReactions &)> callback) {
 | |
| 	using namespace Data;
 | |
| 	using namespace rpl::mappers;
 | |
| 
 | |
| 	const auto iconHeight = st::editPeerReactionsPreview;
 | |
| 	box->setTitle(tr::lng_manage_peer_reactions());
 | |
| 
 | |
| 	enum class Option {
 | |
| 		All,
 | |
| 		Some,
 | |
| 		None,
 | |
| 	};
 | |
| 	struct State {
 | |
| 		base::flat_map<ReactionId, not_null<Ui::SettingsButton*>> toggles;
 | |
| 		rpl::variable<Option> option; // For groups.
 | |
| 		rpl::variable<bool> anyToggled; // For channels.
 | |
| 		rpl::event_stream<bool> forceToggleAll; // For channels.
 | |
| 	};
 | |
| 	const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
 | |
| 		? Option::All
 | |
| 		: allowed.some.empty()
 | |
| 		? Option::None
 | |
| 		: Option::Some;
 | |
| 	const auto state = box->lifetime().make_state<State>(State{
 | |
| 		.option = optionInitial,
 | |
| 		.anyToggled = (optionInitial != Option::None),
 | |
| 	});
 | |
| 
 | |
| 	const auto collect = [=] {
 | |
| 		auto result = AllowedReactions();
 | |
| 		if (!isGroup || state->option.current() == Option::Some) {
 | |
| 			result.some.reserve(state->toggles.size());
 | |
| 			for (const auto &[id, button] : state->toggles) {
 | |
| 				if (button->toggled()) {
 | |
| 					result.some.push_back(id);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		result.type = isGroup
 | |
| 			? (state->option.current() != Option::All
 | |
| 				? AllowedReactionsType::Some
 | |
| 				: AllowedReactionsType::All)
 | |
| 			: (result.some.size() == state->toggles.size())
 | |
| 			? AllowedReactionsType::Default
 | |
| 			: AllowedReactionsType::Some;
 | |
| 		return result;
 | |
| 	};
 | |
| 
 | |
| 	const auto container = box->verticalLayout();
 | |
| 
 | |
| 	const auto enabled = isGroup ? nullptr : Settings::AddButton(
 | |
| 		container,
 | |
| 		tr::lng_manage_peer_reactions_enable(),
 | |
| 		st::manageGroupButton.button
 | |
| 	).get();
 | |
| 	if (enabled && !list.empty()) {
 | |
| 		AddReactionAnimatedIcon(
 | |
| 			enabled,
 | |
| 			enabled->sizeValue(
 | |
| 			) | rpl::map([=](const QSize &size) {
 | |
| 				return QPoint(
 | |
| 					st::manageGroupButton.iconPosition.x(),
 | |
| 					(size.height() - iconHeight) / 2);
 | |
| 			}),
 | |
| 			iconHeight,
 | |
| 			list.front(),
 | |
| 			rpl::never<>(),
 | |
| 			rpl::never<>(),
 | |
| 			&enabled->lifetime());
 | |
| 	}
 | |
| 	if (enabled) {
 | |
| 		enabled->toggleOn(state->anyToggled.value());
 | |
| 		enabled->toggledChanges(
 | |
| 		) | rpl::filter([=](bool value) {
 | |
| 			return (value != state->anyToggled.current());
 | |
| 		}) | rpl::start_to_stream(state->forceToggleAll, enabled->lifetime());
 | |
| 	}
 | |
| 	const auto group = std::make_shared<Ui::RadioenumGroup<Option>>(
 | |
| 		state->option.current());
 | |
| 	group->setChangedCallback([=](Option value) {
 | |
| 		state->option = value;
 | |
| 	});
 | |
| 	const auto addOption = [&](Option option, const QString &text) {
 | |
| 		if (!isGroup) {
 | |
| 			return;
 | |
| 		}
 | |
| 		container->add(
 | |
| 			object_ptr<Ui::Radioenum<Option>>(
 | |
| 				container,
 | |
| 				group,
 | |
| 				option,
 | |
| 				text,
 | |
| 				st::settingsSendType),
 | |
| 			st::settingsSendTypePadding);
 | |
| 	};
 | |
| 	addOption(Option::All, tr::lng_manage_peer_reactions_all(tr::now));
 | |
| 	addOption(Option::Some, tr::lng_manage_peer_reactions_some(tr::now));
 | |
| 	addOption(Option::None, tr::lng_manage_peer_reactions_none(tr::now));
 | |
| 
 | |
| 	const auto about = [](Option option) {
 | |
| 		switch (option) {
 | |
| 		case Option::All: return tr::lng_manage_peer_reactions_all_about();
 | |
| 		case Option::Some: return tr::lng_manage_peer_reactions_some_about();
 | |
| 		case Option::None: return tr::lng_manage_peer_reactions_none_about();
 | |
| 		}
 | |
| 		Unexpected("Option value in EditAllowedReactionsBox.");
 | |
| 	};
 | |
| 	Settings::AddSkip(container);
 | |
| 	Settings::AddDividerText(
 | |
| 		container,
 | |
| 		(isGroup
 | |
| 			? (state->option.value()
 | |
| 				| rpl::map(about)
 | |
| 				| rpl::flatten_latest())
 | |
| 			: tr::lng_manage_peer_reactions_about_channel()));
 | |
| 
 | |
| 	const auto wrap = enabled ? nullptr : container->add(
 | |
| 		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
 | |
| 			container,
 | |
| 			object_ptr<Ui::VerticalLayout>(container)));
 | |
| 	if (wrap) {
 | |
| 		wrap->toggleOn(state->option.value() | rpl::map(_1 == Option::Some));
 | |
| 		wrap->finishAnimating();
 | |
| 	}
 | |
| 	const auto reactions = wrap ? wrap->entity() : container.get();
 | |
| 
 | |
| 	Settings::AddSkip(reactions);
 | |
| 	Settings::AddSubsectionTitle(
 | |
| 		reactions,
 | |
| 		(enabled
 | |
| 			? tr::lng_manage_peer_reactions_available()
 | |
| 			: tr::lng_manage_peer_reactions_some_title()));
 | |
| 
 | |
| 	const auto like = QString::fromUtf8("\xf0\x9f\x91\x8d");
 | |
| 	const auto dislike = QString::fromUtf8("\xf0\x9f\x91\x8e");
 | |
| 	const auto activeOnStart = [&](const ReactionId &id) {
 | |
| 		const auto inSome = ranges::contains(allowed.some, id);
 | |
| 		if (!isGroup) {
 | |
| 			return inSome || (allowed.type != AllowedReactionsType::Some);
 | |
| 		}
 | |
| 		const auto emoji = id.emoji();
 | |
| 		const auto isDefault = (emoji == like) || (emoji == dislike);
 | |
| 		return (allowed.type != AllowedReactionsType::Some)
 | |
| 			? isDefault
 | |
| 			: (inSome || (isDefault && allowed.some.empty()));
 | |
| 	};
 | |
| 	const auto add = [&](const Reaction &entry) {
 | |
| 		const auto button = Settings::AddButton(
 | |
| 			reactions,
 | |
| 			rpl::single(entry.title),
 | |
| 			st::manageGroupButton.button);
 | |
| 		AddReactionAnimatedIcon(
 | |
| 			button,
 | |
| 			button->sizeValue(
 | |
| 			) | rpl::map([=](const QSize &size) {
 | |
| 				return QPoint(
 | |
| 					st::editPeerReactionsIconLeft,
 | |
| 					(size.height() - iconHeight) / 2);
 | |
| 			}),
 | |
| 			iconHeight,
 | |
| 			entry,
 | |
| 			button->events(
 | |
| 			) | rpl::filter([=](not_null<QEvent*> event) {
 | |
| 				return event->type() == QEvent::Enter;
 | |
| 			}) | rpl::to_empty,
 | |
| 			rpl::never<>(),
 | |
| 			&button->lifetime());
 | |
| 		state->toggles.emplace(entry.id, button);
 | |
| 		button->toggleOn(rpl::single(
 | |
| 			activeOnStart(entry.id)
 | |
| 		) | rpl::then(enabled
 | |
| 			? (state->forceToggleAll.events() | rpl::type_erased())
 | |
| 			: rpl::never<bool>()
 | |
| 		));
 | |
| 		if (enabled) {
 | |
| 			button->toggledChanges(
 | |
| 			) | rpl::start_with_next([=](bool toggled) {
 | |
| 				if (toggled) {
 | |
| 					state->anyToggled = true;
 | |
| 				} else if (collect().some.empty()) {
 | |
| 					state->anyToggled = false;
 | |
| 				}
 | |
| 			}, button->lifetime());
 | |
| 		}
 | |
| 	};
 | |
| 	for (const auto &entry : list) {
 | |
| 		add(entry);
 | |
| 	}
 | |
| 	for (const auto &id : allowed.some) {
 | |
| 		if (const auto customId = id.custom()) {
 | |
| 			// Some possible forward compatibility.
 | |
| 			const auto button = Settings::AddButton(
 | |
| 				reactions,
 | |
| 				rpl::single(u"Custom reaction"_q),
 | |
| 				st::manageGroupButton.button);
 | |
| 			AddReactionCustomIcon(
 | |
| 				button,
 | |
| 				button->sizeValue(
 | |
| 				) | rpl::map([=](const QSize &size) {
 | |
| 					return QPoint(
 | |
| 						st::editPeerReactionsIconLeft,
 | |
| 						(size.height() - iconHeight) / 2);
 | |
| 				}),
 | |
| 				iconHeight,
 | |
| 				navigation->parentController(),
 | |
| 				customId,
 | |
| 				rpl::never<>(),
 | |
| 				&button->lifetime());
 | |
| 			state->toggles.emplace(id, button);
 | |
| 			button->toggleOn(rpl::single(true));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	box->addButton(tr::lng_settings_save(), [=] {
 | |
| 		const auto result = collect();
 | |
| 		box->closeBox();
 | |
| 		callback(result);
 | |
| 	});
 | |
| 	box->addButton(tr::lng_cancel(), [=] {
 | |
| 		box->closeBox();
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void SaveAllowedReactions(
 | |
| 		not_null<PeerData*> peer,
 | |
| 		const Data::AllowedReactions &allowed) {
 | |
| 	auto ids = allowed.some | ranges::views::transform(
 | |
| 		Data::ReactionToMTP
 | |
| 	) | ranges::to<QVector<MTPReaction>>;
 | |
| 
 | |
| 	using Type = Data::AllowedReactionsType;
 | |
| 	const auto updated = (allowed.type != Type::Some)
 | |
| 		? MTP_chatReactionsAll(MTP_flags((allowed.type == Type::Default)
 | |
| 			? MTPDchatReactionsAll::Flag(0)
 | |
| 			: MTPDchatReactionsAll::Flag::f_allow_custom))
 | |
| 		: allowed.some.empty()
 | |
| 		? MTP_chatReactionsNone()
 | |
| 		: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
 | |
| 	peer->session().api().request(MTPmessages_SetChatAvailableReactions(
 | |
| 		peer->input,
 | |
| 		updated
 | |
| 	)).done([=](const MTPUpdates &result) {
 | |
| 		peer->session().api().applyUpdates(result);
 | |
| 		if (const auto chat = peer->asChat()) {
 | |
| 			chat->setAllowedReactions(Data::Parse(updated));
 | |
| 		} else if (const auto channel = peer->asChannel()) {
 | |
| 			channel->setAllowedReactions(Data::Parse(updated));
 | |
| 		} else {
 | |
| 			Unexpected("Invalid peer type in SaveAllowedReactions.");
 | |
| 		}
 | |
| 	}).fail([=](const MTP::Error &error) {
 | |
| 		if (error.type() == qstr("REACTION_INVALID")) {
 | |
| 			peer->updateFullForced();
 | |
| 			peer->owner().reactions().refreshDefault();
 | |
| 		}
 | |
| 	}).send();
 | |
| }
 | 
