Add a box to create / edit invite links.
This commit is contained in:
		
							parent
							
								
									01ecf0ca93
								
							
						
					
					
						commit
						1cce383d15
					
				
					 9 changed files with 392 additions and 86 deletions
				
			
		| 
						 | 
					@ -1166,9 +1166,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
"lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content.";
 | 
					"lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"lng_group_invite_create" = "Create an invite link"; // #TODO links legacy
 | 
					"lng_group_invite_create" = "Create an invite link"; // #TODO links legacy
 | 
				
			||||||
"lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link."; // #TODO links legacy
 | 
					 | 
				
			||||||
"lng_group_invite_about_channel" = "Telegram users will be able to join\nyour channel by following this link."; // #TODO links legacy
 | 
					 | 
				
			||||||
"lng_group_invite_create_new" = "Revoke invite link"; // #TODO links legacy
 | 
					 | 
				
			||||||
"lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you.";
 | 
					"lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you.";
 | 
				
			||||||
"lng_group_invite_copied" = "Invite link copied to clipboard.";
 | 
					"lng_group_invite_copied" = "Invite link copied to clipboard.";
 | 
				
			||||||
"lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already.";
 | 
					"lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already.";
 | 
				
			||||||
| 
						 | 
					@ -1186,12 +1183,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
"lng_group_invite_manage_about" = "You can create additional invite links that have limited time or number of usages.";
 | 
					"lng_group_invite_manage_about" = "You can create additional invite links that have limited time or number of usages.";
 | 
				
			||||||
"lng_group_invite_title" = "Invite links";
 | 
					"lng_group_invite_title" = "Invite links";
 | 
				
			||||||
"lng_group_invite_add" = "Create a New Link";
 | 
					"lng_group_invite_add" = "Create a New Link";
 | 
				
			||||||
 | 
					"lng_group_invite_add_about" = "You can generate invite links that will expire after they've been used.";
 | 
				
			||||||
"lng_group_invite_expires_at" = "This link expires {when}.";
 | 
					"lng_group_invite_expires_at" = "This link expires {when}.";
 | 
				
			||||||
"lng_group_invite_created_by" = "Link created by";
 | 
					"lng_group_invite_created_by" = "Link created by";
 | 
				
			||||||
"lng_group_invite_context_copy" = "Copy";
 | 
					"lng_group_invite_context_copy" = "Copy";
 | 
				
			||||||
"lng_group_invite_context_share" = "Share";
 | 
					"lng_group_invite_context_share" = "Share";
 | 
				
			||||||
"lng_group_invite_context_edit" = "Edit";
 | 
					"lng_group_invite_context_edit" = "Edit";
 | 
				
			||||||
"lng_group_invite_context_revoke" = "Revoke";
 | 
					"lng_group_invite_context_revoke" = "Revoke";
 | 
				
			||||||
 | 
					"lng_group_invite_context_delete" = "Delete";
 | 
				
			||||||
 | 
					"lng_group_invite_context_delete_all" = "Delete all";
 | 
				
			||||||
 | 
					"lng_group_invite_revoked_title" = "Revoked links";
 | 
				
			||||||
"lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?";
 | 
					"lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?";
 | 
				
			||||||
"lng_group_invite_link_expired" = "Expired";
 | 
					"lng_group_invite_link_expired" = "Expired";
 | 
				
			||||||
"lng_group_invite_link_revoked" = "Revoked";
 | 
					"lng_group_invite_link_revoked" = "Revoked";
 | 
				
			||||||
| 
						 | 
					@ -1199,10 +1200,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
"lng_group_invite_new_title" = "New Link";
 | 
					"lng_group_invite_new_title" = "New Link";
 | 
				
			||||||
"lng_group_invite_expire_title" = "Limit by time period";
 | 
					"lng_group_invite_expire_title" = "Limit by time period";
 | 
				
			||||||
"lng_group_invite_expire_about" = "You can make the link expire after a certain time.";
 | 
					"lng_group_invite_expire_about" = "You can make the link expire after a certain time.";
 | 
				
			||||||
"lng_group_invite_expire_custom" = "Set custom duration";
 | 
					"lng_group_invite_expire_never" = "No limit";
 | 
				
			||||||
"lng_group_invite_usage_title" = "Limit number of uses.";
 | 
					"lng_group_invite_expire_custom" = "Custom";
 | 
				
			||||||
 | 
					"lng_group_invite_usage_title" = "Limit number of uses";
 | 
				
			||||||
"lng_group_invite_usage_about" = "You can make the link expire after it has been used for a certain number of times.";
 | 
					"lng_group_invite_usage_about" = "You can make the link expire after it has been used for a certain number of times.";
 | 
				
			||||||
"lng_group_invite_usage_custom" = "Enter custom limit";
 | 
					"lng_group_invite_expire_after" = "Expire after";
 | 
				
			||||||
 | 
					"lng_group_invite_custom_limit" = "Enter custom limit";
 | 
				
			||||||
 | 
					"lng_group_invite_usage_any" = "No limit";
 | 
				
			||||||
 | 
					"lng_group_invite_usage_custom" = "Custom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
 | 
					"lng_channel_public_link_copied" = "Link copied to clipboard.";
 | 
				
			||||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
 | 
					"lng_context_about_private_link" = "This link will only work for members of this chat.";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -252,7 +252,7 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QPointer<Ui::SlideWrap<>> _aboutSponsored;
 | 
						QPointer<Ui::SlideWrap<>> _aboutSponsored;
 | 
				
			||||||
	QPointer<HostInput> _host;
 | 
						QPointer<HostInput> _host;
 | 
				
			||||||
	QPointer<Ui::PortInput> _port;
 | 
						QPointer<Ui::NumberInput> _port;
 | 
				
			||||||
	QPointer<Ui::InputField> _user;
 | 
						QPointer<Ui::InputField> _user;
 | 
				
			||||||
	QPointer<Ui::PasswordInput> _password;
 | 
						QPointer<Ui::PasswordInput> _password;
 | 
				
			||||||
	QPointer<Base64UrlInput> _secret;
 | 
						QPointer<Base64UrlInput> _secret;
 | 
				
			||||||
| 
						 | 
					@ -928,11 +928,12 @@ void ProxyBox::setupSocketAddress(const ProxyData &data) {
 | 
				
			||||||
		st::connectionHostInputField,
 | 
							st::connectionHostInputField,
 | 
				
			||||||
		tr::lng_connection_host_ph(),
 | 
							tr::lng_connection_host_ph(),
 | 
				
			||||||
		data.host);
 | 
							data.host);
 | 
				
			||||||
	_port = Ui::CreateChild<Ui::PortInput>(
 | 
						_port = Ui::CreateChild<Ui::NumberInput>(
 | 
				
			||||||
		address,
 | 
							address,
 | 
				
			||||||
		st::connectionPortInputField,
 | 
							st::connectionPortInputField,
 | 
				
			||||||
		tr::lng_connection_port_ph(),
 | 
							tr::lng_connection_port_ph(),
 | 
				
			||||||
		data.port ? QString::number(data.port) : QString());
 | 
							data.port ? QString::number(data.port) : QString(),
 | 
				
			||||||
 | 
							65535);
 | 
				
			||||||
	address->widthValue(
 | 
						address->widthValue(
 | 
				
			||||||
	) | rpl::start_with_next([=](int width) {
 | 
						) | rpl::start_with_next([=](int width) {
 | 
				
			||||||
		_port->moveToRight(0, 0);
 | 
							_port->moveToRight(0, 0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1004,7 +1004,10 @@ void Controller::fillManageSection() {
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			}) | rpl::flatten_latest(
 | 
								}) | rpl::flatten_latest(
 | 
				
			||||||
			) | ToPositiveNumberString(),
 | 
								) | ToPositiveNumberString(),
 | 
				
			||||||
			[=] { Ui::show(Box(ManageInviteLinksBox, _peer)); },
 | 
								[=] { Ui::show(
 | 
				
			||||||
 | 
									Box(ManageInviteLinksBox, _peer),
 | 
				
			||||||
 | 
									Ui::LayerOption::KeepOther);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			st::infoIconInviteLinks);
 | 
								st::infoIconInviteLinks);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (canViewAdmins) {
 | 
						if (canViewAdmins) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,11 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "ui/abstract_button.h"
 | 
					#include "ui/abstract_button.h"
 | 
				
			||||||
#include "ui/widgets/buttons.h"
 | 
					#include "ui/widgets/buttons.h"
 | 
				
			||||||
#include "ui/widgets/popup_menu.h"
 | 
					#include "ui/widgets/popup_menu.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/checkbox.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/input_fields.h"
 | 
				
			||||||
#include "ui/controls/invite_link_label.h"
 | 
					#include "ui/controls/invite_link_label.h"
 | 
				
			||||||
#include "ui/controls/invite_link_buttons.h"
 | 
					#include "ui/controls/invite_link_buttons.h"
 | 
				
			||||||
#include "ui/text/text_utilities.h"
 | 
					#include "ui/text/text_utilities.h"
 | 
				
			||||||
#include "ui/toast/toast.h"
 | 
					#include "ui/toast/toast.h"
 | 
				
			||||||
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
 | 
					#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
 | 
				
			||||||
 | 
					#include "history/view/history_view_schedule_box.h" // ChooseDateTimeBox.
 | 
				
			||||||
#include "history/history_message.h" // GetErrorTextForSending.
 | 
					#include "history/history_message.h" // GetErrorTextForSending.
 | 
				
			||||||
#include "history/history.h"
 | 
					#include "history/history.h"
 | 
				
			||||||
#include "lang/lang_keys.h"
 | 
					#include "lang/lang_keys.h"
 | 
				
			||||||
| 
						 | 
					@ -40,6 +43,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "window/window_session_controller.h"
 | 
					#include "window/window_session_controller.h"
 | 
				
			||||||
#include "api/api_common.h"
 | 
					#include "api/api_common.h"
 | 
				
			||||||
#include "styles/style_info.h"
 | 
					#include "styles/style_info.h"
 | 
				
			||||||
 | 
					#include "styles/style_layers.h" // st::boxDividerLabel
 | 
				
			||||||
 | 
					#include "styles/style_settings.h" // st::settingsDividerLabelPadding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <xxhash.h>
 | 
					#include <xxhash.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,8 +53,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
namespace {
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
constexpr auto kPreloadPages = 2;
 | 
					constexpr auto kPreloadPages = 2;
 | 
				
			||||||
constexpr auto kExpireSoonNominator = 3;
 | 
					constexpr auto kMaxLimit = std::numeric_limits<int>::max();
 | 
				
			||||||
constexpr auto kExpireSoonDenominator = 4;
 | 
					constexpr auto kHour = 3600;
 | 
				
			||||||
 | 
					constexpr auto kDay = 86400;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class Color {
 | 
					enum class Color {
 | 
				
			||||||
	Permanent,
 | 
						Permanent,
 | 
				
			||||||
| 
						 | 
					@ -124,6 +130,27 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[nodiscard]] QString FormatExpireDate(TimeId date) {
 | 
				
			||||||
 | 
						if (date > 0) {
 | 
				
			||||||
 | 
							return langDateTime(base::unixtime::parse(date));
 | 
				
			||||||
 | 
						} else if (-date < kDay) {
 | 
				
			||||||
 | 
							return tr::lng_group_call_duration_hours(
 | 
				
			||||||
 | 
								tr::now,
 | 
				
			||||||
 | 
								lt_count,
 | 
				
			||||||
 | 
								(-date / kHour));
 | 
				
			||||||
 | 
						} else if (-date < 7 * kDay) {
 | 
				
			||||||
 | 
							return tr::lng_group_call_duration_days(
 | 
				
			||||||
 | 
								tr::now,
 | 
				
			||||||
 | 
								lt_count,
 | 
				
			||||||
 | 
								(-date / kDay));
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return tr::lng_local_storage_limit_weeks(
 | 
				
			||||||
 | 
								tr::now,
 | 
				
			||||||
 | 
								lt_count,
 | 
				
			||||||
 | 
								(-date / (7 * kDay)));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
 | 
					[[nodiscard]] uint64 ComputeRowId(const QString &link) {
 | 
				
			||||||
	return XXH64(link.data(), link.size() * sizeof(ushort), 0);
 | 
						return XXH64(link.data(), link.size() * sizeof(ushort), 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -291,9 +318,226 @@ void EditLinkBox(
 | 
				
			||||||
		not_null<Ui::GenericBox*> box,
 | 
							not_null<Ui::GenericBox*> box,
 | 
				
			||||||
		not_null<PeerData*> peer,
 | 
							not_null<PeerData*> peer,
 | 
				
			||||||
		const InviteLinkData &data) {
 | 
							const InviteLinkData &data) {
 | 
				
			||||||
	box->setTitle(data.link.isEmpty()
 | 
						const auto link = data.link;
 | 
				
			||||||
 | 
						box->setTitle(link.isEmpty()
 | 
				
			||||||
		? tr::lng_group_invite_new_title()
 | 
							? tr::lng_group_invite_new_title()
 | 
				
			||||||
		: tr::lng_group_invite_edit_title());
 | 
							: tr::lng_group_invite_edit_title());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						using namespace Settings;
 | 
				
			||||||
 | 
						const auto container = box->verticalLayout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AddSubsectionTitle(container, tr::lng_group_invite_expire_title());
 | 
				
			||||||
 | 
						const auto expiresWrap = container->add(object_ptr<Ui::VerticalLayout>(
 | 
				
			||||||
 | 
							container));
 | 
				
			||||||
 | 
						AddSkip(container);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AddDividerText(container, tr::lng_group_invite_expire_about());
 | 
				
			||||||
 | 
						AddSkip(container);
 | 
				
			||||||
 | 
						AddSubsectionTitle(container, tr::lng_group_invite_usage_title());
 | 
				
			||||||
 | 
						const auto usagesWrap = container->add(object_ptr<Ui::VerticalLayout>(
 | 
				
			||||||
 | 
							container));
 | 
				
			||||||
 | 
						AddSkip(container);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AddDividerText(container, tr::lng_group_invite_usage_about());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static const auto addButton = [](
 | 
				
			||||||
 | 
								not_null<Ui::VerticalLayout*> container,
 | 
				
			||||||
 | 
								const std::shared_ptr<Ui::RadiobuttonGroup> &group,
 | 
				
			||||||
 | 
								int value,
 | 
				
			||||||
 | 
								const QString &text) {
 | 
				
			||||||
 | 
							return container->add(
 | 
				
			||||||
 | 
								object_ptr<Ui::Radiobutton>(
 | 
				
			||||||
 | 
									container,
 | 
				
			||||||
 | 
									group,
 | 
				
			||||||
 | 
									value,
 | 
				
			||||||
 | 
									text),
 | 
				
			||||||
 | 
								st::inviteLinkLimitMargin);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto now = base::unixtime::now();
 | 
				
			||||||
 | 
						const auto expire = data.expireDate ? data.expireDate : kMaxLimit;
 | 
				
			||||||
 | 
						const auto expireGroup = std::make_shared<Ui::RadiobuttonGroup>(expire);
 | 
				
			||||||
 | 
						const auto usage = data.usageLimit ? data.usageLimit : kMaxLimit;
 | 
				
			||||||
 | 
						const auto usageGroup = std::make_shared<Ui::RadiobuttonGroup>(usage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						using Buttons = base::flat_map<int, base::unique_qptr<Ui::Radiobutton>>;
 | 
				
			||||||
 | 
						struct State {
 | 
				
			||||||
 | 
							Buttons expireButtons;
 | 
				
			||||||
 | 
							Buttons usageButtons;
 | 
				
			||||||
 | 
							int expireValue = 0;
 | 
				
			||||||
 | 
							int usageValue = 0;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto state = container->lifetime().make_state<State>(State{
 | 
				
			||||||
 | 
							.expireValue = expire,
 | 
				
			||||||
 | 
							.usageValue = usage
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						const auto regenerate = [=] {
 | 
				
			||||||
 | 
							expireGroup->setValue(state->expireValue);
 | 
				
			||||||
 | 
							usageGroup->setValue(state->usageValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto expires = std::vector{ kMaxLimit, -kHour, -kDay, -kDay * 7, 0 };
 | 
				
			||||||
 | 
							auto usages = std::vector{ kMaxLimit, 1, 10, 100, 0 };
 | 
				
			||||||
 | 
							auto defaults = State();
 | 
				
			||||||
 | 
							for (auto i = begin(expires); i != end(expires); ++i) {
 | 
				
			||||||
 | 
								if (*i == state->expireValue) {
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								} else if (*i == kMaxLimit) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								} else if (!*i || (now - *i >= state->expireValue)) {
 | 
				
			||||||
 | 
									expires.insert(i, state->expireValue);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for (auto i = begin(usages); i != end(usages); ++i) {
 | 
				
			||||||
 | 
								if (*i == state->usageValue) {
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								} else if (*i == kMaxLimit) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								} else if (!*i || *i > state->usageValue) {
 | 
				
			||||||
 | 
									usages.insert(i, state->usageValue);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							state->expireButtons.clear();
 | 
				
			||||||
 | 
							state->usageButtons.clear();
 | 
				
			||||||
 | 
							for (const auto limit : expires) {
 | 
				
			||||||
 | 
								const auto text = (limit == kMaxLimit)
 | 
				
			||||||
 | 
									? tr::lng_group_invite_expire_never(tr::now)
 | 
				
			||||||
 | 
									: !limit
 | 
				
			||||||
 | 
									? tr::lng_group_invite_expire_custom(tr::now)
 | 
				
			||||||
 | 
									: FormatExpireDate(limit);
 | 
				
			||||||
 | 
								state->expireButtons.emplace(
 | 
				
			||||||
 | 
									limit,
 | 
				
			||||||
 | 
									addButton(expiresWrap, expireGroup, limit, text));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for (const auto limit : usages) {
 | 
				
			||||||
 | 
								const auto text = (limit == kMaxLimit)
 | 
				
			||||||
 | 
									? tr::lng_group_invite_usage_any(tr::now)
 | 
				
			||||||
 | 
									: !limit
 | 
				
			||||||
 | 
									? tr::lng_group_invite_usage_custom(tr::now)
 | 
				
			||||||
 | 
									: QString("%L1").arg(limit);
 | 
				
			||||||
 | 
								state->usageButtons.emplace(
 | 
				
			||||||
 | 
									limit,
 | 
				
			||||||
 | 
									addButton(usagesWrap, usageGroup, limit, text));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto guard = Ui::MakeWeak(box);
 | 
				
			||||||
 | 
						expireGroup->setChangedCallback([=](int value) {
 | 
				
			||||||
 | 
							if (value) {
 | 
				
			||||||
 | 
								state->expireValue = value;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							expireGroup->setValue(state->expireValue);
 | 
				
			||||||
 | 
							box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
 | 
				
			||||||
 | 
								const auto save = [=](TimeId result) {
 | 
				
			||||||
 | 
									if (!result) {
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (guard) {
 | 
				
			||||||
 | 
										state->expireValue = result;
 | 
				
			||||||
 | 
										regenerate();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									box->closeBox();
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								const auto now = base::unixtime::now();
 | 
				
			||||||
 | 
								const auto time = (state->expireValue == kMaxLimit)
 | 
				
			||||||
 | 
									? (now + kDay)
 | 
				
			||||||
 | 
									: (state->expireValue > now)
 | 
				
			||||||
 | 
									? state->expireValue
 | 
				
			||||||
 | 
									: (state->expireValue < 0)
 | 
				
			||||||
 | 
									? (now - state->expireValue)
 | 
				
			||||||
 | 
									: (now + kDay);
 | 
				
			||||||
 | 
								HistoryView::ChooseDateTimeBox(
 | 
				
			||||||
 | 
									box,
 | 
				
			||||||
 | 
									tr::lng_group_invite_expire_after(),
 | 
				
			||||||
 | 
									tr::lng_settings_save(),
 | 
				
			||||||
 | 
									save,
 | 
				
			||||||
 | 
									time);
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						usageGroup->setChangedCallback([=](int value) {
 | 
				
			||||||
 | 
							if (value) {
 | 
				
			||||||
 | 
								state->usageValue = value;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							usageGroup->setValue(state->usageValue);
 | 
				
			||||||
 | 
							box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
 | 
				
			||||||
 | 
								const auto height = st::boxPadding.bottom()
 | 
				
			||||||
 | 
									+ st::defaultInputField.heightMin
 | 
				
			||||||
 | 
									+ st::boxPadding.bottom();
 | 
				
			||||||
 | 
								box->setTitle(tr::lng_group_invite_expire_after());
 | 
				
			||||||
 | 
								const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
 | 
				
			||||||
 | 
									box,
 | 
				
			||||||
 | 
									height));
 | 
				
			||||||
 | 
								const auto input = Ui::CreateChild<Ui::NumberInput>(
 | 
				
			||||||
 | 
									wrap,
 | 
				
			||||||
 | 
									st::defaultInputField,
 | 
				
			||||||
 | 
									tr::lng_group_invite_custom_limit(),
 | 
				
			||||||
 | 
									(state->usageValue == kMaxLimit
 | 
				
			||||||
 | 
										? QString()
 | 
				
			||||||
 | 
										: QString::number(state->usageValue)),
 | 
				
			||||||
 | 
									200'000);
 | 
				
			||||||
 | 
								wrap->widthValue(
 | 
				
			||||||
 | 
								) | rpl::start_with_next([=](int width) {
 | 
				
			||||||
 | 
									input->resize(width, input->height());
 | 
				
			||||||
 | 
									input->moveToLeft(0, st::boxPadding.bottom());
 | 
				
			||||||
 | 
								}, input->lifetime());
 | 
				
			||||||
 | 
								box->setFocusCallback([=] {
 | 
				
			||||||
 | 
									input->setFocusFast();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const auto save = [=] {
 | 
				
			||||||
 | 
									const auto value = input->getLastText().toInt();
 | 
				
			||||||
 | 
									if (value <= 0) {
 | 
				
			||||||
 | 
										input->showError();
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (guard) {
 | 
				
			||||||
 | 
										state->usageValue = value;
 | 
				
			||||||
 | 
										regenerate();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									box->closeBox();
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								QObject::connect(input, &Ui::NumberInput::submitted, save);
 | 
				
			||||||
 | 
								box->addButton(tr::lng_settings_save(), save);
 | 
				
			||||||
 | 
								box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						regenerate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto &saveLabel = link.isEmpty()
 | 
				
			||||||
 | 
							? tr::lng_formatting_link_create
 | 
				
			||||||
 | 
							: tr::lng_settings_save;
 | 
				
			||||||
 | 
						box->addButton(saveLabel(), [=] {
 | 
				
			||||||
 | 
							const auto expireDate = (state->expireValue == kMaxLimit)
 | 
				
			||||||
 | 
								? 0
 | 
				
			||||||
 | 
								: (state->expireValue < 0)
 | 
				
			||||||
 | 
								? (base::unixtime::now() - state->expireValue)
 | 
				
			||||||
 | 
								: state->expireValue;
 | 
				
			||||||
 | 
							const auto usageLimit = (state->usageValue == kMaxLimit)
 | 
				
			||||||
 | 
								? 0
 | 
				
			||||||
 | 
								: state->usageValue;
 | 
				
			||||||
 | 
							const auto done = [=](const Api::InviteLink &result) {
 | 
				
			||||||
 | 
								box->closeBox();
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							if (link.isEmpty()) {
 | 
				
			||||||
 | 
								peer->session().api().inviteLinks().create(
 | 
				
			||||||
 | 
									peer,
 | 
				
			||||||
 | 
									done,
 | 
				
			||||||
 | 
									expireDate,
 | 
				
			||||||
 | 
									usageLimit);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								peer->session().api().inviteLinks().edit(
 | 
				
			||||||
 | 
									peer,
 | 
				
			||||||
 | 
									link,
 | 
				
			||||||
 | 
									expireDate,
 | 
				
			||||||
 | 
									usageLimit,
 | 
				
			||||||
 | 
									done);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void CreateLinkBox(
 | 
					void CreateLinkBox(
 | 
				
			||||||
| 
						 | 
					@ -380,7 +624,7 @@ class Controller final
 | 
				
			||||||
	, public RowDelegate
 | 
						, public RowDelegate
 | 
				
			||||||
	, public base::has_weak_ptr {
 | 
						, public base::has_weak_ptr {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit Controller(not_null<PeerData*> peer);
 | 
						Controller(not_null<PeerData*> peer, bool revoked);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void prepare() override;
 | 
						void prepare() override;
 | 
				
			||||||
	void rowClicked(not_null<PeerListRow*> row) override;
 | 
						void rowClicked(not_null<PeerListRow*> row) override;
 | 
				
			||||||
| 
						 | 
					@ -406,6 +650,7 @@ private:
 | 
				
			||||||
		not_null<PeerListRow*> row);
 | 
							not_null<PeerListRow*> row);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	not_null<PeerData*> _peer;
 | 
						not_null<PeerData*> _peer;
 | 
				
			||||||
 | 
						bool _revoked = false;
 | 
				
			||||||
	base::unique_qptr<Ui::PopupMenu> _menu;
 | 
						base::unique_qptr<Ui::PopupMenu> _menu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::array<QImage, int(Color::Count)> _icons;
 | 
						std::array<QImage, int(Color::Count)> _icons;
 | 
				
			||||||
| 
						 | 
					@ -413,8 +658,9 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Controller::Controller(not_null<PeerData*> peer)
 | 
					Controller::Controller(not_null<PeerData*> peer, bool revoked)
 | 
				
			||||||
: _peer(peer) {
 | 
					: _peer(peer)
 | 
				
			||||||
 | 
					, _revoked(revoked) {
 | 
				
			||||||
	style::PaletteChanged(
 | 
						style::PaletteChanged(
 | 
				
			||||||
	) | rpl::start_with_next([=] {
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
		for (auto &image : _icons) {
 | 
							for (auto &image : _icons) {
 | 
				
			||||||
| 
						 | 
					@ -559,6 +805,7 @@ void Controller::rowPaintIcon(
 | 
				
			||||||
		auto hq = PainterHighQualityEnabler(p);
 | 
							auto hq = PainterHighQualityEnabler(p);
 | 
				
			||||||
		auto pen = QPen((*bg)->c);
 | 
							auto pen = QPen((*bg)->c);
 | 
				
			||||||
		pen.setWidth(stroke);
 | 
							pen.setWidth(stroke);
 | 
				
			||||||
 | 
							pen.setCapStyle(Qt::RoundCap);
 | 
				
			||||||
		p.setPen(pen);
 | 
							p.setPen(pen);
 | 
				
			||||||
		p.setBrush(Qt::NoBrush);
 | 
							p.setBrush(Qt::NoBrush);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -568,7 +815,7 @@ void Controller::rowPaintIcon(
 | 
				
			||||||
			margins,
 | 
								margins,
 | 
				
			||||||
			margins,
 | 
								margins,
 | 
				
			||||||
			margins,
 | 
								margins,
 | 
				
			||||||
		}), 0, kFullArcLength * progress);
 | 
							}), (kFullArcLength / 4), kFullArcLength * (1. - progress));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -746,6 +993,26 @@ void AddPermanentLinkBlock(
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					not_null<Ui::RpWidget*> AddLinksList(
 | 
				
			||||||
 | 
							not_null<Ui::VerticalLayout*> container,
 | 
				
			||||||
 | 
							not_null<PeerData*> peer,
 | 
				
			||||||
 | 
							bool revoked) {
 | 
				
			||||||
 | 
						const auto delegate = container->lifetime().make_state<
 | 
				
			||||||
 | 
							PeerListContentDelegateSimple
 | 
				
			||||||
 | 
						>();
 | 
				
			||||||
 | 
						const auto controller = container->lifetime().make_state<Controller>(
 | 
				
			||||||
 | 
							peer,
 | 
				
			||||||
 | 
							revoked);
 | 
				
			||||||
 | 
						controller->setStyleOverrides(&st::inviteLinkList);
 | 
				
			||||||
 | 
						const auto content = container->add(object_ptr<PeerListContent>(
 | 
				
			||||||
 | 
							container,
 | 
				
			||||||
 | 
							controller));
 | 
				
			||||||
 | 
						delegate->setContent(content);
 | 
				
			||||||
 | 
						controller->setDelegate(delegate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return content;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ManageInviteLinksBox(
 | 
					void ManageInviteLinksBox(
 | 
				
			||||||
		not_null<Ui::GenericBox*> box,
 | 
							not_null<Ui::GenericBox*> box,
 | 
				
			||||||
		not_null<PeerData*> peer) {
 | 
							not_null<PeerData*> peer) {
 | 
				
			||||||
| 
						 | 
					@ -760,14 +1027,34 @@ void ManageInviteLinksBox(
 | 
				
			||||||
		box->getDelegate()->show(Box(CreateLinkBox, peer));
 | 
							box->getDelegate()->show(Box(CreateLinkBox, peer));
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto delegate = box->lifetime().make_state<
 | 
						const auto list = AddLinksList(container, peer, false);
 | 
				
			||||||
		PeerListContentDelegateSimple
 | 
						const auto dividerAbout = container->add(object_ptr<Ui::SlideWrap<>>(
 | 
				
			||||||
	>();
 | 
					 | 
				
			||||||
	const auto controller = box->lifetime().make_state<Controller>(peer);
 | 
					 | 
				
			||||||
	controller->setStyleOverrides(&st::inviteLinkList);
 | 
					 | 
				
			||||||
	const auto content = container->add(object_ptr<PeerListContent>(
 | 
					 | 
				
			||||||
		container,
 | 
							container,
 | 
				
			||||||
		controller));
 | 
							object_ptr<Ui::DividerLabel>(
 | 
				
			||||||
	delegate->setContent(content);
 | 
								container,
 | 
				
			||||||
	controller->setDelegate(delegate);
 | 
								object_ptr<Ui::FlatLabel>(
 | 
				
			||||||
 | 
									container,
 | 
				
			||||||
 | 
									tr::lng_group_invite_add_about(),
 | 
				
			||||||
 | 
									st::boxDividerLabel),
 | 
				
			||||||
 | 
								st::settingsDividerLabelPadding)));
 | 
				
			||||||
 | 
						const auto divider = container->add(object_ptr<Ui::SlideWrap<>>(
 | 
				
			||||||
 | 
							container,
 | 
				
			||||||
 | 
							object_ptr<Ui::BoxContentDivider>(container)));
 | 
				
			||||||
 | 
						const auto header = container->add(object_ptr<Ui::SlideWrap<>>(
 | 
				
			||||||
 | 
							container,
 | 
				
			||||||
 | 
							object_ptr<Ui::FlatLabel>(
 | 
				
			||||||
 | 
								container,
 | 
				
			||||||
 | 
								tr::lng_group_invite_revoked_title(),
 | 
				
			||||||
 | 
								st::settingsSubsectionTitle),
 | 
				
			||||||
 | 
							st::inviteLinkRevokedTitlePadding));
 | 
				
			||||||
 | 
						const auto revoked = AddLinksList(container, peer, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rpl::combine(
 | 
				
			||||||
 | 
							list->heightValue(),
 | 
				
			||||||
 | 
							revoked->heightValue()
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](int list, int revoked) {
 | 
				
			||||||
 | 
							dividerAbout->toggle(!list, anim::type::instant);
 | 
				
			||||||
 | 
							divider->toggle(list > 0 && revoked > 0, anim::type::instant);
 | 
				
			||||||
 | 
							header->toggle(revoked > 0, anim::type::instant);
 | 
				
			||||||
 | 
						}, header->lifetime());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -117,9 +117,6 @@ private:
 | 
				
			||||||
		rpl::producer<QString> &&text,
 | 
							rpl::producer<QString> &&text,
 | 
				
			||||||
		not_null<const style::FlatLabel*> st);
 | 
							not_null<const style::FlatLabel*> st);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void createInviteLink();
 | 
					 | 
				
			||||||
	void revokeInviteLink(const QString &link);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void fillPrivaciesButtons(
 | 
						void fillPrivaciesButtons(
 | 
				
			||||||
		not_null<Ui::VerticalLayout*> parent,
 | 
							not_null<Ui::VerticalLayout*> parent,
 | 
				
			||||||
		std::optional<Privacy> savedValue = std::nullopt);
 | 
							std::optional<Privacy> savedValue = std::nullopt);
 | 
				
			||||||
| 
						 | 
					@ -185,7 +182,10 @@ void Controller::createContent() {
 | 
				
			||||||
		_wrap.get(),
 | 
							_wrap.get(),
 | 
				
			||||||
		tr::lng_group_invite_manage(),
 | 
							tr::lng_group_invite_manage(),
 | 
				
			||||||
		rpl::single(QString()),
 | 
							rpl::single(QString()),
 | 
				
			||||||
		[=] { Ui::show(Box(ManageInviteLinksBox, _peer)); },
 | 
							[=] { Ui::show(
 | 
				
			||||||
 | 
								Box(ManageInviteLinksBox, _peer),
 | 
				
			||||||
 | 
								Ui::LayerOption::KeepOther);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		st::manageGroupButton,
 | 
							st::manageGroupButton,
 | 
				
			||||||
		&st::infoIconInviteLinks));
 | 
							&st::infoIconInviteLinks));
 | 
				
			||||||
	AddSkip(_wrap.get());
 | 
						AddSkip(_wrap.get());
 | 
				
			||||||
| 
						 | 
					@ -529,31 +529,6 @@ void Controller::showUsernameResult(
 | 
				
			||||||
	_usernameResultTexts.fire(std::move(text));
 | 
						_usernameResultTexts.fire(std::move(text));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Controller::createInviteLink() {
 | 
					 | 
				
			||||||
	const auto callback = crl::guard(this, [=](Fn<void()> &&close) {
 | 
					 | 
				
			||||||
		close();
 | 
					 | 
				
			||||||
		_peer->session().api().inviteLinks().create(_peer->migrateToOrMe());
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	auto box = Box<ConfirmBox>(
 | 
					 | 
				
			||||||
		(_isGroup
 | 
					 | 
				
			||||||
			? tr::lng_group_invite_about
 | 
					 | 
				
			||||||
			: tr::lng_group_invite_about_channel)(tr::now),
 | 
					 | 
				
			||||||
		std::move(callback));
 | 
					 | 
				
			||||||
	Ui::show(std::move(box), Ui::LayerOption::KeepOther);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Controller::revokeInviteLink(const QString &link) {
 | 
					 | 
				
			||||||
	const auto callback = crl::guard(this, [=](Fn<void()> &&close) {
 | 
					 | 
				
			||||||
		close();
 | 
					 | 
				
			||||||
		const auto peer = _peer->migrateToOrMe();
 | 
					 | 
				
			||||||
		peer->session().api().inviteLinks().revoke(peer, link);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	auto box = Box<ConfirmBox>(
 | 
					 | 
				
			||||||
		tr::lng_group_invite_about_new(tr::now),
 | 
					 | 
				
			||||||
		std::move(callback));
 | 
					 | 
				
			||||||
	Ui::show(std::move(box), Ui::LayerOption::KeepOther);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {
 | 
					object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {
 | 
				
			||||||
	Expects(_wrap != nullptr);
 | 
						Expects(_wrap != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -601,14 +601,13 @@ bool CanScheduleUntilOnline(not_null<PeerData*> peer) {
 | 
				
			||||||
		&& (peer->asUser()->onlineTill > 0);
 | 
							&& (peer->asUser()->onlineTill > 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ScheduleBox(
 | 
					ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
 | 
				
			||||||
		not_null<Ui::GenericBox*> box,
 | 
							not_null<Ui::GenericBox*> box,
 | 
				
			||||||
		SendMenu::Type type,
 | 
							rpl::producer<QString> title,
 | 
				
			||||||
		Fn<void(Api::SendOptions)> done,
 | 
							rpl::producer<QString> submit,
 | 
				
			||||||
 | 
							Fn<void(TimeId)> done,
 | 
				
			||||||
		TimeId time) {
 | 
							TimeId time) {
 | 
				
			||||||
	box->setTitle((type == SendMenu::Type::Reminder)
 | 
						box->setTitle(std::move(title));
 | 
				
			||||||
		? tr::lng_remind_title()
 | 
					 | 
				
			||||||
		: tr::lng_schedule_title());
 | 
					 | 
				
			||||||
	box->setWidth(st::boxWideWidth);
 | 
						box->setWidth(st::boxWideWidth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto date = Ui::CreateChild<rpl::variable<QDate>>(
 | 
						const auto date = Ui::CreateChild<rpl::variable<QDate>>(
 | 
				
			||||||
| 
						 | 
					@ -716,46 +715,67 @@ void ScheduleBox(
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	const auto save = [=](bool silent, bool untilOnline = false) {
 | 
						const auto save = [=] {
 | 
				
			||||||
 | 
							if (const auto result = collect()) {
 | 
				
			||||||
 | 
								done(result);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						timeInput->submitRequests(
 | 
				
			||||||
 | 
						) | rpl::start_with_next(
 | 
				
			||||||
 | 
							save,
 | 
				
			||||||
 | 
							timeInput->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto result = ChooseDateTimeBoxDescriptor();
 | 
				
			||||||
 | 
						box->setFocusCallback([=] { timeInput->setFocusFast(); });
 | 
				
			||||||
 | 
						result.submit = box->addButton(std::move(submit), save);
 | 
				
			||||||
 | 
						box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduleBox(
 | 
				
			||||||
 | 
							not_null<Ui::GenericBox*> box,
 | 
				
			||||||
 | 
							SendMenu::Type type,
 | 
				
			||||||
 | 
							Fn<void(Api::SendOptions)> done,
 | 
				
			||||||
 | 
							TimeId time) {
 | 
				
			||||||
 | 
						const auto save = [=](bool silent, TimeId scheduleDate) {
 | 
				
			||||||
 | 
							if (!scheduleDate) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		// Pro tip: Hold Ctrl key to send a silent scheduled message!
 | 
							// Pro tip: Hold Ctrl key to send a silent scheduled message!
 | 
				
			||||||
		auto ctrl =
 | 
							auto ctrl =
 | 
				
			||||||
			(QGuiApplication::keyboardModifiers() == Qt::ControlModifier);
 | 
								(QGuiApplication::keyboardModifiers() == Qt::ControlModifier);
 | 
				
			||||||
		auto result = Api::SendOptions();
 | 
							auto result = Api::SendOptions();
 | 
				
			||||||
		result.silent = silent || ctrl;
 | 
							result.silent = silent || ctrl;
 | 
				
			||||||
		result.scheduled = untilOnline
 | 
							result.scheduled = scheduleDate;
 | 
				
			||||||
			? Data::ScheduledMessages::kScheduledUntilOnlineTimestamp
 | 
							const auto copy = done;
 | 
				
			||||||
			: collect();
 | 
					 | 
				
			||||||
		if (!result.scheduled) {
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		auto copy = done;
 | 
					 | 
				
			||||||
		box->closeBox();
 | 
							box->closeBox();
 | 
				
			||||||
		copy(result);
 | 
							copy(result);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	timeInput->submitRequests(
 | 
						auto descriptor = ChooseDateTimeBox(
 | 
				
			||||||
	) | rpl::start_with_next([=] {
 | 
							box,
 | 
				
			||||||
		save(false);
 | 
							(type == SendMenu::Type::Reminder
 | 
				
			||||||
	}, timeInput->lifetime());
 | 
								? tr::lng_remind_title()
 | 
				
			||||||
 | 
								: tr::lng_schedule_title()),
 | 
				
			||||||
 | 
							tr::lng_schedule_button(),
 | 
				
			||||||
 | 
							[=](TimeId result) { save(false, result); },
 | 
				
			||||||
 | 
							time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	box->setFocusCallback([=] { timeInput->setFocusFast(); });
 | 
					 | 
				
			||||||
	const auto submit = box->addButton(tr::lng_schedule_button(), [=] {
 | 
					 | 
				
			||||||
		save(false);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	SendMenu::SetupMenuAndShortcuts(
 | 
						SendMenu::SetupMenuAndShortcuts(
 | 
				
			||||||
		submit.data(),
 | 
							descriptor.submit.data(),
 | 
				
			||||||
		[=] { return SendMenu::Type::SilentOnly; },
 | 
							[=] { return SendMenu::Type::SilentOnly; },
 | 
				
			||||||
		[=] { save(true); },
 | 
							[=] { save(true, descriptor.collect()); },
 | 
				
			||||||
		nullptr);
 | 
							nullptr);
 | 
				
			||||||
	box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | 
						box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (type == SendMenu::Type::ScheduledToUser) {
 | 
						if (type == SendMenu::Type::ScheduledToUser) {
 | 
				
			||||||
		const auto sendUntilOnline = box->addTopButton(st::infoTopBarMenu);
 | 
							const auto sendUntilOnline = box->addTopButton(st::infoTopBarMenu);
 | 
				
			||||||
 | 
							const auto timestamp
 | 
				
			||||||
 | 
								= Data::ScheduledMessages::kScheduledUntilOnlineTimestamp;
 | 
				
			||||||
		FillSendUntilOnlineMenu(
 | 
							FillSendUntilOnlineMenu(
 | 
				
			||||||
			sendUntilOnline.data(),
 | 
								sendUntilOnline.data(),
 | 
				
			||||||
			[=] { save(false, true); });
 | 
								[=] { save(false, timestamp); });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace HistoryView
 | 
					} // namespace HistoryView
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,19 @@ namespace HistoryView {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[nodiscard]] TimeId DefaultScheduleTime();
 | 
					[[nodiscard]] TimeId DefaultScheduleTime();
 | 
				
			||||||
[[nodiscard]] bool CanScheduleUntilOnline(not_null<PeerData*> peer);
 | 
					[[nodiscard]] bool CanScheduleUntilOnline(not_null<PeerData*> peer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ChooseDateTimeBoxDescriptor {
 | 
				
			||||||
 | 
						QPointer<Ui::RoundButton> submit;
 | 
				
			||||||
 | 
						Fn<TimeId()> collect;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
 | 
				
			||||||
 | 
						not_null<Ui::GenericBox*> box,
 | 
				
			||||||
 | 
						rpl::producer<QString> title,
 | 
				
			||||||
 | 
						rpl::producer<QString> submit,
 | 
				
			||||||
 | 
						Fn<void(TimeId)> done,
 | 
				
			||||||
 | 
						TimeId time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ScheduleBox(
 | 
					void ScheduleBox(
 | 
				
			||||||
	not_null<Ui::GenericBox*> box,
 | 
						not_null<Ui::GenericBox*> box,
 | 
				
			||||||
	SendMenu::Type type,
 | 
						SendMenu::Type type,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -917,3 +917,5 @@ inviteLinkIconSkip: 7px;
 | 
				
			||||||
inviteLinkIconStroke: 2px;
 | 
					inviteLinkIconStroke: 2px;
 | 
				
			||||||
inviteLinkIcon: icon {{ "info/edit/links_link", mediaviewFileExtFg }};
 | 
					inviteLinkIcon: icon {{ "info/edit/links_link", mediaviewFileExtFg }};
 | 
				
			||||||
inviteLinkThreeDotsSkip: 8px;
 | 
					inviteLinkThreeDotsSkip: 8px;
 | 
				
			||||||
 | 
					inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 9px);
 | 
				
			||||||
 | 
					inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1 +1 @@
 | 
				
			||||||
Subproject commit 912f0b48a648e7f12e7bcba82a0f0ec326aab8dd
 | 
					Subproject commit a5fb99372184d5f3c00e5e851aaa16d8d28d2ce1
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue