Added support of suggestion to validate cloud password to settings.

This commit is contained in:
23rd 2025-05-28 13:33:09 +03:00
parent 727acca217
commit 1ae3122c20
12 changed files with 324 additions and 30 deletions

View file

@ -1446,6 +1446,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_start.h
settings/cloud_password/settings_cloud_password_step.cpp
settings/cloud_password/settings_cloud_password_step.h
settings/cloud_password/settings_cloud_password_validate_icon.cpp
settings/cloud_password/settings_cloud_password_validate_icon.h
settings/settings_active_sessions.cpp
settings/settings_active_sessions.h
settings/settings_advanced.cpp

View file

@ -840,6 +840,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}";
"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do";
"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}";
"lng_settings_suggestion_password_title" = "Your password";
"lng_settings_suggestion_password_about" = "Your account is protected by 2-Step Veritifaction. Do you still remember your password?";
"lng_settings_suggestion_password_yes" = "Yes, definitely";
"lng_settings_suggestion_password_no" = "Not sure";
"lng_settings_suggestion_password_step_input_title" = "Enter your password";
"lng_settings_suggestion_password_step_input_about" = "Do you still remember your password?";
"lng_settings_suggestion_password_step_finish_title" = "Perfect!";
"lng_settings_suggestion_password_step_finish_about" = "You still remember your password.";
"lng_settings_power_menu" = "Battery and Animations";
"lng_settings_power_title" = "Power Usage";

View file

@ -9,6 +9,7 @@
<file alias="cloud_password/password_input.tgs">../../animations/cloud_password/password_input.tgs</file>
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
<file alias="cloud_password/validate.tgs">../../animations/cloud_password/validate.tgs</file>
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file>

View file

@ -306,4 +306,9 @@ std::optional<UserIds> PromoSuggestions::knownBirthdaysToday() const {
return _contactBirthdaysToday;
}
QString PromoSuggestions::SugValidatePassword() {
static const auto key = u"VALIDATE_PASSWORD"_q;
return key;
}
} // namespace Data

View file

@ -51,6 +51,8 @@ public:
[[nodiscard]] auto knownBirthdaysToday() const
-> std::optional<std::vector<UserId>>;
[[nodiscard]] static QString SugValidatePassword();
private:
void setTopPromoted(
History *promoted,

View file

@ -34,6 +34,7 @@ struct StepData {
QString email;
int unconfirmedEmailLengthCode;
bool setOnlyRecoveryEmail = false;
bool suggestionValidate = false;
struct ProcessRecover {
bool setNewPassword = false;

View file

@ -12,20 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/unixtime.h"
#include "core/core_cloud_password.h"
#include "data/components/promo_suggestions.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_hint.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "settings/cloud_password/settings_cloud_password_validate_icon.h"
#include "ui/boxes/boost_box.h" // Ui::StartFireworks.
#include "ui/boxes/confirm_box.h"
#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/password_input.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
@ -53,6 +59,10 @@ RecreateResetPassword:
Continue to RecreateResetHint.
Clear password and Back to Settings.
Back to Settings.
ValidatePassword:
- Submit to show good validate.
- Back to Main Settings.
*/
namespace Settings {
@ -72,9 +82,7 @@ Icon CreateInteractiveLottieIcon(
const auto raw = object.data();
const auto width = descriptor.sizeOverride.width();
raw->resize(QRect(
QPoint(),
descriptor.sizeOverride).marginsAdded(padding).size());
raw->resize((Rect(descriptor.sizeOverride) + padding).size());
auto owned = Lottie::MakeIcon(std::move(descriptor));
const auto icon = owned.get();
@ -118,7 +126,10 @@ public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
not_null<QWidget*> parent) override;
void setupContent();
void setupValidateGood();
protected:
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
@ -130,6 +141,8 @@ private:
not_null<Ui::FlatLabel*> info,
Fn<void()> recoverCallback);
QWidget *_parent = nullptr;
rpl::variable<std::vector<Type>> _removesFromStack;
rpl::lifetime _requestLifetime;
@ -143,12 +156,58 @@ rpl::producer<QString> Input::title() {
return tr::lng_settings_cloud_password_password_title();
}
QPointer<Ui::RpWidget> Input::createPinnedToTop(
not_null<QWidget*> parent) {
_parent = parent;
return nullptr;
}
void Input::setupValidateGood() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
if (_parent) {
Ui::StartFireworks(_parent);
}
if (auto owned = CreateValidateGoodIcon(&controller()->session())) {
owned->setParent(content);
content->add(
object_ptr<Ui::CenterWrap<>>(
content,
std::move(owned)),
QMargins(0, st::lineWidth * 75, 0, 0));
}
SetupHeader(
content,
QString(),
rpl::never<>(),
tr::lng_settings_suggestion_password_step_finish_title(),
tr::lng_settings_suggestion_password_step_finish_about());
const auto button = AddDoneButton(content, tr::lng_share_done());
button->setClickedCallback([=] {
showBack();
});
Ui::ToggleChildrenVisibility(this, true);
Ui::ResizeFitChild(this, content);
content->resizeToWidth(width());
Ui::SendPendingMoveResizeEvents(content);
}
void Input::setupContent() {
if (QWidget::children().count() > 0) {
return;
}
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto currentStepDataPassword = base::take(currentStepData.password);
const auto currentStepProcessRecover = base::take(
currentStepData.processRecover);
const auto currentStepValidate = base::take(
currentStepData.suggestionValidate);
setStepData(currentStepData);
const auto currentState = cloudPassword().stateCurrent();
@ -167,11 +226,10 @@ void Input::setupContent() {
const auto icon = CreateInteractiveLottieIcon(
content,
{
.name = u"cloud_password/password_input"_q,
.sizeOverride = {
st::settingsCloudPasswordIconSize,
st::settingsCloudPasswordIconSize
},
.name = currentStepValidate
? u"cloud_password/validate"_q
: u"cloud_password/password_input"_q,
.sizeOverride = Size(st::settingsCloudPasswordIconSize),
},
st::settingLocalPasscodeIconPadding);
@ -179,12 +237,16 @@ void Input::setupContent() {
content,
QString(),
rpl::never<>(),
isCheck
currentStepValidate
? tr::lng_settings_suggestion_password_step_input_title()
: isCheck
? tr::lng_settings_cloud_password_check_subtitle()
: hasPassword
? tr::lng_settings_cloud_password_manage_password_change()
: tr::lng_settings_cloud_password_password_subtitle(),
isCheck
currentStepValidate
? tr::lng_settings_suggestion_password_step_input_about()
: isCheck
? tr::lng_settings_cloud_password_manage_about1()
: tr::lng_cloud_password_about());
@ -340,7 +402,9 @@ void Input::setupContent() {
Ui::AddSkip(content);
}
if (!newInput->text().isEmpty()) {
if (currentStepValidate) {
icon.icon->animate(icon.update, 0, icon.icon->framesCount() - 1);
} else if (!newInput->text().isEmpty()) {
icon.icon->jumpTo(icon.icon->framesCount() / 2, icon.update);
}
@ -376,10 +440,18 @@ void Input::setupContent() {
}
}
if (currentStepValidate) {
controller()->session().promoSuggestions().dismiss(
Data::PromoSuggestions::SugValidatePassword());
setupValidateGood();
delete content;
} else {
auto data = stepData();
data.currentPassword = pass;
setStepData(std::move(data));
showOther(CloudPasswordManageId());
}
});
};
@ -412,6 +484,7 @@ void Input::setupContent() {
}
});
if (!currentStepValidate) {
base::qt_signal_producer(
newInput.get(),
&QLineEdit::textChanged // Covers Undo.
@ -423,6 +496,7 @@ void Input::setupContent() {
const auto to = empty ? 0 : (icon.icon->framesCount() / 2 - 1);
icon.icon->animate(icon.update, from, to);
}, content->lifetime());
}
const auto submit = [=] {
if (!reenterInput || reenterInput->hasFocus()) {
@ -437,7 +511,7 @@ void Input::setupContent() {
QObject::connect(reenterInput, &MaskedInputField::submitted, submit);
}
setFocusCallback([=] {
setFocusCallback(crl::guard(content, [=] {
if (isCheck || newInput->text().isEmpty()) {
newInput->setFocus();
} else if (reenterInput->text().isEmpty()) {
@ -445,7 +519,7 @@ void Input::setupContent() {
} else {
newInput->setFocus();
}
});
}));
Ui::ResizeFitChild(this, content);
}
@ -586,10 +660,33 @@ void Input::setupRecoverButton(
});
}
class SuggestionInput : public Input {
public:
SuggestionInput(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Input(parent, controller)
, _stepData(StepData{ .suggestionValidate = true }) {
setStepDataReference(_stepData);
}
[[nodiscard]] static Type Id() {
return SectionFactory<SuggestionInput>::Instance();
}
private:
std::any _stepData;
};
} // namespace CloudPassword
Type CloudPasswordInputId() {
return CloudPassword::Input::Id();
}
Type CloudPasswordSuggestionInputId() {
return CloudPassword::SuggestionInput::Id();
}
} // namespace Settings

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Settings {
Type CloudPasswordInputId();
Type CloudPasswordSuggestionInputId();
} // namespace Settings

View file

@ -0,0 +1,74 @@
/*
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 "settings/cloud_password/settings_cloud_password_validate_icon.h"
#include "apiwrap.h"
#include "base/object_ptr.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "main/main_session.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "styles/style_settings.h"
namespace Settings {
namespace {
[[nodiscard]] DocumentData *EmojiValidateGood(
not_null<Main::Session*> session) {
auto emoji = TextWithEntities{
.text = (QString(QChar(0xD83D)) + QChar(0xDC4D)),
};
if (const auto e = Ui::Emoji::Find(emoji.text)) {
const auto sticker = session->emojiStickersPack().stickerForEmoji(e);
return sticker.document;
}
return nullptr;
}
} // namespace
object_ptr<Ui::RpWidget> CreateValidateGoodIcon(
not_null<Main::Session*> session) {
const auto document = EmojiValidateGood(session);
if (!document) {
return nullptr;
}
auto owned = object_ptr<Ui::RpWidget>((QWidget*)nullptr);
const auto widget = owned.data();
struct State {
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
};
const auto state = widget->lifetime().make_state<State>();
const auto size = st::settingsCloudPasswordIconSize;
state->emoji = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
session->data().customEmojiManager().create(
document,
[=] { widget->update(); },
Data::CustomEmojiManager::SizeTag::Normal,
size),
1,
true);
widget->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(widget);
state->emoji->paint(p, Ui::Text::CustomEmojiPaintContext{
.textColor = st::windowFg->c,
.now = crl::now(),
});
}, widget->lifetime());
const auto padding = st::settingLocalPasscodeIconPadding;
widget->resize((Rect(Size(size)) + padding).size());
return owned;
}
} // namespace Settings

View file

@ -0,0 +1,27 @@
/*
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
template <typename Object>
class object_ptr;
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Settings {
[[nodiscard]] object_ptr<Ui::RpWidget> CreateValidateGoodIcon(
not_null<Main::Session*> session);
} // namespace Settings

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/settings_main.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "api/api_credits.h"
#include "core/application.h"
#include "core/click_handler_types.h"
@ -522,6 +523,77 @@ void SetupValidatePhoneNumberSuggestion(
Ui::AddSkip(content);
}
void SetupValidatePasswordSuggestion(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> showOther) {
if (!controller->session().promoSuggestions().current(
Data::PromoSuggestions::SugValidatePassword())
|| controller->session().promoSuggestions().current(
kSugValidatePhone.utf8())) {
return;
}
const auto mainWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto content = mainWrap->entity();
Ui::AddSubsectionTitle(
content,
tr::lng_settings_suggestion_password_title(),
QMargins(
st::boxRowPadding.left()
- st::defaultSubsectionTitlePadding.left(),
0,
0,
0));
const auto label = content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_settings_suggestion_password_about(),
st::boxLabel),
st::boxRowPadding);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto wrap = content->add(
object_ptr<Ui::FixedHeightWidget>(
content,
st::inviteLinkButton.height),
st::inviteLinkButtonsPadding);
const auto yes = Ui::CreateChild<Ui::RoundButton>(
wrap,
tr::lng_settings_suggestion_password_yes(),
st::inviteLinkButton);
yes->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
yes->setClickedCallback([=] {
controller->session().promoSuggestions().dismiss(
Data::PromoSuggestions::SugValidatePassword());
mainWrap->toggle(false, anim::type::normal);
});
const auto no = Ui::CreateChild<Ui::RoundButton>(
wrap,
tr::lng_settings_suggestion_password_no(),
st::inviteLinkButton);
no->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
no->setClickedCallback([=] {
showOther(Settings::CloudPasswordSuggestionInputId());
});
wrap->widthValue() | rpl::start_with_next([=](int width) {
const auto buttonWidth = (width - st::inviteLinkButtonsSkip) / 2;
yes->setFullWidth(buttonWidth);
no->setFullWidth(buttonWidth);
yes->moveToLeft(0, 0, width);
no->moveToRight(0, 0, width);
}, wrap->lifetime());
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
}
void SetupSections(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
@ -533,6 +605,10 @@ void SetupSections(
controller,
container,
showOther);
SetupValidatePasswordSuggestion(
controller,
container,
showOther);
const auto addSection = [&](
rpl::producer<QString> label,