Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5bc567eb8 | ||
|
|
5f91a97778 | ||
|
|
63aa3576d8 | ||
|
|
7749794d6b | ||
|
|
368468447f | ||
|
|
54fa16424d | ||
|
|
fc7f1991dd | ||
|
|
910d0a7e47 | ||
|
|
6e8fb2ec06 | ||
|
|
94d943f3af | ||
|
|
41e6e32962 | ||
|
|
2f58a7d3c4 | ||
|
|
e364b80d0a | ||
|
|
f3efa85206 | ||
|
|
42f2f3c99f | ||
|
|
a6d0367d71 | ||
|
|
bcbf009a62 | ||
|
|
fedd8bece3 | ||
|
|
031525e7e3 | ||
|
|
4d5cb6398e | ||
|
|
3ef352b63c | ||
|
|
9809c12fb8 | ||
|
|
3e4ac35913 | ||
|
|
a72953411b | ||
|
|
3967e58627 | ||
|
|
db46f84f2c | ||
|
|
9a57347973 | ||
|
|
c9c988e5f4 | ||
|
|
5e2acdeaa3 | ||
|
|
5a4d1a1e85 | ||
|
|
fbd9437775 | ||
|
|
2eaa17b938 | ||
|
|
01eacadca5 | ||
|
|
9f155e0053 | ||
|
|
2f9dc6ca2a | ||
|
|
3d6f6cdd8f | ||
|
|
7e3c54f8d0 | ||
|
|
540ee0bc44 | ||
|
|
627170520a | ||
|
|
77d50d9177 | ||
|
|
c47140c62d | ||
|
|
546dfb08ef | ||
|
|
376b592e5a | ||
|
|
c27db754a7 | ||
|
|
5fb71cb165 | ||
|
|
972666440e | ||
|
|
549d7c77e5 | ||
|
|
de3b30ed7d | ||
|
|
a4f5e3f411 | ||
|
|
df19b62e92 | ||
|
|
caf2be13b3 |
@@ -1072,6 +1072,20 @@ PRIVATE
|
||||
profile/profile_block_widget.h
|
||||
profile/profile_cover_drop_area.cpp
|
||||
profile/profile_cover_drop_area.h
|
||||
settings/cloud_password/settings_cloud_password_common.cpp
|
||||
settings/cloud_password/settings_cloud_password_common.h
|
||||
settings/cloud_password/settings_cloud_password_email.cpp
|
||||
settings/cloud_password/settings_cloud_password_email.h
|
||||
settings/cloud_password/settings_cloud_password_email_confirm.cpp
|
||||
settings/cloud_password/settings_cloud_password_email_confirm.h
|
||||
settings/cloud_password/settings_cloud_password_hint.cpp
|
||||
settings/cloud_password/settings_cloud_password_hint.h
|
||||
settings/cloud_password/settings_cloud_password_input.cpp
|
||||
settings/cloud_password/settings_cloud_password_input.h
|
||||
settings/cloud_password/settings_cloud_password_manage.cpp
|
||||
settings/cloud_password/settings_cloud_password_manage.h
|
||||
settings/cloud_password/settings_cloud_password_start.cpp
|
||||
settings/cloud_password/settings_cloud_password_start.h
|
||||
settings/settings_advanced.cpp
|
||||
settings/settings_advanced.h
|
||||
settings/settings_blocked_peers.cpp
|
||||
@@ -1092,6 +1106,8 @@ PRIVATE
|
||||
settings/settings_information.h
|
||||
settings/settings_intro.cpp
|
||||
settings/settings_intro.h
|
||||
settings/settings_local_passcode.cpp
|
||||
settings/settings_local_passcode.h
|
||||
settings/settings_main.cpp
|
||||
settings/settings_main.h
|
||||
settings/settings_notifications.cpp
|
||||
|
||||
BIN
Telegram/Resources/animations/cloud_password/email.tgs
Normal file
BIN
Telegram/Resources/animations/cloud_password/email.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/cloud_password/hint.tgs
Normal file
BIN
Telegram/Resources/animations/cloud_password/hint.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/cloud_password/intro.tgs
Normal file
BIN
Telegram/Resources/animations/cloud_password/intro.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/cloud_password/password_input.tgs
Normal file
BIN
Telegram/Resources/animations/cloud_password/password_input.tgs
Normal file
Binary file not shown.
Binary file not shown.
BIN
Telegram/Resources/animations/local_passcode_enter.tgs
Normal file
BIN
Telegram/Resources/animations/local_passcode_enter.tgs
Normal file
Binary file not shown.
@@ -490,8 +490,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_forwards_privacy" = "Forwarded messages";
|
||||
"lng_settings_profile_photo_privacy" = "Profile photo";
|
||||
"lng_settings_sessions_about" = "Control your sessions on other devices.";
|
||||
"lng_settings_passcode_disable" = "Disable passcode";
|
||||
"lng_settings_password_disable" = "Disable cloud password";
|
||||
"lng_settings_passcode_disable" = "Disable Passcode";
|
||||
"lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?";
|
||||
"lng_settings_password_disable" = "Disable Cloud Password";
|
||||
"lng_settings_password_abort" = "Abort two-step verification setup";
|
||||
"lng_settings_password_reenter_email" = "Re-enter recovery email";
|
||||
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
|
||||
@@ -540,6 +541,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_security_bots" = "Bots and websites";
|
||||
"lng_settings_clear_payment_info" = "Clear Payment and Shipping Info";
|
||||
|
||||
"lng_settings_cloud_password_on" = "On";
|
||||
"lng_settings_cloud_password_off" = "Off";
|
||||
"lng_settings_cloud_password_start_title" = "Two-Step Verification";
|
||||
"lng_settings_cloud_password_password_title" = "Password";
|
||||
"lng_settings_cloud_password_hint_title" = "Password Hint";
|
||||
"lng_settings_cloud_password_email_title" = "Recovery Email";
|
||||
"lng_settings_cloud_password_start_about" = "Protect your Telegram account with an additional password.";
|
||||
"lng_settings_cloud_password_hint_about" = "You can create a hint for your password.";
|
||||
"lng_settings_cloud_password_email_about" = "Please enter your new recovery email. It is the only way to recover a forgotten password.";
|
||||
"lng_settings_cloud_password_password_subtitle" = "Create Password";
|
||||
"lng_settings_cloud_password_check_subtitle" = "Your Password";
|
||||
"lng_settings_cloud_password_hint_subtitle" = "Add Password Hint";
|
||||
"lng_settings_cloud_password_email_subtitle" = "Add Recovery Email";
|
||||
"lng_settings_cloud_password_email_recovery_subtitle" = "Password Recovery";
|
||||
"lng_settings_cloud_password_manage_about1" = "You have Two-Step Verification enabled, so your account is protected with an additional password.";
|
||||
"lng_settings_cloud_password_manage_about2" = "This email is the only way to recover a forgotten password.";
|
||||
"lng_settings_cloud_password_manage_disable_sure" = "Are you sure you want to disable your password?";
|
||||
"lng_settings_cloud_password_manage_email_new" = "Set Recovery Email";
|
||||
"lng_settings_cloud_password_manage_email_change" = "Change Recovery Email";
|
||||
"lng_settings_cloud_password_manage_password_change" = "Change Password";
|
||||
"lng_settings_cloud_password_skip_hint" = "Skip hint";
|
||||
"lng_settings_cloud_password_save" = "Save and Finish";
|
||||
"lng_settings_cloud_password_email_confirm" = "Confirm and Finish";
|
||||
"lng_settings_cloud_password_reset_in" = "You can reset your password in {duration}.";
|
||||
|
||||
"lng_clear_payment_info_title" = "Clear payment info";
|
||||
"lng_clear_payment_info_sure" = "Are you sure you want to clear your payment and shipping info?";
|
||||
"lng_clear_payment_info_shipping" = "Shipping info";
|
||||
@@ -634,7 +660,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_passcode_remove_button" = "Remove";
|
||||
|
||||
"lng_passcode_turn_on" = "Turn on local passcode";
|
||||
"lng_passcode_change" = "Change local passcode";
|
||||
"lng_passcode_change" = "Change Passcode";
|
||||
"lng_passcode_create" = "Local passcode";
|
||||
"lng_passcode_remove" = "Remove local passcode";
|
||||
"lng_passcode_turn_off" = "Turn off";
|
||||
@@ -647,6 +673,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_passcode_enter_new" = "Enter new passcode";
|
||||
"lng_passcode_confirm_new" = "Re-enter new passcode";
|
||||
"lng_passcode_about" = "When a local passcode is set, a lock icon appears at the top of your chats list. Click it to lock the app.\n\nNote: if you forget your local passcode, you'll need to relogin in Telegram Desktop.";
|
||||
"lng_passcode_about1" = "When a local passcode is set, a lock icon appears at the top of your chats list.";
|
||||
"lng_passcode_about2" = "Click it to lock Telegram Desktop.";
|
||||
"lng_passcode_about3" = "Note: if you forget your passcode, you'll need to log out of Telegram Desktop and log in again.";
|
||||
"lng_passcode_differ" = "Passcodes are different";
|
||||
"lng_passcode_wrong" = "Wrong passcode";
|
||||
"lng_passcode_is_same" = "Passcode was not changed";
|
||||
@@ -655,6 +684,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_passcode_submit" = "Submit";
|
||||
"lng_passcode_logout" = "Log out";
|
||||
"lng_passcode_need_unblock" = "You need to unlock me first.";
|
||||
"lng_passcode_create_button" = "Save Passcode";
|
||||
"lng_passcode_check_button" = "Submit";
|
||||
"lng_passcode_change_button" = "Save Passcode";
|
||||
"lng_passcode_create_title" = "Create Local Passcode";
|
||||
"lng_passcode_check_title" = "Enter Passcode";
|
||||
"lng_passcode_change_title" = "Enter Passcode";
|
||||
|
||||
"lng_cloud_password_waiting_code" = "Confirmation code sent to {email}...";
|
||||
"lng_cloud_password_confirm" = "Confirm recovery email";
|
||||
@@ -668,12 +703,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_cloud_password_reset_cancel" = "Cancel password reset";
|
||||
"lng_cloud_password_enter_old" = "Enter current password";
|
||||
"lng_cloud_password_enter_first" = "Enter a password";
|
||||
"lng_cloud_password_enter_new" = "Enter new password";
|
||||
"lng_cloud_password_confirm_new" = "Re-enter new password";
|
||||
"lng_cloud_password_hint" = "Enter password hint";
|
||||
"lng_cloud_password_enter_new" = "Enter password";
|
||||
"lng_cloud_password_confirm_new" = "Re-enter password";
|
||||
"lng_cloud_password_hint" = "Enter Password Hint";
|
||||
"lng_cloud_password_change_hint" = "Enter new password hint";
|
||||
"lng_cloud_password_bad" = "Password and hint cannot be the same.";
|
||||
"lng_cloud_password_email" = "Enter recovery email";
|
||||
"lng_cloud_password_email" = "Enter Email";
|
||||
"lng_cloud_password_bad_email" = "Incorrect email, please try other.";
|
||||
"lng_cloud_password_about" = "This password will be asked when you log in on a new device in addition to the SMS code.";
|
||||
"lng_cloud_password_about_recover" = "Warning! Are you sure you don't want to\nadd a password recovery email?\n\nIf you forget your password, you will\nlose access to your Telegram account.";
|
||||
@@ -695,6 +730,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_cloud_password_reset_cancel_title" = "Cancel reset";
|
||||
"lng_cloud_password_reset_cancel_sure" = "Cancel the password reset process? If you request a new reset later, it will take another 7 days.";
|
||||
"lng_cloud_password_reset_later" = "You recently requested a password reset that was cancelled. Please wait {duration} before making a new request.";
|
||||
"lng_cloud_password_expired" = "Please re-enter your password.";
|
||||
|
||||
"lng_connection_auto_connecting" = "Default (connecting...)";
|
||||
"lng_connection_auto" = "Default ({transport} used)";
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<RCC>
|
||||
<qresource prefix="/animations">
|
||||
<file alias="change_number.tgs">../../animations/change_number.tgs</file>
|
||||
</qresource>
|
||||
<qresource prefix="/animations">
|
||||
<file alias="blocked_peers_empty.tgs">../../animations/blocked_peers_empty.tgs</file>
|
||||
</qresource>
|
||||
<qresource prefix="/animations">
|
||||
<file alias="filters.tgs">../../animations/filters.tgs</file>
|
||||
<file alias="local_passcode_enter.tgs">../../animations/local_passcode_enter.tgs</file>
|
||||
<file alias="cloud_password/intro.tgs">../../animations/cloud_password/intro.tgs</file>
|
||||
<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>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -125,7 +125,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
|
||||
chatEmpty#29562865 id:long = Chat;
|
||||
chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
|
||||
chatForbidden#6592a1a7 id:long title:string = Chat;
|
||||
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
|
||||
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
|
||||
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
|
||||
chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?Vector<string> = ChatFull;
|
||||
@@ -414,9 +414,9 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
|
||||
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
|
||||
upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File;
|
||||
|
||||
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
|
||||
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
|
||||
|
||||
config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
|
||||
config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
|
||||
|
||||
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
|
||||
|
||||
@@ -874,7 +874,7 @@ phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long d
|
||||
phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
|
||||
phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
|
||||
|
||||
phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
|
||||
phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
|
||||
phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection;
|
||||
|
||||
phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector<string> = PhoneCallProtocol;
|
||||
@@ -1227,7 +1227,7 @@ messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> use
|
||||
|
||||
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
|
||||
|
||||
messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
|
||||
messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
|
||||
|
||||
messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
|
||||
|
||||
@@ -1295,7 +1295,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
|
||||
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
|
||||
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
|
||||
|
||||
sponsoredMessage#3a836df8 flags:# random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
|
||||
sponsoredMessage#3a836df8 flags:# recommended:flags.5?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
|
||||
|
||||
messages.sponsoredMessages#65a4c7d5 messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
|
||||
|
||||
@@ -1807,6 +1807,7 @@ phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = U
|
||||
phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates;
|
||||
phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels;
|
||||
phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl;
|
||||
phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool;
|
||||
|
||||
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
|
||||
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
|
||||
@@ -1823,4 +1824,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 140
|
||||
// LAYER 142
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.7.4.0" />
|
||||
Version="3.7.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,7,4,0
|
||||
PRODUCTVERSION 3,7,4,0
|
||||
FILEVERSION 3,7,5,0
|
||||
PRODUCTVERSION 3,7,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.7.4.0"
|
||||
VALUE "FileVersion", "3.7.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.7.4.0"
|
||||
VALUE "ProductVersion", "3.7.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,7,4,0
|
||||
PRODUCTVERSION 3,7,4,0
|
||||
FILEVERSION 3,7,5,0
|
||||
PRODUCTVERSION 3,7,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.7.4.0"
|
||||
VALUE "FileVersion", "3.7.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.7.4.0"
|
||||
VALUE "ProductVersion", "3.7.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -7,18 +7,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_cloud_password.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
// #TODO Add ability to set recovery email separately.
|
||||
[[nodiscard]] Core::CloudPasswordState ProcessMtpState(
|
||||
const MTPaccount_password &state) {
|
||||
return state.match([&](const MTPDaccount_password &data) {
|
||||
base::RandomAddSeed(bytes::make_span(data.vsecure_random().v));
|
||||
return Core::ParseCloudPasswordState(data);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CloudPassword::CloudPassword(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void CloudPassword::apply(Core::CloudPasswordState state) {
|
||||
if (_state) {
|
||||
*_state = std::move(state);
|
||||
} else {
|
||||
_state = std::make_unique<Core::CloudPasswordState>(std::move(state));
|
||||
}
|
||||
_stateChanges.fire_copy(*_state);
|
||||
}
|
||||
|
||||
void CloudPassword::reload() {
|
||||
if (_requestId) {
|
||||
return;
|
||||
@@ -26,16 +45,7 @@ void CloudPassword::reload() {
|
||||
_requestId = _api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
_requestId = 0;
|
||||
result.match([&](const MTPDaccount_password &data) {
|
||||
base::RandomAddSeed(bytes::make_span(data.vsecure_random().v));
|
||||
if (_state) {
|
||||
*_state = Core::ParseCloudPasswordState(data);
|
||||
} else {
|
||||
_state = std::make_unique<Core::CloudPasswordState>(
|
||||
Core::ParseCloudPasswordState(data));
|
||||
}
|
||||
_stateChanges.fire_copy(*_state);
|
||||
});
|
||||
apply(ProcessMtpState(result));
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
@@ -109,4 +119,426 @@ auto CloudPassword::cancelResetPassword()
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<CloudPassword::SetOk, QString> CloudPassword::set(
|
||||
const QString &oldPassword,
|
||||
const QString &newPassword,
|
||||
const QString &hint,
|
||||
bool hasRecoveryEmail,
|
||||
const QString &recoveryEmail) {
|
||||
|
||||
const auto generatePasswordCheck = [=](
|
||||
const Core::CloudPasswordState &latestState) {
|
||||
if (oldPassword.isEmpty() || !latestState.hasPassword) {
|
||||
return Core::CloudPasswordResult{
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
};
|
||||
}
|
||||
const auto hash = Core::ComputeCloudPasswordHash(
|
||||
latestState.mtp.request.algo,
|
||||
bytes::make_span(oldPassword.toUtf8()));
|
||||
return Core::ComputeCloudPasswordCheck(
|
||||
latestState.mtp.request,
|
||||
hash);
|
||||
};
|
||||
|
||||
const auto finish = [=](auto consumer, int unconfirmedEmailLengthCode) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
apply(ProcessMtpState(result));
|
||||
if (unconfirmedEmailLengthCode) {
|
||||
consumer.put_next(SetOk{ unconfirmedEmailLengthCode });
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).handleFloodErrors().send();
|
||||
};
|
||||
|
||||
const auto sendMTPaccountUpdatePasswordSettings = [=](
|
||||
const Core::CloudPasswordState &latestState,
|
||||
const QByteArray &secureSecret,
|
||||
auto consumer) {
|
||||
const auto newPasswordBytes = newPassword.toUtf8();
|
||||
const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
|
||||
latestState.mtp.newPassword,
|
||||
bytes::make_span(newPasswordBytes));
|
||||
if (!newPassword.isEmpty() && newPasswordHash.modpow.empty()) {
|
||||
consumer.put_error("INTERNAL_SERVER_ERROR");
|
||||
return;
|
||||
}
|
||||
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
||||
const auto flags = Flag::f_new_algo
|
||||
| Flag::f_new_password_hash
|
||||
| Flag::f_hint
|
||||
| (secureSecret.isEmpty() ? Flag(0) : Flag::f_new_secure_settings)
|
||||
| ((!hasRecoveryEmail) ? Flag(0) : Flag::f_email);
|
||||
|
||||
auto newSecureSecret = bytes::vector();
|
||||
auto newSecureSecretId = 0ULL;
|
||||
if (!secureSecret.isEmpty()) {
|
||||
newSecureSecretId = Passport::CountSecureSecretId(
|
||||
bytes::make_span(secureSecret));
|
||||
newSecureSecret = Passport::EncryptSecureSecret(
|
||||
bytes::make_span(secureSecret),
|
||||
Core::ComputeSecureSecretHash(
|
||||
latestState.mtp.newSecureSecret,
|
||||
bytes::make_span(newPasswordBytes)));
|
||||
}
|
||||
const auto settings = MTP_account_passwordInputSettings(
|
||||
MTP_flags(flags),
|
||||
Core::PrepareCloudPasswordAlgo(newPassword.isEmpty()
|
||||
? v::null
|
||||
: latestState.mtp.newPassword),
|
||||
newPassword.isEmpty()
|
||||
? MTP_bytes()
|
||||
: MTP_bytes(newPasswordHash.modpow),
|
||||
MTP_string(hint),
|
||||
MTP_string(recoveryEmail),
|
||||
MTP_secureSecretSettings(
|
||||
Core::PrepareSecureSecretAlgo(
|
||||
latestState.mtp.newSecureSecret),
|
||||
MTP_bytes(newSecureSecret),
|
||||
MTP_long(newSecureSecretId)));
|
||||
_api.request(MTPaccount_UpdatePasswordSettings(
|
||||
generatePasswordCheck(latestState).result,
|
||||
settings
|
||||
)).done([=] {
|
||||
finish(consumer, 0);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
const auto prefix = u"EMAIL_UNCONFIRMED_"_q;
|
||||
if (type.startsWith(prefix)) {
|
||||
const auto codeLength = base::StringViewMid(
|
||||
type,
|
||||
prefix.size()).toInt();
|
||||
|
||||
finish(consumer, codeLength);
|
||||
} else {
|
||||
consumer.put_error_copy(type);
|
||||
}
|
||||
}).handleFloodErrors().send();
|
||||
};
|
||||
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
const auto latestState = ProcessMtpState(result);
|
||||
|
||||
if (latestState.hasPassword
|
||||
&& !oldPassword.isEmpty()
|
||||
&& !newPassword.isEmpty()) {
|
||||
|
||||
_api.request(MTPaccount_GetPasswordSettings(
|
||||
generatePasswordCheck(latestState).result
|
||||
)).done([=](const MTPaccount_PasswordSettings &result) {
|
||||
using Settings = MTPDaccount_passwordSettings;
|
||||
const auto &data = result.match([&](
|
||||
const Settings &data) -> const Settings & {
|
||||
return data;
|
||||
});
|
||||
auto secureSecret = QByteArray();
|
||||
if (const auto wrapped = data.vsecure_settings()) {
|
||||
using Secure = MTPDsecureSecretSettings;
|
||||
const auto &settings = wrapped->match([](
|
||||
const Secure &data) -> const Secure & {
|
||||
return data;
|
||||
});
|
||||
const auto passwordUtf = oldPassword.toUtf8();
|
||||
const auto secret = Passport::DecryptSecureSecret(
|
||||
bytes::make_span(settings.vsecure_secret().v),
|
||||
Core::ComputeSecureSecretHash(
|
||||
Core::ParseSecureSecretAlgo(
|
||||
settings.vsecure_algo()),
|
||||
bytes::make_span(passwordUtf)));
|
||||
if (secret.empty()) {
|
||||
LOG(("API Error: "
|
||||
"Failed to decrypt secure secret."));
|
||||
consumer.put_error("SUGGEST_SECRET_RESET");
|
||||
return;
|
||||
} else if (Passport::CountSecureSecretId(secret)
|
||||
!= settings.vsecure_secret_id().v) {
|
||||
LOG(("API Error: Wrong secure secret id."));
|
||||
consumer.put_error("SUGGEST_SECRET_RESET");
|
||||
return;
|
||||
} else {
|
||||
secureSecret = QByteArray(
|
||||
reinterpret_cast<const char*>(secret.data()),
|
||||
secret.size());
|
||||
}
|
||||
}
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
const auto latestState = ProcessMtpState(result);
|
||||
sendMTPaccountUpdatePasswordSettings(
|
||||
latestState,
|
||||
secureSecret,
|
||||
consumer);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
sendMTPaccountUpdatePasswordSettings(
|
||||
latestState,
|
||||
QByteArray(),
|
||||
consumer);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CloudPassword::check(
|
||||
const QString &password) {
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
const auto latestState = ProcessMtpState(result);
|
||||
const auto input = [&] {
|
||||
if (password.isEmpty()) {
|
||||
return Core::CloudPasswordResult{
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
};
|
||||
}
|
||||
const auto hash = Core::ComputeCloudPasswordHash(
|
||||
latestState.mtp.request.algo,
|
||||
bytes::make_span(password.toUtf8()));
|
||||
return Core::ComputeCloudPasswordCheck(
|
||||
latestState.mtp.request,
|
||||
hash);
|
||||
}();
|
||||
|
||||
_api.request(MTPaccount_GetPasswordSettings(
|
||||
input.result
|
||||
)).done([=](const MTPaccount_PasswordSettings &result) {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CloudPassword::confirmEmail(
|
||||
const QString &code) {
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPaccount_ConfirmPasswordEmail(
|
||||
MTP_string(code)
|
||||
)).done([=] {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
apply(ProcessMtpState(result));
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).handleFloodErrors().send();
|
||||
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CloudPassword::resendEmailCode() {
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPaccount_ResendPasswordEmail(
|
||||
)).done([=] {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
apply(ProcessMtpState(result));
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).handleFloodErrors().send();
|
||||
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<CloudPassword::SetOk, QString> CloudPassword::setEmail(
|
||||
const QString &oldPassword,
|
||||
const QString &recoveryEmail) {
|
||||
const auto generatePasswordCheck = [=](
|
||||
const Core::CloudPasswordState &latestState) {
|
||||
if (oldPassword.isEmpty() || !latestState.hasPassword) {
|
||||
return Core::CloudPasswordResult{
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
};
|
||||
}
|
||||
const auto hash = Core::ComputeCloudPasswordHash(
|
||||
latestState.mtp.request.algo,
|
||||
bytes::make_span(oldPassword.toUtf8()));
|
||||
return Core::ComputeCloudPasswordCheck(
|
||||
latestState.mtp.request,
|
||||
hash);
|
||||
};
|
||||
|
||||
const auto finish = [=](auto consumer, int unconfirmedEmailLengthCode) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
apply(ProcessMtpState(result));
|
||||
if (unconfirmedEmailLengthCode) {
|
||||
consumer.put_next(SetOk{ unconfirmedEmailLengthCode });
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).handleFloodErrors().send();
|
||||
};
|
||||
|
||||
const auto sendMTPaccountUpdatePasswordSettings = [=](
|
||||
const Core::CloudPasswordState &latestState,
|
||||
auto consumer) {
|
||||
const auto settings = MTP_account_passwordInputSettings(
|
||||
MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
|
||||
MTP_passwordKdfAlgoUnknown(),
|
||||
MTP_bytes(),
|
||||
MTP_string(),
|
||||
MTP_string(recoveryEmail),
|
||||
MTPSecureSecretSettings());
|
||||
_api.request(MTPaccount_UpdatePasswordSettings(
|
||||
generatePasswordCheck(latestState).result,
|
||||
settings
|
||||
)).done([=] {
|
||||
finish(consumer, 0);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
const auto prefix = u"EMAIL_UNCONFIRMED_"_q;
|
||||
if (type.startsWith(prefix)) {
|
||||
const auto codeLength = base::StringViewMid(
|
||||
type,
|
||||
prefix.size()).toInt();
|
||||
|
||||
finish(consumer, codeLength);
|
||||
} else {
|
||||
consumer.put_error_copy(type);
|
||||
}
|
||||
}).handleFloodErrors().send();
|
||||
};
|
||||
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
const auto latestState = ProcessMtpState(result);
|
||||
sendMTPaccountUpdatePasswordSettings(latestState, consumer);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CloudPassword::recoverPassword(
|
||||
const QString &code,
|
||||
const QString &newPassword,
|
||||
const QString &newHint) {
|
||||
|
||||
const auto finish = [=](auto consumer) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
apply(ProcessMtpState(result));
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).handleFloodErrors().send();
|
||||
};
|
||||
|
||||
const auto sendMTPaccountUpdatePasswordSettings = [=](
|
||||
const Core::CloudPasswordState &latestState,
|
||||
auto consumer) {
|
||||
const auto newPasswordBytes = newPassword.toUtf8();
|
||||
const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
|
||||
latestState.mtp.newPassword,
|
||||
bytes::make_span(newPasswordBytes));
|
||||
if (!newPassword.isEmpty() && newPasswordHash.modpow.empty()) {
|
||||
consumer.put_error("INTERNAL_SERVER_ERROR");
|
||||
return;
|
||||
}
|
||||
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
||||
const auto flags = Flag::f_new_algo
|
||||
| Flag::f_new_password_hash
|
||||
| Flag::f_hint;
|
||||
|
||||
const auto settings = MTP_account_passwordInputSettings(
|
||||
MTP_flags(flags),
|
||||
Core::PrepareCloudPasswordAlgo(newPassword.isEmpty()
|
||||
? v::null
|
||||
: latestState.mtp.newPassword),
|
||||
newPassword.isEmpty()
|
||||
? MTP_bytes()
|
||||
: MTP_bytes(newPasswordHash.modpow),
|
||||
MTP_string(newHint),
|
||||
MTP_string(),
|
||||
MTPSecureSecretSettings());
|
||||
|
||||
_api.request(MTPauth_RecoverPassword(
|
||||
MTP_flags(newPassword.isEmpty()
|
||||
? MTPauth_RecoverPassword::Flags(0)
|
||||
: MTPauth_RecoverPassword::Flag::f_new_settings),
|
||||
MTP_string(code),
|
||||
settings
|
||||
)).done([=](const MTPauth_Authorization &result) {
|
||||
finish(consumer);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
consumer.put_error_copy(type);
|
||||
}).handleFloodErrors().send();
|
||||
};
|
||||
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPaccount_GetPassword(
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
const auto latestState = ProcessMtpState(result);
|
||||
sendMTPaccountUpdatePasswordSettings(latestState, consumer);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<QString, QString> CloudPassword::requestPasswordRecovery() {
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPauth_RequestPasswordRecovery(
|
||||
)).done([=](const MTPauth_PasswordRecovery &result) {
|
||||
result.match([&](const MTPDauth_passwordRecovery &data) {
|
||||
consumer.put_next(qs(data.vemail_pattern().v));
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
auto CloudPassword::checkRecoveryEmailAddressCode(const QString &code)
|
||||
-> rpl::producer<rpl::no_value, QString> {
|
||||
return [=](auto consumer) {
|
||||
_api.request(MTPauth_CheckRecoveryPassword(
|
||||
MTP_string(code)
|
||||
)).done([=] {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).handleFloodErrors().send();
|
||||
|
||||
return rpl::lifetime();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace Api {
|
||||
|
||||
class CloudPassword final {
|
||||
public:
|
||||
struct SetOk {
|
||||
int unconfirmedEmailLengthCode = 0;
|
||||
};
|
||||
|
||||
using ResetRetryDate = int;
|
||||
explicit CloudPassword(not_null<ApiWrap*> api);
|
||||
|
||||
@@ -34,7 +38,31 @@ public:
|
||||
rpl::producer<ResetRetryDate, QString> resetPassword();
|
||||
rpl::producer<rpl::no_value, QString> cancelResetPassword();
|
||||
|
||||
rpl::producer<SetOk, QString> set(
|
||||
const QString &oldPassword,
|
||||
const QString &newPassword,
|
||||
const QString &hint,
|
||||
bool hasRecoveryEmail,
|
||||
const QString &recoveryEmail);
|
||||
rpl::producer<rpl::no_value, QString> check(const QString &password);
|
||||
|
||||
rpl::producer<rpl::no_value, QString> confirmEmail(const QString &code);
|
||||
rpl::producer<rpl::no_value, QString> resendEmailCode();
|
||||
rpl::producer<SetOk, QString> setEmail(
|
||||
const QString &oldPassword,
|
||||
const QString &recoveryEmail);
|
||||
|
||||
rpl::producer<rpl::no_value, QString> recoverPassword(
|
||||
const QString &code,
|
||||
const QString &newPassword,
|
||||
const QString &newHint);
|
||||
rpl::producer<QString, QString> requestPasswordRecovery();
|
||||
rpl::producer<rpl::no_value, QString> checkRecoveryEmailAddressCode(
|
||||
const QString &code);
|
||||
|
||||
private:
|
||||
void apply(Core::CloudPasswordState state);
|
||||
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _requestId = 0;
|
||||
std::unique_ptr<Core::CloudPasswordState> _state;
|
||||
|
||||
@@ -154,9 +154,10 @@ void StartPendingReset(
|
||||
PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
|
||||
const Core::CloudPasswordState ¤t) {
|
||||
auto result = CloudFields();
|
||||
result.curRequest = current.request;
|
||||
result.newAlgo = current.newPassword;
|
||||
result.newSecureSecretAlgo = current.newSecureSecret;
|
||||
result.hasPassword = current.hasPassword;
|
||||
result.mtp.curRequest = current.mtp.request;
|
||||
result.mtp.newAlgo = current.mtp.newPassword;
|
||||
result.mtp.newSecureSecretAlgo = current.mtp.newSecureSecret;
|
||||
result.hasRecovery = current.hasRecovery;
|
||||
result.notEmptyPassport = current.notEmptyPassport;
|
||||
result.hint = current.hint;
|
||||
@@ -200,21 +201,21 @@ PasscodeBox::PasscodeBox(
|
||||
, _newPasscode(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
(fields.curRequest
|
||||
(fields.hasPassword
|
||||
? tr::lng_cloud_password_enter_new()
|
||||
: tr::lng_cloud_password_enter_first()))
|
||||
, _reenterPasscode(this, st::defaultInputField, tr::lng_cloud_password_confirm_new())
|
||||
, _passwordHint(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
(fields.curRequest
|
||||
(fields.hasPassword
|
||||
? tr::lng_cloud_password_change_hint()
|
||||
: tr::lng_cloud_password_hint()))
|
||||
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
|
||||
, _recover(this, tr::lng_signin_recover(tr::now))
|
||||
, _showRecoverLink(_cloudFields.hasRecovery || !_cloudFields.pendingResetDate) {
|
||||
Expects(session != nullptr || !fields.fromRecoveryCode.isEmpty());
|
||||
Expects(!_turningOff || _cloudFields.curRequest);
|
||||
Expects(!_turningOff || _cloudFields.hasPassword);
|
||||
|
||||
if (!_cloudFields.hint.isEmpty()) {
|
||||
_hintText.setText(
|
||||
@@ -248,7 +249,7 @@ rpl::producer<MTPauth_Authorization> PasscodeBox::newAuthorization() const {
|
||||
|
||||
bool PasscodeBox::currentlyHave() const {
|
||||
return _cloudPwd
|
||||
? (!!_cloudFields.curRequest)
|
||||
? _cloudFields.hasPassword
|
||||
: _session->domain().local().hasLocalPasscode();
|
||||
}
|
||||
|
||||
@@ -609,7 +610,7 @@ void PasscodeBox::handleSrpIdInvalid() {
|
||||
const auto now = crl::now();
|
||||
if (_lastSrpIdInvalidTime > 0
|
||||
&& now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) {
|
||||
_cloudFields.curRequest.id = 0;
|
||||
_cloudFields.mtp.curRequest.id = 0;
|
||||
_oldError = Lang::Hard::ServerError();
|
||||
update();
|
||||
} else {
|
||||
@@ -743,14 +744,14 @@ void PasscodeBox::checkPassword(
|
||||
CheckPasswordCallback callback) {
|
||||
const auto passwordUtf = oldPassword.toUtf8();
|
||||
_checkPasswordHash = Core::ComputeCloudPasswordHash(
|
||||
_cloudFields.curRequest.algo,
|
||||
_cloudFields.mtp.curRequest.algo,
|
||||
bytes::make_span(passwordUtf));
|
||||
checkPasswordHash(std::move(callback));
|
||||
}
|
||||
|
||||
void PasscodeBox::checkPasswordHash(CheckPasswordCallback callback) {
|
||||
_checkPasswordCallback = std::move(callback);
|
||||
if (_cloudFields.curRequest.id) {
|
||||
if (_cloudFields.mtp.curRequest.id) {
|
||||
passwordChecked();
|
||||
} else {
|
||||
requestPasswordData();
|
||||
@@ -758,16 +759,16 @@ void PasscodeBox::checkPasswordHash(CheckPasswordCallback callback) {
|
||||
}
|
||||
|
||||
void PasscodeBox::passwordChecked() {
|
||||
if (!_cloudFields.curRequest || !_cloudFields.curRequest.id || !_checkPasswordCallback) {
|
||||
if (!_cloudFields.mtp.curRequest || !_cloudFields.mtp.curRequest.id || !_checkPasswordCallback) {
|
||||
return serverError();
|
||||
}
|
||||
const auto check = Core::ComputeCloudPasswordCheck(
|
||||
_cloudFields.curRequest,
|
||||
_cloudFields.mtp.curRequest,
|
||||
_checkPasswordHash);
|
||||
if (!check) {
|
||||
return serverError();
|
||||
}
|
||||
_cloudFields.curRequest.id = 0;
|
||||
_cloudFields.mtp.curRequest.id = 0;
|
||||
_checkPasswordCallback(check);
|
||||
}
|
||||
|
||||
@@ -782,7 +783,7 @@ void PasscodeBox::requestPasswordData() {
|
||||
).done([=](const MTPaccount_Password &result) {
|
||||
_setRequest = 0;
|
||||
result.match([&](const MTPDaccount_password &data) {
|
||||
_cloudFields.curRequest = Core::ParseCloudPasswordCheckRequest(data);
|
||||
_cloudFields.mtp.curRequest = Core::ParseCloudPasswordCheckRequest(data);
|
||||
passwordChecked();
|
||||
});
|
||||
}).send();
|
||||
@@ -820,7 +821,7 @@ void PasscodeBox::sendClearCloudPassword(
|
||||
check.result,
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(flags),
|
||||
Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
|
||||
Core::PrepareCloudPasswordAlgo(_cloudFields.mtp.newAlgo),
|
||||
MTP_bytes(), // new_password_hash
|
||||
MTP_string(hint),
|
||||
MTP_string(email),
|
||||
@@ -835,7 +836,7 @@ void PasscodeBox::sendClearCloudPassword(
|
||||
void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
|
||||
const auto newPasswordBytes = newPassword.toUtf8();
|
||||
const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
|
||||
_cloudFields.newAlgo,
|
||||
_cloudFields.mtp.newAlgo,
|
||||
bytes::make_span(newPasswordBytes));
|
||||
if (newPasswordHash.modpow.empty()) {
|
||||
return serverError();
|
||||
@@ -851,7 +852,7 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) {
|
||||
|
||||
const auto settings = MTP_account_passwordInputSettings(
|
||||
MTP_flags(flags),
|
||||
Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
|
||||
Core::PrepareCloudPasswordAlgo(_cloudFields.mtp.newAlgo),
|
||||
MTP_bytes(newPasswordHash.modpow),
|
||||
MTP_string(hint),
|
||||
MTP_string(email),
|
||||
@@ -989,7 +990,7 @@ void PasscodeBox::sendChangeCloudPassword(
|
||||
const QByteArray &secureSecret) {
|
||||
const auto newPasswordBytes = newPassword.toUtf8();
|
||||
const auto newPasswordHash = Core::ComputeCloudPasswordDigest(
|
||||
_cloudFields.newAlgo,
|
||||
_cloudFields.mtp.newAlgo,
|
||||
bytes::make_span(newPasswordBytes));
|
||||
if (newPasswordHash.modpow.empty()) {
|
||||
return serverError();
|
||||
@@ -1007,19 +1008,19 @@ void PasscodeBox::sendChangeCloudPassword(
|
||||
newSecureSecret = Passport::EncryptSecureSecret(
|
||||
bytes::make_span(secureSecret),
|
||||
Core::ComputeSecureSecretHash(
|
||||
_cloudFields.newSecureSecretAlgo,
|
||||
_cloudFields.mtp.newSecureSecretAlgo,
|
||||
bytes::make_span(newPasswordBytes)));
|
||||
}
|
||||
_setRequest = _api.request(MTPaccount_UpdatePasswordSettings(
|
||||
check.result,
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(flags),
|
||||
Core::PrepareCloudPasswordAlgo(_cloudFields.newAlgo),
|
||||
Core::PrepareCloudPasswordAlgo(_cloudFields.mtp.newAlgo),
|
||||
MTP_bytes(newPasswordHash.modpow),
|
||||
MTP_string(hint),
|
||||
MTPstring(), // email is not changing
|
||||
MTP_secureSecretSettings(
|
||||
Core::PrepareSecureSecretAlgo(_cloudFields.newSecureSecretAlgo),
|
||||
Core::PrepareSecureSecretAlgo(_cloudFields.mtp.newSecureSecretAlgo),
|
||||
MTP_bytes(newSecureSecret),
|
||||
MTP_long(newSecureSecretId)))
|
||||
)).done([=] {
|
||||
@@ -1294,7 +1295,8 @@ void RecoverBox::proceedToChange(const QString &code) {
|
||||
fields.hasRecovery = false;
|
||||
// we could've been turning off, no need to force new password then
|
||||
// like if (_cloudFields.turningOff) { just RecoverPassword else Check }
|
||||
fields.curRequest = {};
|
||||
fields.mtp.curRequest = {};
|
||||
fields.hasPassword = false;
|
||||
auto box = Box<PasscodeBox>(_session, fields);
|
||||
|
||||
box->boxClosing(
|
||||
|
||||
@@ -36,13 +36,17 @@ public:
|
||||
struct CloudFields {
|
||||
static CloudFields From(const Core::CloudPasswordState ¤t);
|
||||
|
||||
Core::CloudPasswordCheckRequest curRequest;
|
||||
Core::CloudPasswordAlgo newAlgo;
|
||||
struct Mtp {
|
||||
Core::CloudPasswordCheckRequest curRequest;
|
||||
Core::CloudPasswordAlgo newAlgo;
|
||||
Core::SecureSecretAlgo newSecureSecretAlgo;
|
||||
};
|
||||
Mtp mtp;
|
||||
bool hasPassword = false;
|
||||
bool hasRecovery = false;
|
||||
QString fromRecoveryCode;
|
||||
bool notEmptyPassport = false;
|
||||
QString hint;
|
||||
Core::SecureSecretAlgo newSecureSecretAlgo;
|
||||
bool turningOff = false;
|
||||
TimeId pendingResetDate = 0;
|
||||
|
||||
|
||||
@@ -804,7 +804,7 @@ void Controller::fillSignaturesButton() {
|
||||
tr::lng_edit_sign_messages(),
|
||||
rpl::single(QString()),
|
||||
[] {},
|
||||
{ &st::infoIconSignature, Settings::kIconLightBlue }
|
||||
{ &st::infoRoundedIconSignature, Settings::kIconLightBlue }
|
||||
)->toggleOn(rpl::single(channel->addsSignature())
|
||||
)->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
@@ -1009,7 +1009,7 @@ void Controller::fillManageSection() {
|
||||
*Data::PeerAllowedReactions(_peer),
|
||||
done));
|
||||
},
|
||||
{ &st::infoIconReactions, Settings::kIconRed });
|
||||
{ &st::infoRoundedIconReactions, Settings::kIconRed });
|
||||
}
|
||||
if (canEditPermissions) {
|
||||
AddButtonWithCount(
|
||||
@@ -1058,7 +1058,7 @@ void Controller::fillManageSection() {
|
||||
0),
|
||||
Ui::LayerOption::KeepOther);
|
||||
},
|
||||
{ &st::infoIconInviteLinks, Settings::kIconLightOrange });
|
||||
{ &st::infoRoundedIconInviteLinks, Settings::kIconLightOrange });
|
||||
|
||||
if (_privacySavedValue) {
|
||||
_privacyTypeUpdates.events_starting_with_copy(
|
||||
@@ -1086,7 +1086,7 @@ void Controller::fillManageSection() {
|
||||
_peer,
|
||||
ParticipantsBoxController::Role::Admins);
|
||||
},
|
||||
{ &st::infoIconAdministrators, Settings::kIconLightBlue });
|
||||
{ &st::infoRoundedIconAdministrators, Settings::kIconLightBlue });
|
||||
}
|
||||
if (canViewMembers) {
|
||||
AddButtonWithCount(
|
||||
@@ -1135,7 +1135,7 @@ void Controller::fillManageSection() {
|
||||
tr::lng_manage_peer_recent_actions(),
|
||||
rpl::single(QString()), //Empty count.
|
||||
std::move(callback),
|
||||
{ &st::infoIconRecentActions, Settings::kIconPurple });
|
||||
{ &st::infoRoundedIconRecentActions, Settings::kIconPurple });
|
||||
}
|
||||
|
||||
if (canEditStickers || canDeleteChannel) {
|
||||
@@ -1177,7 +1177,7 @@ void Controller::fillPendingRequestsButton() {
|
||||
: tr::lng_manage_peer_requests_channel()),
|
||||
rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(),
|
||||
[=] { RequestsBoxController::Start(_navigation, _peer); },
|
||||
{ &st::infoIconRequests, Settings::kIconRed });
|
||||
{ &st::infoRoundedIconRequests, Settings::kIconRed });
|
||||
std::move(
|
||||
pendingRequestsCount
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
|
||||
@@ -199,7 +199,7 @@ void Controller::createContent() {
|
||||
Ui::LayerOption::KeepOther);
|
||||
},
|
||||
st::manageGroupButton,
|
||||
{ &st::infoIconInviteLinks, Settings::kIconLightOrange }));
|
||||
{ &st::infoRoundedIconInviteLinks, Settings::kIconLightOrange }));
|
||||
AddSkip(_wrap.get());
|
||||
AddDividerText(_wrap.get(), tr::lng_group_invite_manage_about());
|
||||
|
||||
|
||||
@@ -58,11 +58,24 @@ const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
|
||||
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
|
||||
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
|
||||
|
||||
[[nodiscard]] base::flat_set<int64> CollectEndpointIds(
|
||||
const QVector<MTPPhoneConnection> &list) {
|
||||
auto result = base::flat_set<int64>();
|
||||
result.reserve(list.size());
|
||||
for (const auto &connection : list) {
|
||||
connection.match([&](const MTPDphoneConnection &data) {
|
||||
result.emplace(int64(data.vid().v));
|
||||
}, [](const MTPDphoneConnectionWebrtc &) {
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppendEndpoint(
|
||||
std::vector<tgcalls::Endpoint> &list,
|
||||
const MTPPhoneConnection &connection) {
|
||||
connection.match([&](const MTPDphoneConnection &data) {
|
||||
if (data.vpeer_tag().v.length() != 16) {
|
||||
if (data.vpeer_tag().v.length() != 16 || data.is_tcp()) {
|
||||
return;
|
||||
}
|
||||
tgcalls::Endpoint endpoint = {
|
||||
@@ -84,8 +97,41 @@ void AppendEndpoint(
|
||||
|
||||
void AppendServer(
|
||||
std::vector<tgcalls::RtcServer> &list,
|
||||
const MTPPhoneConnection &connection) {
|
||||
const MTPPhoneConnection &connection,
|
||||
const base::flat_set<int64> &ids) {
|
||||
connection.match([&](const MTPDphoneConnection &data) {
|
||||
const auto hex = [](const QByteArray &value) {
|
||||
const auto digit = [](uchar c) {
|
||||
return char((c < 10) ? ('0' + c) : ('a' + c - 10));
|
||||
};
|
||||
auto result = std::string();
|
||||
result.reserve(value.size() * 2);
|
||||
for (const auto ch : value) {
|
||||
result += digit(uchar(ch) / 16);
|
||||
result += digit(uchar(ch) % 16);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto host = data.vip().v;
|
||||
const auto hostv6 = data.vipv6().v;
|
||||
const auto port = uint16_t(data.vport().v);
|
||||
const auto username = std::string("reflector");
|
||||
const auto password = hex(data.vpeer_tag().v);
|
||||
const auto i = ids.find(int64(data.vid().v));
|
||||
Assert(i != end(ids));
|
||||
const auto id = uint8_t((i - begin(ids)) + 1);
|
||||
const auto pushTurn = [&](const QString &host) {
|
||||
list.push_back(tgcalls::RtcServer{
|
||||
.id = id,
|
||||
.host = host.toStdString(),
|
||||
.port = port,
|
||||
.login = username,
|
||||
.password = password,
|
||||
.isTurn = true,
|
||||
});
|
||||
};
|
||||
pushTurn(host);
|
||||
pushTurn(hostv6);
|
||||
}, [&](const MTPDphoneConnectionWebrtc &data) {
|
||||
const auto host = qs(data.vip());
|
||||
const auto hostv6 = qs(data.vipv6());
|
||||
@@ -782,10 +828,20 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
kAuthKeySize>>();
|
||||
memcpy(encryptionKeyValue->data(), _authKey.data(), kAuthKeySize);
|
||||
|
||||
const auto &settings = Core::App().settings();
|
||||
const auto version = call.vprotocol().match([&](
|
||||
const MTPDphoneCallProtocol &data) {
|
||||
return data.vlibrary_versions().v;
|
||||
}).value(0, MTP_bytes(kDefaultVersion)).v;
|
||||
|
||||
LOG(("Call Info: Creating instance with version '%1', allowP2P: %2").arg(
|
||||
QString::fromUtf8(version),
|
||||
Logs::b(call.is_p2p_allowed())));
|
||||
|
||||
const auto versionString = version.toStdString();
|
||||
const auto &settings = Core::App().settings();
|
||||
const auto weak = base::make_weak(this);
|
||||
tgcalls::Descriptor descriptor = {
|
||||
.version = versionString,
|
||||
.config = tgcalls::Config{
|
||||
.initializationTimeout =
|
||||
serverConfig.callConnectTimeoutMs / 1000.,
|
||||
@@ -851,11 +907,12 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
QDir().mkpath(callLogFolder);
|
||||
}
|
||||
|
||||
const auto ids = CollectEndpointIds(call.vconnections().v);
|
||||
for (const auto &connection : call.vconnections().v) {
|
||||
AppendEndpoint(descriptor.endpoints, connection);
|
||||
}
|
||||
for (const auto &connection : call.vconnections().v) {
|
||||
AppendServer(descriptor.rtcServers, connection);
|
||||
AppendServer(descriptor.rtcServers, connection, ids);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -873,18 +930,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto version = call.vprotocol().match([&](
|
||||
const MTPDphoneCallProtocol &data) {
|
||||
return data.vlibrary_versions().v;
|
||||
}).value(0, MTP_bytes(kDefaultVersion)).v;
|
||||
|
||||
LOG(("Call Info: Creating instance with version '%1', allowP2P: %2").arg(
|
||||
QString::fromUtf8(version),
|
||||
Logs::b(descriptor.config.enableP2P)));
|
||||
_instance = tgcalls::Meta::Create(
|
||||
version.toStdString(),
|
||||
std::move(descriptor));
|
||||
_instance = tgcalls::Meta::Create(versionString, std::move(descriptor));
|
||||
if (!_instance) {
|
||||
LOG(("Call Error: Wrong library version: %1."
|
||||
).arg(QString::fromUtf8(version)));
|
||||
|
||||
@@ -464,8 +464,8 @@ void Application::clearEmojiSourceImages() {
|
||||
}
|
||||
|
||||
bool Application::isActiveForTrayMenu() const {
|
||||
if (_primaryWindow) {
|
||||
return _primaryWindow->widget()->isActiveForTrayMenu();
|
||||
if (_primaryWindow && _primaryWindow->widget()->isActiveForTrayMenu()) {
|
||||
return true;
|
||||
}
|
||||
return ranges::any_of(ranges::views::values(_secondaryWindows), [=](
|
||||
const std::unique_ptr<Window::Controller> &controller) {
|
||||
|
||||
@@ -303,18 +303,53 @@ bytes::vector ComputeSecureSecretHash(
|
||||
CloudPasswordState ParseCloudPasswordState(
|
||||
const MTPDaccount_password &data) {
|
||||
auto result = CloudPasswordState();
|
||||
result.request = ParseCloudPasswordCheckRequest(data);
|
||||
result.unknownAlgorithm = data.vcurrent_algo() && !result.request;
|
||||
result.mtp.request = ParseCloudPasswordCheckRequest(data);
|
||||
result.hasPassword = (!!result.mtp.request);
|
||||
result.mtp.unknownAlgorithm = data.vcurrent_algo() && !result.hasPassword;
|
||||
result.hasRecovery = data.is_has_recovery();
|
||||
result.notEmptyPassport = data.is_has_secure_values();
|
||||
result.hint = qs(data.vhint().value_or_empty());
|
||||
result.newPassword = ValidateNewCloudPasswordAlgo(
|
||||
result.mtp.newPassword = ValidateNewCloudPasswordAlgo(
|
||||
ParseCloudPasswordAlgo(data.vnew_algo()));
|
||||
result.newSecureSecret = ValidateNewSecureSecretAlgo(
|
||||
result.mtp.newSecureSecret = ValidateNewSecureSecretAlgo(
|
||||
ParseSecureSecretAlgo(data.vnew_secure_algo()));
|
||||
result.unconfirmedPattern =
|
||||
qs(data.vemail_unconfirmed_pattern().value_or_empty());
|
||||
result.pendingResetDate = data.vpending_reset_date().value_or_empty();
|
||||
|
||||
result.outdatedClient = [&] {
|
||||
const auto badSecureAlgo = data.vnew_secure_algo().match([](
|
||||
const MTPDsecurePasswordKdfAlgoUnknown &) {
|
||||
return true;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
if (badSecureAlgo) {
|
||||
return true;
|
||||
}
|
||||
if (data.vcurrent_algo()) {
|
||||
const auto badCurrentAlgo = data.vcurrent_algo()->match([](
|
||||
const MTPDpasswordKdfAlgoUnknown &) {
|
||||
return true;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
if (badCurrentAlgo) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto badNewAlgo = data.vnew_algo().match([](
|
||||
const MTPDpasswordKdfAlgoUnknown &) {
|
||||
return true;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
if (badNewAlgo) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -122,13 +122,18 @@ bytes::vector ComputeSecureSecretHash(
|
||||
bytes::const_span password);
|
||||
|
||||
struct CloudPasswordState {
|
||||
CloudPasswordCheckRequest request;
|
||||
bool unknownAlgorithm = false;
|
||||
struct Mtp {
|
||||
CloudPasswordCheckRequest request;
|
||||
bool unknownAlgorithm = false;
|
||||
CloudPasswordAlgo newPassword;
|
||||
SecureSecretAlgo newSecureSecret;
|
||||
};
|
||||
Mtp mtp;
|
||||
bool hasPassword = false;
|
||||
bool hasRecovery = false;
|
||||
bool notEmptyPassport = false;
|
||||
bool outdatedClient = false;
|
||||
QString hint;
|
||||
CloudPasswordAlgo newPassword;
|
||||
SecureSecretAlgo newSecureSecret;
|
||||
QString unconfirmedPattern;
|
||||
TimeId pendingResetDate = 0;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 3007004;
|
||||
constexpr auto AppVersionStr = "3.7.4";
|
||||
constexpr auto AppVersion = 3007005;
|
||||
constexpr auto AppVersionStr = "3.7.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -1194,7 +1194,9 @@ bool DocumentData::hasRemoteLocation() const {
|
||||
}
|
||||
|
||||
bool DocumentData::useStreamingLoader() const {
|
||||
if (const auto info = sticker()) {
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
} else if (const auto info = sticker()) {
|
||||
return info->isWebm();
|
||||
}
|
||||
return isAnimation()
|
||||
|
||||
@@ -316,7 +316,7 @@ infoProfileSeparatorPadding: margins(
|
||||
|
||||
infoIconFg: windowBoldFg;
|
||||
infoIconInformation: icon {{ "info/info_information", infoIconFg }};
|
||||
infoIconRequests: icon {{ "info/edit/group_manage_join_requests", infoIconFg }};
|
||||
infoIconAddMember: icon {{ "info/info_add_member", infoIconFg }};
|
||||
infoIconNotifications: icon {{ "info/info_notifications", infoIconFg }};
|
||||
infoIconMediaPhoto: icon {{ "info/info_media_photo", infoIconFg }};
|
||||
infoIconMediaVideo: icon {{ "info/info_media_video", infoIconFg }};
|
||||
@@ -327,11 +327,14 @@ infoIconMediaLink: icon {{ "info/info_media_link", infoIconFg }};
|
||||
infoIconMediaGroup: icon {{ "info/info_common_groups", infoIconFg }};
|
||||
infoIconMediaVoice: icon {{ "info/info_media_voice", infoIconFg }};
|
||||
infoIconMediaRound: icon {{ "info/info_media_round", infoIconFg }};
|
||||
infoIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg }};
|
||||
infoIconAdministrators: icon {{ "info/edit/group_manage_admins", settingsIconFg }};
|
||||
infoIconInviteLinks: icon {{ "info/edit/group_manage_links", settingsIconFg }};
|
||||
infoIconReactions: icon {{ "info/edit/group_manage_reactions", settingsIconFg }};
|
||||
infoIconSignature: icon {{ "info/edit/channel_manage_signature", settingsIconFg }};
|
||||
|
||||
infoRoundedIconRequests: icon {{ "info/edit/group_manage_join_requests", settingsIconFg }};
|
||||
infoRoundedIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg }};
|
||||
infoRoundedIconAdministrators: icon {{ "info/edit/group_manage_admins", settingsIconFg }};
|
||||
infoRoundedIconInviteLinks: icon {{ "info/edit/group_manage_links", settingsIconFg }};
|
||||
infoRoundedIconReactions: icon {{ "info/edit/group_manage_reactions", settingsIconFg }};
|
||||
infoRoundedIconSignature: icon {{ "info/edit/channel_manage_signature", settingsIconFg }};
|
||||
|
||||
infoIconShare: icon {{ "info/info_share", infoIconFg }};
|
||||
infoIconEdit: icon {{ "info/info_edit", infoIconFg }};
|
||||
infoIconDelete: icon {{ "info/info_delete", infoIconFg }};
|
||||
|
||||
@@ -61,7 +61,9 @@ ContentWidget::ContentWidget(
|
||||
refreshSearchField(shown);
|
||||
}, lifetime());
|
||||
}
|
||||
_scrollTopSkip.changes(
|
||||
rpl::merge(
|
||||
_scrollTopSkip.changes(),
|
||||
_scrollBottomSkip.changes()
|
||||
) | rpl::start_with_next([this] {
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
@@ -79,7 +81,7 @@ void ContentWidget::updateControlsGeometry() {
|
||||
|
||||
auto newScrollTop = _scroll->scrollTop() + _topDelta;
|
||||
auto scrollGeometry = rect().marginsRemoved(
|
||||
QMargins(0, _scrollTopSkip.current(), 0, 0));
|
||||
{ 0, _scrollTopSkip.current(), 0, _scrollBottomSkip.current() });
|
||||
if (_scroll->geometry() != scrollGeometry) {
|
||||
_scroll->setGeometry(scrollGeometry);
|
||||
}
|
||||
@@ -159,9 +161,11 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
|
||||
}
|
||||
|
||||
int ContentWidget::scrollTillBottom(int forHeight) const {
|
||||
auto scrollHeight = forHeight - _scrollTopSkip.current();
|
||||
auto scrollBottom = _scroll->scrollTop() + scrollHeight;
|
||||
auto desired = _innerDesiredHeight;
|
||||
const auto scrollHeight = forHeight
|
||||
- _scrollTopSkip.current()
|
||||
- _scrollBottomSkip.current();
|
||||
const auto scrollBottom = _scroll->scrollTop() + scrollHeight;
|
||||
const auto desired = _innerDesiredHeight;
|
||||
return std::max(desired - scrollBottom, 0);
|
||||
}
|
||||
|
||||
@@ -173,6 +177,10 @@ void ContentWidget::setScrollTopSkip(int scrollTopSkip) {
|
||||
_scrollTopSkip = scrollTopSkip;
|
||||
}
|
||||
|
||||
void ContentWidget::setScrollBottomSkip(int scrollBottomSkip) {
|
||||
_scrollBottomSkip = scrollBottomSkip;
|
||||
}
|
||||
|
||||
rpl::producer<int> ContentWidget::scrollHeightValue() const {
|
||||
return _scroll->heightValue();
|
||||
}
|
||||
@@ -187,8 +195,9 @@ rpl::producer<int> ContentWidget::desiredHeightValue() const {
|
||||
using namespace rpl::mappers;
|
||||
return rpl::combine(
|
||||
_innerWrap->entity()->desiredHeightValue(),
|
||||
_scrollTopSkip.value()
|
||||
) | rpl::map(_1 + _2);
|
||||
_scrollTopSkip.value(),
|
||||
_scrollBottomSkip.value()
|
||||
) | rpl::map(_1 + _2 + _3);
|
||||
}
|
||||
|
||||
rpl::producer<bool> ContentWidget::desiredShadowVisibility() const {
|
||||
|
||||
@@ -94,6 +94,7 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void setScrollTopSkip(int scrollTopSkip);
|
||||
void setScrollBottomSkip(int scrollBottomSkip);
|
||||
int scrollTopSave() const;
|
||||
void scrollTopRestore(int scrollTop);
|
||||
void scrollTo(const Ui::ScrollToRequest &request);
|
||||
@@ -109,6 +110,7 @@ private:
|
||||
|
||||
style::color _bg;
|
||||
rpl::variable<int> _scrollTopSkip = -1;
|
||||
rpl::variable<int> _scrollBottomSkip = -1;
|
||||
rpl::event_stream<int> _scrollTillBottomChanges;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
Ui::PaddingWrap<Ui::RpWidget> *_innerWrap = nullptr;
|
||||
|
||||
@@ -290,6 +290,10 @@ void Controller::showBackFromStack(const Window::SectionShow ¶ms) {
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::removeFromStack(const std::vector<Section> §ions) const {
|
||||
_widget->removeFromStack(sections);
|
||||
}
|
||||
|
||||
auto Controller::produceSearchQuery(
|
||||
const QString &query) const -> SearchQuery {
|
||||
Expects(_key.peer() != nullptr);
|
||||
@@ -338,6 +342,14 @@ rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(
|
||||
limitAfter);
|
||||
}
|
||||
|
||||
std::any &Controller::stepDataReference() {
|
||||
return _stepData;
|
||||
}
|
||||
|
||||
void Controller::takeStepData(not_null<Controller*> another) {
|
||||
_stepData = base::take(another->_stepData);
|
||||
}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
|
||||
} // namespace Info
|
||||
|
||||
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <rpl/variable.h>
|
||||
#include "data/data_search_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "settings/settings_common.h"
|
||||
|
||||
namespace Ui {
|
||||
class SearchFieldController;
|
||||
@@ -212,6 +211,11 @@ public:
|
||||
void showBackFromStack(
|
||||
const Window::SectionShow ¶ms = Window::SectionShow()) override;
|
||||
|
||||
void removeFromStack(const std::vector<Section> §ions) const;
|
||||
|
||||
void takeStepData(not_null<Controller*> another);
|
||||
std::any &stepDataReference();
|
||||
|
||||
rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -236,8 +240,11 @@ private:
|
||||
rpl::variable<bool> _seachEnabledByContent = false;
|
||||
bool _searchStartsFocused = false;
|
||||
|
||||
// Data between sections based on steps.
|
||||
std::any _stepData;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info
|
||||
} // namespace Info
|
||||
|
||||
@@ -247,7 +247,7 @@ int LayerWidget::resizeGetHeight(int newWidth) {
|
||||
if (!parentWidget() || !_content) {
|
||||
return 0;
|
||||
}
|
||||
constexpr auto kMaxAttempts = 5;
|
||||
constexpr auto kMaxAttempts = 16;
|
||||
auto attempts = 0;
|
||||
while (true) {
|
||||
_inResize = true;
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/info_top_bar.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
|
||||
#include "settings/settings_chat.h"
|
||||
#include "settings/settings_main.h"
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
@@ -395,7 +396,9 @@ void WrapWidget::createTopBar() {
|
||||
addTopBarMenuButton();
|
||||
addProfileCallsButton();
|
||||
} else if (section.type() == Section::Type::Settings
|
||||
&& (section.settingsType() == ::Settings::Main::Id()
|
||||
&& (section.settingsType()
|
||||
== ::Settings::CloudPasswordEmailConfirmId()
|
||||
|| section.settingsType() == ::Settings::Main::Id()
|
||||
|| section.settingsType() == ::Settings::Chat::Id())) {
|
||||
addTopBarMenuButton();
|
||||
} else if (section.type() == Section::Type::Downloads) {
|
||||
@@ -596,6 +599,26 @@ bool WrapWidget::showBackFromStackInternal(
|
||||
return (wrap() == Wrap::Layer);
|
||||
}
|
||||
|
||||
void WrapWidget::removeFromStack(const std::vector<Section> §ions) {
|
||||
for (const auto §ion : sections) {
|
||||
const auto it = ranges::find_if(_historyStack, [&](
|
||||
const StackItem &item) {
|
||||
const auto &s = item.section->section();
|
||||
if (s.type() != section.type()) {
|
||||
return false;
|
||||
} else if (s.type() == Section::Type::Media) {
|
||||
return (s.mediaType() == section.mediaType());
|
||||
} else if (s.type() == Section::Type::Settings) {
|
||||
return (s.settingsType() == section.settingsType());
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (it != end(_historyStack)) {
|
||||
_historyStack.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> WrapWidget::topWidget() const {
|
||||
// This was done for tabs support.
|
||||
//
|
||||
@@ -900,6 +923,9 @@ void WrapWidget::showNewContent(
|
||||
auto newController = createController(
|
||||
_controller->parentController(),
|
||||
memento);
|
||||
if (_controller && newController) {
|
||||
newController->takeStepData(_controller.get());
|
||||
}
|
||||
auto newContent = object_ptr<ContentWidget>(nullptr);
|
||||
if (needAnimation) {
|
||||
newContent = createContent(memento, newController.get());
|
||||
|
||||
@@ -106,6 +106,7 @@ public:
|
||||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms) override;
|
||||
bool showBackFromStackInternal(const Window::SectionShow ¶ms);
|
||||
void removeFromStack(const std::vector<Section> §ions);
|
||||
std::shared_ptr<Window::SectionMemento> createMemento() override;
|
||||
|
||||
rpl::producer<int> desiredHeightValue() const override;
|
||||
|
||||
@@ -529,7 +529,7 @@ void ActionsFiller::addInviteToGroupAction(
|
||||
InviteToChatButton(user) | rpl::filter(notEmpty),
|
||||
InviteToChatButton(user) | rpl::map(notEmpty),
|
||||
[=] { AddBotToGroupBoxController::Start(user); },
|
||||
&st::infoIconRequests);
|
||||
&st::infoIconAddMember);
|
||||
const auto about = _wrap->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
_wrap.data(),
|
||||
@@ -719,7 +719,7 @@ void ActionsFiller::addJoinChannelAction(
|
||||
tr::lng_profile_join_channel(),
|
||||
rpl::duplicate(joinVisible),
|
||||
[=] { channel->session().api().joinChannel(channel); },
|
||||
&st::infoIconRequests);
|
||||
&st::infoIconAddMember);
|
||||
_wrap->add(object_ptr<Ui::SlideWrap<Ui::FixedHeightWidget>>(
|
||||
_wrap,
|
||||
CreateSkipWidget(
|
||||
|
||||
@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/settings/info_settings_widget.h"
|
||||
|
||||
#include "info/info_memento.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_main.h"
|
||||
#include "settings/settings_information.h"
|
||||
@@ -48,12 +47,30 @@ Widget::Widget(
|
||||
, _inner(
|
||||
setInnerWidget(
|
||||
_type()->create(this, controller->parentController())))
|
||||
, _pinnedToTop(_inner->createPinnedToTop(this)) {
|
||||
, _pinnedToTop(_inner->createPinnedToTop(this))
|
||||
, _pinnedToBottom(_inner->createPinnedToBottom(this)) {
|
||||
_inner->sectionShowOther(
|
||||
) | rpl::start_with_next([=](Type type) {
|
||||
controller->showSettings(type);
|
||||
}, _inner->lifetime());
|
||||
|
||||
_inner->sectionShowBack(
|
||||
) | rpl::start_with_next([=] {
|
||||
controller->showBackFromStack();
|
||||
}, _inner->lifetime());
|
||||
|
||||
_inner->setStepDataReference(controller->stepDataReference());
|
||||
|
||||
_removesFromStack.events(
|
||||
) | rpl::start_with_next([=](const std::vector<Type> &types) {
|
||||
const auto sections = ranges::views::all(
|
||||
types
|
||||
) | ranges::views::transform([](Type type) {
|
||||
return Section(type);
|
||||
}) | ranges::to_vector;
|
||||
controller->removeFromStack(sections);
|
||||
}, _inner->lifetime());
|
||||
|
||||
if (_pinnedToTop) {
|
||||
_inner->widthValue(
|
||||
) | rpl::start_with_next([=](int w) {
|
||||
@@ -66,6 +83,26 @@ Widget::Widget(
|
||||
setScrollTopSkip(h);
|
||||
}, _pinnedToTop->lifetime());
|
||||
}
|
||||
|
||||
if (_pinnedToBottom) {
|
||||
const auto processHeight = [=](int bottomHeight, int height) {
|
||||
setScrollBottomSkip(bottomHeight);
|
||||
_pinnedToBottom->moveToLeft(
|
||||
_pinnedToBottom->x(),
|
||||
height - bottomHeight);
|
||||
};
|
||||
|
||||
_inner->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
_pinnedToBottom->resizeToWidth(s.width());
|
||||
processHeight(_pinnedToBottom->height(), height());
|
||||
}, _pinnedToBottom->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_pinnedToBottom->heightValue(),
|
||||
heightValue()
|
||||
) | rpl::start_with_next(processHeight, _pinnedToBottom->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
Widget::~Widget() = default;
|
||||
@@ -99,6 +136,13 @@ void Widget::saveChanges(FnMut<void()> done) {
|
||||
|
||||
void Widget::showFinished() {
|
||||
_inner->showFinished();
|
||||
|
||||
_inner->removeFromStack(
|
||||
) | rpl::start_to_stream(_removesFromStack, lifetime());
|
||||
}
|
||||
|
||||
void Widget::setInnerFocus() {
|
||||
_inner->setInnerFocus();
|
||||
}
|
||||
|
||||
rpl::producer<bool> Widget::desiredShadowVisibility() const {
|
||||
|
||||
@@ -66,6 +66,7 @@ public:
|
||||
void saveChanges(FnMut<void()> done) override;
|
||||
|
||||
void showFinished() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
rpl::producer<bool> desiredShadowVisibility() const override;
|
||||
|
||||
@@ -82,6 +83,9 @@ private:
|
||||
|
||||
not_null<::Settings::AbstractSection*> _inner;
|
||||
QPointer<Ui::RpWidget> _pinnedToTop;
|
||||
QPointer<Ui::RpWidget> _pinnedToBottom;
|
||||
|
||||
rpl::event_stream<std::vector<Type>> _removesFromStack;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -346,7 +346,7 @@ void CodeWidget::gotPassword(const MTPaccount_Password &result) {
|
||||
LOG(("API Error: No current password received on login."));
|
||||
_code->setFocus();
|
||||
return;
|
||||
} else if (!getData()->pwdState.request) {
|
||||
} else if (!getData()->pwdState.hasPassword) {
|
||||
const auto callback = [=](Fn<void()> &&close) {
|
||||
Core::UpdateApplication();
|
||||
close();
|
||||
|
||||
@@ -36,7 +36,7 @@ PasswordCheckWidget::PasswordCheckWidget(
|
||||
, _codeField(this, st::introPassword, tr::lng_signin_code())
|
||||
, _toRecover(this, tr::lng_signin_recover(tr::now))
|
||||
, _toPassword(this, tr::lng_signin_try_password(tr::now)) {
|
||||
Expects(!!_passwordState.request);
|
||||
Expects(_passwordState.hasPassword);
|
||||
|
||||
Lang::Updated(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -169,7 +169,7 @@ void PasswordCheckWidget::handleSrpIdInvalid() {
|
||||
const auto now = crl::now();
|
||||
if (_lastSrpIdInvalidTime > 0
|
||||
&& now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) {
|
||||
_passwordState.request.id = 0;
|
||||
_passwordState.mtp.request.id = 0;
|
||||
showError(rpl::single(Lang::Hard::ServerError()));
|
||||
} else {
|
||||
_lastSrpIdInvalidTime = now;
|
||||
@@ -178,7 +178,7 @@ void PasswordCheckWidget::handleSrpIdInvalid() {
|
||||
}
|
||||
|
||||
void PasswordCheckWidget::checkPasswordHash() {
|
||||
if (_passwordState.request.id) {
|
||||
if (_passwordState.mtp.request.id) {
|
||||
passwordChecked();
|
||||
} else {
|
||||
requestPasswordData();
|
||||
@@ -201,12 +201,12 @@ void PasswordCheckWidget::requestPasswordData() {
|
||||
|
||||
void PasswordCheckWidget::passwordChecked() {
|
||||
const auto check = Core::ComputeCloudPasswordCheck(
|
||||
_passwordState.request,
|
||||
_passwordState.mtp.request,
|
||||
_passwordHash);
|
||||
if (!check) {
|
||||
return serverError();
|
||||
}
|
||||
_passwordState.request.id = 0;
|
||||
_passwordState.mtp.request.id = 0;
|
||||
_sentRequest = api().request(
|
||||
MTPauth_CheckPassword(check.result)
|
||||
).done([=](const MTPauth_Authorization &result) {
|
||||
@@ -226,7 +226,8 @@ void PasswordCheckWidget::codeSubmitDone(
|
||||
auto fields = PasscodeBox::CloudFields::From(_passwordState);
|
||||
fields.fromRecoveryCode = code;
|
||||
fields.hasRecovery = false;
|
||||
fields.curRequest = {};
|
||||
fields.mtp.curRequest = {};
|
||||
fields.hasPassword = false;
|
||||
auto box = Box<PasscodeBox>(&api().instance(), nullptr, fields);
|
||||
const auto boxShared = std::make_shared<QPointer<PasscodeBox>>();
|
||||
|
||||
@@ -391,7 +392,7 @@ void PasswordCheckWidget::submit() {
|
||||
|
||||
const auto password = _pwdField->getLastText().toUtf8();
|
||||
_passwordHash = Core::ComputeCloudPasswordHash(
|
||||
_passwordState.request.algo,
|
||||
_passwordState.mtp.request.algo,
|
||||
bytes::make_span(password));
|
||||
checkPasswordHash();
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ void QrWidget::sendCheckPasswordRequest() {
|
||||
LOG(("API Error: No current password received on login."));
|
||||
goReplace<QrWidget>(Animate::Forward);
|
||||
return;
|
||||
} else if (!getData()->pwdState.request) {
|
||||
} else if (!getData()->pwdState.hasPassword) {
|
||||
const auto callback = [=](Fn<void()> &&close) {
|
||||
Core::UpdateApplication();
|
||||
close();
|
||||
|
||||
@@ -81,10 +81,6 @@ bool PlaybackErrorHappened() {
|
||||
}
|
||||
|
||||
void EnumeratePlaybackDevices() {
|
||||
if (!Webrtc::InitPipewireStubs()) {
|
||||
LOG(("Audio Info: Failed to load pipewire 0.3 stubs."));
|
||||
}
|
||||
|
||||
auto deviceNames = QStringList();
|
||||
auto devices = [&] {
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) {
|
||||
@@ -200,6 +196,10 @@ void Start(not_null<Instance*> instance) {
|
||||
qRegisterMetaType<AudioMsgId>();
|
||||
qRegisterMetaType<VoiceWaveform>();
|
||||
|
||||
if (!Webrtc::InitPipewireStubs()) {
|
||||
LOG(("Audio Info: Failed to load pipewire 0.3 stubs."));
|
||||
}
|
||||
|
||||
auto loglevel = getenv("ALSOFT_LOGLEVEL");
|
||||
LOG(("OpenAL Logging Level: %1").arg(loglevel ? loglevel : "(not set)"));
|
||||
|
||||
|
||||
@@ -988,17 +988,24 @@ void FormController::recoverPassword() {
|
||||
const auto &data = result.c_auth_passwordRecovery();
|
||||
const auto pattern = qs(data.vemail_pattern());
|
||||
auto fields = PasscodeBox::CloudFields{
|
||||
.newAlgo = _password.newAlgo,
|
||||
.mtp = PasscodeBox::CloudFields::Mtp {
|
||||
.newAlgo = _password.newAlgo,
|
||||
.newSecureSecretAlgo = _password.newSecureAlgo,
|
||||
},
|
||||
.hasRecovery = _password.hasRecovery,
|
||||
.newSecureSecretAlgo = _password.newSecureAlgo,
|
||||
.pendingResetDate = _password.pendingResetDate,
|
||||
};
|
||||
|
||||
// MSVC x64 (non-LTO) Release build fails with a linker error:
|
||||
// - unresolved external variant::variant(variant const &)
|
||||
// It looks like a MSVC bug and this works like a workaround.
|
||||
const auto force = fields.mtp.newSecureSecretAlgo;
|
||||
|
||||
const auto box = _view->show(Box<RecoverBox>(
|
||||
&_controller->session().mtp(),
|
||||
&_controller->session(),
|
||||
pattern,
|
||||
fields));
|
||||
|
||||
box->newPasswordSet(
|
||||
) | rpl::start_with_next([=](const QByteArray &password) {
|
||||
if (password.isEmpty()) {
|
||||
@@ -2687,7 +2694,7 @@ void FormController::cancel() {
|
||||
_view->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_passport_stop_sure(),
|
||||
.confirmed = [=] { cancelSure(); },
|
||||
.cancelled = [=] { cancelAbort(); },
|
||||
.cancelled = [=](Fn<void()> close) { cancelAbort(); close(); },
|
||||
.confirmText = tr::lng_passport_stop(),
|
||||
}));
|
||||
} else {
|
||||
|
||||
@@ -683,11 +683,19 @@ void PanelController::setupPassword() {
|
||||
}
|
||||
|
||||
auto fields = PasscodeBox::CloudFields{
|
||||
.newAlgo = settings.newAlgo,
|
||||
.mtp = PasscodeBox::CloudFields::Mtp{
|
||||
.newAlgo = settings.newAlgo,
|
||||
.newSecureSecretAlgo = settings.newSecureAlgo,
|
||||
},
|
||||
.hasRecovery = settings.hasRecovery,
|
||||
.newSecureSecretAlgo = settings.newSecureAlgo,
|
||||
.pendingResetDate = settings.pendingResetDate,
|
||||
};
|
||||
|
||||
// MSVC x64 (non-LTO) Release build fails with a linker error:
|
||||
// - unresolved external variant::variant(variant const &)
|
||||
// It looks like a MSVC bug and this works like a workaround.
|
||||
const auto force = fields.mtp.newSecureSecretAlgo;
|
||||
|
||||
auto box = show(Box<PasscodeBox>(&_form->window()->session(), fields));
|
||||
box->newPasswordSet(
|
||||
) | rpl::start_with_next([=](const QByteArray &password) {
|
||||
|
||||
@@ -177,7 +177,7 @@ CheckoutProcess::CheckoutProcess(
|
||||
if (mode == Mode::Payment) {
|
||||
_session->api().cloudPassword().state(
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
|
||||
_form->setHasPassword(!!state.request);
|
||||
_form->setHasPassword(state.hasPassword);
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
@@ -624,7 +624,7 @@ void CheckoutProcess::requestPassword() {
|
||||
|
||||
void CheckoutProcess::panelSetPassword() {
|
||||
getPasswordState([=](const Core::CloudPasswordState &state) {
|
||||
if (state.request) {
|
||||
if (state.hasPassword) {
|
||||
return;
|
||||
}
|
||||
auto owned = Box<PasscodeBox>(
|
||||
|
||||
@@ -81,6 +81,15 @@ namespace Platform {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool IsAnyActiveForTrayMenu() {
|
||||
for (const NSWindow *w in [[NSApplication sharedApplication] windows]) {
|
||||
if (w.isKeyWindow) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage TrayIconBack(bool darkMode) {
|
||||
static const auto WithColor = [](QColor color) {
|
||||
return st::macTrayIcon.instance(color, 100);
|
||||
@@ -332,7 +341,7 @@ void Tray::createIcon() {
|
||||
// instead of showing the menu, when the window is not activated.
|
||||
_nativeIcon->clicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (Core::App().isActiveForTrayMenu()) {
|
||||
if (IsAnyActiveForTrayMenu()) {
|
||||
_nativeIcon->showMenu(_menu.get());
|
||||
} else {
|
||||
_nativeIcon->deactivateButton();
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
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_common.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/timer.h"
|
||||
#include "core/application.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.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_input.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_manage.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_start.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Settings::CloudPassword {
|
||||
|
||||
void OneEdgeBoxContentDivider::skipEdge(Qt::Edge edge, bool skip) {
|
||||
const auto was = _skipEdges;
|
||||
if (skip) {
|
||||
_skipEdges |= edge;
|
||||
} else {
|
||||
_skipEdges &= ~edge;
|
||||
}
|
||||
if (was != _skipEdges) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void OneEdgeBoxContentDivider::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
p.fillRect(e->rect(), Ui::BoxContentDivider::color());
|
||||
if (!(_skipEdges & Qt::TopEdge)) {
|
||||
Ui::BoxContentDivider::paintTop(p);
|
||||
}
|
||||
if (!(_skipEdges & Qt::BottomEdge)) {
|
||||
Ui::BoxContentDivider::paintBottom(p);
|
||||
}
|
||||
}
|
||||
|
||||
BottomButton CreateBottomDisableButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QRect> &§ionGeometryValue,
|
||||
rpl::producer<QString> &&buttonText,
|
||||
Fn<void()> &&callback) {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
|
||||
|
||||
AddSkip(content);
|
||||
|
||||
AddButton(
|
||||
content,
|
||||
std::move(buttonText),
|
||||
st::settingsAttentionButton
|
||||
)->addClickHandler(std::move(callback));
|
||||
|
||||
const auto divider = Ui::CreateChild<OneEdgeBoxContentDivider>(
|
||||
parent.get());
|
||||
divider->skipEdge(Qt::TopEdge, true);
|
||||
rpl::combine(
|
||||
std::move(sectionGeometryValue),
|
||||
parent->geometryValue(),
|
||||
content->geometryValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QRect &r,
|
||||
const QRect &parentRect,
|
||||
const QRect &bottomRect) {
|
||||
const auto top = r.y() + r.height();
|
||||
divider->setGeometry(
|
||||
0,
|
||||
top,
|
||||
r.width(),
|
||||
parentRect.height() - top - bottomRect.height());
|
||||
}, divider->lifetime());
|
||||
divider->show();
|
||||
|
||||
return {
|
||||
.content = Ui::MakeWeak(not_null<Ui::RpWidget*>{ content }),
|
||||
.isBottomFillerShown = divider->geometryValue(
|
||||
) | rpl::map([](const QRect &r) {
|
||||
return r.height() > 0;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
void SetupAutoCloseTimer(rpl::lifetime &lifetime, Fn<void()> callback) {
|
||||
constexpr auto kTimerCheck = crl::time(1000 * 60);
|
||||
constexpr auto kAutoCloseTimeout = crl::time(1000 * 60 * 10);
|
||||
|
||||
const auto timer = lifetime.make_state<base::Timer>([=] {
|
||||
const auto idle = crl::now() - Core::App().lastNonIdleTime();
|
||||
if (idle >= kAutoCloseTimeout) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
timer->callEach(kTimerCheck);
|
||||
}
|
||||
|
||||
void SetupHeader(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
const QString &lottie,
|
||||
rpl::producer<> &&showFinished,
|
||||
rpl::producer<QString> &&subtitle,
|
||||
rpl::producer<QString> &&about) {
|
||||
if (!lottie.isEmpty()) {
|
||||
const auto &size = st::settingsCloudPasswordIconSize;
|
||||
auto icon = CreateLottieIcon(
|
||||
content,
|
||||
{ .name = lottie, .sizeOverride = { size, size } },
|
||||
st::settingLocalPasscodeIconPadding);
|
||||
content->add(std::move(icon.widget));
|
||||
std::move(
|
||||
showFinished
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, content->lifetime());
|
||||
}
|
||||
AddSkip(content);
|
||||
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
std::move(subtitle),
|
||||
st::changePhoneTitle)),
|
||||
st::changePhoneTitlePadding);
|
||||
|
||||
{
|
||||
const auto &st = st::settingLocalPasscodeDescription;
|
||||
const auto wrap = content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(content, std::move(about), st)),
|
||||
st::changePhoneDescriptionPadding);
|
||||
wrap->resize(
|
||||
wrap->width(),
|
||||
st::settingLocalPasscodeDescriptionHeight);
|
||||
}
|
||||
}
|
||||
|
||||
not_null<Ui::PasswordInput*> AddPasswordField(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
rpl::producer<QString> &&placeholder,
|
||||
const QString &text) {
|
||||
const auto &st = st::settingLocalPasscodeInputField;
|
||||
auto container = object_ptr<Ui::RpWidget>(content);
|
||||
container->resize(container->width(), st.heightMin);
|
||||
const auto field = Ui::CreateChild<Ui::PasswordInput>(
|
||||
container.data(),
|
||||
st,
|
||||
std::move(placeholder),
|
||||
text);
|
||||
|
||||
container->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
field->moveToLeft((r.width() - field->width()) / 2, 0);
|
||||
}, container->lifetime());
|
||||
|
||||
content->add(std::move(container));
|
||||
return field;
|
||||
}
|
||||
|
||||
not_null<Ui::CenterWrap<Ui::InputField>*> AddWrappedField(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
rpl::producer<QString> &&placeholder,
|
||||
const QString &text) {
|
||||
return content->add(object_ptr<Ui::CenterWrap<Ui::InputField>>(
|
||||
content,
|
||||
object_ptr<Ui::InputField>(
|
||||
content,
|
||||
st::settingLocalPasscodeInputField,
|
||||
std::move(placeholder),
|
||||
text)));
|
||||
}
|
||||
|
||||
not_null<Ui::LinkButton*> AddLinkButton(
|
||||
not_null<Ui::CenterWrap<Ui::InputField>*> wrap,
|
||||
rpl::producer<QString> &&text) {
|
||||
const auto button = Ui::CreateChild<Ui::LinkButton>(
|
||||
wrap->parentWidget(),
|
||||
QString());
|
||||
std::move(
|
||||
text
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
button->setText(text);
|
||||
}, button->lifetime());
|
||||
|
||||
wrap->geometryValue(
|
||||
) | rpl::start_with_next([=](QRect r) {
|
||||
r.translate(wrap->entity()->pos().x(), 0);
|
||||
button->moveToLeft(r.x(), r.y() + r.height() + st::passcodeTextLine);
|
||||
}, button->lifetime());
|
||||
return button;
|
||||
}
|
||||
|
||||
not_null<Ui::FlatLabel*> AddError(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
Ui::PasswordInput *input) {
|
||||
const auto error = content->add(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
// Set any text to resize.
|
||||
tr::lng_language_name(tr::now),
|
||||
st::settingLocalPasscodeError)),
|
||||
st::changePhoneDescriptionPadding)->entity();
|
||||
error->hide();
|
||||
if (input) {
|
||||
QObject::connect(input, &Ui::MaskedInputField::changed, [=] {
|
||||
error->hide();
|
||||
});
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
not_null<Ui::RoundButton*> AddDoneButton(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
rpl::producer<QString> &&text) {
|
||||
const auto button = content->add(
|
||||
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
|
||||
content,
|
||||
object_ptr<Ui::RoundButton>(
|
||||
content,
|
||||
std::move(text),
|
||||
st::changePhoneButton)),
|
||||
st::settingLocalPasscodeButtonPadding)->entity();
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
return button;
|
||||
}
|
||||
|
||||
void AddSkipInsteadOfField(not_null<Ui::VerticalLayout*> content) {
|
||||
AddSkip(content, st::settingLocalPasscodeInputField.heightMin);
|
||||
}
|
||||
|
||||
void AddSkipInsteadOfError(not_null<Ui::VerticalLayout*> content) {
|
||||
auto dummy = base::make_unique_q<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_language_name(tr::now),
|
||||
st::settingLocalPasscodeError);
|
||||
const auto &padding = st::changePhoneDescriptionPadding;
|
||||
AddSkip(content, dummy->height() + padding.top() + padding.bottom());
|
||||
dummy = nullptr;
|
||||
}
|
||||
|
||||
AbstractStep::AbstractStep(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: AbstractSection(parent)
|
||||
, _controller(controller) {
|
||||
}
|
||||
|
||||
not_null<Window::SessionController*> AbstractStep::controller() const {
|
||||
return _controller;
|
||||
}
|
||||
|
||||
Api::CloudPassword &AbstractStep::cloudPassword() {
|
||||
return _controller->session().api().cloudPassword();
|
||||
}
|
||||
|
||||
rpl::producer<AbstractStep::Types> AbstractStep::removeTypes() {
|
||||
return rpl::never<Types>();
|
||||
}
|
||||
|
||||
void AbstractStep::showBack() {
|
||||
_showBack.fire({});
|
||||
}
|
||||
|
||||
void AbstractStep::showOther(Type type) {
|
||||
_showOther.fire_copy(type);
|
||||
}
|
||||
|
||||
void AbstractStep::setFocusCallback(Fn<void()> callback) {
|
||||
_setInnerFocusCallback = callback;
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractStep::showFinishes() const {
|
||||
return _showFinished.events();
|
||||
}
|
||||
|
||||
void AbstractStep::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
void AbstractStep::setInnerFocus() {
|
||||
if (_setInnerFocusCallback) {
|
||||
_setInnerFocusCallback();
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractStep::isPasswordInvalidError(const QString &type) {
|
||||
if (type == u"PASSWORD_HASH_INVALID"_q
|
||||
|| type == u"SRP_PASSWORD_CHANGED"_q) {
|
||||
|
||||
// Most likely the cloud password has been changed on another device.
|
||||
// Quit.
|
||||
_quits.fire(AbstractStep::Types{
|
||||
CloudPasswordStartId(),
|
||||
CloudPasswordInputId(),
|
||||
CloudPasswordHintId(),
|
||||
CloudPasswordEmailId(),
|
||||
CloudPasswordEmailConfirmId(),
|
||||
CloudPasswordManageId(),
|
||||
});
|
||||
controller()->show(
|
||||
Ui::MakeInformBox(tr::lng_cloud_password_expired()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
rpl::producer<Type> AbstractStep::sectionShowOther() {
|
||||
return _showOther.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractStep::sectionShowBack() {
|
||||
return _showBack.events();
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<Type>> AbstractStep::removeFromStack() {
|
||||
return rpl::merge(removeTypes(), _quits.events());
|
||||
}
|
||||
|
||||
void AbstractStep::setStepDataReference(std::any &data) {
|
||||
_stepData = &data;
|
||||
}
|
||||
|
||||
StepData AbstractStep::stepData() const {
|
||||
if (!_stepData || !_stepData->has_value()) {
|
||||
StepData();
|
||||
}
|
||||
const auto my = std::any_cast<StepData>(_stepData);
|
||||
return my ? (*my) : StepData();
|
||||
}
|
||||
|
||||
void AbstractStep::setStepData(StepData data) {
|
||||
if (_stepData) {
|
||||
*_stepData = data;
|
||||
}
|
||||
}
|
||||
|
||||
AbstractStep::~AbstractStep() = default;
|
||||
|
||||
} // namespace Settings::CloudPassword
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/widgets/box_content_divider.h"
|
||||
|
||||
namespace Ui {
|
||||
template <typename Widget>
|
||||
class CenterWrap;
|
||||
class FlatLabel;
|
||||
class InputField;
|
||||
class LinkButton;
|
||||
class PasswordInput;
|
||||
class RoundButton;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
class CloudPassword;
|
||||
} // namespace Api
|
||||
|
||||
namespace Settings::CloudPassword {
|
||||
|
||||
struct StepData {
|
||||
QString currentPassword;
|
||||
QString password;
|
||||
QString hint;
|
||||
QString email;
|
||||
int unconfirmedEmailLengthCode;
|
||||
bool setOnlyRecoveryEmail = false;
|
||||
|
||||
struct ProcessRecover {
|
||||
bool setNewPassword = false;
|
||||
QString checkedCode;
|
||||
QString emailPattern;
|
||||
};
|
||||
ProcessRecover processRecover;
|
||||
};
|
||||
|
||||
void SetupAutoCloseTimer(rpl::lifetime &lifetime, Fn<void()> callback);
|
||||
|
||||
void SetupHeader(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
const QString &lottie,
|
||||
rpl::producer<> &&showFinished,
|
||||
rpl::producer<QString> &&subtitle,
|
||||
rpl::producer<QString> &&about);
|
||||
|
||||
[[nodiscard]] not_null<Ui::PasswordInput*> AddPasswordField(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
rpl::producer<QString> &&placeholder,
|
||||
const QString &text);
|
||||
|
||||
[[nodiscard]] not_null<Ui::CenterWrap<Ui::InputField>*> AddWrappedField(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
rpl::producer<QString> &&placeholder,
|
||||
const QString &text);
|
||||
|
||||
[[nodiscard]] not_null<Ui::FlatLabel*> AddError(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
Ui::PasswordInput *input);
|
||||
|
||||
[[nodiscard]] not_null<Ui::RoundButton*> AddDoneButton(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
rpl::producer<QString> &&text);
|
||||
|
||||
[[nodiscard]] not_null<Ui::LinkButton*> AddLinkButton(
|
||||
not_null<Ui::CenterWrap<Ui::InputField>*> wrap,
|
||||
rpl::producer<QString> &&text);
|
||||
|
||||
void AddSkipInsteadOfField(not_null<Ui::VerticalLayout*> content);
|
||||
void AddSkipInsteadOfError(not_null<Ui::VerticalLayout*> content);
|
||||
|
||||
struct BottomButton {
|
||||
QPointer<Ui::RpWidget> content;
|
||||
rpl::producer<bool> isBottomFillerShown;
|
||||
};
|
||||
|
||||
BottomButton CreateBottomDisableButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QRect> &§ionGeometryValue,
|
||||
rpl::producer<QString> &&buttonText,
|
||||
Fn<void()> &&callback);
|
||||
|
||||
class OneEdgeBoxContentDivider : public Ui::BoxContentDivider {
|
||||
public:
|
||||
using Ui::BoxContentDivider::BoxContentDivider;
|
||||
|
||||
void skipEdge(Qt::Edge edge, bool skip);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
Qt::Edges _skipEdges;
|
||||
|
||||
};
|
||||
|
||||
class AbstractStep : public AbstractSection {
|
||||
public:
|
||||
using Types = std::vector<Type>;
|
||||
AbstractStep(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
~AbstractStep();
|
||||
|
||||
void showFinished() override final;
|
||||
void setInnerFocus() override final;
|
||||
[[nodiscard]] rpl::producer<Type> sectionShowOther() override final;
|
||||
[[nodiscard]] rpl::producer<> sectionShowBack() override final;
|
||||
|
||||
[[nodiscard]] rpl::producer<Types> removeFromStack() override final;
|
||||
|
||||
void setStepDataReference(std::any &data) override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] not_null<Window::SessionController*> controller() const;
|
||||
[[nodiscard]] Api::CloudPassword &cloudPassword();
|
||||
|
||||
[[nodiscard]] virtual rpl::producer<Types> removeTypes();
|
||||
|
||||
bool isPasswordInvalidError(const QString &type);
|
||||
|
||||
void showBack();
|
||||
void showOther(Type type);
|
||||
|
||||
void setFocusCallback(Fn<void()> callback);
|
||||
|
||||
[[nodiscard]] rpl::producer<> showFinishes() const;
|
||||
|
||||
StepData stepData() const;
|
||||
void setStepData(StepData data);
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
Fn<void()> _setInnerFocusCallback;
|
||||
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::event_stream<Type> _showOther;
|
||||
rpl::event_stream<> _showBack;
|
||||
rpl::event_stream<Types> _quits;
|
||||
|
||||
std::any *_stepData;
|
||||
|
||||
};
|
||||
|
||||
template <typename SectionType>
|
||||
class TypedAbstractStep : public AbstractStep {
|
||||
public:
|
||||
using AbstractStep::AbstractStep;
|
||||
|
||||
void setStepDataReference(std::any &data) override final {
|
||||
AbstractStep::setStepDataReference(data);
|
||||
static_cast<SectionType*>(this)->setupContent();
|
||||
}
|
||||
|
||||
[[nodiscard]] static Type Id() {
|
||||
return &SectionMetaImplementation<SectionType>::Meta;
|
||||
}
|
||||
[[nodiscard]] Type id() const final override {
|
||||
return Id();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace Settings::CloudPassword
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
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_email.h"
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "lang/lang_keys.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_manage.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
/*
|
||||
Available actions for follow states.
|
||||
|
||||
CreateEmail:
|
||||
– Continue to EmailConfirm.
|
||||
+ Continue to Manage if Email is confirmed already.
|
||||
– Warn and Skip to Manage.
|
||||
– Back to CreateHint.
|
||||
|
||||
ChangeEmail from ChangePassword:
|
||||
– Continue to EmailConfirm.
|
||||
+ Continue to Manage if Email is confirmed already.
|
||||
– Warn and Skip to Manage.
|
||||
– Back to ChangeHint.
|
||||
|
||||
ChangeEmail from Manage:
|
||||
– Continue to EmailConfirm.
|
||||
+ Continue to Manage if Email is confirmed already.
|
||||
– Back to Manage.
|
||||
*/
|
||||
|
||||
namespace Settings {
|
||||
namespace CloudPassword {
|
||||
|
||||
class Email : public TypedAbstractStep<Email> {
|
||||
public:
|
||||
using TypedAbstractStep::TypedAbstractStep;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setupContent();
|
||||
|
||||
private:
|
||||
rpl::lifetime _requestLifetime;
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<QString> Email::title() {
|
||||
return tr::lng_settings_cloud_password_email_title();
|
||||
}
|
||||
|
||||
void Email::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
auto currentStepData = stepData();
|
||||
const auto currentStepDataEmail = base::take(currentStepData.email);
|
||||
const auto setOnly = base::take(currentStepData.setOnlyRecoveryEmail);
|
||||
setStepData(currentStepData);
|
||||
|
||||
const auto state = cloudPassword().stateCurrent();
|
||||
const auto hasRecovery = state && state->hasRecovery;
|
||||
|
||||
SetupHeader(
|
||||
content,
|
||||
u"cloud_password/email"_q,
|
||||
showFinishes(),
|
||||
hasRecovery
|
||||
? tr::lng_settings_cloud_password_manage_email_change()
|
||||
: tr::lng_settings_cloud_password_email_subtitle(),
|
||||
tr::lng_settings_cloud_password_email_about());
|
||||
|
||||
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
||||
|
||||
const auto wrap = AddWrappedField(
|
||||
content,
|
||||
tr::lng_cloud_password_email(),
|
||||
currentStepDataEmail);
|
||||
const auto newInput = wrap->entity();
|
||||
const auto error = AddError(content, nullptr);
|
||||
QObject::connect(newInput, &Ui::InputField::changed, [=] {
|
||||
error->hide();
|
||||
});
|
||||
AddSkipInsteadOfField(content);
|
||||
|
||||
const auto send = [=](Fn<void()> close) {
|
||||
Expects(!_requestLifetime);
|
||||
|
||||
const auto data = stepData();
|
||||
|
||||
_requestLifetime = (setOnly
|
||||
? cloudPassword().setEmail(data.currentPassword, data.email)
|
||||
: cloudPassword().set(
|
||||
data.currentPassword,
|
||||
data.password,
|
||||
data.hint,
|
||||
!data.email.isEmpty(),
|
||||
data.email)
|
||||
) | rpl::start_with_next_error_done([=](Api::CloudPassword::SetOk d) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
auto data = stepData();
|
||||
data.unconfirmedEmailLengthCode = d.unconfirmedEmailLengthCode;
|
||||
setStepData(std::move(data));
|
||||
showOther(CloudPasswordEmailConfirmId());
|
||||
}, [=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->show();
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
} else if (AbstractStep::isPasswordInvalidError(type)) {
|
||||
} else if (type == u"EMAIL_INVALID"_q) {
|
||||
error->show();
|
||||
error->setText(tr::lng_cloud_password_bad_email(tr::now));
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
newInput->selectAll();
|
||||
}
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
auto empty = StepData();
|
||||
empty.currentPassword = stepData().password.isEmpty()
|
||||
? stepData().currentPassword
|
||||
: stepData().password;
|
||||
setStepData(std::move(empty));
|
||||
showOther(CloudPasswordManageId());
|
||||
});
|
||||
|
||||
if (close) {
|
||||
_requestLifetime.add(close);
|
||||
}
|
||||
};
|
||||
|
||||
const auto confirm = [=](const QString &email) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = stepData();
|
||||
data.email = email;
|
||||
setStepData(std::move(data));
|
||||
|
||||
if (!email.isEmpty()) {
|
||||
send(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
controller()->show(Ui::MakeConfirmBox({
|
||||
.text = { tr::lng_cloud_password_about_recover() },
|
||||
.confirmed = crl::guard(this, send),
|
||||
.confirmText = tr::lng_cloud_password_skip_email(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
|
||||
const auto skip = AddLinkButton(
|
||||
wrap,
|
||||
tr::lng_cloud_password_skip_email());
|
||||
skip->setClickedCallback([=] {
|
||||
confirm(QString());
|
||||
});
|
||||
skip->setVisible(!setOnly);
|
||||
|
||||
const auto button = AddDoneButton(
|
||||
content,
|
||||
tr::lng_settings_cloud_password_save());
|
||||
button->setClickedCallback([=] {
|
||||
const auto newText = newInput->getLastText();
|
||||
if (newText.isEmpty()) {
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
} else {
|
||||
confirm(newText);
|
||||
}
|
||||
});
|
||||
|
||||
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
|
||||
QObject::connect(newInput, &Ui::InputField::submitted, submit);
|
||||
|
||||
setFocusCallback([=] { newInput->setFocus(); });
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
} // namespace CloudPassword
|
||||
|
||||
Type CloudPasswordEmailId() {
|
||||
return CloudPassword::Email::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type CloudPasswordEmailId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
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_email_confirm.h"
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_common.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_email.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_hint.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_input.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_manage.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_start.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/sent_code_field.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
/*
|
||||
Available actions for follow states.
|
||||
|
||||
CreateEmailConfirm from CreateEmail:
|
||||
– Continue to Manage.
|
||||
– Abort to Settings.
|
||||
– Back to Settings.
|
||||
|
||||
ChangeEmailConfirm from ChangeEmail:
|
||||
– Continue to Manage.
|
||||
– Abort to Settings.
|
||||
– Back to Settings.
|
||||
|
||||
Recover from CreatePassword:
|
||||
– Continue to RecreateResetPassword.
|
||||
– Back to Settings.
|
||||
*/
|
||||
|
||||
namespace Settings {
|
||||
namespace CloudPassword {
|
||||
|
||||
class EmailConfirm : public TypedAbstractStep<EmailConfirm> {
|
||||
public:
|
||||
using TypedAbstractStep::TypedAbstractStep;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setupContent();
|
||||
|
||||
protected:
|
||||
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
|
||||
|
||||
private:
|
||||
rpl::lifetime _requestLifetime;
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<QString> EmailConfirm::title() {
|
||||
return tr::lng_settings_cloud_password_email_title();
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<Type>> EmailConfirm::removeTypes() {
|
||||
return rpl::single(std::vector<Type>{
|
||||
CloudPasswordStartId(),
|
||||
CloudPasswordInputId(),
|
||||
CloudPasswordHintId(),
|
||||
CloudPasswordEmailId(),
|
||||
CloudPasswordEmailConfirmId(),
|
||||
CloudPasswordManageId(),
|
||||
});
|
||||
}
|
||||
|
||||
void EmailConfirm::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
auto currentStepData = stepData();
|
||||
const auto currentStepDataCodeLength = base::take(
|
||||
currentStepData.unconfirmedEmailLengthCode);
|
||||
// If we go back from Email Confirm to Privacy Settings
|
||||
// we should forget the current password.
|
||||
const auto currentPassword = base::take(currentStepData.currentPassword);
|
||||
const auto typedPassword = base::take(currentStepData.password);
|
||||
const auto recoverEmailPattern = base::take(
|
||||
currentStepData.processRecover.emailPattern);
|
||||
setStepData(currentStepData);
|
||||
|
||||
const auto state = cloudPassword().stateCurrent();
|
||||
if (!state) {
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
return;
|
||||
}
|
||||
cloudPassword().state(
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
|
||||
if (!_requestLifetime
|
||||
&& state.unconfirmedPattern.isEmpty()
|
||||
&& recoverEmailPattern.isEmpty()) {
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
SetupHeader(
|
||||
content,
|
||||
u"cloud_password/email"_q,
|
||||
showFinishes(),
|
||||
state->unconfirmedPattern.isEmpty()
|
||||
? tr::lng_settings_cloud_password_email_recovery_subtitle()
|
||||
: tr::lng_cloud_password_confirm(),
|
||||
rpl::single(
|
||||
tr::lng_cloud_password_waiting_code(
|
||||
tr::now,
|
||||
lt_email,
|
||||
state->unconfirmedPattern.isEmpty()
|
||||
? recoverEmailPattern
|
||||
: state->unconfirmedPattern)));
|
||||
|
||||
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
||||
|
||||
auto objectInput = object_ptr<Ui::SentCodeField>(
|
||||
content,
|
||||
st::settingLocalPasscodeInputField,
|
||||
tr::lng_change_phone_code_title());
|
||||
const auto newInput = objectInput.data();
|
||||
const auto wrap = content->add(
|
||||
object_ptr<Ui::CenterWrap<Ui::InputField>>(
|
||||
content,
|
||||
std::move(objectInput)));
|
||||
|
||||
const auto error = AddError(content, nullptr);
|
||||
QObject::connect(newInput, &Ui::InputField::changed, [=] {
|
||||
error->hide();
|
||||
});
|
||||
AddSkipInsteadOfField(content);
|
||||
|
||||
const auto resendInfo = Ui::CreateChild<Ui::FlatLabel>(
|
||||
error->parentWidget(),
|
||||
tr::lng_cloud_password_resent(tr::now),
|
||||
st::changePhoneLabel);
|
||||
resendInfo->hide();
|
||||
error->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
resendInfo->setGeometry(r);
|
||||
}, resendInfo->lifetime());
|
||||
error->shownValue(
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
if (shown) {
|
||||
resendInfo->hide();
|
||||
}
|
||||
}, resendInfo->lifetime());
|
||||
|
||||
const auto resend = AddLinkButton(wrap, tr::lng_cloud_password_resend());
|
||||
resend->setClickedCallback([=] {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
_requestLifetime = cloudPassword().resendEmailCode(
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
error->show();
|
||||
error->setText(Lang::Hard::ServerError());
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
error->hide();
|
||||
resendInfo->show();
|
||||
newInput->hideError();
|
||||
});
|
||||
});
|
||||
|
||||
if (!recoverEmailPattern.isEmpty()) {
|
||||
resend->setText(tr::lng_signin_try_password(tr::now));
|
||||
|
||||
resend->setClickedCallback([=] {
|
||||
const auto reset = [=](Fn<void()> close) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
_requestLifetime = cloudPassword().resetPassword(
|
||||
) | rpl::start_with_next_error_done([=](
|
||||
Api::CloudPassword::ResetRetryDate retryDate) {
|
||||
_requestLifetime.destroy();
|
||||
const auto left = std::max(
|
||||
retryDate - base::unixtime::now(),
|
||||
60);
|
||||
controller()->show(Ui::MakeInformBox(
|
||||
tr::lng_cloud_password_reset_later(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::FormatResetCloudPasswordIn(left))));
|
||||
}, [=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
cloudPassword().reload();
|
||||
using PasswordState = Core::CloudPasswordState;
|
||||
_requestLifetime = cloudPassword().state(
|
||||
) | rpl::filter([=](const PasswordState &s) {
|
||||
return s.pendingResetDate != 0;
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const PasswordState &s) {
|
||||
const auto left = (s.pendingResetDate
|
||||
- base::unixtime::now());
|
||||
if (left > 0) {
|
||||
_requestLifetime.destroy();
|
||||
controller()->show(Ui::MakeInformBox(
|
||||
tr::lng_settings_cloud_password_reset_in(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::FormatResetCloudPasswordIn(left))));
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
}
|
||||
});
|
||||
});
|
||||
_requestLifetime.add(close);
|
||||
};
|
||||
|
||||
controller()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_cloud_password_reset_with_email(),
|
||||
.confirmed = reset,
|
||||
.confirmText = tr::lng_cloud_password_reset_ok(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const auto button = AddDoneButton(
|
||||
content,
|
||||
recoverEmailPattern.isEmpty()
|
||||
? tr::lng_settings_cloud_password_email_confirm()
|
||||
: tr::lng_passcode_check_button());
|
||||
button->setClickedCallback([=] {
|
||||
const auto newText = newInput->getDigitsOnly();
|
||||
if (newText.isEmpty()) {
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
} else if (!_requestLifetime && recoverEmailPattern.isEmpty()) {
|
||||
_requestLifetime = cloudPassword().confirmEmail(
|
||||
newText
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
error->show();
|
||||
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
} else if (type == u"CODE_INVALID"_q) {
|
||||
error->setText(tr::lng_signin_wrong_code(tr::now));
|
||||
} else if (type == u"EMAIL_HASH_EXPIRED"_q) {
|
||||
// Show box?
|
||||
error->setText(Lang::Hard::EmailConfirmationExpired());
|
||||
} else {
|
||||
error->setText(Lang::Hard::ServerError());
|
||||
}
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
auto empty = StepData();
|
||||
const auto anyPassword = currentPassword.isEmpty()
|
||||
? typedPassword
|
||||
: currentPassword;
|
||||
empty.currentPassword = anyPassword;
|
||||
setStepData(std::move(empty));
|
||||
// If we don't have the current password
|
||||
// Then we should go to Privacy Settings.
|
||||
if (anyPassword.isEmpty()) {
|
||||
showBack();
|
||||
} else {
|
||||
showOther(CloudPasswordManageId());
|
||||
}
|
||||
});
|
||||
} else if (!_requestLifetime) {
|
||||
_requestLifetime = cloudPassword().checkRecoveryEmailAddressCode(
|
||||
newText
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
error->show();
|
||||
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == u"PASSWORD_RECOVERY_NA"_q) {
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
} else if (type == u"PASSWORD_RECOVERY_EXPIRED"_q) {
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
} else if (type == u"CODE_INVALID"_q) {
|
||||
error->setText(tr::lng_signin_wrong_code(tr::now));
|
||||
} else {
|
||||
error->setText(Logs::DebugEnabled()
|
||||
// internal server error
|
||||
? type
|
||||
: Lang::Hard::ServerError());
|
||||
}
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
auto empty = StepData();
|
||||
empty.processRecover.checkedCode = newText;
|
||||
empty.processRecover.setNewPassword = true;
|
||||
setStepData(std::move(empty));
|
||||
showOther(CloudPasswordInputId());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
|
||||
newInput->setAutoSubmit(currentStepDataCodeLength, submit);
|
||||
QObject::connect(newInput, &Ui::InputField::submitted, submit);
|
||||
|
||||
setFocusCallback([=] { newInput->setFocus(); });
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
} // namespace CloudPassword
|
||||
|
||||
Type CloudPasswordEmailConfirmId() {
|
||||
return CloudPassword::EmailConfirm::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type CloudPasswordEmailConfirmId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
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_hint.h"
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_common.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_email.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_manage.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
/*
|
||||
Available actions for follow states.
|
||||
|
||||
CreateHint:
|
||||
– Continue to Email.
|
||||
– Skip to Email.
|
||||
– Back to CreatePassword.
|
||||
|
||||
ChangeHint:
|
||||
– Continue to Email.
|
||||
– Skip to Email.
|
||||
– Back to ChangePassword.
|
||||
|
||||
RecreateResetHint:
|
||||
– Continue to Manage.
|
||||
– Skip to Manage.
|
||||
– Back to RecreateResetPassword.
|
||||
*/
|
||||
|
||||
namespace Settings {
|
||||
namespace CloudPassword {
|
||||
|
||||
class Hint : public TypedAbstractStep<Hint> {
|
||||
public:
|
||||
using TypedAbstractStep::TypedAbstractStep;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setupContent();
|
||||
|
||||
private:
|
||||
rpl::lifetime _requestLifetime;
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<QString> Hint::title() {
|
||||
return tr::lng_settings_cloud_password_hint_title();
|
||||
}
|
||||
|
||||
void Hint::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
auto currentStepData = stepData();
|
||||
const auto currentStepDataHint = base::take(currentStepData.hint);
|
||||
setStepData(currentStepData);
|
||||
|
||||
SetupHeader(
|
||||
content,
|
||||
u"cloud_password/hint"_q,
|
||||
showFinishes(),
|
||||
tr::lng_settings_cloud_password_hint_subtitle(),
|
||||
tr::lng_settings_cloud_password_hint_about());
|
||||
|
||||
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
||||
|
||||
const auto wrap = AddWrappedField(
|
||||
content,
|
||||
tr::lng_cloud_password_hint(),
|
||||
currentStepDataHint);
|
||||
const auto newInput = wrap->entity();
|
||||
const auto error = AddError(content, nullptr);
|
||||
QObject::connect(newInput, &Ui::InputField::changed, [=] {
|
||||
error->hide();
|
||||
});
|
||||
AddSkipInsteadOfField(content);
|
||||
|
||||
const auto save = [=](const QString &hint) {
|
||||
if (currentStepData.processRecover.setNewPassword) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
_requestLifetime = cloudPassword().recoverPassword(
|
||||
currentStepData.processRecover.checkedCode,
|
||||
currentStepData.password,
|
||||
hint
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
error->show();
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
} else {
|
||||
error->setText(Lang::Hard::ServerError());
|
||||
}
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
auto empty = StepData();
|
||||
empty.currentPassword = stepData().password;
|
||||
setStepData(std::move(empty));
|
||||
showOther(CloudPasswordManageId());
|
||||
});
|
||||
} else {
|
||||
auto data = stepData();
|
||||
data.hint = hint;
|
||||
setStepData(std::move(data));
|
||||
showOther(CloudPasswordEmailId());
|
||||
}
|
||||
};
|
||||
|
||||
AddLinkButton(
|
||||
wrap,
|
||||
tr::lng_settings_cloud_password_skip_hint()
|
||||
)->setClickedCallback([=] {
|
||||
save(QString());
|
||||
});
|
||||
|
||||
const auto button = AddDoneButton(content, tr::lng_continue());
|
||||
button->setClickedCallback([=] {
|
||||
const auto newText = newInput->getLastText();
|
||||
if (newText.isEmpty()) {
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
} else if (newText == stepData().password) {
|
||||
error->show();
|
||||
error->setText(tr::lng_cloud_password_bad(tr::now));
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
} else {
|
||||
save(newText);
|
||||
}
|
||||
});
|
||||
|
||||
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
|
||||
QObject::connect(newInput, &Ui::InputField::submitted, submit);
|
||||
|
||||
setFocusCallback([=] { newInput->setFocus(); });
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
} // namespace CloudPassword
|
||||
|
||||
Type CloudPasswordHintId() {
|
||||
return CloudPassword::Hint::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type CloudPasswordHintId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
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_input.h"
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.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 "ui/boxes/confirm_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
/*
|
||||
Available actions for follow states.
|
||||
|
||||
CreatePassword:
|
||||
– Continue to CreateHint.
|
||||
– Back to Start.
|
||||
|
||||
ChangePassword:
|
||||
– Continue to ChangeHint.
|
||||
– Back to Manage.
|
||||
|
||||
CheckPassword:
|
||||
– Continue to Manage.
|
||||
– Recover to EmailConfirm.
|
||||
– Reset and wait (+ Cancel reset).
|
||||
– Reset now and Back to Settings.
|
||||
– Back to Settings.
|
||||
|
||||
RecreateResetPassword:
|
||||
– Continue to RecreateResetHint.
|
||||
– Clear password and Back to Settings.
|
||||
– Back to Settings.
|
||||
*/
|
||||
|
||||
namespace Settings {
|
||||
namespace CloudPassword {
|
||||
namespace {
|
||||
|
||||
struct Icon {
|
||||
not_null<Lottie::Icon*> icon;
|
||||
Fn<void()> update;
|
||||
};
|
||||
|
||||
Icon CreateInteractiveLottieIcon(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Lottie::IconDescriptor &&descriptor,
|
||||
style::margins padding) {
|
||||
auto object = object_ptr<Ui::RpWidget>(container);
|
||||
const auto raw = object.data();
|
||||
|
||||
const auto width = descriptor.sizeOverride.width();
|
||||
raw->resize(QRect(
|
||||
QPoint(),
|
||||
descriptor.sizeOverride).marginsAdded(padding).size());
|
||||
|
||||
auto owned = Lottie::MakeIcon(std::move(descriptor));
|
||||
const auto icon = owned.get();
|
||||
|
||||
raw->lifetime().add([kept = std::move(owned)]{});
|
||||
|
||||
raw->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
const auto left = (raw->width() - width) / 2;
|
||||
icon->paint(p, left, padding.top());
|
||||
}, raw->lifetime());
|
||||
|
||||
container->add(std::move(object));
|
||||
return { .icon = icon, .update = [=] { raw->update(); } };
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::LinkButton*> AddLinkButton(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
not_null<Ui::PasswordInput*> input) {
|
||||
const auto button = Ui::CreateChild<Ui::LinkButton>(
|
||||
content.get(),
|
||||
QString());
|
||||
|
||||
rpl::merge(
|
||||
content->geometryValue(),
|
||||
input->geometryValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto topLeft = input->mapTo(content, input->pos());
|
||||
button->moveToLeft(
|
||||
input->pos().x(),
|
||||
topLeft.y() + input->height() + st::passcodeTextLine);
|
||||
}, button->lifetime());
|
||||
return button;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Input : public TypedAbstractStep<Input> {
|
||||
public:
|
||||
using TypedAbstractStep::TypedAbstractStep;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setupContent();
|
||||
|
||||
protected:
|
||||
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
|
||||
|
||||
private:
|
||||
void setupRecoverButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Ui::LinkButton*> button,
|
||||
not_null<Ui::FlatLabel*> info,
|
||||
Fn<void()> recoverCallback);
|
||||
|
||||
rpl::variable<std::vector<Type>> _removesFromStack;
|
||||
rpl::lifetime _requestLifetime;
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<std::vector<Type>> Input::removeTypes() {
|
||||
return _removesFromStack.value();
|
||||
}
|
||||
|
||||
rpl::producer<QString> Input::title() {
|
||||
return tr::lng_settings_cloud_password_password_title();
|
||||
}
|
||||
|
||||
void Input::setupContent() {
|
||||
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);
|
||||
setStepData(currentStepData);
|
||||
|
||||
const auto currentState = cloudPassword().stateCurrent();
|
||||
const auto hasPassword = !currentStepProcessRecover.setNewPassword
|
||||
&& (currentState ? currentState->hasPassword : false);
|
||||
const auto isCheck = currentStepData.currentPassword.isEmpty()
|
||||
&& hasPassword
|
||||
&& !currentStepProcessRecover.setNewPassword;
|
||||
|
||||
if (currentStepProcessRecover.setNewPassword) {
|
||||
_removesFromStack = std::vector<Type>{
|
||||
CloudPasswordEmailConfirmId()
|
||||
};
|
||||
}
|
||||
|
||||
const auto icon = CreateInteractiveLottieIcon(
|
||||
content,
|
||||
{
|
||||
.name = u"cloud_password/password_input"_q,
|
||||
.sizeOverride = {
|
||||
st::settingsCloudPasswordIconSize,
|
||||
st::settingsCloudPasswordIconSize
|
||||
},
|
||||
},
|
||||
st::settingLocalPasscodeIconPadding);
|
||||
|
||||
SetupHeader(
|
||||
content,
|
||||
QString(),
|
||||
rpl::never<>(),
|
||||
isCheck
|
||||
? tr::lng_settings_cloud_password_check_subtitle()
|
||||
: hasPassword
|
||||
? tr::lng_settings_cloud_password_manage_password_change()
|
||||
: tr::lng_settings_cloud_password_password_subtitle(),
|
||||
tr::lng_cloud_password_about());
|
||||
|
||||
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
||||
|
||||
const auto newInput = AddPasswordField(
|
||||
content,
|
||||
isCheck
|
||||
? tr::lng_cloud_password_enter_old()
|
||||
: tr::lng_cloud_password_enter_new(),
|
||||
currentStepDataPassword);
|
||||
const auto reenterInput = isCheck
|
||||
? (Ui::PasswordInput*)(nullptr)
|
||||
: AddPasswordField(
|
||||
content,
|
||||
tr::lng_cloud_password_confirm_new(),
|
||||
currentStepDataPassword).get();
|
||||
const auto error = AddError(content, newInput);
|
||||
if (reenterInput) {
|
||||
QObject::connect(reenterInput, &Ui::MaskedInputField::changed, [=] {
|
||||
error->hide();
|
||||
});
|
||||
}
|
||||
|
||||
if (isCheck) {
|
||||
AddSkipInsteadOfField(content);
|
||||
|
||||
const auto hint = currentState ? currentState->hint : QString();
|
||||
const auto hintInfo = Ui::CreateChild<Ui::FlatLabel>(
|
||||
error->parentWidget(),
|
||||
tr::lng_signin_hint(tr::now, lt_password_hint, hint),
|
||||
st::defaultFlatLabel);
|
||||
hintInfo->setVisible(!hint.isEmpty());
|
||||
error->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
hintInfo->setGeometry(r);
|
||||
}, hintInfo->lifetime());
|
||||
error->shownValue(
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
if (shown) {
|
||||
hintInfo->hide();
|
||||
} else {
|
||||
hintInfo->setVisible(!hint.isEmpty());
|
||||
}
|
||||
}, hintInfo->lifetime());
|
||||
|
||||
auto recoverCallback = [=] {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
const auto state = cloudPassword().stateCurrent();
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
if (state->hasRecovery) {
|
||||
_requestLifetime = cloudPassword().requestPasswordRecovery(
|
||||
) | rpl::start_with_next_error([=](const QString &pattern) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
auto data = stepData();
|
||||
data.processRecover = currentStepProcessRecover;
|
||||
data.processRecover.emailPattern = pattern;
|
||||
setStepData(std::move(data));
|
||||
showOther(CloudPasswordEmailConfirmId());
|
||||
}, [=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
error->show();
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
} else {
|
||||
error->setText(Lang::Hard::ServerError());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const auto callback = [=](Fn<void()> close) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
close();
|
||||
_requestLifetime = cloudPassword().resetPassword(
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
});
|
||||
};
|
||||
controller()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_cloud_password_reset_no_email(),
|
||||
.confirmed = callback,
|
||||
.confirmText = tr::lng_cloud_password_reset_ok(),
|
||||
.cancelText = tr::lng_cancel(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const auto recover = AddLinkButton(content, newInput);
|
||||
const auto resetInfo = Ui::CreateChild<Ui::FlatLabel>(
|
||||
content,
|
||||
QString(),
|
||||
st::boxDividerLabel);
|
||||
recover->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
resetInfo->moveToLeft(r.x(), r.y() + st::passcodeTextLine);
|
||||
}, resetInfo->lifetime());
|
||||
|
||||
setupRecoverButton(
|
||||
content,
|
||||
recover,
|
||||
resetInfo,
|
||||
std::move(recoverCallback));
|
||||
} else if (currentStepProcessRecover.setNewPassword && reenterInput) {
|
||||
const auto skip = AddLinkButton(content, reenterInput);
|
||||
skip->setText(tr::lng_settings_auto_night_disable(tr::now));
|
||||
skip->setClickedCallback([=] {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
_requestLifetime = cloudPassword().recoverPassword(
|
||||
currentStepProcessRecover.checkedCode,
|
||||
QString(),
|
||||
QString()
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
error->show();
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
} else {
|
||||
error->setText(Lang::Hard::ServerError());
|
||||
}
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
controller()->show(
|
||||
Ui::MakeInformBox(tr::lng_cloud_password_removed()));
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
});
|
||||
});
|
||||
AddSkip(content);
|
||||
}
|
||||
|
||||
if (!newInput->text().isEmpty()) {
|
||||
icon.icon->jumpTo(icon.icon->framesCount() / 2, icon.update);
|
||||
}
|
||||
|
||||
const auto checkPassword = [=](const QString &pass) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
_requestLifetime = cloudPassword().check(
|
||||
pass
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
newInput->selectAll();
|
||||
error->show();
|
||||
if (MTP::IsFloodError(type)) {
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
} else if (type == u"PASSWORD_HASH_INVALID"_q
|
||||
|| type == u"SRP_PASSWORD_CHANGED"_q) {
|
||||
error->setText(tr::lng_cloud_password_wrong(tr::now));
|
||||
} else {
|
||||
error->setText(Lang::Hard::ServerError());
|
||||
}
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
if (const auto state = cloudPassword().stateCurrent()) {
|
||||
if (state->pendingResetDate > 0) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
lifetime = cloudPassword().cancelResetPassword(
|
||||
) | rpl::start_with_next([] {});
|
||||
}
|
||||
}
|
||||
|
||||
auto data = stepData();
|
||||
data.currentPassword = pass;
|
||||
setStepData(std::move(data));
|
||||
showOther(CloudPasswordManageId());
|
||||
});
|
||||
};
|
||||
|
||||
const auto button = AddDoneButton(
|
||||
content,
|
||||
isCheck ? tr::lng_passcode_check_button() : tr::lng_continue());
|
||||
button->setClickedCallback([=] {
|
||||
const auto newText = newInput->text();
|
||||
const auto reenterText = isCheck ? QString() : reenterInput->text();
|
||||
if (newText.isEmpty()) {
|
||||
newInput->setFocus();
|
||||
newInput->showError();
|
||||
} else if (reenterInput && reenterText.isEmpty()) {
|
||||
reenterInput->setFocus();
|
||||
reenterInput->showError();
|
||||
} else if (reenterInput && (newText != reenterText)) {
|
||||
reenterInput->setFocus();
|
||||
reenterInput->showError();
|
||||
reenterInput->selectAll();
|
||||
error->show();
|
||||
error->setText(tr::lng_cloud_password_differ(tr::now));
|
||||
} else if (isCheck) {
|
||||
checkPassword(newText);
|
||||
} else {
|
||||
auto data = stepData();
|
||||
data.processRecover = currentStepProcessRecover;
|
||||
data.password = newText;
|
||||
setStepData(std::move(data));
|
||||
showOther(CloudPasswordHintId());
|
||||
}
|
||||
});
|
||||
|
||||
base::qt_signal_producer(
|
||||
newInput.get(),
|
||||
&QLineEdit::textChanged // Covers Undo.
|
||||
) | rpl::map([=] {
|
||||
return newInput->text().isEmpty();
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool empty) {
|
||||
const auto from = icon.icon->frameIndex();
|
||||
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()) {
|
||||
button->clicked({}, Qt::LeftButton);
|
||||
} else {
|
||||
reenterInput->setFocus();
|
||||
}
|
||||
};
|
||||
QObject::connect(newInput, &Ui::MaskedInputField::submitted, submit);
|
||||
if (reenterInput) {
|
||||
using namespace Ui;
|
||||
QObject::connect(reenterInput, &MaskedInputField::submitted, submit);
|
||||
}
|
||||
|
||||
setFocusCallback([=] {
|
||||
if (isCheck || newInput->text().isEmpty()) {
|
||||
newInput->setFocus();
|
||||
} else if (reenterInput->text().isEmpty()) {
|
||||
reenterInput->setFocus();
|
||||
} else {
|
||||
newInput->setFocus();
|
||||
}
|
||||
});
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
void Input::setupRecoverButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Ui::LinkButton*> button,
|
||||
not_null<Ui::FlatLabel*> info,
|
||||
Fn<void()> recoverCallback) {
|
||||
|
||||
struct Status {
|
||||
enum class SuggestAction {
|
||||
Recover,
|
||||
Reset,
|
||||
CancelReset,
|
||||
};
|
||||
SuggestAction suggest = SuggestAction::Recover;
|
||||
TimeId left = 0;
|
||||
};
|
||||
|
||||
struct State {
|
||||
base::Timer timer;
|
||||
rpl::variable<Status> status;
|
||||
};
|
||||
|
||||
const auto state = container->lifetime().make_state<State>();
|
||||
|
||||
const auto updateStatus = [=] {
|
||||
const auto passwordState = cloudPassword().stateCurrent();
|
||||
const auto date = passwordState ? passwordState->pendingResetDate : 0;
|
||||
const auto left = (date - base::unixtime::now());
|
||||
state->status = Status{
|
||||
.suggest = ((left > 0)
|
||||
? Status::SuggestAction::CancelReset
|
||||
: date
|
||||
? Status::SuggestAction::Reset
|
||||
: Status::SuggestAction::Recover),
|
||||
.left = left,
|
||||
};
|
||||
};
|
||||
state->timer.setCallback(updateStatus);
|
||||
updateStatus();
|
||||
|
||||
state->status.value(
|
||||
) | rpl::start_with_next([=](const Status &status) {
|
||||
switch (status.suggest) {
|
||||
case Status::SuggestAction::Recover: {
|
||||
info->setText(QString());
|
||||
button->setText(tr::lng_signin_recover(tr::now));
|
||||
} break;
|
||||
case Status::SuggestAction::Reset: {
|
||||
info->setText(QString());
|
||||
button->setText(tr::lng_cloud_password_reset_ready(tr::now));
|
||||
} break;
|
||||
case Status::SuggestAction::CancelReset: {
|
||||
info->setText(
|
||||
tr::lng_settings_cloud_password_reset_in(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::FormatResetCloudPasswordIn(status.left)));
|
||||
button->setText(
|
||||
tr::lng_cloud_password_reset_cancel_title(tr::now));
|
||||
} break;
|
||||
}
|
||||
}, container->lifetime());
|
||||
|
||||
cloudPassword().state(
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &passState) {
|
||||
updateStatus();
|
||||
state->timer.cancel();
|
||||
if (passState.pendingResetDate) {
|
||||
state->timer.callEach(999);
|
||||
}
|
||||
}, container->lifetime());
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
const auto passState = cloudPassword().stateCurrent();
|
||||
if (_requestLifetime || !passState) {
|
||||
return;
|
||||
}
|
||||
updateStatus();
|
||||
const auto suggest = state->status.current().suggest;
|
||||
if (suggest == Status::SuggestAction::Recover) {
|
||||
recoverCallback();
|
||||
} else if (suggest == Status::SuggestAction::CancelReset) {
|
||||
const auto cancel = [=](Fn<void()> close) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
close();
|
||||
_requestLifetime = cloudPassword().cancelResetPassword(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
_requestLifetime.destroy();
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
});
|
||||
};
|
||||
controller()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_cloud_password_reset_cancel_sure(),
|
||||
.confirmed = cancel,
|
||||
.confirmText = tr::lng_box_yes(),
|
||||
.cancelText = tr::lng_box_no(),
|
||||
}));
|
||||
} else if (suggest == Status::SuggestAction::Reset) {
|
||||
_requestLifetime = cloudPassword().resetPassword(
|
||||
) | rpl::start_with_next_error_done([=](
|
||||
Api::CloudPassword::ResetRetryDate retryDate) {
|
||||
_requestLifetime.destroy();
|
||||
const auto left = std::max(
|
||||
retryDate - base::unixtime::now(),
|
||||
60);
|
||||
controller()->show(Ui::MakeInformBox(
|
||||
tr::lng_cloud_password_reset_later(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::FormatResetCloudPasswordIn(left))));
|
||||
}, [=](const QString &type) {
|
||||
_requestLifetime.destroy();
|
||||
}, [=] {
|
||||
_requestLifetime.destroy();
|
||||
|
||||
cloudPassword().reload();
|
||||
using PasswordState = Core::CloudPasswordState;
|
||||
_requestLifetime = cloudPassword().state(
|
||||
) | rpl::filter([=](const PasswordState &s) {
|
||||
return !s.hasPassword;
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const PasswordState &s) {
|
||||
_requestLifetime.destroy();
|
||||
controller()->show(Ui::MakeInformBox(
|
||||
tr::lng_cloud_password_removed()));
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace CloudPassword
|
||||
|
||||
Type CloudPasswordInputId() {
|
||||
return CloudPassword::Input::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type CloudPasswordInputId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
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_manage.h"
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.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_email.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_hint.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_input.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_start.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
/*
|
||||
Available actions for follow states.
|
||||
|
||||
From CreateEmail
|
||||
From CreateEmailConfirm
|
||||
From ChangeEmail
|
||||
From ChangeEmailConfirm
|
||||
From CheckPassword
|
||||
From RecreateResetHint:
|
||||
– Continue to ChangePassword.
|
||||
– Continue to ChangeEmail.
|
||||
– DisablePassword and Back to Settings.
|
||||
– Back to Settings.
|
||||
*/
|
||||
|
||||
namespace Settings {
|
||||
namespace CloudPassword {
|
||||
namespace {
|
||||
|
||||
void SetupTopContent(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
rpl::producer<> showFinished) {
|
||||
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(parent.get());
|
||||
const auto verticalLayout = parent->add(
|
||||
object_ptr<Ui::VerticalLayout>(parent.get()));
|
||||
|
||||
auto icon = CreateLottieIcon(
|
||||
verticalLayout,
|
||||
{
|
||||
.name = u"cloud_password/intro"_q,
|
||||
.sizeOverride = {
|
||||
st::settingsFilterIconSize,
|
||||
st::settingsFilterIconSize,
|
||||
},
|
||||
},
|
||||
st::settingsFilterIconPadding);
|
||||
std::move(
|
||||
showFinished
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, verticalLayout->lifetime());
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
verticalLayout,
|
||||
tr::lng_settings_cloud_password_manage_about1(),
|
||||
st::settingsFilterDividerLabel)),
|
||||
st::settingsFilterDividerLabelPadding);
|
||||
|
||||
verticalLayout->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
divider->setGeometry(r);
|
||||
}, divider->lifetime());
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Manage : public TypedAbstractStep<Manage> {
|
||||
public:
|
||||
using TypedAbstractStep::TypedAbstractStep;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setupContent();
|
||||
|
||||
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
|
||||
|
||||
private:
|
||||
rpl::variable<bool> _isBottomFillerShown;
|
||||
|
||||
QString _currentPassword;
|
||||
|
||||
rpl::lifetime _requestLifetime;
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<QString> Manage::title() {
|
||||
return tr::lng_settings_cloud_password_start_title();
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<Type>> Manage::removeTypes() {
|
||||
return rpl::single(std::vector<Type>{
|
||||
CloudPasswordStartId(),
|
||||
CloudPasswordInputId(),
|
||||
CloudPasswordHintId(),
|
||||
CloudPasswordEmailId(),
|
||||
CloudPasswordEmailConfirmId(),
|
||||
CloudPasswordManageId(),
|
||||
});
|
||||
}
|
||||
|
||||
void Manage::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
auto currentStepData = stepData();
|
||||
_currentPassword = base::take(currentStepData.currentPassword);
|
||||
// If we go back from Password Manage to Privacy Settings
|
||||
// we should forget the current password.
|
||||
setStepData(std::move(currentStepData));
|
||||
|
||||
const auto quit = [=] {
|
||||
setStepData(StepData());
|
||||
showBack();
|
||||
};
|
||||
|
||||
SetupAutoCloseTimer(content->lifetime(), quit);
|
||||
|
||||
const auto state = cloudPassword().stateCurrent();
|
||||
if (!state) {
|
||||
quit();
|
||||
return;
|
||||
}
|
||||
cloudPassword().state(
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
|
||||
if (!_requestLifetime && !state.hasPassword) {
|
||||
quit();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
const auto showOtherAndRememberPassword = [=](Type type) {
|
||||
// Remember the current password to have ability
|
||||
// to return from Change Password to Password Manage.
|
||||
auto data = stepData();
|
||||
data.currentPassword = _currentPassword;
|
||||
setStepData(std::move(data));
|
||||
|
||||
showOther(type);
|
||||
};
|
||||
|
||||
SetupTopContent(content, showFinishes());
|
||||
|
||||
AddSkip(content);
|
||||
AddButton(
|
||||
content,
|
||||
tr::lng_settings_cloud_password_manage_password_change(),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconKey, kIconLightBlue }
|
||||
)->setClickedCallback([=] {
|
||||
showOtherAndRememberPassword(CloudPasswordInputId());
|
||||
});
|
||||
AddButton(
|
||||
content,
|
||||
state->hasRecovery
|
||||
? tr::lng_settings_cloud_password_manage_email_change()
|
||||
: tr::lng_settings_cloud_password_manage_email_new(),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconEmail, kIconLightOrange }
|
||||
)->setClickedCallback([=] {
|
||||
auto data = stepData();
|
||||
data.setOnlyRecoveryEmail = true;
|
||||
setStepData(std::move(data));
|
||||
|
||||
showOtherAndRememberPassword(CloudPasswordEmailId());
|
||||
});
|
||||
AddSkip(content);
|
||||
|
||||
using Divider = CloudPassword::OneEdgeBoxContentDivider;
|
||||
const auto divider = Ui::CreateChild<Divider>(this);
|
||||
divider->lower();
|
||||
const auto about = content->add(
|
||||
object_ptr<Ui::PaddingWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_settings_cloud_password_manage_about2(),
|
||||
st::boxDividerLabel),
|
||||
st::settingsDividerLabelPadding));
|
||||
rpl::combine(
|
||||
about->geometryValue(),
|
||||
content->widthValue()
|
||||
) | rpl::start_with_next([=](QRect r, int w) {
|
||||
r.setWidth(w);
|
||||
divider->setGeometry(r);
|
||||
}, divider->lifetime());
|
||||
_isBottomFillerShown.value(
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
divider->skipEdge(Qt::BottomEdge, shown);
|
||||
}, divider->lifetime());
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> Manage::createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
|
||||
const auto disable = [=](Fn<void()> close) {
|
||||
if (_requestLifetime) {
|
||||
return;
|
||||
}
|
||||
_requestLifetime = cloudPassword().set(
|
||||
_currentPassword,
|
||||
QString(),
|
||||
QString(),
|
||||
false,
|
||||
QString()
|
||||
) | rpl::start_with_error_done([=](const QString &type) {
|
||||
AbstractStep::isPasswordInvalidError(type);
|
||||
}, [=] {
|
||||
setStepData(StepData());
|
||||
close();
|
||||
showBack();
|
||||
});
|
||||
};
|
||||
|
||||
auto callback = [=] {
|
||||
controller()->show(
|
||||
Ui::MakeConfirmBox({
|
||||
.text = tr::lng_settings_cloud_password_manage_disable_sure(),
|
||||
.confirmed = disable,
|
||||
.confirmText = tr::lng_settings_auto_night_disable(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
auto bottomButton = CloudPassword::CreateBottomDisableButton(
|
||||
parent,
|
||||
geometryValue(),
|
||||
tr::lng_settings_password_disable(),
|
||||
std::move(callback));
|
||||
|
||||
_isBottomFillerShown = base::take(bottomButton.isBottomFillerShown);
|
||||
|
||||
return bottomButton.content;
|
||||
}
|
||||
|
||||
} // namespace CloudPassword
|
||||
|
||||
Type CloudPasswordManageId() {
|
||||
return CloudPassword::Manage::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type CloudPasswordManageId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
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_start.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_common.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_input.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Settings {
|
||||
namespace CloudPassword {
|
||||
|
||||
class Start : public TypedAbstractStep<Start> {
|
||||
public:
|
||||
using TypedAbstractStep::TypedAbstractStep;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setupContent();
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<QString> Start::title() {
|
||||
return tr::lng_settings_cloud_password_start_title();
|
||||
}
|
||||
|
||||
void Start::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
SetupHeader(
|
||||
content,
|
||||
u"cloud_password/intro"_q,
|
||||
showFinishes(),
|
||||
tr::lng_settings_cloud_password_start_title(),
|
||||
tr::lng_settings_cloud_password_start_about());
|
||||
|
||||
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
||||
|
||||
AddSkipInsteadOfField(content);
|
||||
AddSkipInsteadOfField(content);
|
||||
AddSkipInsteadOfError(content);
|
||||
|
||||
AddDoneButton(
|
||||
content,
|
||||
tr::lng_settings_cloud_password_password_subtitle()
|
||||
)->setClickedCallback([=] {
|
||||
showOther(CloudPasswordInputId());
|
||||
});
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
} // namespace CloudPassword
|
||||
|
||||
Type CloudPasswordStartId() {
|
||||
return CloudPassword::Start::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type CloudPasswordStartId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -141,6 +141,21 @@ settingsCloudPasswordLabel: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 20px;
|
||||
}
|
||||
settingsCloudPasswordLabelPadding: margins(22px, 8px, 10px, 8px);
|
||||
settingsCloudPasswordIconSize: 100px;
|
||||
|
||||
settingLocalPasscodeInputField: InputField(defaultInputField) {
|
||||
width: 256px;
|
||||
}
|
||||
settingLocalPasscodeDescription: FlatLabel(changePhoneDescription) {
|
||||
minWidth: 256px;
|
||||
}
|
||||
settingLocalPasscodeDescriptionHeight: 52px;
|
||||
settingLocalPasscodeError: FlatLabel(changePhoneError) {
|
||||
minWidth: 256px;
|
||||
}
|
||||
settingLocalPasscodeDescriptionBottomSkip: 15px;
|
||||
settingLocalPasscodeIconPadding: margins(0px, 19px, 0px, 5px);
|
||||
settingLocalPasscodeButtonPadding: margins(0px, 19px, 0px, 35px);
|
||||
|
||||
settingsInfoPhotoHeight: 161px;
|
||||
settingsInfoPhotoSize: 100px;
|
||||
|
||||
@@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "settings/settings_common.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
|
||||
#include "settings/settings_chat.h"
|
||||
#include "settings/settings_advanced.h"
|
||||
#include "settings/settings_information.h"
|
||||
@@ -17,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_calls.h"
|
||||
#include "settings/settings_experimental.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -296,6 +300,17 @@ void FillMenu(
|
||||
tr::lng_settings_bg_theme_create(tr::now),
|
||||
[=] { window->show(Box(Window::Theme::CreateBox, window)); },
|
||||
&st::menuIconChangeColors);
|
||||
} else if (type == CloudPasswordEmailConfirmId()) {
|
||||
const auto api = &controller->session().api();
|
||||
if (const auto state = api->cloudPassword().stateCurrent()) {
|
||||
if (state->unconfirmedPattern.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
addAction(
|
||||
tr::lng_settings_password_abort(tr::now),
|
||||
[=] { api->cloudPassword().clearUnconfirmedPassword(); },
|
||||
&st::menuIconCancel);
|
||||
} else {
|
||||
const auto &list = Core::App().domain().accounts();
|
||||
if (list.size() < ::Main::Domain::kMaxAccounts) {
|
||||
|
||||
@@ -78,16 +78,30 @@ public:
|
||||
[[nodiscard]] virtual rpl::producer<Type> sectionShowOther() {
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] virtual rpl::producer<> sectionShowBack() {
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] virtual rpl::producer<std::vector<Type>> removeFromStack() {
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] virtual rpl::producer<QString> title() = 0;
|
||||
virtual void sectionSaveChanges(FnMut<void()> done) {
|
||||
done();
|
||||
}
|
||||
virtual void showFinished() {
|
||||
}
|
||||
virtual void setInnerFocus() {
|
||||
}
|
||||
[[nodiscard]] virtual QPointer<Ui::RpWidget> createPinnedToTop(
|
||||
not_null<QWidget*> parent) {
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] virtual QPointer<Ui::RpWidget> createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
return nullptr;
|
||||
}
|
||||
virtual void setStepDataReference(std::any &data) {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename SectionType>
|
||||
|
||||
@@ -567,6 +567,46 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
||||
};
|
||||
}
|
||||
|
||||
void SetupTopContent(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
rpl::producer<> showFinished) {
|
||||
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(parent.get());
|
||||
const auto verticalLayout = parent->add(
|
||||
object_ptr<Ui::VerticalLayout>(parent.get()));
|
||||
|
||||
auto icon = CreateLottieIcon(
|
||||
verticalLayout,
|
||||
{
|
||||
.name = u"filters"_q,
|
||||
.sizeOverride = {
|
||||
st::settingsFilterIconSize,
|
||||
st::settingsFilterIconSize,
|
||||
},
|
||||
},
|
||||
st::settingsFilterIconPadding);
|
||||
std::move(
|
||||
showFinished
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, verticalLayout->lifetime());
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
verticalLayout,
|
||||
tr::lng_filters_about(),
|
||||
st::settingsFilterDividerLabel)),
|
||||
st::settingsFilterDividerLabelPadding);
|
||||
|
||||
verticalLayout->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
divider->setGeometry(r);
|
||||
}, divider->lifetime());
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Folders::Folders(
|
||||
@@ -591,6 +631,8 @@ void Folders::setupContent(not_null<Window::SessionController*> controller) {
|
||||
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
SetupTopContent(content, _showFinished.events());
|
||||
|
||||
_save = SetupFoldersContent(controller, content);
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
@@ -600,43 +642,4 @@ void Folders::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> Folders::createPinnedToTop(not_null<QWidget*> parent) {
|
||||
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(parent.get());
|
||||
const auto verticalLayout = Ui::CreateChild<Ui::VerticalLayout>(divider);
|
||||
|
||||
auto icon = CreateLottieIcon(
|
||||
this,
|
||||
{
|
||||
.name = u"filters"_q,
|
||||
.sizeOverride = {
|
||||
st::settingsFilterIconSize,
|
||||
st::settingsFilterIconSize,
|
||||
},
|
||||
},
|
||||
st::settingsFilterIconPadding);
|
||||
_showFinished.events(
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, verticalLayout->lifetime());
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
verticalLayout,
|
||||
tr::lng_filters_about(),
|
||||
st::settingsFilterDividerLabel)),
|
||||
st::settingsFilterDividerLabelPadding);
|
||||
|
||||
verticalLayout->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
divider->resize(s);
|
||||
}, divider->lifetime());
|
||||
|
||||
verticalLayout->resizeToWidth(parent->width());
|
||||
|
||||
return Ui::MakeWeak(not_null<Ui::RpWidget*>{ verticalLayout });
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -22,9 +22,6 @@ public:
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
|
||||
not_null<QWidget*> parent) override;
|
||||
|
||||
private:
|
||||
void setupContent(not_null<Window::SessionController*> controller);
|
||||
|
||||
|
||||
563
Telegram/SourceFiles/settings/settings_local_passcode.cpp
Normal file
563
Telegram/SourceFiles/settings/settings_local_passcode.cpp
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
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/settings_local_passcode.h"
|
||||
|
||||
#include "base/platform/base_platform_last_input.h"
|
||||
#include "boxes/auto_lock_box.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_common.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "storage/storage_domain.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Settings {
|
||||
namespace {
|
||||
|
||||
void SetPasscode(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &pass) {
|
||||
cSetPasscodeBadTries(0);
|
||||
controller->session().domain().local().setPasscode(pass.toUtf8());
|
||||
Core::App().localPasscodeChanged();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace details {
|
||||
|
||||
class LocalPasscodeEnter : public AbstractSection {
|
||||
public:
|
||||
enum class EnterType {
|
||||
Create,
|
||||
Check,
|
||||
Change,
|
||||
};
|
||||
|
||||
LocalPasscodeEnter(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
~LocalPasscodeEnter();
|
||||
|
||||
void showFinished() override;
|
||||
void setInnerFocus() override;
|
||||
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
||||
[[nodiscard]] rpl::producer<> sectionShowBack() override;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
protected:
|
||||
void setupContent();
|
||||
|
||||
[[nodiscard]] virtual EnterType enterType() const = 0;
|
||||
|
||||
private:
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::event_stream<> _setInnerFocus;
|
||||
rpl::event_stream<Type> _showOther;
|
||||
rpl::event_stream<> _showBack;
|
||||
|
||||
};
|
||||
|
||||
LocalPasscodeEnter::LocalPasscodeEnter(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: AbstractSection(parent)
|
||||
, _controller(controller) {
|
||||
}
|
||||
|
||||
rpl::producer<QString> LocalPasscodeEnter::title() {
|
||||
return tr::lng_settings_passcode_title();
|
||||
}
|
||||
|
||||
void LocalPasscodeEnter::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
const auto isCreate = (enterType() == EnterType::Create);
|
||||
const auto isCheck = (enterType() == EnterType::Check);
|
||||
[[maybe_unused]] const auto isChange = (enterType() == EnterType::Change);
|
||||
|
||||
auto icon = CreateLottieIcon(
|
||||
content,
|
||||
{
|
||||
.name = u"local_passcode_enter"_q,
|
||||
.sizeOverride = {
|
||||
st::changePhoneIconSize,
|
||||
st::changePhoneIconSize,
|
||||
},
|
||||
},
|
||||
st::settingLocalPasscodeIconPadding);
|
||||
content->add(std::move(icon.widget));
|
||||
_showFinished.events(
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, content->lifetime());
|
||||
|
||||
if (isChange) {
|
||||
CloudPassword::SetupAutoCloseTimer(
|
||||
content->lifetime(),
|
||||
[=] { _showBack.fire({}); });
|
||||
}
|
||||
|
||||
AddSkip(content);
|
||||
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
isCreate
|
||||
? tr::lng_passcode_create_title()
|
||||
: isCheck
|
||||
? tr::lng_passcode_check_title()
|
||||
: tr::lng_passcode_change_title(),
|
||||
st::changePhoneTitle)),
|
||||
st::changePhoneTitlePadding);
|
||||
|
||||
const auto addDescription = [&](rpl::producer<QString> &&text) {
|
||||
const auto &st = st::settingLocalPasscodeDescription;
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(content, std::move(text), st)),
|
||||
st::changePhoneDescriptionPadding);
|
||||
};
|
||||
|
||||
addDescription(tr::lng_passcode_about1());
|
||||
AddSkip(content);
|
||||
addDescription(tr::lng_passcode_about2());
|
||||
|
||||
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
||||
|
||||
const auto addField = [&](rpl::producer<QString> &&text) {
|
||||
const auto &st = st::settingLocalPasscodeInputField;
|
||||
auto container = object_ptr<Ui::RpWidget>(content);
|
||||
container->resize(container->width(), st.heightMin);
|
||||
const auto field = Ui::CreateChild<Ui::PasswordInput>(
|
||||
container.data(),
|
||||
st,
|
||||
std::move(text));
|
||||
|
||||
container->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
field->moveToLeft((r.width() - field->width()) / 2, 0);
|
||||
}, container->lifetime());
|
||||
|
||||
content->add(std::move(container));
|
||||
return field;
|
||||
};
|
||||
|
||||
const auto addError = [&](not_null<Ui::PasswordInput*> input) {
|
||||
const auto error = content->add(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
// Set any text to resize.
|
||||
tr::lng_language_name(tr::now),
|
||||
st::settingLocalPasscodeError)),
|
||||
st::changePhoneDescriptionPadding)->entity();
|
||||
error->hide();
|
||||
QObject::connect(input.get(), &Ui::MaskedInputField::changed, [=] {
|
||||
error->hide();
|
||||
});
|
||||
return error;
|
||||
};
|
||||
|
||||
const auto newPasscode = addField(tr::lng_passcode_enter_first());
|
||||
|
||||
const auto reenterPasscode = isCheck
|
||||
? (Ui::PasswordInput*)(nullptr)
|
||||
: addField(tr::lng_passcode_confirm_new());
|
||||
const auto error = addError(isCheck ? newPasscode : reenterPasscode);
|
||||
|
||||
const auto button = content->add(
|
||||
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
|
||||
content,
|
||||
object_ptr<Ui::RoundButton>(
|
||||
content,
|
||||
isCreate
|
||||
? tr::lng_passcode_create_button()
|
||||
: isCheck
|
||||
? tr::lng_passcode_check_button()
|
||||
: tr::lng_passcode_change_button(),
|
||||
st::changePhoneButton)),
|
||||
st::settingLocalPasscodeButtonPadding)->entity();
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
button->setClickedCallback([=] {
|
||||
const auto newText = newPasscode->text();
|
||||
const auto reenterText = reenterPasscode
|
||||
? reenterPasscode->text()
|
||||
: QString();
|
||||
if (isCreate || isChange) {
|
||||
if (newText.isEmpty()) {
|
||||
newPasscode->setFocus();
|
||||
newPasscode->showError();
|
||||
} else if (reenterText.isEmpty()) {
|
||||
reenterPasscode->setFocus();
|
||||
reenterPasscode->showError();
|
||||
} else if (newText != reenterText) {
|
||||
reenterPasscode->setFocus();
|
||||
reenterPasscode->showError();
|
||||
reenterPasscode->selectAll();
|
||||
error->show();
|
||||
error->setText(tr::lng_passcode_differ(tr::now));
|
||||
} else {
|
||||
if (isChange) {
|
||||
const auto &domain = _controller->session().domain();
|
||||
if (domain.local().checkPasscode(newText.toUtf8())) {
|
||||
newPasscode->setFocus();
|
||||
newPasscode->showError();
|
||||
newPasscode->selectAll();
|
||||
error->show();
|
||||
error->setText(tr::lng_passcode_is_same(tr::now));
|
||||
return;
|
||||
}
|
||||
}
|
||||
SetPasscode(_controller, newText);
|
||||
if (isCreate) {
|
||||
_showOther.fire(LocalPasscodeManageId());
|
||||
} else if (isChange) {
|
||||
_showBack.fire({});
|
||||
}
|
||||
}
|
||||
} else if (isCheck) {
|
||||
if (!passcodeCanTry()) {
|
||||
newPasscode->setFocus();
|
||||
newPasscode->showError();
|
||||
error->show();
|
||||
error->setText(tr::lng_flood_error(tr::now));
|
||||
return;
|
||||
}
|
||||
const auto &domain = _controller->session().domain();
|
||||
if (domain.local().checkPasscode(newText.toUtf8())) {
|
||||
cSetPasscodeBadTries(0);
|
||||
_showOther.fire(LocalPasscodeManageId());
|
||||
} else {
|
||||
cSetPasscodeBadTries(cPasscodeBadTries() + 1);
|
||||
cSetPasscodeLastTry(crl::now());
|
||||
|
||||
newPasscode->selectAll();
|
||||
newPasscode->setFocus();
|
||||
newPasscode->showError();
|
||||
error->show();
|
||||
error->setText(tr::lng_passcode_wrong(tr::now));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const auto submit = [=] {
|
||||
if (!reenterPasscode || reenterPasscode->hasFocus()) {
|
||||
button->clicked({}, Qt::LeftButton);
|
||||
} else {
|
||||
reenterPasscode->setFocus();
|
||||
}
|
||||
};
|
||||
connect(newPasscode, &Ui::MaskedInputField::submitted, submit);
|
||||
if (reenterPasscode) {
|
||||
connect(reenterPasscode, &Ui::MaskedInputField::submitted, submit);
|
||||
}
|
||||
|
||||
_setInnerFocus.events(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (newPasscode->text().isEmpty()) {
|
||||
newPasscode->setFocus();
|
||||
} else if (reenterPasscode && reenterPasscode->text().isEmpty()) {
|
||||
reenterPasscode->setFocus();
|
||||
} else {
|
||||
newPasscode->setFocus();
|
||||
}
|
||||
}, content->lifetime());
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
void LocalPasscodeEnter::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
void LocalPasscodeEnter::setInnerFocus() {
|
||||
_setInnerFocus.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<Type> LocalPasscodeEnter::sectionShowOther() {
|
||||
return _showOther.events();
|
||||
}
|
||||
|
||||
rpl::producer<> LocalPasscodeEnter::sectionShowBack() {
|
||||
return _showBack.events();
|
||||
}
|
||||
|
||||
LocalPasscodeEnter::~LocalPasscodeEnter() = default;
|
||||
|
||||
} // namespace details
|
||||
|
||||
class LocalPasscodeCreate;
|
||||
class LocalPasscodeCheck;
|
||||
class LocalPasscodeChange;
|
||||
|
||||
template <typename SectionType>
|
||||
class TypedLocalPasscodeEnter : public details::LocalPasscodeEnter {
|
||||
public:
|
||||
TypedLocalPasscodeEnter(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: details::LocalPasscodeEnter(parent, controller) {
|
||||
setupContent();
|
||||
}
|
||||
|
||||
[[nodiscard]] static Type Id() {
|
||||
return &SectionMetaImplementation<SectionType>::Meta;
|
||||
}
|
||||
[[nodiscard]] Type id() const final override {
|
||||
return Id();
|
||||
}
|
||||
|
||||
protected:
|
||||
[[nodiscard]] EnterType enterType() const final override {
|
||||
if constexpr (std::is_same_v<SectionType, LocalPasscodeCreate>) {
|
||||
return EnterType::Create;
|
||||
}
|
||||
if constexpr (std::is_same_v<SectionType, LocalPasscodeCheck>) {
|
||||
return EnterType::Check;
|
||||
}
|
||||
if constexpr (std::is_same_v<SectionType, LocalPasscodeChange>) {
|
||||
return EnterType::Change;
|
||||
}
|
||||
return EnterType::Create;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class LocalPasscodeCreate final
|
||||
: public TypedLocalPasscodeEnter<LocalPasscodeCreate> {
|
||||
public:
|
||||
using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter;
|
||||
|
||||
};
|
||||
|
||||
class LocalPasscodeCheck final
|
||||
: public TypedLocalPasscodeEnter<LocalPasscodeCheck> {
|
||||
public:
|
||||
using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter;
|
||||
|
||||
};
|
||||
|
||||
class LocalPasscodeChange final
|
||||
: public TypedLocalPasscodeEnter<LocalPasscodeChange> {
|
||||
public:
|
||||
using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter;
|
||||
|
||||
};
|
||||
|
||||
class LocalPasscodeManage : public Section<LocalPasscodeManage> {
|
||||
public:
|
||||
LocalPasscodeManage(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
~LocalPasscodeManage();
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
void showFinished() override;
|
||||
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
||||
[[nodiscard]] rpl::producer<> sectionShowBack() override;
|
||||
|
||||
[[nodiscard]] rpl::producer<std::vector<Type>> removeFromStack() override;
|
||||
|
||||
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) override;
|
||||
|
||||
private:
|
||||
void setupContent();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
rpl::variable<bool> _isBottomFillerShown;
|
||||
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::event_stream<Type> _showOther;
|
||||
rpl::event_stream<> _showBack;
|
||||
|
||||
};
|
||||
|
||||
LocalPasscodeManage::LocalPasscodeManage(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: Section(parent)
|
||||
, _controller(controller) {
|
||||
setupContent();
|
||||
}
|
||||
|
||||
rpl::producer<QString> LocalPasscodeManage::title() {
|
||||
return tr::lng_settings_passcode_title();
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<Type>> LocalPasscodeManage::removeFromStack() {
|
||||
return rpl::single(std::vector<Type>{
|
||||
LocalPasscodeManage::Id(),
|
||||
LocalPasscodeCreate::Id(),
|
||||
LocalPasscodeCheck::Id(),
|
||||
LocalPasscodeChange::Id(),
|
||||
});
|
||||
}
|
||||
|
||||
void LocalPasscodeManage::setupContent() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
struct State {
|
||||
rpl::event_stream<> autoLockBoxClosing;
|
||||
};
|
||||
const auto state = content->lifetime().make_state<State>();
|
||||
|
||||
CloudPassword::SetupAutoCloseTimer(
|
||||
content->lifetime(),
|
||||
[=] { _showBack.fire({}); });
|
||||
|
||||
AddSkip(content);
|
||||
|
||||
AddButton(
|
||||
content,
|
||||
tr::lng_passcode_change(),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconLock, kIconLightBlue }
|
||||
)->addClickHandler([=] {
|
||||
_showOther.fire(LocalPasscodeChange::Id());
|
||||
});
|
||||
|
||||
auto autolockLabel = state->autoLockBoxClosing.events_starting_with(
|
||||
{}
|
||||
) | rpl::map([] {
|
||||
const auto autolock = Core::App().settings().autoLock();
|
||||
const auto hours = autolock / 3600;
|
||||
const auto minutes = (autolock - (hours * 3600)) / 60;
|
||||
|
||||
return (hours && minutes)
|
||||
? tr::lng_passcode_autolock_hours_minutes(
|
||||
tr::now,
|
||||
lt_hours_count,
|
||||
QString::number(hours),
|
||||
lt_minutes_count,
|
||||
QString::number(minutes))
|
||||
: minutes
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_hours(tr::now, lt_count, hours);
|
||||
});
|
||||
|
||||
AddButtonWithLabel(
|
||||
content,
|
||||
(base::Platform::LastUserInputTimeSupported()
|
||||
? tr::lng_passcode_autolock_away
|
||||
: tr::lng_passcode_autolock_inactive)(),
|
||||
std::move(autolockLabel),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconTimer, kIconGreen }
|
||||
)->addClickHandler([=] {
|
||||
const auto box = _controller->show(Box<AutoLockBox>());
|
||||
box->boxClosing(
|
||||
) | rpl::start_to_stream(state->autoLockBoxClosing, box->lifetime());
|
||||
});
|
||||
|
||||
AddSkip(content);
|
||||
|
||||
using Divider = CloudPassword::OneEdgeBoxContentDivider;
|
||||
const auto divider = Ui::CreateChild<Divider>(this);
|
||||
divider->lower();
|
||||
const auto about = content->add(
|
||||
object_ptr<Ui::PaddingWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
rpl::combine(
|
||||
tr::lng_passcode_about1(),
|
||||
tr::lng_passcode_about3()
|
||||
) | rpl::map([](const QString &s1, const QString &s2) {
|
||||
return s1 + "\n\n" + s2;
|
||||
}),
|
||||
st::boxDividerLabel),
|
||||
st::settingsDividerLabelPadding));
|
||||
about->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
divider->setGeometry(r);
|
||||
}, divider->lifetime());
|
||||
_isBottomFillerShown.value(
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
divider->skipEdge(Qt::BottomEdge, shown);
|
||||
}, divider->lifetime());
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> LocalPasscodeManage::createPinnedToBottom(
|
||||
not_null<Ui::RpWidget*> parent) {
|
||||
auto callback = [=] {
|
||||
_controller->show(
|
||||
Ui::MakeConfirmBox({
|
||||
.text = tr::lng_settings_passcode_disable_sure(),
|
||||
.confirmed = [=](Fn<void()> &&close) {
|
||||
SetPasscode(_controller, QString());
|
||||
|
||||
close();
|
||||
_showBack.fire({});
|
||||
},
|
||||
.confirmText = tr::lng_settings_auto_night_disable(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
auto bottomButton = CloudPassword::CreateBottomDisableButton(
|
||||
parent,
|
||||
geometryValue(),
|
||||
tr::lng_settings_passcode_disable(),
|
||||
std::move(callback));
|
||||
|
||||
_isBottomFillerShown = base::take(bottomButton.isBottomFillerShown);
|
||||
|
||||
return bottomButton.content;
|
||||
}
|
||||
|
||||
void LocalPasscodeManage::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<Type> LocalPasscodeManage::sectionShowOther() {
|
||||
return _showOther.events();
|
||||
}
|
||||
|
||||
rpl::producer<> LocalPasscodeManage::sectionShowBack() {
|
||||
return _showBack.events();
|
||||
}
|
||||
|
||||
LocalPasscodeManage::~LocalPasscodeManage() = default;
|
||||
|
||||
Type LocalPasscodeCreateId() {
|
||||
return LocalPasscodeCreate::Id();
|
||||
}
|
||||
|
||||
Type LocalPasscodeCheckId() {
|
||||
return LocalPasscodeCheck::Id();
|
||||
}
|
||||
|
||||
Type LocalPasscodeManageId() {
|
||||
return LocalPasscodeManage::Id();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
19
Telegram/SourceFiles/settings/settings_local_passcode.h
Normal file
19
Telegram/SourceFiles/settings/settings_local_passcode.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Type LocalPasscodeCreateId();
|
||||
Type LocalPasscodeCheckId();
|
||||
Type LocalPasscodeManageId();
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -115,6 +115,9 @@ Cover::Cover(
|
||||
_name->setSelectable(true);
|
||||
_name->setContextCopyText(tr::lng_profile_copy_fullname(tr::now));
|
||||
|
||||
_phone->setSelectable(true);
|
||||
_phone->setContextCopyText(tr::lng_profile_copy_phone(tr::now));
|
||||
|
||||
initViewers();
|
||||
setupChildGeometry();
|
||||
|
||||
|
||||
@@ -13,11 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_self_destruct.h"
|
||||
#include "api/api_sensitive_content.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_input.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_start.h"
|
||||
#include "settings/settings_blocked_peers.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_local_passcode.h"
|
||||
#include "settings/settings_privacy_controllers.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/edit_privacy_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "boxes/auto_lock_box.h"
|
||||
@@ -223,380 +226,104 @@ void SetupArchiveAndMute(
|
||||
|
||||
void SetupLocalPasscode(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::lng_settings_passcode_title());
|
||||
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Fn<void(Type)> showOther) {
|
||||
auto has = rpl::single(rpl::empty) | rpl::then(
|
||||
controller->session().domain().local().localPasscodeChanged()
|
||||
) | rpl::map([=] {
|
||||
return controller->session().domain().local().hasLocalPasscode();
|
||||
});
|
||||
auto text = rpl::combine(
|
||||
tr::lng_passcode_change(),
|
||||
tr::lng_passcode_turn_on(),
|
||||
base::duplicate(has),
|
||||
[](const QString &change, const QString &create, bool has) {
|
||||
return has ? change : create;
|
||||
auto label = rpl::combine(
|
||||
tr::lng_settings_cloud_password_on(),
|
||||
tr::lng_settings_cloud_password_off(),
|
||||
std::move(has),
|
||||
[](const QString &on, const QString &off, bool has) {
|
||||
return has ? on : off;
|
||||
});
|
||||
AddButton(
|
||||
AddButtonWithLabel(
|
||||
container,
|
||||
std::move(text),
|
||||
tr::lng_settings_passcode_title(),
|
||||
std::move(label),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconLock, kIconGreen }
|
||||
)->addClickHandler([=] {
|
||||
controller->show(Box<PasscodeBox>(&controller->session(), false));
|
||||
if (controller->session().domain().local().hasLocalPasscode()) {
|
||||
showOther(LocalPasscodeCheckId());
|
||||
} else {
|
||||
showOther(LocalPasscodeCreateId());
|
||||
}
|
||||
});
|
||||
|
||||
const auto wrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
const auto inner = wrap->entity();
|
||||
AddButton(
|
||||
inner,
|
||||
tr::lng_settings_passcode_disable(),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconMinus, kIconRed }
|
||||
)->addClickHandler([=] {
|
||||
controller->show(Box<PasscodeBox>(&controller->session(), true));
|
||||
});
|
||||
|
||||
const auto autoLockBoxClosing =
|
||||
container->lifetime().make_state<rpl::event_stream<>>();
|
||||
const auto label = base::Platform::LastUserInputTimeSupported()
|
||||
? tr::lng_passcode_autolock_away
|
||||
: tr::lng_passcode_autolock_inactive;
|
||||
auto value = autoLockBoxClosing->events_starting_with(
|
||||
{}
|
||||
) | rpl::map([] {
|
||||
const auto autolock = Core::App().settings().autoLock();
|
||||
const auto hours = autolock / 3600;
|
||||
const auto minutes = (autolock - (hours * 3600)) / 60;
|
||||
|
||||
return (hours && minutes)
|
||||
? tr::lng_passcode_autolock_hours_minutes(
|
||||
tr::now,
|
||||
lt_hours_count,
|
||||
QString::number(hours),
|
||||
lt_minutes_count,
|
||||
QString::number(minutes))
|
||||
: minutes
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_hours(tr::now, lt_count, hours);
|
||||
});
|
||||
|
||||
AddButtonWithLabel(
|
||||
inner,
|
||||
label(),
|
||||
std::move(value),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconTimer, kIconGreen }
|
||||
)->addClickHandler([=] {
|
||||
const auto box = controller->show(Box<AutoLockBox>());
|
||||
box->boxClosing(
|
||||
) | rpl::start_to_stream(*autoLockBoxClosing, box->lifetime());
|
||||
});
|
||||
|
||||
wrap->toggleOn(base::duplicate(has));
|
||||
}
|
||||
|
||||
void SetupCloudPassword(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Fn<void(Type)> showOther) {
|
||||
using namespace rpl::mappers;
|
||||
using State = Core::CloudPasswordState;
|
||||
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::lng_settings_password_title());
|
||||
|
||||
const auto session = &controller->session();
|
||||
auto has = rpl::single(
|
||||
false
|
||||
) | rpl::then(controller->session().api().cloudPassword().state(
|
||||
) | rpl::map([](const State &state) {
|
||||
return state.request
|
||||
|| state.unknownAlgorithm
|
||||
|| !state.unconfirmedPattern.isEmpty();
|
||||
})) | rpl::distinct_until_changed();
|
||||
auto pattern = session->api().cloudPassword().state(
|
||||
) | rpl::map([](const State &state) {
|
||||
return state.unconfirmedPattern;
|
||||
});
|
||||
auto confirmation = rpl::single(
|
||||
tr::lng_profile_loading(tr::now)
|
||||
) | rpl::then(rpl::duplicate(
|
||||
pattern
|
||||
) | rpl::filter([](const QString &pattern) {
|
||||
return !pattern.isEmpty();
|
||||
}) | rpl::map([](const QString &pattern) {
|
||||
return tr::lng_cloud_password_waiting_code(tr::now, lt_email, pattern);
|
||||
}));
|
||||
auto unconfirmed = rpl::duplicate(
|
||||
pattern
|
||||
) | rpl::map([](const QString &pattern) {
|
||||
return !pattern.isEmpty();
|
||||
});
|
||||
auto noconfirmed = rpl::single(
|
||||
true
|
||||
) | rpl::then(rpl::duplicate(
|
||||
unconfirmed
|
||||
));
|
||||
auto resetAt = session->api().cloudPassword().state(
|
||||
) | rpl::map([](const State &state) {
|
||||
return state.pendingResetDate;
|
||||
});
|
||||
const auto label = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
|
||||
container,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
base::duplicate(confirmation),
|
||||
st::settingsCloudPasswordLabel),
|
||||
QMargins(
|
||||
st::settingsButtonNoIcon.padding.left(),
|
||||
st::settingsButtonNoIcon.padding.top(),
|
||||
st::settingsButtonNoIcon.padding.right(),
|
||||
(st::settingsButtonNoIcon.height
|
||||
- st::settingsCloudPasswordLabel.style.font->height
|
||||
+ st::settingsButtonNoIcon.padding.bottom()))));
|
||||
label->toggleOn(base::duplicate(noconfirmed))->setDuration(0);
|
||||
|
||||
std::move(
|
||||
confirmation
|
||||
) | rpl::start_with_next([=] {
|
||||
container->resizeToWidth(container->width());
|
||||
}, label->lifetime());
|
||||
|
||||
auto text = rpl::combine(
|
||||
tr::lng_cloud_password_set(),
|
||||
tr::lng_cloud_password_edit(),
|
||||
base::duplicate(has)
|
||||
) | rpl::map([](const QString &set, const QString &edit, bool has) {
|
||||
return has ? edit : set;
|
||||
});
|
||||
const auto change = container->add(
|
||||
object_ptr<Ui::SlideWrap<Button>>(
|
||||
container,
|
||||
CreateButton(
|
||||
container,
|
||||
std::move(text),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconKey, kIconLightBlue })));
|
||||
change->toggleOn(rpl::duplicate(
|
||||
noconfirmed
|
||||
) | rpl::map(
|
||||
!_1
|
||||
))->setDuration(0);
|
||||
change->entity()->addClickHandler([=] {
|
||||
if (CheckEditCloudPassword(session)) {
|
||||
controller->show(EditCloudPasswordBox(session));
|
||||
} else {
|
||||
controller->show(CloudPasswordAppOutdatedBox());
|
||||
}
|
||||
});
|
||||
|
||||
const auto confirm = container->add(
|
||||
object_ptr<Ui::SlideWrap<Button>>(
|
||||
container,
|
||||
CreateButton(
|
||||
container,
|
||||
tr::lng_cloud_password_confirm(),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconEmail, kIconLightOrange })));
|
||||
confirm->toggleOn(rpl::single(
|
||||
false
|
||||
) | rpl::then(rpl::duplicate(
|
||||
unconfirmed
|
||||
)))->setDuration(0);
|
||||
confirm->entity()->addClickHandler([=] {
|
||||
const auto state = session->api().cloudPassword().stateCurrent();
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
auto validation = ConfirmRecoveryEmail(
|
||||
&controller->session(),
|
||||
state->unconfirmedPattern);
|
||||
|
||||
std::move(
|
||||
validation.reloadRequests
|
||||
) | rpl::start_with_next([=] {
|
||||
session->api().cloudPassword().reload();
|
||||
}, validation.box->lifetime());
|
||||
|
||||
std::move(
|
||||
validation.cancelRequests
|
||||
) | rpl::start_with_next([=] {
|
||||
session->api().cloudPassword().clearUnconfirmedPassword();
|
||||
}, validation.box->lifetime());
|
||||
|
||||
controller->show(std::move(validation.box));
|
||||
});
|
||||
|
||||
const auto remove = [=] {
|
||||
if (CheckEditCloudPassword(session)) {
|
||||
RemoveCloudPassword(controller);
|
||||
} else {
|
||||
controller->show(CloudPasswordAppOutdatedBox());
|
||||
}
|
||||
enum class PasswordState {
|
||||
Loading,
|
||||
On,
|
||||
Off,
|
||||
Unconfirmed,
|
||||
};
|
||||
const auto disable = container->add(
|
||||
object_ptr<Ui::SlideWrap<Button>>(
|
||||
container,
|
||||
CreateButton(
|
||||
container,
|
||||
tr::lng_settings_password_disable(),
|
||||
st::settingsButton,
|
||||
{ &st::settingsIconMinus, kIconRed })));
|
||||
disable->toggleOn(rpl::combine(
|
||||
rpl::duplicate(has),
|
||||
rpl::duplicate(noconfirmed),
|
||||
_1 && !_2));
|
||||
disable->entity()->addClickHandler(remove);
|
||||
const auto session = &controller->session();
|
||||
auto passwordState = rpl::single(
|
||||
PasswordState::Loading
|
||||
) | rpl::then(session->api().cloudPassword().state(
|
||||
) | rpl::map([](const State &state) {
|
||||
return (!state.unconfirmedPattern.isEmpty())
|
||||
? PasswordState::Unconfirmed
|
||||
: state.hasPassword
|
||||
? PasswordState::On
|
||||
: PasswordState::Off;
|
||||
})) | rpl::distinct_until_changed();
|
||||
|
||||
auto resetInSeconds = rpl::duplicate(
|
||||
resetAt
|
||||
) | rpl::filter([](TimeId time) {
|
||||
return time != 0;
|
||||
}) | rpl::map([](TimeId time) {
|
||||
return rpl::single(rpl::empty) | rpl::then(base::timer_each(
|
||||
999
|
||||
)) | rpl::map([=] {
|
||||
const auto now = base::unixtime::now();
|
||||
return (time - now);
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::take_while([](TimeId left) {
|
||||
return left > 0;
|
||||
}) | rpl::then(rpl::single(TimeId(0)));
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_spawning(container->lifetime());
|
||||
|
||||
auto resetText = rpl::duplicate(
|
||||
resetInSeconds
|
||||
) | rpl::map([](TimeId left) {
|
||||
return (left > 0);
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::map([](bool waiting) {
|
||||
return waiting
|
||||
? tr::lng_cloud_password_reset_in()
|
||||
: tr::lng_cloud_password_reset_ready();
|
||||
}) | rpl::flatten_latest();
|
||||
|
||||
constexpr auto kMinute = 60;
|
||||
constexpr auto kHour = 3600;
|
||||
constexpr auto kDay = 86400;
|
||||
auto resetLabel = rpl::duplicate(
|
||||
resetInSeconds
|
||||
) | rpl::map([](TimeId left) {
|
||||
return (left >= kDay)
|
||||
? ((left / kDay) * kDay)
|
||||
: (left >= kHour)
|
||||
? ((left / kHour) * kHour)
|
||||
: (left >= kMinute)
|
||||
? ((left / kMinute) * kMinute)
|
||||
: left;
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::map([](TimeId left) {
|
||||
const auto days = left / kDay;
|
||||
const auto hours = left / kHour;
|
||||
const auto minutes = left / kMinute;
|
||||
return days
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: hours
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: minutes
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: left
|
||||
? tr::lng_seconds(tr::now, lt_count, left)
|
||||
: QString();
|
||||
auto label = rpl::duplicate(
|
||||
passwordState
|
||||
) | rpl::map([=](PasswordState state) {
|
||||
return (state == PasswordState::Loading)
|
||||
? tr::lng_profile_loading(tr::now)
|
||||
: (state == PasswordState::On)
|
||||
? tr::lng_settings_cloud_password_on(tr::now)
|
||||
: tr::lng_settings_cloud_password_off(tr::now);
|
||||
});
|
||||
|
||||
const auto reset = container->add(
|
||||
object_ptr<Ui::SlideWrap<Button>>(
|
||||
container,
|
||||
CreateButton(
|
||||
container,
|
||||
rpl::duplicate(resetText),
|
||||
st::settingsButton))
|
||||
)->setDuration(0);
|
||||
CreateRightLabel(
|
||||
reset->entity(),
|
||||
std::move(resetLabel),
|
||||
AddButtonWithLabel(
|
||||
container,
|
||||
tr::lng_settings_cloud_password_start_title(),
|
||||
std::move(label),
|
||||
st::settingsButton,
|
||||
std::move(resetText));
|
||||
|
||||
reset->toggleOn(rpl::duplicate(
|
||||
resetAt
|
||||
) | rpl::map([](TimeId time) {
|
||||
return time != 0;
|
||||
}));
|
||||
const auto sent = std::make_shared<bool>(false);
|
||||
reset->entity()->addClickHandler([=] {
|
||||
const auto api = &session->api();
|
||||
const auto state = api->cloudPassword().stateCurrent();
|
||||
const auto date = state ? state->pendingResetDate : TimeId(0);
|
||||
if (!date || *sent) {
|
||||
{ &st::settingsIconKey, kIconLightBlue }
|
||||
)->addClickHandler([=, passwordState = base::duplicate(passwordState)] {
|
||||
const auto state = rpl::variable<PasswordState>(
|
||||
base::duplicate(passwordState)).current();
|
||||
if (state == PasswordState::Loading) {
|
||||
return;
|
||||
} else if (base::unixtime::now() >= date) {
|
||||
*sent = true;
|
||||
api->cloudPassword().resetPassword(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
*sent = false;
|
||||
}, [=] {
|
||||
*sent = false;
|
||||
}, container->lifetime());
|
||||
} else {
|
||||
const auto cancel = [=] {
|
||||
Ui::hideLayer();
|
||||
*sent = true;
|
||||
api->cloudPassword().cancelResetPassword(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
*sent = false;
|
||||
}, [=] {
|
||||
*sent = false;
|
||||
}, container->lifetime());
|
||||
};
|
||||
Ui::show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_cloud_password_reset_cancel_sure(),
|
||||
.confirmed = cancel,
|
||||
.confirmText = tr::lng_box_yes(),
|
||||
.cancelText = tr::lng_box_no(),
|
||||
}));
|
||||
} else if (state == PasswordState::On) {
|
||||
showOther(CloudPasswordInputId());
|
||||
} else if (state == PasswordState::Off) {
|
||||
showOther(CloudPasswordStartId());
|
||||
} else if (state == PasswordState::Unconfirmed) {
|
||||
showOther(CloudPasswordEmailConfirmId());
|
||||
}
|
||||
});
|
||||
|
||||
const auto abort = container->add(
|
||||
object_ptr<Ui::SlideWrap<Button>>(
|
||||
container,
|
||||
CreateButton(
|
||||
container,
|
||||
tr::lng_settings_password_abort(),
|
||||
st::settingsAttentionButton)));
|
||||
abort->toggleOn(rpl::combine(
|
||||
rpl::duplicate(has),
|
||||
rpl::duplicate(noconfirmed),
|
||||
_1 && _2));
|
||||
abort->entity()->addClickHandler(remove);
|
||||
|
||||
const auto reloadOnActivation = [=](Qt::ApplicationState state) {
|
||||
if (label->toggled() && state == Qt::ApplicationActive) {
|
||||
if (/*label->toggled() && */state == Qt::ApplicationActive) {
|
||||
controller->session().api().cloudPassword().reload();
|
||||
}
|
||||
};
|
||||
QObject::connect(
|
||||
static_cast<QGuiApplication*>(QCoreApplication::instance()),
|
||||
&QGuiApplication::applicationStateChanged,
|
||||
label,
|
||||
reloadOnActivation);
|
||||
|
||||
session->api().cloudPassword().reload();
|
||||
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
AddDividerText(container, tr::lng_settings_cloud_password_start_about());
|
||||
}
|
||||
|
||||
void SetupSensitiveContent(
|
||||
@@ -826,8 +553,8 @@ void SetupSecurity(
|
||||
container,
|
||||
rpl::duplicate(updateTrigger),
|
||||
showOther);
|
||||
SetupLocalPasscode(controller, container);
|
||||
SetupCloudPassword(controller, container);
|
||||
SetupLocalPasscode(controller, container, showOther);
|
||||
SetupCloudPassword(controller, container, showOther);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -848,12 +575,7 @@ bool CheckEditCloudPassword(not_null<::Main::Session*> session) {
|
||||
const auto current = session->api().cloudPassword().stateCurrent();
|
||||
Assert(current.has_value());
|
||||
|
||||
if (!current->unknownAlgorithm
|
||||
&& !v::is_null(current->newPassword)
|
||||
&& !v::is_null(current->newSecureSecret)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return !current->outdatedClient;
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> EditCloudPasswordBox(not_null<Main::Session*> session) {
|
||||
@@ -885,7 +607,7 @@ void RemoveCloudPassword(not_null<Window::SessionController*> controller) {
|
||||
const auto current = session->api().cloudPassword().stateCurrent();
|
||||
Assert(current.has_value());
|
||||
|
||||
if (!current->request) {
|
||||
if (!current->hasPassword) {
|
||||
session->api().cloudPassword().clearUnconfirmedPassword();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -405,7 +405,9 @@ QString FormatTTLTiny(float64 ttl) {
|
||||
? tr::lng_weeks_tiny(tr::now, lt_count, int(ttl / (86400 * 7)))
|
||||
: (ttl <= (86400 * 31) * 11)
|
||||
? tr::lng_months_tiny({}, lt_count, int(ttl / (86400 * 31)))
|
||||
: tr::lng_years_tiny({}, lt_count, std::round(ttl / (86400 * 365)));
|
||||
: (ttl <= 86400 * 366)
|
||||
? tr::lng_years_tiny({}, lt_count, std::round(ttl / (86400 * 365)))
|
||||
: QString();
|
||||
}
|
||||
|
||||
QString FormatMuteFor(float64 sec) {
|
||||
@@ -429,7 +431,13 @@ QString FormatMuteForTiny(float64 sec) {
|
||||
? tr::lng_weeks_tiny(tr::now, lt_count, std::round(sec / (86400 * 7)))
|
||||
: (sec <= (86400 * 31) * 11)
|
||||
? tr::lng_months_tiny({}, lt_count, std::round(sec / (86400 * 31)))
|
||||
: tr::lng_years_tiny({}, lt_count, std::round(sec / (86400 * 365)));
|
||||
: (sec <= 86400 * 366)
|
||||
? tr::lng_years_tiny({}, lt_count, std::round(sec / (86400 * 365)))
|
||||
: QString();
|
||||
}
|
||||
|
||||
QString FormatResetCloudPasswordIn(float64 sec) {
|
||||
return (sec >= 3600) ? FormatTTL(sec) : FormatDurationText(sec);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -31,6 +31,7 @@ inline constexpr auto FileStatusSizeFailed = 0x7FFFFFF2;
|
||||
[[nodiscard]] QString FormatTTLTiny(float64 ttl);
|
||||
[[nodiscard]] QString FormatMuteFor(float64 sec);
|
||||
[[nodiscard]] QString FormatMuteForTiny(float64 sec);
|
||||
[[nodiscard]] QString FormatResetCloudPasswordIn(float64 sec);
|
||||
|
||||
struct CurrencyRule {
|
||||
const char *international = "";
|
||||
|
||||
2
Telegram/ThirdParty/tgcalls
vendored
2
Telegram/ThirdParty/tgcalls
vendored
Submodule Telegram/ThirdParty/tgcalls updated: c07b0b7428...a12aa52167
@@ -970,8 +970,9 @@ win:
|
||||
release:
|
||||
cmake --build build --config RelWithDebInfo --parallel
|
||||
mac:
|
||||
git clone -b 1.22.0 https://github.com/kcat/openal-soft.git
|
||||
git clone https://github.com/kcat/openal-soft.git
|
||||
cd openal-soft
|
||||
git checkout af8e756d
|
||||
CFLAGS=$UNGUARDED CPPFLAGS=$UNGUARDED cmake -B build . \\
|
||||
-D CMAKE_INSTALL_PREFIX:PATH=$USED_PREFIX \\
|
||||
-D ALSOFT_EXAMPLES=OFF \\
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 3007004
|
||||
AppVersion 3007005
|
||||
AppVersionStrMajor 3.7
|
||||
AppVersionStrSmall 3.7.4
|
||||
AppVersionStr 3.7.4
|
||||
AppVersionStrSmall 3.7.5
|
||||
AppVersionStr 3.7.5
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 3.7.4.beta
|
||||
AppVersionOriginal 3.7.5.beta
|
||||
|
||||
Submodule Telegram/lib_ui updated: 9b6e11db62...83553d0826
@@ -1,3 +1,9 @@
|
||||
3.7.5 beta (12.05.22)
|
||||
|
||||
- Improve cloud password management design.
|
||||
- Fix a crash in shared media search.
|
||||
- Fix audio recording on macOS.
|
||||
|
||||
3.7.4 beta (03.05.22)
|
||||
|
||||
- More icons for chat folders.
|
||||
|
||||
Reference in New Issue
Block a user