Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26e023058c | ||
|
|
6236590ca4 | ||
|
|
ea51f976f2 | ||
|
|
719f3428ec | ||
|
|
2df4d19474 | ||
|
|
2a409e3734 | ||
|
|
0171a4e874 | ||
|
|
59e5ffe743 | ||
|
|
2bcbb5a5be | ||
|
|
5b4694a4eb | ||
|
|
54d6673d0b | ||
|
|
e07a7a4b4c | ||
|
|
f2d11e7432 | ||
|
|
1a115cc7e5 | ||
|
|
a00941f7ce | ||
|
|
634d21e486 | ||
|
|
95d8742e3c | ||
|
|
bd8dee0972 | ||
|
|
b34099f49e | ||
|
|
0380e66c30 | ||
|
|
907b6f0a78 | ||
|
|
21f4bbbe7b | ||
|
|
69d9072ff0 | ||
|
|
d5ae9bcba2 | ||
|
|
86c0dfb295 | ||
|
|
01821c30e5 | ||
|
|
7f66e0fdfe | ||
|
|
2569df9e5a | ||
|
|
5f8143e6a4 | ||
|
|
1fc7dabd3e | ||
|
|
5d18d7c813 | ||
|
|
4e8f5541af | ||
|
|
a8ac18e4fd | ||
|
|
a6c15217c0 | ||
|
|
57351dd42a | ||
|
|
58d21ff916 | ||
|
|
8e45b09083 | ||
|
|
44014e62ba | ||
|
|
ff65daa9fe | ||
|
|
ec515080b5 | ||
|
|
255dbf9405 | ||
|
|
aebb40dc1e | ||
|
|
b20c2b4774 | ||
|
|
3b3a705a67 | ||
|
|
711aa51046 | ||
|
|
4d54cf1370 | ||
|
|
e023092744 | ||
|
|
2e421e8aed | ||
|
|
afe9d38c48 | ||
|
|
3f751bfbb0 | ||
|
|
b1f33890d6 | ||
|
|
92333e982c | ||
|
|
16ca2d39c5 | ||
|
|
977dee3599 | ||
|
|
546766fb13 | ||
|
|
ddf4a36bdc | ||
|
|
fa3a76b3d8 | ||
|
|
d5de064019 | ||
|
|
37b018257e | ||
|
|
14034c255e | ||
|
|
cbbccd0364 | ||
|
|
b8204a317d | ||
|
|
499e3113b9 | ||
|
|
656e4869e6 | ||
|
|
defec611e3 | ||
|
|
49def354bd | ||
|
|
712b3f481c | ||
|
|
b3a723c871 | ||
|
|
de16a66a4a | ||
|
|
b0f191515a | ||
|
|
89ccaccb88 | ||
|
|
1f070da202 | ||
|
|
963e969d2a | ||
|
|
4734700ac5 | ||
|
|
d9da2edd7c | ||
|
|
6d48ca850e | ||
|
|
3e7ac7eb26 | ||
|
|
520a644150 | ||
|
|
3a56b7cabd | ||
|
|
efa72578cd | ||
|
|
b6087ce7ce | ||
|
|
537400d8b2 | ||
|
|
4c9931ab02 | ||
|
|
0a4038d061 | ||
|
|
2d5188b968 | ||
|
|
4bab7583ba | ||
|
|
b2f29b674d | ||
|
|
574f4a73cb | ||
|
|
05e3ddce0c | ||
|
|
3c101b0a50 | ||
|
|
e998bd0b3f | ||
|
|
251176df47 | ||
|
|
97c15865a5 | ||
|
|
9d4558de2b | ||
|
|
38f7f48c17 | ||
|
|
9534121676 | ||
|
|
10b76d921b |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,3 +10,6 @@
|
||||
[submodule "Telegram/ThirdParty/Catch"]
|
||||
path = Telegram/ThirdParty/Catch
|
||||
url = https://github.com/philsquared/Catch
|
||||
[submodule "Telegram/ThirdParty/crl"]
|
||||
path = Telegram/ThirdParty/crl
|
||||
url = https://github.com/telegramdesktop/crl.git
|
||||
|
||||
@@ -196,7 +196,6 @@ outSemiboldPalette: TextPalette(outTextPalette) {
|
||||
selectLinkFg: msgOutServiceFgSelected;
|
||||
}
|
||||
|
||||
mediaPadding: margins(0px, 0px, 0px, 0px);
|
||||
mediaCaptionSkip: 5px;
|
||||
mediaInBubbleSkip: 5px;
|
||||
mediaThumbSize: 48px;
|
||||
|
||||
@@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_menu_about" = "About";
|
||||
"lng_menu_update" = "Update";
|
||||
"lng_menu_back" = "Back";
|
||||
"lng_menu_night_mode" = "Night mode";
|
||||
"lng_menu_night_mode" = "Night Mode";
|
||||
|
||||
"lng_disable_notifications_from_tray" = "Disable notifications";
|
||||
"lng_enable_notifications_from_tray" = "Enable notifications";
|
||||
@@ -125,9 +125,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_flood_error" = "Too many tries. Please try again later.";
|
||||
"lng_gif_error" = "An error has occurred while reading GIF animation :(";
|
||||
"lng_edit_error" = "You cannot edit this message";
|
||||
"lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining.";
|
||||
"lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining this one.";
|
||||
"lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again.";
|
||||
"lng_error_start_minimized_passcoded" = "You have set a local passcode, so the app can't be launched minimized. App will ask you to enter the passcode before it can start working.";
|
||||
"lng_error_start_minimized_passcoded" = "You have set a local passcode, so Telegram Desktop can't be launched minimised; it will ask you to enter your passcode before it can start working.";
|
||||
"lng_error_pinned_max#one" = "Sorry, you can pin no more than {count} chat to the top.";
|
||||
"lng_error_pinned_max#other" = "Sorry, you can pin no more than {count} chats to the top.";
|
||||
"lng_error_public_groups_denied" = "Unfortunately, you were banned from participating in public groups.\n{more_info}";
|
||||
@@ -137,8 +137,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them.";
|
||||
"lng_error_cant_ban_admin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them.";
|
||||
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
|
||||
"lng_sure_add_admin_unban" = "This user is currently restricted or banned in this group. Are you sure you want to unban and promote them?";
|
||||
"lng_sure_ban_admin" = "This user is an admin in this group. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
|
||||
"lng_sure_add_admin_unban" = "This user is currently restricted or banned. Are you sure you want to unban and promote them?";
|
||||
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_ban_user_group" = "Ban {user} in the group?";
|
||||
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
|
||||
"lng_sure_enable" = "Enable";
|
||||
@@ -166,8 +167,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_photo_caption" = "Caption";
|
||||
"lng_photos_comment" = "Comment";
|
||||
|
||||
"lng_phone_title" = "Your Phone";
|
||||
"lng_phone_desc" = "Please confirm your country code and\nenter your phone number.";
|
||||
"lng_phone_title" = "Your Phone Number";
|
||||
"lng_phone_desc" = "Please confirm your country code and\nenter your mobile phone number.";
|
||||
"lng_phone_notreg" = "If you don't have a Telegram account yet,\nplease [b]sign up[/b] with {link_start}Android / iPhone{link_end} or {signup_start}here{signup_end}";
|
||||
"lng_country_code" = "Country Code";
|
||||
"lng_bad_country_code" = "Invalid Country Code";
|
||||
@@ -179,7 +180,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below.";
|
||||
"lng_code_telegram" = "Please enter the code you've just received\nin your previous [b]Telegram[/b] app.";
|
||||
"lng_code_no_telegram" = "Send code via SMS";
|
||||
"lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}";
|
||||
"lng_code_call" = "Telegram will call you in {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Requesting a call from Telegram...";
|
||||
"lng_code_called" = "Telegram dialed your number";
|
||||
|
||||
@@ -187,7 +188,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_bad_phone_noreg" = "Phone number not registered.";
|
||||
"lng_bad_code" = "You have entered an invalid code.";
|
||||
"lng_bad_name" = "Please enter your first and last name.";
|
||||
"lng_bad_photo" = "Bad image selected.";
|
||||
"lng_bad_photo" = "Sorry, Telegram can't process that type of image.";
|
||||
|
||||
"lng_signin_title" = "Cloud password check";
|
||||
"lng_signin_desc" = "Please enter your cloud password.";
|
||||
@@ -201,8 +202,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_signin_bad_password" = "You have entered a wrong password.";
|
||||
"lng_signin_wrong_code" = "You have entered an invalid code.";
|
||||
"lng_signin_try_password" = "Having trouble accessing your e-mail?";
|
||||
"lng_signin_password_removed" = "Your cloud password was disabled.\nYou can set a new one in Settings.";
|
||||
"lng_signin_no_email_forgot" = "Since you haven't provided a recovery\ne-mail when setting up your password, your remaining options are either to remember your password or to reset your account.";
|
||||
"lng_signin_password_removed" = "Your cloud password was disabled.\nYou can set up a new one in Settings.";
|
||||
"lng_signin_no_email_forgot" = "Since you didn't provide a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account.";
|
||||
"lng_signin_cant_email_forgot" = "If you can't restore access to the e-mail, your remaining options are either to remember your password or to reset your account.";
|
||||
"lng_signin_reset_account" = "Reset your account";
|
||||
"lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?";
|
||||
@@ -251,10 +252,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_username_invalid" = "This username is invalid.";
|
||||
"lng_username_occupied" = "This username is already occupied.";
|
||||
"lng_username_too_short" = "This username is too short.";
|
||||
"lng_username_bad_symbols" = "This username has bad symbols.";
|
||||
"lng_username_bad_symbols" = "Only a-z, 0-9, and underscores allowed.";
|
||||
"lng_username_available" = "This username is available.";
|
||||
"lng_username_not_found" = "User @{user} not found.";
|
||||
"lng_username_link_willbe" = "Such link will open a chat with you:";
|
||||
"lng_username_link_willbe" = "This link will open a chat with you:";
|
||||
"lng_username_link" = "This link opens a chat with you:";
|
||||
"lng_username_copied" = "Link copied to clipboard.";
|
||||
|
||||
@@ -322,9 +323,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_settings_adaptive_wide" = "Adaptive layout for wide screens";
|
||||
|
||||
"lng_backgrounds_header" = "Choose your new chat background";
|
||||
"lng_theme_sure_keep" = "Keep this color theme?";
|
||||
"lng_theme_reverting#one" = "Reverting to the old color theme in {count} second.";
|
||||
"lng_theme_reverting#other" = "Reverting to the old color theme in {count} seconds.";
|
||||
"lng_theme_sure_keep" = "Keep this theme?";
|
||||
"lng_theme_reverting#one" = "Reverting to the old theme in {count} second.";
|
||||
"lng_theme_reverting#other" = "Reverting to the old theme in {count} seconds.";
|
||||
"lng_theme_keep_changes" = "Keep changes";
|
||||
"lng_theme_revert" = "Revert";
|
||||
|
||||
@@ -339,7 +340,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_download_path_dir_radio" = "Custom folder, cleared only manually";
|
||||
"lng_download_path_choose" = "Choose download path";
|
||||
"lng_sure_clear_downloads" = "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall.";
|
||||
"lng_download_path_failed" = "File download could not be started. It could happen because of a bad download location.\n\nYou can change download path in Settings.";
|
||||
"lng_download_path_failed" = "File download could not be started.\n\nThis might be because the download location you've selected is invalid. Try changing the \"Download path\" in Settings.";
|
||||
"lng_download_path_settings" = "Settings";
|
||||
"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?";
|
||||
"lng_download_path_clearing" = "Clearing...";
|
||||
@@ -404,7 +405,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_cloud_password_bad" = "Password and hint cannot be the same.";
|
||||
"lng_cloud_password_email" = "Enter recovery e-mail";
|
||||
"lng_cloud_password_bad_email" = "Incorrect e-mail, please try other.";
|
||||
"lng_cloud_password_about" = "This password will be asked when you log in on a new device in addition to the pin code.";
|
||||
"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 e-mail?\n\nIf you forget your password, you will\nlose access to your Telegram account.";
|
||||
"lng_cloud_password_skip_email" = "Skip e-mail";
|
||||
"lng_cloud_password_almost" = "A confirmation link was sent to the\ne-mail you provided. Two-step verification will be enabled as soon as you follow that link.";
|
||||
@@ -445,9 +446,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_settings_reset_button" = "Terminate";
|
||||
"lng_settings_manage_local_storage" = "Manage local storage";
|
||||
"lng_settings_ask_question" = "Ask a Question";
|
||||
"lng_settings_ask_sure" = "Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions.";
|
||||
"lng_settings_ask_sure" = "Please note that Telegram Support is run by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions.";
|
||||
"lng_settings_faq_button" = "Go to FAQ";
|
||||
"lng_settings_ask_ok" = "Ask";
|
||||
"lng_settings_ask_ok" = "Ask a Volunteer";
|
||||
"lng_settings_faq" = "Telegram FAQ";
|
||||
"lng_settings_logout" = "Log Out";
|
||||
"lng_sure_logout" = "Are you sure you want to log out?";
|
||||
@@ -461,7 +462,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_sessions_terminate_all" = "Terminate all other sessions";
|
||||
|
||||
"lng_blocked_list_title" = "Blocked users";
|
||||
"lng_blocked_list_unknown_phone" = "unknown phone";
|
||||
"lng_blocked_list_unknown_phone" = "unknown phone number";
|
||||
"lng_blocked_list_unblock" = "Unblock";
|
||||
"lng_blocked_list_add" = "Block user";
|
||||
"lng_blocked_list_add_title" = "Select user to block";
|
||||
@@ -499,7 +500,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_edit_privacy_groups_always_title" = "Always allow";
|
||||
"lng_edit_privacy_groups_never_title" = "Never allow";
|
||||
|
||||
"lng_edit_privacy_calls_title" = "Phone calls privacy";
|
||||
"lng_edit_privacy_calls_title" = "Telegram call privacy";
|
||||
"lng_edit_privacy_calls_description" = "You can restrict who can call you:";
|
||||
"lng_edit_privacy_calls_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_calls_always#one" = "Always allow {count} user";
|
||||
@@ -577,6 +578,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_kick" = "Remove";
|
||||
"lng_profile_sure_kick" = "Remove {user} from the group?";
|
||||
"lng_profile_sure_kick_channel" = "Remove {user} from the channel?";
|
||||
"lng_profile_sure_remove_admin" = "Remove {user} from admins?";
|
||||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_photos#one" = "{count} photo";
|
||||
"lng_profile_photos#other" = "{count} photos";
|
||||
@@ -697,6 +699,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_channel_banned_status_restricted_by" = "Restricted by {user}";
|
||||
|
||||
"lng_group_blocked_list_about" = "Banned users are removed from the group and can only come back if invited by an admin.\nInvite links don't work for them.";
|
||||
"lng_channel_blocked_list_about" = "Banned users are removed from the channel.\nInvite links don't work for them.";
|
||||
|
||||
"lng_chat_all_members_admins" = "All Members Are Admins";
|
||||
"lng_chat_about_all_admins" = "Group members can add new members, edit name and photo of the group.";
|
||||
@@ -724,7 +727,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_create_channel_link_invalid" = "This link is invalid";
|
||||
"lng_create_channel_link_occupied" = "Sorry, this link is already occupied";
|
||||
"lng_create_channel_link_too_short" = "Sorry, this link is too short";
|
||||
"lng_create_channel_link_bad_symbols" = "Sorry, this link has bad symbols";
|
||||
"lng_create_channel_link_bad_symbols" = "Only 0-9, a-z, and underscores allowed.";
|
||||
"lng_create_channel_link_available" = "This link is available";
|
||||
"lng_create_channel_link_copied" = "Link copied to clipboard";
|
||||
|
||||
@@ -735,18 +738,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_failed_add_not_mutual" = "Sorry, if a person leaves a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs).";
|
||||
"lng_failed_add_not_mutual_channel" = "Sorry, if a person leaves a channel, only a mutual contact can bring them back (they need to have your phone number, and you need theirs).";
|
||||
|
||||
"lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?";
|
||||
"lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
|
||||
"lng_sure_delete_contact" = "Are you sure you want to delete {contact} from your contact list?";
|
||||
"lng_sure_delete_history" = "Are you sure you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
|
||||
"lng_sure_delete_group_history" = "Are you sure, you want to delete all message history in «{group}»?\n\nThis action cannot be undone.";
|
||||
"lng_sure_delete_and_exit" = "Are you sure, you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone.";
|
||||
"lng_sure_leave_channel" = "Are you sure, you want to leave\nthis channel?";
|
||||
"lng_sure_delete_channel" = "Are you sure, you want to delete this channel? All members will be removed and all messages will be lost.";
|
||||
"lng_sure_leave_group" = "Are you sure, you want to leave\nthis group?";
|
||||
"lng_sure_leave_channel" = "Are you sure you want to leave\nthis channel?";
|
||||
"lng_sure_delete_channel" = "Are you sure you want to delete this channel? All members will be removed and all messages will be lost.";
|
||||
"lng_sure_leave_group" = "Are you sure you want to leave this group?";
|
||||
"lng_sure_delete_group" = "Are you sure, you want to delete this group? All members will be removed and all messages will be lost.";
|
||||
"lng_sure_delete_saved_messages" = "Are you sure, you want to delete all your saved messages?\n\nThis action cannot be undone.";
|
||||
|
||||
"lng_message_empty" = "Empty Message";
|
||||
"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}";
|
||||
"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the latest version in Settings, or install it from {link}";
|
||||
|
||||
"lng_duration_seconds#one" = "{count} second";
|
||||
"lng_duration_seconds#other" = "{count} seconds";
|
||||
@@ -813,7 +816,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_migrate_reached#one" = "{count} member limit reached";
|
||||
"lng_profile_migrate_reached#other" = "{count} members limit reached";
|
||||
"lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup.";
|
||||
"lng_profile_migrate_body" = "To add more members, you can upgrade your group to a supergroup.";
|
||||
"lng_profile_migrate_learn_more" = "Learn more »";
|
||||
"lng_profile_migrate_button" = "Upgrade to supergroup";
|
||||
"lng_profile_convert_title" = "Convert to supergroup";
|
||||
@@ -864,8 +867,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_cancel_edit_post_yes" = "Yes";
|
||||
"lng_cancel_edit_post_no" = "No";
|
||||
|
||||
"lng_bot_share_location_unavailable" = "Sorry, the location sharing is currently unavailable in Telegram Desktop.";
|
||||
"lng_bot_share_phone" = "Share Phone Number?";
|
||||
"lng_bot_share_location_unavailable" = "Sorry, location sharing is currently unavailable in Telegram Desktop.";
|
||||
"lng_bot_share_phone" = "Do you want to share your phone number with this bot?";
|
||||
"lng_bot_share_phone_confirm" = "Share";
|
||||
|
||||
"lng_attach_failed" = "Failed";
|
||||
@@ -900,7 +903,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_faved_stickers_add" = "Add to Favorites";
|
||||
"lng_faved_stickers_remove" = "Remove from Favorites";
|
||||
"lng_group_stickers" = "Group stickers";
|
||||
"lng_group_stickers_description" = "You can choose sticker set which will be available for every member while in the group chat.";
|
||||
"lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat.";
|
||||
"lng_group_stickers_add" = "Choose sticker set";
|
||||
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
@@ -936,6 +939,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_stickers_group_from_featured" = "Choose from trending stickers";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
"lng_in_dlg_video" = "Video";
|
||||
"lng_in_dlg_audio_file" = "Audio file";
|
||||
"lng_in_dlg_contact" = "Contact";
|
||||
@@ -989,7 +993,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_open_this_link" = "Open this link?";
|
||||
"lng_open_link" = "Open";
|
||||
"lng_allow_bot_pass" = "Do you allow {bot_name} to pass your Telegram name and id to the web pages you open via this bot?";
|
||||
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID to the web pages you open via this bot?";
|
||||
"lng_allow_bot" = "Allow";
|
||||
|
||||
"lng_bot_start" = "Start";
|
||||
@@ -998,7 +1002,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_bot_groups_not_found" = "No groups found";
|
||||
"lng_bot_sure_invite" = "Add the bot to «{group}»?";
|
||||
"lng_bot_already_in_group" = "The bot is already a member of the group.";
|
||||
"lng_bot_choose_chat" = "Select a Chat";
|
||||
"lng_bot_choose_chat" = "Select a chat";
|
||||
"lng_bot_no_chats" = "You have no chats";
|
||||
"lng_bot_chats_not_found" = "No chats found";
|
||||
"lng_bot_sure_share_game" = "Share the game with {user}?";
|
||||
@@ -1091,18 +1095,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_context_forward_selected" = "Forward Selected";
|
||||
"lng_context_delete_selected" = "Delete Selected";
|
||||
"lng_context_clear_selection" = "Clear Selection";
|
||||
"lng_send_images_compress#one" = "Compress image";
|
||||
"lng_send_images_compress#other" = "Compress images";
|
||||
"lng_send_image_empty" = "Could not send an empty file: {name}";
|
||||
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
|
||||
"lng_send_images_selected#one" = "{count} image selected";
|
||||
"lng_send_images_selected#other" = "{count} images selected";
|
||||
"lng_send_photos#one" = "Send {count} photo";
|
||||
"lng_send_photos#other" = "Send {count} photos";
|
||||
"lng_send_separate_photos" = "Send as separate photos";
|
||||
"lng_send_separate_photos_videos" = "Send as separate media";
|
||||
"lng_send_files_selected#one" = "{count} file selected";
|
||||
"lng_send_files_selected#other" = "{count} files selected";
|
||||
"lng_send_files#one" = "Send {count} file";
|
||||
"lng_send_files#other" = "Send {count} files";
|
||||
"lng_send_album" = "Send as an album";
|
||||
"lng_send_photo" = "Send as a photo";
|
||||
"lng_send_file" = "Send as a file";
|
||||
|
||||
"lng_forward_choose" = "Choose recipient...";
|
||||
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
||||
@@ -1122,7 +1129,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_share_cant" = "Sorry, no way to share here :(";
|
||||
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
|
||||
"lng_reply_cant_forward" = "Sorry, no way to reply to an old message in supergroup :( Do you wish to forward it and add your comment?";
|
||||
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
||||
|
||||
"lng_share_title" = "Share to";
|
||||
"lng_share_copy_link" = "Copy share link";
|
||||
@@ -1131,7 +1138,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_share_game_link_copied" = "Game link copied to clipboard.";
|
||||
"lng_share_done" = "Done!";
|
||||
|
||||
"lng_contact_phone" = "Phone number";
|
||||
"lng_contact_phone" = "Phone Number";
|
||||
"lng_enter_contact_data" = "New Contact";
|
||||
"lng_edit_contact_title" = "Edit contact name";
|
||||
"lng_edit_channel_title" = "Edit channel";
|
||||
@@ -1145,8 +1152,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_add_contact" = "Create";
|
||||
"lng_add_contact_button" = "New contact";
|
||||
"lng_contacts_header" = "Contacts";
|
||||
"lng_contact_not_joined" = "Unfortunately {name} did not join Telegram yet, but you can send your friend an invitation.\n\nWe will notify you about any of your contacts who is joining Telegram.";
|
||||
"lng_try_other_contact" = "Try other";
|
||||
"lng_contact_not_joined" = "Unfortunately {name} has not joined Telegram yet, but you can send them an invitation.\n\nWe will notify you about any of your contacts who join Telegram.";
|
||||
"lng_try_other_contact" = "Try someone else";
|
||||
"lng_create_group_link" = "Link";
|
||||
"lng_create_group_invite_link" = "Invite link";
|
||||
"lng_create_group_description" = "Description (optional)";
|
||||
@@ -1233,7 +1240,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_confirm_phone_enter_code" = "Please enter the code.";
|
||||
|
||||
"lng_theme_editor_no_keys" = "No keys in the palette yet";
|
||||
"lng_theme_editor_cant_change_theme" = "You can not apply new themes while you're editing the color palette. Please close the theme editor first.";
|
||||
"lng_theme_editor_cant_change_theme" = "You can't apply a new theme while you're editing the colour palette. Please close the theme editor first.";
|
||||
"lng_theme_editor_new_keys" = "Not in the palette yet";
|
||||
"lng_theme_editor_background_image" = "Background image";
|
||||
"lng_theme_editor_saved_to_jpg" = "Saved to JPEG, {size}";
|
||||
@@ -1274,7 +1281,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_call_bar_hangup" = "End call";
|
||||
|
||||
"lng_call_box_title" = "Calls";
|
||||
"lng_call_box_about" = "You didn't make any calls yet.";
|
||||
"lng_call_box_about" = "You haven't made any Telegram calls yet.";
|
||||
"lng_call_box_status_today" = "{time}";
|
||||
"lng_call_box_status_yesterday" = "Yesterday at {time}";
|
||||
"lng_call_box_status_date" = "{date} at {time}";
|
||||
@@ -1295,7 +1302,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_player_message_yesterday" = "Yesterday at {time}";
|
||||
"lng_player_message_date" = "{date} at {time}";
|
||||
|
||||
"lng_rights_edit_admin" = "Edit administrator";
|
||||
"lng_rights_edit_admin" = "Manage permissions";
|
||||
"lng_rights_edit_admin_header" = "What can this admin do?";
|
||||
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with the same (or more limited) permissions.";
|
||||
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
|
||||
@@ -1399,9 +1406,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_admin_log_banned" = "banned {user}";
|
||||
"lng_admin_log_restricted" = "changed restrictions for {user} {until}";
|
||||
"lng_admin_log_promoted" = "changed privileges for {user}";
|
||||
"lng_admin_log_changed_stickers_group" = "{from} changed group {sticker_set}";
|
||||
"lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}";
|
||||
"lng_admin_log_changed_stickers_set" = "sticker set";
|
||||
"lng_admin_log_removed_stickers_group" = "{from} removed group sticker set";
|
||||
"lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set";
|
||||
"lng_admin_log_user_with_username" = "{name} ({mention})";
|
||||
"lng_admin_log_restricted_forever" = "indefinitely";
|
||||
"lng_admin_log_restricted_until" = "until {date}";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="1.2.0.0" />
|
||||
Version="1.2.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,0,0
|
||||
PRODUCTVERSION 1,2,0,0
|
||||
FILEVERSION 1,2,6,0
|
||||
PRODUCTVERSION 1,2,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -52,10 +52,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "1.2.0.0"
|
||||
VALUE "FileVersion", "1.2.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.2.0.0"
|
||||
VALUE "ProductVersion", "1.2.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,0,0
|
||||
PRODUCTVERSION 1,2,0,0
|
||||
FILEVERSION 1,2,6,0
|
||||
PRODUCTVERSION 1,2,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -43,10 +43,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "1.2.0.0"
|
||||
VALUE "FileVersion", "1.2.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.2.0.0"
|
||||
VALUE "ProductVersion", "1.2.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
wstring updaterName, updaterDir, updateTo, exeName;
|
||||
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
|
||||
|
||||
bool equal(const wstring &a, const wstring &b) {
|
||||
return !_wcsicmp(a.c_str(), b.c_str());
|
||||
@@ -356,6 +356,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
|
||||
if (args) {
|
||||
for (int i = 1; i < argsCount; ++i) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
if (equal(args[i], L"-update")) {
|
||||
needupdate = true;
|
||||
} else if (equal(args[i], L"-autostart")) {
|
||||
@@ -368,17 +369,25 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
} else if (equal(args[i], L"-testmode")) {
|
||||
testmode = true;
|
||||
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
writeprotected = true;
|
||||
updateTo = args[i];
|
||||
for (int i = 0, l = updateTo.size(); i < l; ++i) {
|
||||
if (updateTo[i] == L'/') {
|
||||
updateTo[i] = L'\\';
|
||||
for (int j = 0, l = updateTo.size(); j < l; ++j) {
|
||||
if (updateTo[j] == L'/') {
|
||||
updateTo[j] = L'\\';
|
||||
}
|
||||
}
|
||||
} else if (equal(args[i], L"-workdir") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
customWorkingDir = args[i];
|
||||
} else if (equal(args[i], L"-key") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
customKeyFile = args[i];
|
||||
} else if (equal(args[i], L"-exename") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
exeName = args[i];
|
||||
for (int i = 0, l = exeName.size(); i < l; ++i) {
|
||||
if (exeName[i] == L'/' || exeName[i] == L'\\') {
|
||||
for (int j = 0, l = exeName.size(); j < l; ++j) {
|
||||
if (exeName[j] == L'/' || exeName[j] == L'\\') {
|
||||
exeName = L"Telegram.exe";
|
||||
break;
|
||||
}
|
||||
@@ -391,6 +400,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
if (needupdate) writeLog(L"Need to update!");
|
||||
if (autostart) writeLog(L"From autostart!");
|
||||
if (writeprotected) writeLog(L"Write Protected folder!");
|
||||
if (!customWorkingDir.empty()) writeLog(L"Will pass custom working dir: " + customWorkingDir);
|
||||
|
||||
updaterName = args[0];
|
||||
writeLog(L"Updater name is: " + updaterName);
|
||||
@@ -428,6 +438,13 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
if (debug) targs += L" -debug";
|
||||
if (startintray) targs += L" -startintray";
|
||||
if (testmode) targs += L" -testmode";
|
||||
if (!customWorkingDir.empty()) {
|
||||
targs += L" -workdir \"" + customWorkingDir + L"\"";
|
||||
}
|
||||
if (!customKeyFile.empty()) {
|
||||
targs += L" -key \"" + customKeyFile + L"\"";
|
||||
}
|
||||
writeLog(L"Result arguments: " + targs);
|
||||
|
||||
bool executed = false;
|
||||
if (writeprotected) { // run un-elevated
|
||||
|
||||
@@ -344,9 +344,16 @@ string CurrentExecutablePath(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
|
||||
bool needupdate = true;
|
||||
bool autostart = false;
|
||||
bool debug = false;
|
||||
bool tosettings = false;
|
||||
bool startintray = false;
|
||||
bool testmode = false;
|
||||
bool customWorkingDir = false;
|
||||
|
||||
char *key = 0, *crashreport = 0;
|
||||
char *key = 0;
|
||||
char *workdir = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (equal(argv[i], "-noupdate")) {
|
||||
needupdate = false;
|
||||
@@ -360,12 +367,12 @@ int main(int argc, char *argv[]) {
|
||||
testmode = true;
|
||||
} else if (equal(argv[i], "-tosettings")) {
|
||||
tosettings = true;
|
||||
} else if (equal(argv[i], "-workdir_custom")) {
|
||||
customWorkingDir = true;
|
||||
} else if (equal(argv[i], "-key") && ++i < argc) {
|
||||
key = argv[i];
|
||||
} else if (equal(argv[i], "-workpath") && ++i < argc) {
|
||||
workDir = argv[i];
|
||||
} else if (equal(argv[i], "-crashreport") && ++i < argc) {
|
||||
crashreport = argv[i];
|
||||
workDir = workdir = argv[i];
|
||||
} else if (equal(argv[i], "-exename") && ++i < argc) {
|
||||
exeName = argv[i];
|
||||
} else if (equal(argv[i], "-exepath") && ++i < argc) {
|
||||
@@ -401,6 +408,8 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
if (needupdate) {
|
||||
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
|
||||
customWorkingDir = false;
|
||||
|
||||
writeLog("No workdir, trying to figure it out");
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {
|
||||
@@ -446,22 +455,30 @@ int main(int argc, char *argv[]) {
|
||||
string fullBinaryPath = exePath + exeName;
|
||||
strcpy(path, fullBinaryPath.c_str());
|
||||
|
||||
char *args[MaxArgsCount] = {0}, p_noupdate[] = "-noupdate", p_autostart[] = "-autostart", p_debug[] = "-debug", p_tosettings[] = "-tosettings", p_key[] = "-key", p_startintray[] = "-startintray", p_testmode[] = "-testmode";
|
||||
char *args[MaxArgsCount] = { 0 };
|
||||
char p_noupdate[] = "-noupdate";
|
||||
char p_autostart[] = "-autostart";
|
||||
char p_debug[] = "-debug";
|
||||
char p_tosettings[] = "-tosettings";
|
||||
char p_key[] = "-key";
|
||||
char p_startintray[] = "-startintray";
|
||||
char p_testmode[] = "-testmode";
|
||||
char p_workdir[] = "-workdir";
|
||||
int argIndex = 0;
|
||||
args[argIndex++] = path;
|
||||
if (crashreport) {
|
||||
args[argIndex++] = crashreport;
|
||||
} else {
|
||||
args[argIndex++] = p_noupdate;
|
||||
if (autostart) args[argIndex++] = p_autostart;
|
||||
if (debug) args[argIndex++] = p_debug;
|
||||
if (startintray) args[argIndex++] = p_startintray;
|
||||
if (testmode) args[argIndex++] = p_testmode;
|
||||
if (tosettings) args[argIndex++] = p_tosettings;
|
||||
if (key) {
|
||||
args[argIndex++] = p_key;
|
||||
args[argIndex++] = key;
|
||||
}
|
||||
args[argIndex++] = p_noupdate;
|
||||
if (autostart) args[argIndex++] = p_autostart;
|
||||
if (debug) args[argIndex++] = p_debug;
|
||||
if (startintray) args[argIndex++] = p_startintray;
|
||||
if (testmode) args[argIndex++] = p_testmode;
|
||||
if (tosettings) args[argIndex++] = p_tosettings;
|
||||
if (key) {
|
||||
args[argIndex++] = p_key;
|
||||
args[argIndex++] = key;
|
||||
}
|
||||
if (customWorkingDir && workdir) {
|
||||
args[argIndex++] = p_workdir;
|
||||
args[argIndex++] = workdir;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
switch (pid) {
|
||||
|
||||
@@ -23,7 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
NSString *appName = @"Telegram.app";
|
||||
NSString *appDir = nil;
|
||||
NSString *workDir = nil;
|
||||
NSString *crashReportArg = nil;
|
||||
|
||||
#ifdef _DEBUG
|
||||
BOOL _debug = YES;
|
||||
@@ -90,6 +89,7 @@ int main(int argc, const char * argv[]) {
|
||||
openLog();
|
||||
pid_t procId = 0;
|
||||
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO;
|
||||
BOOL customWorkingDir = NO;
|
||||
NSString *key = nil;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
@@ -102,10 +102,6 @@ int main(int argc, const char * argv[]) {
|
||||
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
|
||||
procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
|
||||
}
|
||||
} else if ([@"-crashreport" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) {
|
||||
crashReportArg = [NSString stringWithUTF8String:argv[i]];
|
||||
}
|
||||
} else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
update = NO;
|
||||
} else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
@@ -118,11 +114,16 @@ int main(int argc, const char * argv[]) {
|
||||
startInTray = YES;
|
||||
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
testMode = YES;
|
||||
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
customWorkingDir = YES;
|
||||
} else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) key = [NSString stringWithUTF8String:argv[i]];
|
||||
}
|
||||
}
|
||||
if (!workDir) workDir = appDir;
|
||||
if (!workDir) {
|
||||
workDir = appDir;
|
||||
customWorkingDir = NO;
|
||||
}
|
||||
openLog();
|
||||
NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc];
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
@@ -242,17 +243,19 @@ int main(int argc, const char * argv[]) {
|
||||
}
|
||||
|
||||
NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
|
||||
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: crashReportArg ? crashReportArg : @"-noupdate", nil];
|
||||
if (!crashReportArg) {
|
||||
if (toSettings) [args addObject:@"-tosettings"];
|
||||
if (_debug) [args addObject:@"-debug"];
|
||||
if (startInTray) [args addObject:@"-startintray"];
|
||||
if (testMode) [args addObject:@"-testmode"];
|
||||
if (autoStart) [args addObject:@"-autostart"];
|
||||
if (key) {
|
||||
[args addObject:@"-key"];
|
||||
[args addObject:key];
|
||||
}
|
||||
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil];
|
||||
if (toSettings) [args addObject:@"-tosettings"];
|
||||
if (_debug) [args addObject:@"-debug"];
|
||||
if (startInTray) [args addObject:@"-startintray"];
|
||||
if (testMode) [args addObject:@"-testmode"];
|
||||
if (autoStart) [args addObject:@"-autostart"];
|
||||
if (key) {
|
||||
[args addObject:@"-key"];
|
||||
[args addObject:key];
|
||||
}
|
||||
if (customWorkingDir) {
|
||||
[args addObject:@"-workdir"];
|
||||
[args addObject:workDir];
|
||||
}
|
||||
writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
|
||||
NSError *error = nil;
|
||||
|
||||
@@ -33,16 +33,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwidget.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "auth_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
@@ -58,6 +61,76 @@ constexpr auto kUnreadMentionsFirstRequestLimit = 10;
|
||||
constexpr auto kUnreadMentionsNextRequestLimit = 100;
|
||||
constexpr auto kSharedMediaLimit = 100;
|
||||
constexpr auto kReadFeaturedSetsTimeout = TimeMs(1000);
|
||||
constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
|
||||
|
||||
bool IsSilentPost(not_null<HistoryItem*> item, bool silent) {
|
||||
const auto history = item->history();
|
||||
return silent
|
||||
&& history->peer->isChannel()
|
||||
&& !history->peer->isMegagroup();
|
||||
}
|
||||
|
||||
MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto filenameAttribute = MTP_documentAttributeFilename(
|
||||
MTP_string(document->filename()));
|
||||
const auto dimensions = document->dimensions;
|
||||
auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
|
||||
if (dimensions.width() > 0 && dimensions.height() > 0) {
|
||||
const auto duration = document->duration();
|
||||
if (duration >= 0) {
|
||||
auto flags = MTPDdocumentAttributeVideo::Flags(0);
|
||||
if (document->isVideoMessage()) {
|
||||
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
|
||||
}
|
||||
attributes.push_back(MTP_documentAttributeVideo(
|
||||
MTP_flags(flags),
|
||||
MTP_int(duration),
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
}
|
||||
}
|
||||
if (document->type == AnimatedDocument) {
|
||||
attributes.push_back(MTP_documentAttributeAnimated());
|
||||
} else if (document->type == StickerDocument && document->sticker()) {
|
||||
attributes.push_back(MTP_documentAttributeSticker(
|
||||
MTP_flags(0),
|
||||
MTP_string(document->sticker()->alt),
|
||||
document->sticker()->set,
|
||||
MTPMaskCoords()));
|
||||
} else if (const auto song = document->song()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_title
|
||||
| MTPDdocumentAttributeAudio::Flag::f_performer;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(song->duration),
|
||||
MTP_string(song->title),
|
||||
MTP_string(song->performer),
|
||||
MTPstring()));
|
||||
} else if (const auto voice = document->voice()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_voice
|
||||
| MTPDdocumentAttributeAudio::Flag::f_waveform;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(voice->duration),
|
||||
MTPstring(),
|
||||
MTPstring(),
|
||||
MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
|
||||
}
|
||||
return MTP_vector<MTPDocumentAttribute>(attributes);
|
||||
}
|
||||
|
||||
FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) {
|
||||
const auto peer = options.history->peer;
|
||||
return FileLoadTo(
|
||||
peer->id,
|
||||
peer->notifySilentPosts(),
|
||||
options.replyTo);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -66,71 +139,40 @@ ApiWrap::ApiWrap(not_null<AuthSession*> session)
|
||||
, _messageDataResolveDelayed([this] { resolveMessageDatas(); })
|
||||
, _webPagesTimer([this] { resolveWebPages(); })
|
||||
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
|
||||
, _featuredSetsReadTimer([this] { readFeaturedSets(); }) {
|
||||
, _featuredSetsReadTimer([this] { readFeaturedSets(); })
|
||||
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout)) {
|
||||
}
|
||||
|
||||
void ApiWrap::start() {
|
||||
Window::Theme::Background()->start();
|
||||
requestAppChangelogs();
|
||||
void ApiWrap::requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
base::lambda<void(const MTPUpdates &result)> callback) {
|
||||
request(MTPhelp_GetAppChangelog(
|
||||
MTP_string(sinceVersion)
|
||||
)).done(
|
||||
callback
|
||||
).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestAppChangelogs() {
|
||||
auto oldAppVersion = Local::oldMapVersion();
|
||||
if (oldAppVersion > 0 && oldAppVersion < AppVersion) {
|
||||
_changelogSubscription = subscribe(_session->data().moreChatsLoaded(), [this, oldAppVersion] {
|
||||
auto oldVersionString = qsl("%1.%2.%3").arg(oldAppVersion / 1000000).arg((oldAppVersion % 1000000) / 1000).arg(oldAppVersion % 1000);
|
||||
request(MTPhelp_GetAppChangelog(MTP_string(oldVersionString))).done([this, oldAppVersion](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
auto resultEmpty = true;
|
||||
switch (result.type()) {
|
||||
case mtpc_updateShortMessage:
|
||||
case mtpc_updateShortChatMessage:
|
||||
case mtpc_updateShort: resultEmpty = false; break;
|
||||
case mtpc_updatesCombined: resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty(); break;
|
||||
case mtpc_updates: resultEmpty = result.c_updates().vupdates.v.isEmpty(); break;
|
||||
case mtpc_updatesTooLong:
|
||||
case mtpc_updateShortSentMessage: LOG(("API Error: Bad updates type in app changelog.")); break;
|
||||
}
|
||||
if (resultEmpty) {
|
||||
addLocalChangelogs(oldAppVersion);
|
||||
}
|
||||
}).send();
|
||||
unsubscribe(base::take(_changelogSubscription));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::addLocalChangelogs(int oldAppVersion) {
|
||||
auto addedSome = false;
|
||||
auto addLocalChangelog = [this, &addedSome](const QString &text) {
|
||||
auto textWithEntities = TextWithEntities { text };
|
||||
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
|
||||
App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
|
||||
addedSome = true;
|
||||
};
|
||||
if (cAlphaVersion() || cBetaVersion()) {
|
||||
auto addLocalAlphaChangelog = [this, oldAppVersion, addLocalChangelog](int changeVersion, const char *changes) {
|
||||
if (oldAppVersion < changeVersion) {
|
||||
auto changeVersionString = QString::number(changeVersion / 1000000) + '.' + QString::number((changeVersion % 1000000) / 1000) + ((changeVersion % 1000) ? ('.' + QString::number(changeVersion % 1000)) : QString());
|
||||
auto text = qsl("New in version %1:\n\n").arg(changeVersionString) + QString::fromUtf8(changes).trimmed();
|
||||
addLocalChangelog(text);
|
||||
}
|
||||
};
|
||||
addLocalAlphaChangelog(1001024, "\xE2\x80\x94 Radically improved navigation. New side panel on the right with quick access to shared media and group members.\n\xE2\x80\x94 Pinned Messages. If you are a channel admin, pin messages to focus your subscribers\xE2\x80\x99 attention on important announcements.\n\xE2\x80\x94 Also supported clearing history in supergroups and added a host of minor improvements.");
|
||||
addLocalAlphaChangelog(1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
|
||||
addLocalAlphaChangelog(1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. Access them from the Chats list or from the side menu.");
|
||||
}
|
||||
if (!addedSome) {
|
||||
auto text = lng_new_version_wrap(lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")).trimmed();
|
||||
addLocalChangelog(text);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId) {
|
||||
void ApiWrap::applyUpdates(
|
||||
const MTPUpdates &updates,
|
||||
uint64 sentMessageRandomId) {
|
||||
App::main()->feedUpdates(updates, sentMessageRandomId);
|
||||
}
|
||||
|
||||
void ApiWrap::sendMessageFail(const RPCError &error) {
|
||||
if (error.type() == qstr("PEER_FLOOD")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
PeerFloodErrorText(PeerFloodType::Send)));
|
||||
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
const auto link = textcmdLink(
|
||||
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
|
||||
lang(lng_cant_more_info));
|
||||
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
|
||||
lt_more_info,
|
||||
link)));
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
|
||||
auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
|
||||
if (callback) {
|
||||
@@ -845,47 +887,65 @@ void ApiWrap::requestSelfParticipant(ChannelData *channel) {
|
||||
_selfParticipantRequests.insert(channel, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::kickParticipant(PeerData *peer, UserData *user, const MTPChannelBannedRights ¤tRights) {
|
||||
auto kick = KickRequest(peer, user);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
if (auto channel = peer->asChannel()) {
|
||||
auto rights = ChannelData::KickedRestrictedRights();
|
||||
auto requestId = request(MTPchannels_EditBanned(channel->inputChannel, user->inputUser, rights)).done([this, channel, user, currentRights, rights](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
channel->applyEditBanned(user, currentRights, rights);
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
|
||||
_kickRequests.insert(kick, requestId);
|
||||
}
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<UserData*> user) {
|
||||
request(MTPmessages_DeleteChatUser(
|
||||
chat->inputChat,
|
||||
user->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::unblockParticipant(PeerData *peer, UserData *user) {
|
||||
auto kick = KickRequest(peer, user);
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
const MTPChannelBannedRights ¤tRights) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
if (auto channel = peer->asChannel()) {
|
||||
auto requestId = request(MTPchannels_EditBanned(channel->inputChannel, user->inputUser, MTP_channelBannedRights(MTP_flags(0), MTP_int(0)))).done([this, peer, user](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
const auto rights = ChannelData::KickedRestrictedRights();
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
rights
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(peer, user));
|
||||
if (auto channel = peer->asMegagroup()) {
|
||||
if (channel->kickedCount() > 0) {
|
||||
channel->setKickedCount(channel->kickedCount() - 1);
|
||||
} else {
|
||||
channel->updateFullForced();
|
||||
}
|
||||
}
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
channel->applyEditBanned(user, currentRights, rights);
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
|
||||
_kickRequests.insert(kick, requestId);
|
||||
}
|
||||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
MTP_channelBannedRights(MTP_flags(0), MTP_int(0))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
if (channel->kickedCount() > 0) {
|
||||
channel->setKickedCount(channel->kickedCount() - 1);
|
||||
} else {
|
||||
channel->updateFullForced();
|
||||
}
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
|
||||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::requestChannelMembersForAdd(
|
||||
@@ -946,7 +1006,7 @@ void ApiWrap::saveStickerSets(const Stickers::Order &localOrder, const Stickers:
|
||||
request(base::take(_stickersClearRecentRequestId)).cancel();
|
||||
|
||||
auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
|
||||
_stickersOrder = localOrder;
|
||||
@@ -1608,18 +1668,18 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
||||
}
|
||||
|
||||
if (!v) return;
|
||||
QMap<uint64, int32> msgsIds; // copied from feedMsgs
|
||||
for (int32 i = 0, l = v->size(); i < l; ++i) {
|
||||
const auto &msg(v->at(i));
|
||||
switch (msg.type()) {
|
||||
case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break;
|
||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
|
||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
|
||||
}
|
||||
|
||||
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
|
||||
for (auto i = 0, l = v->size(); i != l; ++i) {
|
||||
const auto msgId = idFromMessage(v->at(i));
|
||||
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||
}
|
||||
|
||||
for_const (auto msgId, msgsIds) {
|
||||
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
|
||||
for (const auto [position, index] : indices) {
|
||||
const auto item = App::histories().addNewMessage(
|
||||
v->at(index),
|
||||
NewMessageExisting);
|
||||
if (item) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
@@ -1897,45 +1957,49 @@ void ApiWrap::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
case mtpc_updateShortMessage: {
|
||||
auto &d = updates.c_updateShortMessage();
|
||||
auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
|
||||
App::histories().addNewMessage(MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.is_out() ? MTP_int(Auth().userId()) : d.vuser_id,
|
||||
MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(Auth().userId())),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()), NewMessageUnread);
|
||||
App::histories().addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.is_out() ? MTP_int(Auth().userId()) : d.vuser_id,
|
||||
MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(Auth().userId())),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()),
|
||||
NewMessageUnread);
|
||||
} break;
|
||||
|
||||
case mtpc_updateShortChatMessage: {
|
||||
auto &d = updates.c_updateShortChatMessage();
|
||||
auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
|
||||
App::histories().addNewMessage(MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.vfrom_id,
|
||||
MTP_peerChat(d.vchat_id),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()), NewMessageUnread);
|
||||
App::histories().addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.vfrom_id,
|
||||
MTP_peerChat(d.vchat_id),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()),
|
||||
NewMessageUnread);
|
||||
} break;
|
||||
|
||||
case mtpc_updateShortSentMessage: {
|
||||
@@ -2455,6 +2519,7 @@ void ApiWrap::forwardMessages(
|
||||
}
|
||||
|
||||
auto forwardFrom = items.front()->history()->peer;
|
||||
auto currentGroupId = items.front()->groupId();
|
||||
auto ids = QVector<MTPint>();
|
||||
auto randomIds = QVector<MTPlong>();
|
||||
|
||||
@@ -2462,8 +2527,12 @@ void ApiWrap::forwardMessages(
|
||||
if (shared) {
|
||||
++shared->requestsLeft;
|
||||
}
|
||||
const auto finalFlags = sendFlags
|
||||
| (currentGroupId == MessageGroupId()
|
||||
? MTPmessages_ForwardMessages::Flag(0)
|
||||
: MTPmessages_ForwardMessages::Flag::f_grouped);
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
MTP_flags(finalFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
@@ -2508,9 +2577,13 @@ void ApiWrap::forwardMessages(
|
||||
App::historyRegRandom(randomId, newId);
|
||||
}
|
||||
}
|
||||
if (forwardFrom != item->history()->peer) {
|
||||
const auto newFrom = item->history()->peer;
|
||||
const auto newGroupId = item->groupId();
|
||||
if (forwardFrom != newFrom
|
||||
|| currentGroupId != newGroupId) {
|
||||
sendAccumulated();
|
||||
forwardFrom = item->history()->peer;
|
||||
forwardFrom = newFrom;
|
||||
currentGroupId = newGroupId;
|
||||
}
|
||||
ids.push_back(MTP_int(item->id));
|
||||
randomIds.push_back(MTP_long(randomId));
|
||||
@@ -2556,17 +2629,12 @@ void ApiWrap::sendSharedContact(
|
||||
const auto history = options.history;
|
||||
const auto peer = history->peer;
|
||||
|
||||
const auto randomId = rand_value<uint64>();
|
||||
const auto newId = FullMsgId(history->channelId(), clientMsgId());
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
const auto silentPost = channelPost && peer->notifySilentPosts();
|
||||
|
||||
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
|
||||
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (options.replyTo) {
|
||||
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
@@ -2577,14 +2645,11 @@ void ApiWrap::sendSharedContact(
|
||||
} else {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
}
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto messageFromId = channelPost ? 0 : Auth().userId();
|
||||
const auto messagePostAuthor = channelPost
|
||||
? (Auth().user()->firstName + ' ' + Auth().user()->lastName)
|
||||
: QString();
|
||||
history->addNewMessage(
|
||||
const auto item = history->addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
MTP_int(newId.msg),
|
||||
@@ -2608,35 +2673,358 @@ void ApiWrap::sendSharedContact(
|
||||
MTPlong()),
|
||||
NewMessageUnread);
|
||||
|
||||
const auto media = MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
MTP_string(lastName));
|
||||
sendMedia(item, media, peer->notifySilentPosts());
|
||||
}
|
||||
|
||||
void ApiWrap::sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
int duration,
|
||||
const SendOptions &options) {
|
||||
const auto caption = QString();
|
||||
const auto to = FileLoadTaskOptions(options);
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
result,
|
||||
duration,
|
||||
waveform,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
|
||||
void ApiWrap::sendFiles(
|
||||
Storage::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
QString caption,
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendOptions &options) {
|
||||
if (list.files.size() > 1 && !caption.isEmpty()) {
|
||||
auto message = MainWidget::MessageToSend(options.history);
|
||||
message.textWithTags = { caption, TextWithTags::Tags() };
|
||||
message.replyTo = options.replyTo;
|
||||
message.clearDraft = false;
|
||||
App::main()->sendMessage(message);
|
||||
caption = QString();
|
||||
}
|
||||
|
||||
const auto to = FileLoadTaskOptions(options);
|
||||
if (album) {
|
||||
album->silent = to.silent;
|
||||
}
|
||||
auto tasks = std::vector<std::unique_ptr<Task>>();
|
||||
tasks.reserve(list.files.size());
|
||||
for (auto &file : list.files) {
|
||||
if (album) {
|
||||
switch (file.type) {
|
||||
case Storage::PreparedFile::AlbumType::Photo:
|
||||
type = SendMediaType::Photo;
|
||||
break;
|
||||
case Storage::PreparedFile::AlbumType::Video:
|
||||
type = SendMediaType::File;
|
||||
break;
|
||||
default: Unexpected("AlbumType in uploadFilesAfterConfirmation");
|
||||
}
|
||||
}
|
||||
tasks.push_back(std::make_unique<FileLoadTask>(
|
||||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
album));
|
||||
}
|
||||
if (album) {
|
||||
_sendingAlbums.emplace(album->groupId, album);
|
||||
album->items.reserve(tasks.size());
|
||||
for (const auto &task : tasks) {
|
||||
album->items.push_back(SendingAlbum::Item(task->id()));
|
||||
}
|
||||
}
|
||||
_fileLoader->addTasks(std::move(tasks));
|
||||
}
|
||||
|
||||
void ApiWrap::sendFile(
|
||||
const QByteArray &fileContent,
|
||||
SendMediaType type,
|
||||
const SendOptions &options) {
|
||||
auto to = FileLoadTaskOptions(options);
|
||||
auto caption = QString();
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
QString(),
|
||||
fileContent,
|
||||
nullptr,
|
||||
type,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
|
||||
void ApiWrap::sendUploadedPhoto(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
bool silent) {
|
||||
if (const auto item = App::histItemById(localId)) {
|
||||
const auto caption = item->getMedia()
|
||||
? item->getMedia()->getCaption()
|
||||
: TextWithEntities();
|
||||
const auto media = MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
file,
|
||||
MTP_string(caption.text),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
if (const auto groupId = item->groupId()) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
sendMedia(item, media, silent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::sendUploadedDocument(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const base::optional<MTPInputFile> &thumb,
|
||||
bool silent) {
|
||||
if (const auto item = App::histItemById(localId)) {
|
||||
auto media = item->getMedia();
|
||||
if (auto document = media ? media->getDocument() : nullptr) {
|
||||
const auto caption = media->getCaption();
|
||||
const auto groupId = item->groupId();
|
||||
const auto flags = MTPDinputMediaUploadedDocument::Flags(0)
|
||||
| (thumb
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_thumb
|
||||
: MTPDinputMediaUploadedDocument::Flag(0))
|
||||
| (groupId
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_nosound_video
|
||||
: MTPDinputMediaUploadedDocument::Flag(0));
|
||||
const auto media = MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
file,
|
||||
thumb ? *thumb : MTPInputFile(),
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_string(caption.text),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
if (groupId) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
sendMedia(item, media, silent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::cancelLocalItem(not_null<HistoryItem*> item) {
|
||||
Expects(!IsServerMsgId(item->id));
|
||||
|
||||
if (const auto groupId = item->groupId()) {
|
||||
sendAlbumWithCancelled(item, groupId);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::uploadAlbumMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media) {
|
||||
const auto localId = item->fullId();
|
||||
const auto failed = [this] {
|
||||
|
||||
};
|
||||
request(MTPmessages_UploadMedia(
|
||||
item->history()->peer->input,
|
||||
media
|
||||
)).done([=](const MTPMessageMedia &result) {
|
||||
const auto item = App::histItemById(localId);
|
||||
if (!item) {
|
||||
failed();
|
||||
}
|
||||
if (const auto media = item->getMedia()) {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
photo->setWaitingForAlbum();
|
||||
} else if (const auto document = media->getDocument()) {
|
||||
document->setWaitingForAlbum();
|
||||
}
|
||||
}
|
||||
|
||||
switch (result.type()) {
|
||||
case mtpc_messageMediaPhoto: {
|
||||
const auto &data = result.c_messageMediaPhoto();
|
||||
if (data.vphoto.type() != mtpc_photo) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
const auto &photo = data.vphoto.c_photo();
|
||||
const auto flags = MTPDinputMediaPhoto::Flags(0)
|
||||
| (data.has_ttl_seconds()
|
||||
? MTPDinputMediaPhoto::Flag::f_ttl_seconds
|
||||
: MTPDinputMediaPhoto::Flag(0));
|
||||
const auto media = MTP_inputMediaPhoto(
|
||||
MTP_flags(flags),
|
||||
MTP_inputPhoto(photo.vid, photo.vaccess_hash),
|
||||
data.has_caption() ? data.vcaption : MTP_string(QString()),
|
||||
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
|
||||
case mtpc_messageMediaDocument: {
|
||||
const auto &data = result.c_messageMediaDocument();
|
||||
if (data.vdocument.type() != mtpc_document) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
const auto &document = data.vdocument.c_document();
|
||||
const auto flags = MTPDinputMediaDocument::Flags(0)
|
||||
| (data.has_ttl_seconds()
|
||||
? MTPDinputMediaDocument::Flag::f_ttl_seconds
|
||||
: MTPDinputMediaDocument::Flag(0));
|
||||
const auto media = MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
MTP_inputDocument(document.vid, document.vaccess_hash),
|
||||
data.has_caption() ? data.vcaption : MTP_string(QString()),
|
||||
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
failed();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::sendMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent) {
|
||||
const auto randomId = rand_value<uint64>();
|
||||
App::historyRegRandom(randomId, item->fullId());
|
||||
|
||||
sendMediaWithRandomId(item, media, silent, randomId);
|
||||
}
|
||||
|
||||
void ApiWrap::sendMediaWithRandomId(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent,
|
||||
uint64 randomId) {
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyToId();
|
||||
const auto flags = MTPmessages_SendMedia::Flags(0)
|
||||
| (replyTo
|
||||
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
|
||||
: MTPmessages_SendMedia::Flag(0))
|
||||
| (IsSilentPost(item, silent)
|
||||
? MTPmessages_SendMedia::Flag::f_silent
|
||||
: MTPmessages_SendMedia::Flag(0));
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(options.replyTo),
|
||||
MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
MTP_string(lastName)),
|
||||
MTP_flags(flags),
|
||||
history->peer->input,
|
||||
MTP_int(replyTo),
|
||||
media,
|
||||
MTP_long(randomId),
|
||||
MTPnullMarkup
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).fail([](const RPCError &error) {
|
||||
if (error.type() == qstr("PEER_FLOOD")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
PeerFloodErrorText(PeerFloodType::Send)));
|
||||
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
const auto link = textcmdLink(
|
||||
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
|
||||
lang(lng_cant_more_info));
|
||||
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
|
||||
lt_more_info,
|
||||
link)));
|
||||
}
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
)).done([=](const MTPUpdates &result) { applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) { sendMessageFail(error);
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
}
|
||||
|
||||
App::historyRegRandom(randomId, newId);
|
||||
void ApiWrap::sendAlbumWithUploaded(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media) {
|
||||
const auto localId = item->fullId();
|
||||
const auto randomId = rand_value<uint64>();
|
||||
App::historyRegRandom(randomId, localId);
|
||||
|
||||
const auto albumIt = _sendingAlbums.find(groupId.raw());
|
||||
Assert(albumIt != _sendingAlbums.end());
|
||||
const auto &album = albumIt->second;
|
||||
|
||||
const auto proj = [](const SendingAlbum::Item &item) {
|
||||
return item.msgId;
|
||||
};
|
||||
const auto itemIt = ranges::find(album->items, localId, proj);
|
||||
Assert(itemIt != album->items.end());
|
||||
Assert(!itemIt->media);
|
||||
itemIt->media = MTP_inputSingleMedia(media, MTP_long(randomId));
|
||||
|
||||
sendAlbumIfReady(album.get());
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumWithCancelled(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId) {
|
||||
const auto localId = item->fullId();
|
||||
const auto albumIt = _sendingAlbums.find(groupId.raw());
|
||||
Assert(albumIt != _sendingAlbums.end());
|
||||
const auto &album = albumIt->second;
|
||||
|
||||
const auto proj = [](const SendingAlbum::Item &item) {
|
||||
return item.msgId;
|
||||
};
|
||||
const auto itemIt = ranges::find(album->items, localId, proj);
|
||||
Assert(itemIt != album->items.end());
|
||||
album->items.erase(itemIt);
|
||||
|
||||
sendAlbumIfReady(album.get());
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
const auto groupId = album->groupId;
|
||||
if (album->items.empty()) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
}
|
||||
auto sample = (HistoryItem*)nullptr;
|
||||
auto medias = QVector<MTPInputSingleMedia>();
|
||||
medias.reserve(album->items.size());
|
||||
for (const auto &item : album->items) {
|
||||
if (!item.media) {
|
||||
return;
|
||||
} else if (!sample) {
|
||||
sample = App::histItemById(item.msgId);
|
||||
}
|
||||
medias.push_back(*item.media);
|
||||
}
|
||||
if (!sample) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().c_inputSingleMedia();
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia,
|
||||
album->silent,
|
||||
single.vrandom_id.v);
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
}
|
||||
const auto history = sample->history();
|
||||
const auto replyTo = sample->replyToId();
|
||||
const auto flags = MTPmessages_SendMultiMedia::Flags(0)
|
||||
| (replyTo
|
||||
? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id
|
||||
: MTPmessages_SendMultiMedia::Flag(0))
|
||||
| (IsSilentPost(sample, album->silent)
|
||||
? MTPmessages_SendMultiMedia::Flag::f_silent
|
||||
: MTPmessages_SendMultiMedia::Flag(0));
|
||||
history->sendRequestId = request(MTPmessages_SendMultiMedia(
|
||||
MTP_flags(flags),
|
||||
history->peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_vector<MTPInputSingleMedia>(medias)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
sendMessageFail(error);
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
}
|
||||
|
||||
void ApiWrap::readServerHistory(not_null<History*> history) {
|
||||
|
||||
@@ -28,14 +28,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/flat_set.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
|
||||
class TaskQueue;
|
||||
class AuthSession;
|
||||
enum class SparseIdsLoadDirection;
|
||||
struct MessageGroupId;
|
||||
struct SendingAlbum;
|
||||
enum class SendMediaType;
|
||||
|
||||
namespace Storage {
|
||||
enum class SharedMediaType : char;
|
||||
struct PreparedList;
|
||||
} // namespace Storage
|
||||
|
||||
enum class SparseIdsLoadDirection;
|
||||
|
||||
namespace Api {
|
||||
|
||||
inline const MTPVector<MTPChat> *getChatsFromMessagesChats(const MTPmessages_Chats &chats) {
|
||||
@@ -52,7 +56,6 @@ class ApiWrap : private MTP::Sender, private base::Subscriber {
|
||||
public:
|
||||
ApiWrap(not_null<AuthSession*> session);
|
||||
|
||||
void start();
|
||||
void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
|
||||
|
||||
using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>;
|
||||
@@ -66,6 +69,10 @@ public:
|
||||
void requestAdmins(not_null<ChannelData*> channel);
|
||||
void requestParticipantsCountDelayed(not_null<ChannelData*> channel);
|
||||
|
||||
void requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
base::lambda<void(const MTPUpdates &result)> callback);
|
||||
|
||||
void requestChannelMembersForAdd(
|
||||
not_null<ChannelData*> channel,
|
||||
base::lambda<void(const MTPchannels_ChannelParticipants&)> callback);
|
||||
@@ -73,8 +80,14 @@ public:
|
||||
void processFullPeer(UserData *user, const MTPUserFull &result);
|
||||
|
||||
void requestSelfParticipant(ChannelData *channel);
|
||||
void kickParticipant(PeerData *peer, UserData *user, const MTPChannelBannedRights ¤tRights);
|
||||
void unblockParticipant(PeerData *peer, UserData *user);
|
||||
void kickParticipant(not_null<ChatData*> chat, not_null<UserData*> user);
|
||||
void kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
const MTPChannelBannedRights ¤tRights);
|
||||
void unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user);
|
||||
|
||||
void requestWebPageDelayed(WebPageData *page);
|
||||
void clearWebPageRequest(WebPageData *page);
|
||||
@@ -186,6 +199,33 @@ public:
|
||||
void readServerHistory(not_null<History*> history);
|
||||
void readServerHistoryForce(not_null<History*> history);
|
||||
|
||||
void sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
int duration,
|
||||
const SendOptions &options);
|
||||
void sendFiles(
|
||||
Storage::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
QString caption,
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendOptions &options);
|
||||
void sendFile(
|
||||
const QByteArray &fileContent,
|
||||
SendMediaType type,
|
||||
const SendOptions &options);
|
||||
|
||||
void sendUploadedPhoto(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
bool silent);
|
||||
void sendUploadedDocument(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const base::optional<MTPInputFile> &thumb,
|
||||
bool silent);
|
||||
void cancelLocalItem(not_null<HistoryItem*> item);
|
||||
|
||||
~ApiWrap();
|
||||
|
||||
private:
|
||||
@@ -197,8 +237,6 @@ private:
|
||||
using MessageDataRequests = QMap<MsgId, MessageDataRequest>;
|
||||
using SharedMediaType = Storage::SharedMediaType;
|
||||
|
||||
void requestAppChangelogs();
|
||||
void addLocalChangelogs(int oldAppVersion);
|
||||
void updatesReceived(const MTPUpdates &updates);
|
||||
void checkQuitPreventFinished();
|
||||
|
||||
@@ -283,9 +321,30 @@ private:
|
||||
void applyAffectedMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedMessages &result);
|
||||
void sendMessageFail(const RPCError &error);
|
||||
void uploadAlbumMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media);
|
||||
void sendAlbumWithUploaded(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media);
|
||||
void sendAlbumWithCancelled(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId);
|
||||
void sendAlbumIfReady(not_null<SendingAlbum*> album);
|
||||
void sendMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent);
|
||||
void sendMediaWithRandomId(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent,
|
||||
uint64 randomId);
|
||||
|
||||
not_null<AuthSession*> _session;
|
||||
mtpRequestId _changelogSubscription = 0;
|
||||
|
||||
MessageDataRequests _messageDataRequests;
|
||||
QMap<ChannelData*, MessageDataRequests> _channelMessageDataRequests;
|
||||
@@ -304,9 +363,10 @@ private:
|
||||
mtpRequestId _channelMembersForAddRequestId = 0;
|
||||
base::lambda<void(const MTPchannels_ChannelParticipants&)> _channelMembersForAddCallback;
|
||||
|
||||
typedef QPair<PeerData*, UserData*> KickRequest;
|
||||
typedef QMap<KickRequest, mtpRequestId> KickRequests;
|
||||
KickRequests _kickRequests;
|
||||
using KickRequest = std::pair<
|
||||
not_null<ChannelData*>,
|
||||
not_null<UserData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
QMap<ChannelData*, mtpRequestId> _selfParticipantRequests;
|
||||
|
||||
@@ -375,6 +435,8 @@ private:
|
||||
};
|
||||
base::flat_map<not_null<PeerData*>, ReadRequest> _readRequests;
|
||||
base::flat_map<not_null<PeerData*>, MsgId> _readRequestsPending;
|
||||
std::unique_ptr<TaskQueue> _fileLoader;
|
||||
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
||||
|
||||
base::Observable<PeerData*> _fullPeerUpdated;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "messenger.h"
|
||||
@@ -46,6 +47,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "numbers.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
@@ -127,7 +129,8 @@ namespace {
|
||||
LastPhotosList lastPhotos;
|
||||
using LastPhotosMap = QHash<PhotoData*, LastPhotosList::iterator>;
|
||||
LastPhotosMap lastPhotosMap;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace App {
|
||||
|
||||
@@ -218,117 +221,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
TimeId onlineForSort(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return -1;
|
||||
}
|
||||
TimeId online = user->onlineTill;
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return online;
|
||||
|
||||
case -2: {
|
||||
QDate yesterday(date(now).date());
|
||||
return int32(QDateTime(yesterday.addDays(-3)).toTime_t()) + (unixtime() - myunixtime());
|
||||
} break;
|
||||
|
||||
case -3: {
|
||||
QDate weekago(date(now).date());
|
||||
return int32(QDateTime(weekago.addDays(-7)).toTime_t()) + (unixtime() - myunixtime());
|
||||
} break;
|
||||
|
||||
case -4: {
|
||||
QDate monthago(date(now).date());
|
||||
return int32(QDateTime(monthago.addDays(-30)).toTime_t()) + (unixtime() - myunixtime());
|
||||
} break;
|
||||
}
|
||||
return -online;
|
||||
}
|
||||
return online;
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return 86400;
|
||||
}
|
||||
return onlineWillChangeIn(user->onlineTill, now);
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
if (-online > now) return std::max(-online - now, 86400);
|
||||
return 86400;
|
||||
}
|
||||
if (online > now) {
|
||||
return std::max(online - now, 86400);
|
||||
}
|
||||
int32 minutes = (now - online) / 60;
|
||||
if (minutes < 60) {
|
||||
return (minutes + 1) * 60 - (now - online);
|
||||
}
|
||||
int32 hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return (hours + 1) * 3600 - (now - online);
|
||||
}
|
||||
QDateTime dNow(date(now)), dTomorrow(dNow.date().addDays(1));
|
||||
return std::max(dNow.secsTo(dTomorrow), 86400LL);
|
||||
}
|
||||
|
||||
QString onlineText(UserData *user, TimeId now, bool precise) {
|
||||
if (isNotificationsUser(user->id)) {
|
||||
return lang(lng_status_service_notifications);
|
||||
} else if (user->botInfo) {
|
||||
return lang(lng_status_bot);
|
||||
} else if (isServiceUser(user->id)) {
|
||||
return lang(lng_status_support);
|
||||
}
|
||||
return onlineText(user->onlineTill, now, precise);
|
||||
}
|
||||
|
||||
QString onlineText(TimeId online, TimeId now, bool precise) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return lang(lng_status_offline);
|
||||
case -2: return lang(lng_status_recently);
|
||||
case -3: return lang(lng_status_last_week);
|
||||
case -4: return lang(lng_status_last_month);
|
||||
}
|
||||
return (-online > now) ? lang(lng_status_online) : lang(lng_status_recently);
|
||||
}
|
||||
if (online > now) {
|
||||
return lang(lng_status_online);
|
||||
}
|
||||
QString when;
|
||||
if (precise) {
|
||||
QDateTime dOnline(date(online)), dNow(date(now));
|
||||
if (dOnline.date() == dNow.date()) {
|
||||
return lng_status_lastseen_today(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
} else if (dOnline.date().addDays(1) == dNow.date()) {
|
||||
return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
}
|
||||
return lng_status_lastseen_date_time(lt_date, dOnline.date().toString(qsl("dd.MM.yy")), lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
}
|
||||
int32 minutes = (now - online) / 60;
|
||||
if (!minutes) {
|
||||
return lang(lng_status_lastseen_now);
|
||||
} else if (minutes < 60) {
|
||||
return lng_status_lastseen_minutes(lt_count, minutes);
|
||||
}
|
||||
int32 hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return lng_status_lastseen_hours(lt_count, hours);
|
||||
}
|
||||
QDateTime dOnline(date(online)), dNow(date(now));
|
||||
if (dOnline.date() == dNow.date()) {
|
||||
return lng_status_lastseen_today(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
} else if (dOnline.date().addDays(1) == dNow.date()) {
|
||||
return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
}
|
||||
return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy")));
|
||||
}
|
||||
|
||||
namespace {
|
||||
// we should get a full restriction in "{fulltype}: {reason}" format and we
|
||||
// need to find a "-all" tag in {fulltype}, otherwise ignore this restriction
|
||||
@@ -353,27 +245,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
bool onlineColorUse(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return false;
|
||||
}
|
||||
return onlineColorUse(user->onlineTill, now);
|
||||
}
|
||||
|
||||
bool onlineColorUse(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1:
|
||||
case -2:
|
||||
case -3:
|
||||
case -4: return false;
|
||||
}
|
||||
return (-online > now);
|
||||
}
|
||||
return (online > now);
|
||||
}
|
||||
|
||||
UserData *feedUser(const MTPUser &user) {
|
||||
UserData *data = nullptr;
|
||||
bool wasContact = false, minimal = false;
|
||||
@@ -472,7 +343,7 @@ namespace {
|
||||
QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone;
|
||||
|
||||
if (!minimal && d.is_self() && uname != data->username) {
|
||||
SignalHandlers::setCrashAnnotation("Username", uname);
|
||||
CrashReports::SetAnnotation("Username", uname);
|
||||
}
|
||||
data->setName(fname, lname, pname, uname);
|
||||
if (d.has_photo()) {
|
||||
@@ -648,26 +519,34 @@ namespace {
|
||||
}
|
||||
} break;
|
||||
case mtpc_channel: {
|
||||
auto &d = chat.c_channel();
|
||||
const auto &d = chat.c_channel();
|
||||
|
||||
auto peerId = peerFromChannel(d.vid.v);
|
||||
const auto peerId = peerFromChannel(d.vid.v);
|
||||
minimal = d.is_min();
|
||||
if (minimal) {
|
||||
data = App::channelLoaded(peerId);
|
||||
if (!data) {
|
||||
return nullptr; // minimal is not loaded, need to make getDifference
|
||||
// Can't apply minimal to a not loaded channel.
|
||||
// Need to make getDifference.
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
data = App::channel(peerId);
|
||||
data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0));
|
||||
const auto accessHash = d.has_access_hash()
|
||||
? d.vaccess_hash
|
||||
: MTP_long(0);
|
||||
data->input = MTP_inputPeerChannel(d.vid, accessHash);
|
||||
}
|
||||
|
||||
auto cdata = data->asChannel();
|
||||
auto wasInChannel = cdata->amIn();
|
||||
auto canViewAdmins = cdata->canViewAdmins();
|
||||
auto canViewMembers = cdata->canViewMembers();
|
||||
auto canAddMembers = cdata->canAddMembers();
|
||||
const auto cdata = data->asChannel();
|
||||
const auto wasInChannel = cdata->amIn();
|
||||
const auto canViewAdmins = cdata->canViewAdmins();
|
||||
const auto canViewMembers = cdata->canViewMembers();
|
||||
const auto canAddMembers = cdata->canAddMembers();
|
||||
|
||||
if (d.has_participants_count()) {
|
||||
cdata->setMembersCount(d.vparticipants_count.v);
|
||||
}
|
||||
if (minimal) {
|
||||
auto mask = 0
|
||||
| MTPDchannel::Flag::f_broadcast
|
||||
@@ -1033,26 +912,22 @@ namespace {
|
||||
if (m.has_from_id() && peerId == Auth().userPeerId()) {
|
||||
peerId = peerFromUser(m.vfrom_id);
|
||||
}
|
||||
if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
|
||||
if (const auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
|
||||
auto text = qs(m.vmessage);
|
||||
auto entities = m.has_entities() ? TextUtilities::EntitiesFromMTP(m.ventities.v) : EntitiesInText();
|
||||
auto entities = m.has_entities()
|
||||
? TextUtilities::EntitiesFromMTP(m.ventities.v)
|
||||
: EntitiesInText();
|
||||
existing->setText({ text, entities });
|
||||
existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
|
||||
existing->updateReplyMarkup(m.has_reply_markup() ? (&m.vreply_markup) : nullptr);
|
||||
existing->updateReplyMarkup(m.has_reply_markup()
|
||||
? (&m.vreply_markup)
|
||||
: nullptr);
|
||||
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
|
||||
existing->addToUnreadMentions(AddToUnreadMentionsMethod::New);
|
||||
if (auto sharedMediaTypes = existing->sharedMediaTypes()) {
|
||||
Auth().storage().add(Storage::SharedMediaAddNew(
|
||||
peerId,
|
||||
sharedMediaTypes,
|
||||
existing->id));
|
||||
}
|
||||
|
||||
existing->indexAsNewItem();
|
||||
if (!existing->detached()) {
|
||||
App::checkSavedGif(existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
@@ -1104,29 +979,23 @@ namespace {
|
||||
}
|
||||
|
||||
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
|
||||
QMap<uint64, int32> msgsIds;
|
||||
for (int32 i = 0, l = msgs.size(); i < l; ++i) {
|
||||
const auto &msg(msgs.at(i));
|
||||
switch (msg.type()) {
|
||||
case mtpc_message: {
|
||||
const auto &d(msg.c_message());
|
||||
bool needToAdd = true;
|
||||
auto indices = base::flat_map<uint64, int>();
|
||||
for (int i = 0, l = msgs.size(); i != l; ++i) {
|
||||
const auto &msg = msgs[i];
|
||||
if (msg.type() == mtpc_message) {
|
||||
const auto &data = msg.c_message();
|
||||
if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview
|
||||
if (checkEntitiesAndViewsUpdate(d)) { // already in blocks
|
||||
if (checkEntitiesAndViewsUpdate(data)) { // already in blocks
|
||||
LOG(("Skipping message, because it is already in blocks!"));
|
||||
needToAdd = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (needToAdd) {
|
||||
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i);
|
||||
}
|
||||
} break;
|
||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
|
||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
|
||||
}
|
||||
const auto msgId = idFromMessage(msg);
|
||||
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||
}
|
||||
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
|
||||
histories().addNewMessage(msgs.at(i.value()), type);
|
||||
for (const auto [position, index] : indices) {
|
||||
histories().addNewMessage(msgs[index], type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1514,22 +1383,29 @@ namespace {
|
||||
}
|
||||
|
||||
PhotoData *photo(const PhotoId &photo) {
|
||||
PhotosData::const_iterator i = ::photosData.constFind(photo);
|
||||
auto i = ::photosData.constFind(photo);
|
||||
if (i == ::photosData.cend()) {
|
||||
i = ::photosData.insert(photo, new PhotoData(photo));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) {
|
||||
PhotoData *photoSet(
|
||||
const PhotoId &photo,
|
||||
PhotoData *convert,
|
||||
const uint64 &access,
|
||||
int32 date,
|
||||
const ImagePtr &thumb,
|
||||
const ImagePtr &medium,
|
||||
const ImagePtr &full) {
|
||||
if (convert) {
|
||||
if (convert->id != photo) {
|
||||
PhotosData::iterator i = ::photosData.find(convert->id);
|
||||
const auto i = ::photosData.find(convert->id);
|
||||
if (i != ::photosData.cend() && i.value() == convert) {
|
||||
::photosData.erase(i);
|
||||
}
|
||||
convert->id = photo;
|
||||
convert->uploadingData.reset();
|
||||
convert->uploadingData = nullptr;
|
||||
}
|
||||
if (date) {
|
||||
convert->access = access;
|
||||
@@ -1539,9 +1415,9 @@ namespace {
|
||||
updateImage(convert->full, full);
|
||||
}
|
||||
}
|
||||
PhotosData::const_iterator i = ::photosData.constFind(photo);
|
||||
const auto i = ::photosData.constFind(photo);
|
||||
PhotoData *result;
|
||||
LastPhotosMap::iterator inLastIter = lastPhotosMap.end();
|
||||
auto inLastIter = lastPhotosMap.end();
|
||||
if (i == ::photosData.cend()) {
|
||||
if (convert) {
|
||||
result = convert;
|
||||
@@ -1575,27 +1451,39 @@ namespace {
|
||||
}
|
||||
|
||||
DocumentData *document(const DocumentId &document) {
|
||||
DocumentsData::const_iterator i = ::documentsData.constFind(document);
|
||||
auto i = ::documentsData.constFind(document);
|
||||
if (i == ::documentsData.cend()) {
|
||||
i = ::documentsData.insert(document, DocumentData::create(document));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) {
|
||||
DocumentData *documentSet(
|
||||
const DocumentId &document,
|
||||
DocumentData *convert,
|
||||
const uint64 &access,
|
||||
int32 version,
|
||||
int32 date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const ImagePtr &thumb,
|
||||
int32 dc,
|
||||
int32 size,
|
||||
const StorageImageLocation &thumbLocation) {
|
||||
bool versionChanged = false;
|
||||
bool sentSticker = false;
|
||||
if (convert) {
|
||||
MediaKey oldKey = convert->mediaKey();
|
||||
bool idChanged = (convert->id != document);
|
||||
const auto oldKey = convert->mediaKey();
|
||||
const auto idChanged = (convert->id != document);
|
||||
if (idChanged) {
|
||||
DocumentsData::iterator i = ::documentsData.find(convert->id);
|
||||
const auto i = ::documentsData.find(convert->id);
|
||||
if (i != ::documentsData.cend() && i.value() == convert) {
|
||||
::documentsData.erase(i);
|
||||
}
|
||||
|
||||
convert->id = document;
|
||||
convert->status = FileReady;
|
||||
convert->uploadingData = nullptr;
|
||||
sentSticker = (convert->sticker() != 0);
|
||||
}
|
||||
if (date) {
|
||||
@@ -1613,7 +1501,7 @@ namespace {
|
||||
convert->sticker()->loc = thumbLocation;
|
||||
}
|
||||
|
||||
MediaKey newKey = convert->mediaKey();
|
||||
const auto newKey = convert->mediaKey();
|
||||
if (idChanged) {
|
||||
if (convert->isVoiceMessage()) {
|
||||
Local::copyAudio(oldKey, newKey);
|
||||
@@ -1627,7 +1515,7 @@ namespace {
|
||||
Local::writeSavedGifs();
|
||||
}
|
||||
}
|
||||
DocumentsData::const_iterator i = ::documentsData.constFind(document);
|
||||
const auto i = ::documentsData.constFind(document);
|
||||
DocumentData *result;
|
||||
if (i == ::documentsData.cend()) {
|
||||
if (convert) {
|
||||
@@ -1704,10 +1592,23 @@ namespace {
|
||||
return i.value();
|
||||
}
|
||||
|
||||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int32 duration, const QString &author, int32 pendingTill) {
|
||||
WebPageData *webPageSet(
|
||||
const WebPageId &webPage,
|
||||
WebPageData *convert,
|
||||
const QString &type,
|
||||
const QString &url,
|
||||
const QString &displayUrl,
|
||||
const QString &siteName,
|
||||
const QString &title,
|
||||
const TextWithEntities &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
int32 duration,
|
||||
const QString &author,
|
||||
int32 pendingTill) {
|
||||
if (convert) {
|
||||
if (convert->id != webPage) {
|
||||
auto i = webPagesData.find(convert->id);
|
||||
const auto i = webPagesData.find(convert->id);
|
||||
if (i != webPagesData.cend() && i.value() == convert) {
|
||||
webPagesData.erase(i);
|
||||
}
|
||||
@@ -1731,7 +1632,7 @@ namespace {
|
||||
if (App::main()) App::main()->webPageUpdated(convert);
|
||||
}
|
||||
}
|
||||
auto i = webPagesData.constFind(webPage);
|
||||
const auto i = webPagesData.constFind(webPage);
|
||||
WebPageData *result;
|
||||
if (i == webPagesData.cend()) {
|
||||
if (convert) {
|
||||
@@ -2122,15 +2023,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
int msgRadius() {
|
||||
static int MsgRadius = ([]() {
|
||||
return st::historyMessageRadius;
|
||||
auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom());
|
||||
return minMsgHeight / 2;
|
||||
})();
|
||||
return MsgRadius;
|
||||
}
|
||||
|
||||
void createMaskCorners() {
|
||||
QImage mask[4];
|
||||
prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask);
|
||||
@@ -2138,7 +2030,7 @@ namespace {
|
||||
::cornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
::cornersMaskSmall[i].setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
prepareCorners(LargeMaskCorners, msgRadius(), QColor(255, 255, 255), nullptr, mask);
|
||||
prepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
::cornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
::cornersMaskLarge[i].setDevicePixelRatio(cRetinaFactor());
|
||||
@@ -2152,12 +2044,12 @@ namespace {
|
||||
prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg);
|
||||
prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected);
|
||||
prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay);
|
||||
prepareCorners(SelectedOverlayLargeCorners, msgRadius(), st::msgSelectOverlay);
|
||||
prepareCorners(SelectedOverlayLargeCorners, st::historyMessageRadius, st::msgSelectOverlay);
|
||||
prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg);
|
||||
prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected);
|
||||
prepareCorners(InShadowCorners, msgRadius(), st::msgInShadow);
|
||||
prepareCorners(InSelectedShadowCorners, msgRadius(), st::msgInShadowSelected);
|
||||
prepareCorners(ForwardCorners, msgRadius(), st::historyForwardChooseBg);
|
||||
prepareCorners(InShadowCorners, st::historyMessageRadius, st::msgInShadow);
|
||||
prepareCorners(InSelectedShadowCorners, st::historyMessageRadius, st::msgInShadowSelected);
|
||||
prepareCorners(ForwardCorners, st::historyMessageRadius, st::historyForwardChooseBg);
|
||||
prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::mediaviewSaveMsgBg);
|
||||
prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover);
|
||||
prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover);
|
||||
@@ -2169,10 +2061,10 @@ namespace {
|
||||
prepareCorners(Doc3Corners, st::buttonRadius, st::msgFile3Bg);
|
||||
prepareCorners(Doc4Corners, st::buttonRadius, st::msgFile4Bg);
|
||||
|
||||
prepareCorners(MessageInCorners, msgRadius(), st::msgInBg, &st::msgInShadow);
|
||||
prepareCorners(MessageInSelectedCorners, msgRadius(), st::msgInBgSelected, &st::msgInShadowSelected);
|
||||
prepareCorners(MessageOutCorners, msgRadius(), st::msgOutBg, &st::msgOutShadow);
|
||||
prepareCorners(MessageOutSelectedCorners, msgRadius(), st::msgOutBgSelected, &st::msgOutShadowSelected);
|
||||
prepareCorners(MessageInCorners, st::historyMessageRadius, st::msgInBg, &st::msgInShadow);
|
||||
prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected);
|
||||
prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow);
|
||||
prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected);
|
||||
}
|
||||
|
||||
void createCorners() {
|
||||
@@ -2647,42 +2539,46 @@ namespace {
|
||||
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
}
|
||||
|
||||
void complexAdjustRect(ImageRoundCorners corners, QRect &rect, RectParts &parts) {
|
||||
if (corners & ImageRoundCorner::TopLeft) {
|
||||
if (!(corners & ImageRoundCorner::BottomLeft)) {
|
||||
parts = RectPart::NoTopBottom | RectPart::FullTop;
|
||||
rect.setHeight(rect.height() + msgRadius());
|
||||
void rectWithCorners(Painter &p, QRect rect, const style::color &bg, RoundCorners index, RectParts corners) {
|
||||
auto parts = RectPart::Top
|
||||
| RectPart::NoTopBottom
|
||||
| RectPart::Bottom
|
||||
| corners;
|
||||
roundRect(p, rect, bg, index, nullptr, parts);
|
||||
if ((corners & RectPart::AllCorners) != RectPart::AllCorners) {
|
||||
const auto size = ::corners[index].p[0].width() / cIntRetinaFactor();
|
||||
if (!(corners & RectPart::TopLeft)) {
|
||||
p.fillRect(rect.x(), rect.y(), size, size, bg);
|
||||
}
|
||||
if (!(corners & RectPart::TopRight)) {
|
||||
p.fillRect(rect.x() + rect.width() - size, rect.y(), size, size, bg);
|
||||
}
|
||||
if (!(corners & RectPart::BottomLeft)) {
|
||||
p.fillRect(rect.x(), rect.y() + rect.height() - size, size, size, bg);
|
||||
}
|
||||
if (!(corners & RectPart::BottomRight)) {
|
||||
p.fillRect(rect.x() + rect.width() - size, rect.y() + rect.height() - size, size, size, bg);
|
||||
}
|
||||
} else if (corners & ImageRoundCorner::BottomLeft) {
|
||||
parts = RectPart::NoTopBottom | RectPart::FullBottom;
|
||||
rect.setTop(rect.y() - msgRadius());
|
||||
} else {
|
||||
parts = RectPart::NoTopBottom;
|
||||
rect.setTop(rect.y() - msgRadius());
|
||||
rect.setHeight(rect.height() + msgRadius());
|
||||
}
|
||||
}
|
||||
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) {
|
||||
if (radius == ImageRoundRadius::Ellipse) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(p.textPalette().selectOverlay);
|
||||
p.drawEllipse(rect);
|
||||
} else {
|
||||
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
|
||||
auto overlayParts = RectPart::Full | RectPart::None;
|
||||
if (radius == ImageRoundRadius::Large) {
|
||||
complexAdjustRect(corners, rect, overlayParts);
|
||||
}
|
||||
roundRect(p, rect, p.textPalette().selectOverlay, overlayCorners, nullptr, overlayParts);
|
||||
auto overlayCorners = (radius == ImageRoundRadius::Small)
|
||||
? SelectedOverlaySmallCorners
|
||||
: SelectedOverlayLargeCorners;
|
||||
const auto bg = p.textPalette().selectOverlay;
|
||||
rectWithCorners(p, rect, bg, overlayCorners, corners);
|
||||
}
|
||||
}
|
||||
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
|
||||
auto parts = RectPart::Full | RectPart::None;
|
||||
complexAdjustRect(corners, rect, parts);
|
||||
roundRect(p, rect, st::msgInBg, MessageInCorners, nullptr, parts);
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) {
|
||||
rectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners);
|
||||
}
|
||||
|
||||
QImage *cornersMask(ImageRoundRadius radius) {
|
||||
@@ -2766,7 +2662,7 @@ namespace {
|
||||
QImage images[4];
|
||||
switch (radius) {
|
||||
case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break;
|
||||
case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, msgRadius(), bg, nullptr, images); break;
|
||||
case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break;
|
||||
default: p.fillRect(x, y, w, h, bg); return;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,14 +52,6 @@ namespace App {
|
||||
|
||||
QString formatPhone(QString phone);
|
||||
|
||||
TimeId onlineForSort(UserData *user, TimeId now);
|
||||
int32 onlineWillChangeIn(UserData *user, TimeId now);
|
||||
int32 onlineWillChangeIn(TimeId online, TimeId now);
|
||||
QString onlineText(UserData *user, TimeId now, bool precise = false);
|
||||
QString onlineText(TimeId online, TimeId now, bool precise = false);
|
||||
bool onlineColorUse(UserData *user, TimeId now);
|
||||
bool onlineColorUse(TimeId online, TimeId now);
|
||||
|
||||
UserData *feedUser(const MTPUser &user);
|
||||
UserData *feedUsers(const MTPVector<MTPUser> &users); // returns last user
|
||||
PeerData *feedChat(const MTPChat &chat);
|
||||
@@ -270,8 +262,8 @@ namespace App {
|
||||
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
void setProxySettings(QTcpSocket &socket);
|
||||
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners);
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners);
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
|
||||
|
||||
QImage *cornersMask(ImageRoundRadius radius);
|
||||
void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full);
|
||||
|
||||
@@ -26,8 +26,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/localstorage.h"
|
||||
#include "autoupdater.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "messenger.h"
|
||||
#include "base/timer.h"
|
||||
#include "core/crash_report_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -71,8 +73,13 @@ QString _escapeFrom7bit(const QString &str) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Application::Application(int &argc, char **argv) : QApplication(argc, argv) {
|
||||
QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath()));
|
||||
Application::Application(
|
||||
not_null<Core::Launcher*> launcher,
|
||||
int &argc,
|
||||
char **argv)
|
||||
: QApplication(argc, argv)
|
||||
, _launcher(launcher) {
|
||||
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
|
||||
char h[33] = { 0 };
|
||||
hashMd5Hex(d.constData(), d.size(), h);
|
||||
#ifndef OS_MAC_STORE
|
||||
@@ -206,12 +213,12 @@ void Application::singleInstanceChecked() {
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
new NotStartedWindow();
|
||||
} else {
|
||||
SignalHandlers::Status status = SignalHandlers::start();
|
||||
if (status == SignalHandlers::CantOpen) {
|
||||
const auto status = CrashReports::Start();
|
||||
if (status == CrashReports::CantOpen) {
|
||||
new NotStartedWindow();
|
||||
} else if (status == SignalHandlers::LastCrashed) {
|
||||
} else if (status == CrashReports::LastCrashed) {
|
||||
if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now
|
||||
if (SignalHandlers::restart() == SignalHandlers::CantOpen) {
|
||||
if (CrashReports::Restart() == CrashReports::CantOpen) {
|
||||
new NotStartedWindow();
|
||||
} else {
|
||||
Sandbox::launch();
|
||||
@@ -313,7 +320,7 @@ void Application::startApplication() {
|
||||
|
||||
void Application::createMessenger() {
|
||||
Expects(!App::quitting());
|
||||
_messengerInstance = std::make_unique<Messenger>();
|
||||
_messengerInstance = std::make_unique<Messenger>(_launcher);
|
||||
}
|
||||
|
||||
void Application::closeApplication() {
|
||||
|
||||
@@ -21,11 +21,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#pragma once
|
||||
|
||||
class UpdateChecker;
|
||||
|
||||
namespace Core {
|
||||
class Launcher;
|
||||
} // namespace Core
|
||||
|
||||
class Application : public QApplication {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Application(int &argc, char **argv);
|
||||
Application(not_null<Core::Launcher*> launcher, int &argc, char **argv);
|
||||
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
@@ -55,6 +60,7 @@ private:
|
||||
typedef QPair<QLocalSocket*, QByteArray> LocalClient;
|
||||
typedef QList<LocalClient> LocalClients;
|
||||
|
||||
not_null<Core::Launcher*> _launcher;
|
||||
std::unique_ptr<Messenger> _messengerInstance;
|
||||
|
||||
QString _localServerName, _localSocketReadData;
|
||||
|
||||
@@ -22,16 +22,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "messenger.h"
|
||||
#include "core/changelogs.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/serialize_common.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "boxes/send_files_box.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -40,7 +44,8 @@ constexpr auto kAutoLockTimeoutLateMs = TimeMs(3000);
|
||||
} // namespace
|
||||
|
||||
AuthSessionData::Variables::Variables()
|
||||
: selectorTab(ChatHelpers::SelectorTab::Emoji)
|
||||
: sendFilesWay(SendFilesWay::Album)
|
||||
, selectorTab(ChatHelpers::SelectorTab::Emoji)
|
||||
, floatPlayerColumn(Window::Column::Second)
|
||||
, floatPlayerCorner(RectPart::TopRight) {
|
||||
}
|
||||
@@ -79,6 +84,7 @@ QByteArray AuthSessionData::serialize() const {
|
||||
1000000));
|
||||
stream << qint32(_variables.thirdColumnWidth.current());
|
||||
stream << qint32(_variables.thirdSectionExtendedBy);
|
||||
stream << qint32(_variables.sendFilesWay);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -103,6 +109,7 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
|
||||
float64 dialogsWidthRatio = _variables.dialogsWidthRatio.current();
|
||||
int thirdColumnWidth = _variables.thirdColumnWidth.current();
|
||||
int thirdSectionExtendedBy = _variables.thirdSectionExtendedBy;
|
||||
qint32 sendFilesWay = static_cast<qint32>(_variables.sendFilesWay);
|
||||
stream >> selectorTab;
|
||||
stream >> lastSeenWarningSeen;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -188,6 +195,12 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
|
||||
if (_variables.thirdSectionInfoEnabled) {
|
||||
_variables.tabbedSelectorSectionEnabled = false;
|
||||
}
|
||||
auto uncheckedSendFilesWay = static_cast<SendFilesWay>(sendFilesWay);
|
||||
switch (uncheckedSendFilesWay) {
|
||||
case SendFilesWay::Album:
|
||||
case SendFilesWay::Photos:
|
||||
case SendFilesWay::Files: _variables.sendFilesWay = uncheckedSendFilesWay;
|
||||
}
|
||||
}
|
||||
|
||||
void AuthSessionData::markItemLayoutChanged(not_null<const HistoryItem*> item) {
|
||||
@@ -242,13 +255,12 @@ auto AuthSessionData::megagroupParticipantRemoved() const -> rpl::producer<Megag
|
||||
|
||||
rpl::producer<not_null<UserData*>> AuthSessionData::megagroupParticipantRemoved(
|
||||
not_null<ChannelData*> channel) const {
|
||||
return megagroupParticipantRemoved()
|
||||
| rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
})
|
||||
| rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
return megagroupParticipantRemoved(
|
||||
) | rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
}) | rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
void AuthSessionData::addNewMegagroupParticipant(
|
||||
@@ -263,13 +275,12 @@ auto AuthSessionData::megagroupParticipantAdded() const -> rpl::producer<Megagro
|
||||
|
||||
rpl::producer<not_null<UserData*>> AuthSessionData::megagroupParticipantAdded(
|
||||
not_null<ChannelData*> channel) const {
|
||||
return megagroupParticipantAdded()
|
||||
| rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
})
|
||||
| rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
return megagroupParticipantAdded(
|
||||
) | rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
}) | rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
void AuthSessionData::setTabbedSelectorSectionEnabled(bool enabled) {
|
||||
@@ -378,6 +389,13 @@ MessageIdsList AuthSessionData::itemsToIds(
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
MessageIdsList AuthSessionData::groupToIds(
|
||||
not_null<HistoryMessageGroup*> group) const {
|
||||
auto result = itemsToIds(group->others);
|
||||
result.push_back(group->leader->fullId());
|
||||
return result;
|
||||
}
|
||||
|
||||
AuthSession &Auth() {
|
||||
auto result = Messenger::Instance().authSession();
|
||||
Assert(result != nullptr);
|
||||
@@ -392,8 +410,10 @@ AuthSession::AuthSession(UserId userId)
|
||||
, _downloader(std::make_unique<Storage::Downloader>())
|
||||
, _uploader(std::make_unique<Storage::Uploader>())
|
||||
, _storage(std::make_unique<Storage::Facade>())
|
||||
, _notifications(std::make_unique<Window::Notifications::System>(this)) {
|
||||
, _notifications(std::make_unique<Window::Notifications::System>(this))
|
||||
, _changelogs(Core::Changelogs::Create(this)) {
|
||||
Expects(_userId != 0);
|
||||
|
||||
_saveDataTimer.setCallback([this] {
|
||||
Local::writeUserSettings();
|
||||
});
|
||||
@@ -401,7 +421,7 @@ AuthSession::AuthSession(UserId userId)
|
||||
_shouldLockAt = 0;
|
||||
notifications().updateAll();
|
||||
});
|
||||
_api->start();
|
||||
Window::Theme::Background()->start();
|
||||
}
|
||||
|
||||
bool AuthSession::Exists() {
|
||||
|
||||
@@ -26,6 +26,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/timer.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
|
||||
class ApiWrap;
|
||||
enum class SendFilesWay;
|
||||
|
||||
namespace Storage {
|
||||
class Downloader;
|
||||
class Uploader;
|
||||
@@ -47,7 +50,9 @@ namespace ChatHelpers {
|
||||
enum class SelectorTab;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
class ApiWrap;
|
||||
namespace Core {
|
||||
class Changelogs;
|
||||
} // namespace Core
|
||||
|
||||
class AuthSessionData final {
|
||||
public:
|
||||
@@ -108,6 +113,12 @@ public:
|
||||
void setLastSeenWarningSeen(bool lastSeenWarningSeen) {
|
||||
_variables.lastSeenWarningSeen = lastSeenWarningSeen;
|
||||
}
|
||||
void setSendFilesWay(SendFilesWay way) {
|
||||
_variables.sendFilesWay = way;
|
||||
}
|
||||
SendFilesWay sendFilesWay() const {
|
||||
return _variables.sendFilesWay;
|
||||
}
|
||||
ChatHelpers::SelectorTab selectorTab() const {
|
||||
return _variables.selectorTab;
|
||||
}
|
||||
@@ -263,6 +274,7 @@ public:
|
||||
|
||||
HistoryItemsList idsToItems(const MessageIdsList &ids) const;
|
||||
MessageIdsList itemsToIds(const HistoryItemsList &items) const;
|
||||
MessageIdsList groupToIds(not_null<HistoryMessageGroup*> group) const;
|
||||
|
||||
private:
|
||||
struct Variables {
|
||||
@@ -272,6 +284,7 @@ private:
|
||||
static constexpr auto kDefaultThirdColumnWidth = 0;
|
||||
|
||||
bool lastSeenWarningSeen = false;
|
||||
SendFilesWay sendFilesWay;
|
||||
ChatHelpers::SelectorTab selectorTab; // per-window
|
||||
bool tabbedSelectorSectionEnabled = false; // per-window
|
||||
int tabbedSelectorSectionTooltipShown = 0;
|
||||
@@ -405,5 +418,6 @@ private:
|
||||
const std::unique_ptr<Storage::Uploader> _uploader;
|
||||
const std::unique_ptr<Storage::Facade> _storage;
|
||||
const std::unique_ptr<Window::Notifications::System> _notifications;
|
||||
const std::unique_ptr<Core::Changelogs> _changelogs;
|
||||
|
||||
};
|
||||
|
||||
@@ -46,7 +46,6 @@ struct SubscriptionHandlerHelper<void> {
|
||||
template <typename EventType>
|
||||
using SubscriptionHandler = typename SubscriptionHandlerHelper<EventType>::type;
|
||||
|
||||
// Required because QShared/WeakPointer can't point to void.
|
||||
class BaseObservableData {
|
||||
};
|
||||
|
||||
@@ -84,14 +83,17 @@ public:
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
Node(const QSharedPointer<internal::BaseObservableData> &observable) : observable(observable) {
|
||||
Node(const std::shared_ptr<internal::BaseObservableData> &observable)
|
||||
: observable(observable) {
|
||||
}
|
||||
Node *next = nullptr;
|
||||
Node *prev = nullptr;
|
||||
QWeakPointer<internal::BaseObservableData> observable;
|
||||
std::weak_ptr<internal::BaseObservableData> observable;
|
||||
};
|
||||
using RemoveAndDestroyMethod = void(*)(Node*);
|
||||
Subscription(Node *node, RemoveAndDestroyMethod removeAndDestroyMethod) : _node(node), _removeAndDestroyMethod(removeAndDestroyMethod) {
|
||||
Subscription(Node *node, RemoveAndDestroyMethod removeAndDestroyMethod)
|
||||
: _node(node)
|
||||
, _removeAndDestroyMethod(removeAndDestroyMethod) {
|
||||
}
|
||||
|
||||
Node *_node = nullptr;
|
||||
@@ -115,13 +117,13 @@ class CommonObservable {
|
||||
public:
|
||||
Subscription add_subscription(Handler &&handler) {
|
||||
if (!_data) {
|
||||
_data = MakeShared<ObservableData<EventType, Handler>>(this);
|
||||
_data = std::make_shared<ObservableData<EventType, Handler>>(this);
|
||||
}
|
||||
return _data->append(std::move(handler));
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<ObservableData<EventType, Handler>> _data;
|
||||
std::shared_ptr<ObservableData<EventType, Handler>> _data;
|
||||
|
||||
friend class CommonObservableData<EventType, Handler>;
|
||||
friend class BaseObservable<EventType, Handler, base::type_traits<EventType>::is_fast_copy_type::value>;
|
||||
@@ -184,7 +186,11 @@ public:
|
||||
|
||||
private:
|
||||
struct Node : public Subscription::Node {
|
||||
Node(const QSharedPointer<BaseObservableData> &observer, Handler &&handler) : Subscription::Node(observer), handler(std::move(handler)) {
|
||||
Node(
|
||||
const std::shared_ptr<BaseObservableData> &observer,
|
||||
Handler &&handler)
|
||||
: Subscription::Node(observer)
|
||||
, handler(std::move(handler)) {
|
||||
}
|
||||
Handler handler;
|
||||
};
|
||||
@@ -210,8 +216,8 @@ private:
|
||||
}
|
||||
|
||||
static void removeAndDestroyNode(Subscription::Node *node) {
|
||||
if (auto that = node->observable.toStrongRef()) {
|
||||
static_cast<CommonObservableData*>(that.data())->remove(node);
|
||||
if (const auto that = node->observable.lock()) {
|
||||
static_cast<CommonObservableData*>(that.get())->remove(node);
|
||||
}
|
||||
delete static_cast<Node*>(node);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "core/click_handler_types.h"
|
||||
|
||||
AboutBox::AboutBox(QWidget *parent)
|
||||
: _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
|
||||
@@ -43,7 +44,18 @@ void AboutBox::prepare() {
|
||||
|
||||
addButton(langFactory(lng_close), [this] { closeBox(); });
|
||||
|
||||
const auto linkHook = [](const ClickHandlerPtr &link, auto button) {
|
||||
if (const auto url = dynamic_cast<UrlClickHandler*>(link.get())) {
|
||||
url->UrlClickHandler::onClick(button);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
_text3->setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]")));
|
||||
_text1->setClickHandlerHook(linkHook);
|
||||
_text2->setClickHandlerHook(linkHook);
|
||||
_text3->setClickHandlerHook(linkHook);
|
||||
|
||||
_version->setClickedCallback([this] { showVersionHistory(); });
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ QPixmap BoxContent::grabInnerCache() {
|
||||
auto isBottomShadowVisible = !_bottomShadow->isHidden();
|
||||
if (isTopShadowVisible) _topShadow->setVisible(false);
|
||||
if (isBottomShadowVisible) _bottomShadow->setVisible(false);
|
||||
auto result = myGrab(this, _scroll->geometry());
|
||||
auto result = Ui::GrabWidget(this, _scroll->geometry());
|
||||
if (isTopShadowVisible) _topShadow->setVisible(true);
|
||||
if (isBottomShadowVisible) _bottomShadow->setVisible(true);
|
||||
return result;
|
||||
@@ -250,7 +250,6 @@ void AbstractBox::paintEvent(QPaintEvent *e) {
|
||||
auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius));
|
||||
if (paintTopRounded || paintBottomRounded) {
|
||||
auto parts = RectPart::None | 0;
|
||||
parts |= RectPart::None;
|
||||
if (paintTopRounded) parts |= RectPart::FullTop;
|
||||
if (paintBottomRounded) parts |= RectPart::FullBottom;
|
||||
App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts);
|
||||
|
||||
@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -1378,8 +1379,15 @@ RevokePublicLinkBox::Inner::Inner(QWidget *parent, base::lambda<void()> revokeCa
|
||||
|
||||
auto row = ChatRow(peer);
|
||||
row.peer = peer;
|
||||
row.name.setText(st::contactsNameStyle, peer->name, _textNameOptions);
|
||||
row.status.setText(st::defaultTextStyle, Messenger::Instance().createInternalLink(textcmdLink(1, peer->userName())), _textDlgOptions);
|
||||
row.name.setText(
|
||||
st::contactsNameStyle,
|
||||
peer->name,
|
||||
Ui::NameTextOptions());
|
||||
row.status.setText(
|
||||
st::defaultTextStyle,
|
||||
Messenger::Instance().createInternalLink(
|
||||
textcmdLink(1, peer->userName())),
|
||||
Ui::DialogTextOptions());
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,3 +716,11 @@ groupStickersField: InputField(contactsSearchField) {
|
||||
heightMin: 32px;
|
||||
}
|
||||
groupStickersSubTitleHeight: 36px;
|
||||
|
||||
sendMediaPreviewSize: 308px;
|
||||
sendMediaPreviewHeightMax: 1280;
|
||||
sendMediaPreviewPhotoSkip: 10px;
|
||||
sendMediaFileThumbSize: 64px;
|
||||
sendMediaFileThumbSkip: 10px;
|
||||
sendMediaFileNameTop: 7px;
|
||||
sendMediaFileStatusTop: 37px;
|
||||
|
||||
@@ -327,7 +327,7 @@ void ConvertToSupergroupBox::convertDone(const MTPUpdates &updates) {
|
||||
auto handleChats = [](auto &mtpChats) {
|
||||
for_const (auto &mtpChat, mtpChats.v) {
|
||||
if (mtpChat.type() == mtpc_channel) {
|
||||
auto channel = App::channel(mtpChat.c_channel().vid.v);
|
||||
const auto channel = App::channel(mtpChat.c_channel().vid.v);
|
||||
Ui::showPeerHistory(channel, ShowAtUnreadMsgId);
|
||||
Auth().api().requestParticipantsCountDelayed(channel);
|
||||
}
|
||||
@@ -565,13 +565,22 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
|
||||
if (_moderateFrom) {
|
||||
if (_banUser && _banUser->checked()) {
|
||||
Auth().api().kickParticipant(_moderateInChannel, _moderateFrom, MTP_channelBannedRights(MTP_flags(0), MTP_int(0)));
|
||||
Auth().api().kickParticipant(
|
||||
_moderateInChannel,
|
||||
_moderateFrom,
|
||||
MTP_channelBannedRights(MTP_flags(0), MTP_int(0)));
|
||||
}
|
||||
if (_reportSpam->checked()) {
|
||||
MTP::send(MTPchannels_ReportSpam(_moderateInChannel->inputChannel, _moderateFrom->inputUser, MTP_vector<MTPint>(1, MTP_int(_ids[0].msg))));
|
||||
MTP::send(
|
||||
MTPchannels_ReportSpam(
|
||||
_moderateInChannel->inputChannel,
|
||||
_moderateFrom->inputUser,
|
||||
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg))));
|
||||
}
|
||||
if (_deleteAll && _deleteAll->checked()) {
|
||||
App::main()->deleteAllFromUser(_moderateInChannel, _moderateFrom);
|
||||
App::main()->deleteAllFromUser(
|
||||
_moderateInChannel,
|
||||
_moderateFrom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
413
Telegram/SourceFiles/boxes/edit_caption_box.cpp
Normal file
413
Telegram/SourceFiles/boxes/edit_caption_box.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "boxes/edit_caption_box.h"
|
||||
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "mainwidget.h"
|
||||
#include "styles/style_history.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<HistoryMedia*> media,
|
||||
FullMsgId msgId)
|
||||
: _msgId(msgId) {
|
||||
Expects(media->canEditCaption());
|
||||
|
||||
QSize dimensions;
|
||||
ImagePtr image;
|
||||
QString caption;
|
||||
DocumentData *doc = nullptr;
|
||||
|
||||
switch (media->type()) {
|
||||
case MediaTypeGif: {
|
||||
_animated = true;
|
||||
doc = static_cast<HistoryGif*>(media.get())->getDocument();
|
||||
dimensions = doc->dimensions;
|
||||
image = doc->thumb;
|
||||
} break;
|
||||
|
||||
case MediaTypePhoto: {
|
||||
_photo = true;
|
||||
auto photo = static_cast<HistoryPhoto*>(media.get())->getPhoto();
|
||||
dimensions = QSize(photo->full->width(), photo->full->height());
|
||||
image = photo->full;
|
||||
} break;
|
||||
|
||||
case MediaTypeVideo: {
|
||||
_animated = true;
|
||||
doc = static_cast<HistoryVideo*>(media.get())->getDocument();
|
||||
dimensions = doc->dimensions;
|
||||
image = doc->thumb;
|
||||
} break;
|
||||
|
||||
case MediaTypeGrouped: {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
dimensions = QSize(photo->full->width(), photo->full->height());
|
||||
image = photo->full;
|
||||
_photo = true;
|
||||
} else if (const auto doc = media->getDocument()) {
|
||||
dimensions = doc->dimensions;
|
||||
image = doc->thumb;
|
||||
_animated = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case MediaTypeFile:
|
||||
case MediaTypeMusicFile:
|
||||
case MediaTypeVoiceFile: {
|
||||
_doc = true;
|
||||
doc = static_cast<HistoryDocument*>(media.get())->getDocument();
|
||||
image = doc->thumb;
|
||||
} break;
|
||||
}
|
||||
caption = media->getCaption().text;
|
||||
|
||||
if (!_animated && (dimensions.isEmpty() || doc || image->isNull())) {
|
||||
if (image->isNull()) {
|
||||
_thumbw = 0;
|
||||
} else {
|
||||
int32 tw = image->width(), th = image->height();
|
||||
if (tw > th) {
|
||||
_thumbw = (tw * st::msgFileThumbSize) / th;
|
||||
} else {
|
||||
_thumbw = st::msgFileThumbSize;
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::RoundedSmall | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::RoundedBottomLeft | Images::Option::RoundedBottomRight;
|
||||
_thumb = Images::pixmap(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, options, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
auto nameString = doc->isVoiceMessage()
|
||||
? lang(lng_media_audio)
|
||||
: doc->composeNameString();
|
||||
_name.setText(
|
||||
st::semiboldTextStyle,
|
||||
nameString,
|
||||
Ui::NameTextOptions());
|
||||
_status = formatSizeText(doc->size);
|
||||
_statusw = qMax(
|
||||
_name.maxWidth(),
|
||||
st::normalFont->width(_status));
|
||||
_isImage = doc->isImage();
|
||||
_isAudio = (doc->isVoiceMessage() || doc->isAudioFile());
|
||||
}
|
||||
} else {
|
||||
int32 maxW = 0, maxH = 0;
|
||||
if (_animated) {
|
||||
int32 limitW = st::sendMediaPreviewSize;
|
||||
int32 limitH = st::confirmMaxHeight;
|
||||
maxW = qMax(dimensions.width(), 1);
|
||||
maxH = qMax(dimensions.height(), 1);
|
||||
if (maxW * limitH > maxH * limitW) {
|
||||
if (maxW < limitW) {
|
||||
maxH = maxH * limitW / maxW;
|
||||
maxW = limitW;
|
||||
}
|
||||
} else {
|
||||
if (maxH < limitH) {
|
||||
maxW = maxW * limitH / maxH;
|
||||
maxH = limitH;
|
||||
}
|
||||
}
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth | Images::Option::Blurred, maxW, maxH);
|
||||
prepareGifPreview(doc);
|
||||
} else {
|
||||
maxW = dimensions.width();
|
||||
maxH = dimensions.height();
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth, maxW, maxH);
|
||||
}
|
||||
int32 tw = _thumb.width(), th = _thumb.height();
|
||||
if (!tw || !th) {
|
||||
tw = th = 1;
|
||||
}
|
||||
_thumbw = st::sendMediaPreviewSize;
|
||||
if (_thumb.width() < _thumbw) {
|
||||
_thumbw = (_thumb.width() > 20) ? _thumb.width() : 20;
|
||||
}
|
||||
int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight));
|
||||
_thumbh = qRound(th * float64(_thumbw) / tw);
|
||||
if (_thumbh > maxthumbh) {
|
||||
_thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh);
|
||||
_thumbh = maxthumbh;
|
||||
if (_thumbw < 10) {
|
||||
_thumbw = 10;
|
||||
}
|
||||
}
|
||||
_thumbx = (st::boxWideWidth - _thumbw) / 2;
|
||||
|
||||
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_thumb.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
Assert(_animated || _photo || _doc);
|
||||
|
||||
_field.create(this, st::confirmCaptionArea, langFactory(lng_photo_caption), caption);
|
||||
_field->setMaxLength(MaxPhotoCaption);
|
||||
_field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepareGifPreview(DocumentData *document) {
|
||||
auto createGifPreview = [document] {
|
||||
return (document && document->isAnimation());
|
||||
};
|
||||
auto createGifPreviewResult = createGifPreview(); // Clang freeze workaround.
|
||||
if (createGifPreviewResult) {
|
||||
_gifPreview = Media::Clip::MakeReader(document, _msgId, [this](Media::Clip::Notification notification) {
|
||||
clipCallback(notification);
|
||||
});
|
||||
if (_gifPreview) _gifPreview->setAutoplay();
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
if (_gifPreview && _gifPreview->state() == State::Error) {
|
||||
_gifPreview.setBad();
|
||||
}
|
||||
|
||||
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
|
||||
auto s = QSize(_thumbw, _thumbh);
|
||||
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
|
||||
}
|
||||
|
||||
update();
|
||||
} break;
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (_gifPreview && !_gifPreview->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
addButton(langFactory(lng_settings_save), [this] { save(); });
|
||||
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
||||
|
||||
updateBoxSize();
|
||||
connect(_field, &Ui::InputArea::submitted, this, [this] { save(); });
|
||||
connect(_field, &Ui::InputArea::cancelled, this, [this] {
|
||||
closeBox();
|
||||
});
|
||||
connect(_field, &Ui::InputArea::resized, this, [this] {
|
||||
captionResized();
|
||||
});
|
||||
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_field->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void EditCaptionBox::captionResized() {
|
||||
updateBoxSize();
|
||||
resizeEvent(0);
|
||||
update();
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateBoxSize() {
|
||||
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
|
||||
if (_photo || _animated) {
|
||||
newHeight += _thumbh;
|
||||
} else if (_thumbw) {
|
||||
newHeight += 0 + st::msgFileThumbSize + 0;
|
||||
} else if (_doc) {
|
||||
newHeight += 0 + st::msgFileSize + 0;
|
||||
} else {
|
||||
newHeight += st::boxTitleFont->height;
|
||||
}
|
||||
setDimensions(st::boxWideWidth, newHeight);
|
||||
}
|
||||
|
||||
int EditCaptionBox::errorTopSkip() const {
|
||||
return (st::boxButtonPadding.top() / 2);
|
||||
}
|
||||
|
||||
void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
if (_photo || _animated) {
|
||||
if (_thumbx > st::boxPhotoPadding.left()) {
|
||||
p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg);
|
||||
}
|
||||
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
|
||||
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg);
|
||||
}
|
||||
if (_gifPreview && _gifPreview->started()) {
|
||||
auto s = QSize(_thumbw, _thumbh);
|
||||
auto paused = controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
|
||||
auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : getms());
|
||||
p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), frame);
|
||||
} else {
|
||||
p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb);
|
||||
}
|
||||
if (_animated && !_gifPreview) {
|
||||
QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
auto icon = &st::historyFileInPlay;
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
} else if (_doc) {
|
||||
int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
int32 h = _thumbw ? (0 + st::msgFileThumbSize + 0) : (0 + st::msgFileSize + 0);
|
||||
int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0;
|
||||
if (_thumbw) {
|
||||
nameleft = 0 + st::msgFileThumbSize + st::msgFileThumbPadding.right();
|
||||
nametop = st::msgFileThumbNameTop - st::msgFileThumbPadding.top();
|
||||
nameright = 0;
|
||||
statustop = st::msgFileThumbStatusTop - st::msgFileThumbPadding.top();
|
||||
} else {
|
||||
nameleft = 0 + st::msgFileSize + st::msgFilePadding.right();
|
||||
nametop = st::msgFileNameTop - st::msgFilePadding.top();
|
||||
nameright = 0;
|
||||
statustop = st::msgFileStatusTop - st::msgFilePadding.top();
|
||||
}
|
||||
int32 namewidth = w - nameleft - 0;
|
||||
if (namewidth > _statusw) {
|
||||
//w -= (namewidth - _statusw);
|
||||
//namewidth = _statusw;
|
||||
}
|
||||
int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top();
|
||||
|
||||
// App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow);
|
||||
|
||||
if (_thumbw) {
|
||||
QRect rthumb(rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width()));
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
QRect inner(rtlrect(x + 0, y + 0, st::msgFileSize, st::msgFileSize, width()));
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgFileInBg);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument);
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(st::historyFileNameInFg);
|
||||
_name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width());
|
||||
|
||||
auto &status = st::mediaInFg;
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(status);
|
||||
p.drawTextLeft(x + nameleft, y + statustop, width(), _status);
|
||||
} else {
|
||||
p.setFont(st::boxTitleFont);
|
||||
p.setPen(st::boxTextFg);
|
||||
p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), lang(lng_edit_message));
|
||||
}
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawTextLeft(_field->x(), _field->y() + _field->height() + errorTopSkip(), width(), _error);
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_field->resize(st::sendMediaPreviewSize, _field->height());
|
||||
_field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height());
|
||||
}
|
||||
|
||||
void EditCaptionBox::setInnerFocus() {
|
||||
_field->setFocusFast();
|
||||
}
|
||||
|
||||
void EditCaptionBox::save() {
|
||||
if (_saveRequestId) return;
|
||||
|
||||
auto item = App::histItemById(_msgId);
|
||||
if (!item) {
|
||||
_error = lang(lng_edit_deleted);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = MTPmessages_EditMessage::Flag::f_message | 0;
|
||||
if (_previewCancelled) {
|
||||
flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
|
||||
}
|
||||
MTPVector<MTPMessageEntity> sentEntities;
|
||||
if (!sentEntities.v.isEmpty()) {
|
||||
flags |= MTPmessages_EditMessage::Flag::f_entities;
|
||||
}
|
||||
auto text = TextUtilities::PrepareForSending(_field->getLastText(), TextUtilities::PrepareTextOption::CheckLinks);
|
||||
_saveRequestId = MTP::send(
|
||||
MTPmessages_EditMessage(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_string(text),
|
||||
MTPnullMarkup,
|
||||
sentEntities,
|
||||
MTP_inputGeoPointEmpty()),
|
||||
rpcDone(&EditCaptionBox::saveDone),
|
||||
rpcFail(&EditCaptionBox::saveFail));
|
||||
}
|
||||
|
||||
void EditCaptionBox::saveDone(const MTPUpdates &updates) {
|
||||
_saveRequestId = 0;
|
||||
closeBox();
|
||||
if (App::main()) {
|
||||
App::main()->sentUpdatesReceived(updates);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditCaptionBox::saveFail(const RPCError &error) {
|
||||
if (MTP::isDefaultHandledError(error)) return false;
|
||||
|
||||
_saveRequestId = 0;
|
||||
QString err = error.type();
|
||||
if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) {
|
||||
_error = lang(lng_edit_error);
|
||||
} else if (err == qstr("MESSAGE_NOT_MODIFIED")) {
|
||||
closeBox();
|
||||
return true;
|
||||
} else if (err == qstr("MESSAGE_EMPTY")) {
|
||||
_field->setFocus();
|
||||
_field->showError();
|
||||
} else {
|
||||
_error = lang(lng_edit_error);
|
||||
}
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
77
Telegram/SourceFiles/boxes/edit_caption_box.h
Normal file
77
Telegram/SourceFiles/boxes/edit_caption_box.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Ui {
|
||||
class InputArea;
|
||||
} // namespace Ui
|
||||
|
||||
class EditCaptionBox : public BoxContent, public RPCSender {
|
||||
public:
|
||||
EditCaptionBox(QWidget*, not_null<HistoryMedia*> media, FullMsgId msgId);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateBoxSize();
|
||||
void prepareGifPreview(DocumentData *document);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
void save();
|
||||
void captionResized();
|
||||
|
||||
void saveDone(const MTPUpdates &updates);
|
||||
bool saveFail(const RPCError &error);
|
||||
|
||||
int errorTopSkip() const;
|
||||
|
||||
FullMsgId _msgId;
|
||||
bool _animated = false;
|
||||
bool _photo = false;
|
||||
bool _doc = false;
|
||||
|
||||
QPixmap _thumb;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
|
||||
object_ptr<Ui::InputArea> _field = { nullptr };
|
||||
|
||||
int _thumbx = 0;
|
||||
int _thumbw = 0;
|
||||
int _thumbh = 0;
|
||||
Text _name;
|
||||
QString _status;
|
||||
int _statusw = 0;
|
||||
bool _isAudio = false;
|
||||
bool _isImage = false;
|
||||
|
||||
bool _previewCancelled = false;
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
QString _error;
|
||||
|
||||
};
|
||||
@@ -24,9 +24,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "boxes/calendar_box.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -127,7 +129,10 @@ EditParticipantBox::Inner::Inner(
|
||||
st::rightsPhotoButton)
|
||||
, _hasAdminRights(hasAdminRights) {
|
||||
_userPhoto->setPointerCursor(false);
|
||||
_userName.setText(st::rightsNameStyle, App::peerName(_user), _textNameOptions);
|
||||
_userName.setText(
|
||||
st::rightsNameStyle,
|
||||
App::peerName(_user),
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void EditParticipantBox::Inner::removeControl(QPointer<TWidget> widget) {
|
||||
@@ -176,7 +181,7 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto seesAllMessages = (_user->botInfo->readsAllHistory || _hasAdminRights);
|
||||
return lang(seesAllMessages ? lng_status_bot_reads_all : lng_status_bot_not_reads_all);
|
||||
}
|
||||
return App::onlineText(_user->onlineTill, unixtime());
|
||||
return Data::OnlineText(_user->onlineTill, unixtime());
|
||||
};
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen(st::contactsStatusFg);
|
||||
|
||||
@@ -287,10 +287,10 @@ void EditPrivacyBox::createWidgets() {
|
||||
};
|
||||
auto createExceptionLink = [this](Exception exception) {
|
||||
exceptionLink(exception).create(this, object_ptr<Ui::LinkButton>(this, exceptionLinkText(exception)), exceptionLinkMargins());
|
||||
exceptionLink(exception)->heightValue()
|
||||
| rpl::start_with_next([this] {
|
||||
resizeToWidth(width());
|
||||
}, lifetime());
|
||||
exceptionLink(exception)->heightValue(
|
||||
) | rpl::start_with_next([this] {
|
||||
resizeToWidth(width());
|
||||
}, lifetime());
|
||||
exceptionLink(exception)->entity()->setClickedCallback([this, exception] { editExceptionUsers(exception); });
|
||||
};
|
||||
|
||||
|
||||
@@ -127,10 +127,10 @@ void NotificationsBox::prepare() {
|
||||
_sampleOpacities.push_back(Animation());
|
||||
}
|
||||
_countSlider->setActiveSectionFast(_oldCount - 1);
|
||||
_countSlider->sectionActivated()
|
||||
| rpl::start_with_next(
|
||||
[this] { countChanged(); },
|
||||
lifetime());
|
||||
_countSlider->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { countChanged(); },
|
||||
lifetime());
|
||||
|
||||
setMouseTracking(true);
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "observer_peer.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
|
||||
PeerListBox::PeerListBox(
|
||||
@@ -53,10 +55,10 @@ void PeerListBox::createMultiSelect() {
|
||||
|
||||
auto entity = object_ptr<Ui::MultiSelect>(this, st::contactsMultiSelect, langFactory(lng_participant_filter));
|
||||
_select.create(this, std::move(entity));
|
||||
_select->heightValue()
|
||||
| rpl::start_with_next(
|
||||
[this] { updateScrollSkips(); },
|
||||
lifetime());
|
||||
_select->heightValue(
|
||||
) | rpl::start_with_next(
|
||||
[this] { updateScrollSkips(); },
|
||||
lifetime());
|
||||
_select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { content()->submitted(); });
|
||||
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); });
|
||||
_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
|
||||
@@ -103,15 +105,15 @@ void PeerListBox::prepare() {
|
||||
setDimensions(st::boxWideWidth, st::boxMaxListHeight);
|
||||
if (_select) {
|
||||
_select->finishAnimating();
|
||||
myEnsureResized(_select);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
_scrollBottomFixed = true;
|
||||
onScrollToY(0);
|
||||
}
|
||||
|
||||
content()->scrollToRequests()
|
||||
| rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
onScrollToY(request.ymin, request.ymax);
|
||||
}, lifetime());
|
||||
content()->scrollToRequests(
|
||||
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
onScrollToY(request.ymin, request.ymax);
|
||||
}, lifetime());
|
||||
|
||||
if (_init) {
|
||||
_init(this);
|
||||
@@ -357,12 +359,12 @@ void PeerListRow::refreshStatus() {
|
||||
setStatusText(lang(lng_saved_forward_here));
|
||||
} else {
|
||||
auto time = unixtime();
|
||||
setStatusText(App::onlineText(user, time));
|
||||
if (App::onlineColorUse(user, time)) {
|
||||
setStatusText(Data::OnlineText(user, time));
|
||||
if (Data::OnlineTextActive(user, time)) {
|
||||
_statusType = StatusType::Online;
|
||||
}
|
||||
_statusValidTill = getms()
|
||||
+ App::onlineWillChangeIn(user, time) * 1000LL;
|
||||
+ Data::OnlineChangeTimeout(user, time);
|
||||
}
|
||||
} else if (auto chat = peer()->asChat()) {
|
||||
if (!chat->amIn()) {
|
||||
@@ -390,7 +392,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
const auto text = _isSavedMessagesChat
|
||||
? lang(lng_saved_messages)
|
||||
: peer()->name;
|
||||
_name.setText(st.nameStyle, text, _textNameOptions);
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
PeerListRow::~PeerListRow() = default;
|
||||
@@ -515,7 +517,7 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
}
|
||||
|
||||
void PeerListRow::setStatusText(const QString &text) {
|
||||
_status.setText(st::defaultTextStyle, text, _textNameOptions);
|
||||
_status.setText(st::defaultTextStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
float64 PeerListRow::checkedRatio() {
|
||||
|
||||
@@ -715,10 +715,10 @@ public:
|
||||
}
|
||||
void peerListSortRows(
|
||||
base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
|
||||
_content->reorderRows([compare = std::move(compare)](
|
||||
_content->reorderRows([&](
|
||||
auto &&begin,
|
||||
auto &&end) {
|
||||
std::sort(begin, end, [&compare](auto &&a, auto &&b) {
|
||||
std::sort(begin, end, [&](auto &&a, auto &&b) {
|
||||
return compare(*a, *b);
|
||||
});
|
||||
});
|
||||
@@ -726,10 +726,10 @@ public:
|
||||
int peerListPartitionRows(
|
||||
base::lambda<bool(const PeerListRow &a)> border) override {
|
||||
auto result = 0;
|
||||
_content->reorderRows([border = std::move(border), &result](
|
||||
_content->reorderRows([&](
|
||||
auto &&begin,
|
||||
auto &&end) {
|
||||
auto edge = std::stable_partition(begin, end, [&border](
|
||||
auto edge = std::stable_partition(begin, end, [&](
|
||||
auto &¤t) {
|
||||
return border(*current);
|
||||
});
|
||||
|
||||
@@ -750,23 +750,24 @@ void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) {
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
auto history = App::historyLoaded(chat);
|
||||
auto afterRequestId = history ? history->sendRequestId : 0;
|
||||
auto randomId = rand_value<uint64>();
|
||||
auto gameShortName = bot->botInfo->shareGameShortName;
|
||||
auto inputGame = MTP_inputGameShortName(
|
||||
bot->inputUser,
|
||||
MTP_string(gameShortName));
|
||||
auto request = MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
chat->input,
|
||||
MTP_int(0),
|
||||
MTP_inputMediaGame(inputGame),
|
||||
MTP_long(randomId),
|
||||
MTPnullMarkup);
|
||||
auto done = App::main()->rpcDone(&MainWidget::sentUpdatesReceived);
|
||||
auto fail = App::main()->rpcFail(&MainWidget::sendMessageFail);
|
||||
auto requestId = MTP::send(request, done, fail, 0, 0, afterRequestId);
|
||||
const auto history = App::historyLoaded(chat);
|
||||
const auto randomId = rand_value<uint64>();
|
||||
const auto requestId = MTP::send(
|
||||
MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
chat->input,
|
||||
MTP_int(0),
|
||||
MTP_inputMediaGame(
|
||||
MTP_inputGameShortName(
|
||||
bot->inputUser,
|
||||
MTP_string(bot->botInfo->shareGameShortName))),
|
||||
MTP_long(randomId),
|
||||
MTPnullMarkup),
|
||||
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
|
||||
App::main()->rpcFail(&MainWidget::sendMessageFail),
|
||||
0,
|
||||
0,
|
||||
history ? history->sendRequestId : 0);
|
||||
if (history) {
|
||||
history->sendRequestId = requestId;
|
||||
}
|
||||
@@ -778,9 +779,9 @@ void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) {
|
||||
return lng_bot_sure_share_game(lt_user, App::peerName(chat));
|
||||
}
|
||||
return lng_bot_sure_share_game_group(lt_group, chat->name);
|
||||
};
|
||||
}();
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(confirmText(), send),
|
||||
Box<ConfirmBox>(confirmText, std::move(send)),
|
||||
LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -799,17 +800,16 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
}
|
||||
if (auto &info = bot->botInfo) {
|
||||
if (!info->startGroupToken.isEmpty()) {
|
||||
auto request = MTPmessages_StartBot(
|
||||
bot->inputUser,
|
||||
chat->input,
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTP_string(info->startGroupToken));
|
||||
auto done = App::main()->rpcDone(
|
||||
&MainWidget::sentUpdatesReceived);
|
||||
auto fail = App::main()->rpcFail(
|
||||
&MainWidget::addParticipantFail,
|
||||
{ bot, chat });
|
||||
MTP::send(request, done, fail);
|
||||
MTP::send(
|
||||
MTPmessages_StartBot(
|
||||
bot->inputUser,
|
||||
chat->input,
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTP_string(info->startGroupToken)),
|
||||
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
|
||||
App::main()->rpcFail(
|
||||
&MainWidget::addParticipantFail,
|
||||
{ bot, chat }));
|
||||
} else {
|
||||
App::main()->addParticipants(
|
||||
chat,
|
||||
|
||||
@@ -264,17 +264,17 @@ object_ptr<Ui::RpWidget> Controller::createPhotoAndTitleEdit() {
|
||||
auto titleEdit = Ui::AttachParentChild(
|
||||
container,
|
||||
createTitleEdit());
|
||||
photoWrap->heightValue()
|
||||
| rpl::start_with_next([container](int height) {
|
||||
container->resize(container->width(), height);
|
||||
}, photoWrap->lifetime());
|
||||
container->widthValue()
|
||||
| rpl::start_with_next([titleEdit](int width) {
|
||||
auto left = st::editPeerPhotoMargins.left()
|
||||
+ st::defaultUserpicButton.size.width();
|
||||
titleEdit->resizeToWidth(width - left);
|
||||
titleEdit->moveToLeft(left, 0, width);
|
||||
}, titleEdit->lifetime());
|
||||
photoWrap->heightValue(
|
||||
) | rpl::start_with_next([container](int height) {
|
||||
container->resize(container->width(), height);
|
||||
}, photoWrap->lifetime());
|
||||
container->widthValue(
|
||||
) | rpl::start_with_next([titleEdit](int width) {
|
||||
auto left = st::editPeerPhotoMargins.left()
|
||||
+ st::defaultUserpicButton.size.width();
|
||||
titleEdit->resizeToWidth(width - left);
|
||||
titleEdit->moveToLeft(left, 0, width);
|
||||
}, titleEdit->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -439,16 +439,16 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
|
||||
base::lambda<QString()>(),
|
||||
channel->username,
|
||||
true));
|
||||
_controls.username->heightValue()
|
||||
| rpl::start_with_next([placeholder](int height) {
|
||||
placeholder->resize(placeholder->width(), height);
|
||||
}, placeholder->lifetime());
|
||||
placeholder->widthValue()
|
||||
| rpl::start_with_next([this](int width) {
|
||||
_controls.username->resize(
|
||||
width,
|
||||
_controls.username->height());
|
||||
}, placeholder->lifetime());
|
||||
_controls.username->heightValue(
|
||||
) | rpl::start_with_next([placeholder](int height) {
|
||||
placeholder->resize(placeholder->width(), height);
|
||||
}, placeholder->lifetime());
|
||||
placeholder->widthValue(
|
||||
) | rpl::start_with_next([this](int width) {
|
||||
_controls.username->resize(
|
||||
width,
|
||||
_controls.username->height());
|
||||
}, placeholder->lifetime());
|
||||
_controls.username->move(placeholder->pos());
|
||||
|
||||
QObject::connect(
|
||||
@@ -624,12 +624,12 @@ void Controller::showUsernameResult(
|
||||
*st);
|
||||
auto label = _controls.usernameResult.get();
|
||||
label->show();
|
||||
label->widthValue()
|
||||
| rpl::start_with_next([label] {
|
||||
label->moveToRight(
|
||||
st::editPeerUsernamePosition.x(),
|
||||
st::editPeerUsernamePosition.y());
|
||||
}, label->lifetime());
|
||||
label->widthValue(
|
||||
) | rpl::start_with_next([label] {
|
||||
label->moveToRight(
|
||||
st::editPeerUsernamePosition.x(),
|
||||
st::editPeerUsernamePosition.y());
|
||||
}, label->lifetime());
|
||||
}
|
||||
_usernameResultTexts.fire(std::move(text));
|
||||
}
|
||||
@@ -729,10 +729,10 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
|
||||
|
||||
Notify::PeerUpdateValue(
|
||||
_peer,
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged)
|
||||
| rpl::start_with_next([this] {
|
||||
refreshEditInviteLink();
|
||||
}, _controls.editInviteLinkWrap->lifetime());
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged
|
||||
) | rpl::start_with_next([this] {
|
||||
refreshEditInviteLink();
|
||||
}, _controls.editInviteLinkWrap->lifetime());
|
||||
|
||||
return std::move(result);
|
||||
}
|
||||
@@ -794,10 +794,10 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkCreate() {
|
||||
|
||||
Notify::PeerUpdateValue(
|
||||
_peer,
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged)
|
||||
| rpl::start_with_next([this] {
|
||||
refreshCreateInviteLink();
|
||||
}, _controls.createInviteLinkWrap->lifetime());
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged
|
||||
) | rpl::start_with_next([this] {
|
||||
refreshCreateInviteLink();
|
||||
}, _controls.createInviteLinkWrap->lifetime());
|
||||
|
||||
return std::move(result);
|
||||
}
|
||||
@@ -814,7 +814,8 @@ object_ptr<Ui::RpWidget> Controller::createHistoryVisibilityEdit() {
|
||||
auto channel = _peer->asChannel();
|
||||
if (!channel
|
||||
|| !channel->canEditPreHistoryHidden()
|
||||
|| !channel->isMegagroup()) {
|
||||
|| !channel->isMegagroup()
|
||||
|| (channel->isPublic() && !channel->canEditUsername())) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
@@ -1436,15 +1437,15 @@ EditPeerInfoBox::EditPeerInfoBox(
|
||||
|
||||
void EditPeerInfoBox::prepare() {
|
||||
auto controller = std::make_unique<Controller>(this, _peer);
|
||||
_focusRequests.events()
|
||||
| rpl::start_with_next(
|
||||
[c = controller.get()] { c->setFocus(); },
|
||||
lifetime());
|
||||
_focusRequests.events(
|
||||
) | rpl::start_with_next(
|
||||
[c = controller.get()] { c->setFocus(); },
|
||||
lifetime());
|
||||
auto content = controller->createContent();
|
||||
content->heightValue()
|
||||
| rpl::start_with_next([this](int height) {
|
||||
setDimensions(st::boxWideWidth, height);
|
||||
}, content->lifetime());
|
||||
content->heightValue(
|
||||
) | rpl::start_with_next([this](int height) {
|
||||
setDimensions(st::boxWideWidth, height);
|
||||
}, content->lifetime());
|
||||
setInnerWidget(object_ptr<Ui::IgnoreMargins>(
|
||||
this,
|
||||
std::move(content)));
|
||||
|
||||
@@ -85,13 +85,15 @@ void AddButtonWithCount(
|
||||
std::move(count),
|
||||
st::managePeerButtonLabel);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
rpl::combine(button->widthValue(), label->widthValue())
|
||||
| rpl::start_with_next([label](int outerWidth, int width) {
|
||||
label->moveToRight(
|
||||
st::managePeerButtonLabelPosition.x(),
|
||||
st::managePeerButtonLabelPosition.y(),
|
||||
outerWidth);
|
||||
}, label->lifetime());
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
label->widthValue()
|
||||
) | rpl::start_with_next([label](int outerWidth, int width) {
|
||||
label->moveToRight(
|
||||
st::managePeerButtonLabelPosition.x(),
|
||||
st::managePeerButtonLabelPosition.y(),
|
||||
outerWidth);
|
||||
}, label->lifetime());
|
||||
}
|
||||
|
||||
bool HasRecentActions(not_null<ChannelData*> channel) {
|
||||
@@ -102,7 +104,16 @@ void ShowRecentActions(
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<ChannelData*> channel) {
|
||||
controller->showSection(AdminLog::SectionMemento(channel));
|
||||
}
|
||||
|
||||
bool HasEditInfoBox(not_null<ChannelData*> channel) {
|
||||
if (channel->canEditInformation()) {
|
||||
return true;
|
||||
} else if (!channel->isPublic() && channel->canAddMembers()) {
|
||||
// Edit invite link.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FillManageBox(
|
||||
@@ -112,7 +123,7 @@ void FillManageBox(
|
||||
using Profile::ParticipantsBoxController;
|
||||
|
||||
auto isGroup = channel->isMegagroup();
|
||||
if (channel->canEditInformation()) {
|
||||
if (HasEditInfoBox(channel)) {
|
||||
AddButton(
|
||||
content,
|
||||
Lang::Viewer(isGroup
|
||||
@@ -157,18 +168,20 @@ void FillManageBox(
|
||||
st::infoIconAdministrators);
|
||||
}
|
||||
if (channel->canViewBanned()) {
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_restricted_users),
|
||||
Info::Profile::RestrictedCountValue(channel)
|
||||
| ToPositiveNumberString(),
|
||||
[=] {
|
||||
ParticipantsBoxController::Start(
|
||||
controller,
|
||||
channel,
|
||||
ParticipantsBoxController::Role::Restricted);
|
||||
},
|
||||
st::infoIconRestrictedUsers);
|
||||
if (channel->isMegagroup()) {
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_restricted_users),
|
||||
Info::Profile::RestrictedCountValue(channel)
|
||||
| ToPositiveNumberString(),
|
||||
[=] {
|
||||
ParticipantsBoxController::Start(
|
||||
controller,
|
||||
channel,
|
||||
ParticipantsBoxController::Role::Restricted);
|
||||
},
|
||||
st::infoIconRestrictedUsers);
|
||||
}
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_banned_users),
|
||||
@@ -219,12 +232,12 @@ void ManagePeerBox::prepare() {
|
||||
void ManagePeerBox::setupContent() {
|
||||
auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
FillManageBox(controller(), _channel, content);
|
||||
widthValue()
|
||||
| rpl::start_with_next([=](int width) {
|
||||
content->resizeToWidth(width);
|
||||
}, content->lifetime());
|
||||
content->heightValue()
|
||||
| rpl::start_with_next([=](int height) {
|
||||
setDimensions(st::boxWidth, height);
|
||||
}, content->lifetime());
|
||||
widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
content->resizeToWidth(width);
|
||||
}, content->lifetime());
|
||||
content->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
setDimensions(st::boxWidth, height);
|
||||
}, content->lifetime());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,25 +20,40 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <rpl/variable.h>
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
|
||||
namespace Ui {
|
||||
class Checkbox;
|
||||
template <typename Enum>
|
||||
class Radioenum;
|
||||
template <typename Enum>
|
||||
class RadioenumGroup;
|
||||
class RoundButton;
|
||||
class InputArea;
|
||||
class EmptyUserpic;
|
||||
struct GroupMediaLayout;
|
||||
} // namespace Ui
|
||||
|
||||
enum class SendFilesWay {
|
||||
Album,
|
||||
Photos,
|
||||
Files,
|
||||
};
|
||||
|
||||
class SendFilesBox : public BoxContent {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SendFilesBox(QWidget*, QImage image, CompressConfirm compressed);
|
||||
SendFilesBox(QWidget*, const QStringList &files, CompressConfirm compressed);
|
||||
SendFilesBox(QWidget*, const QString &phone, const QString &firstname, const QString &lastname);
|
||||
SendFilesBox(
|
||||
QWidget*,
|
||||
Storage::PreparedList &&list,
|
||||
CompressConfirm compressed);
|
||||
|
||||
void setConfirmedCallback(base::lambda<void(const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter)> callback) {
|
||||
void setConfirmedCallback(
|
||||
base::lambda<void(
|
||||
Storage::PreparedList &&list,
|
||||
SendFilesWay way,
|
||||
const QString &caption,
|
||||
bool ctrlShiftEnter)> callback) {
|
||||
_confirmedCallback = std::move(callback);
|
||||
}
|
||||
void setCancelledCallback(base::lambda<void()> callback) {
|
||||
@@ -55,116 +70,64 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void onCompressedChange();
|
||||
void onSend(bool ctrlShiftEnter = false);
|
||||
void onCaptionResized();
|
||||
void onClose() {
|
||||
closeBox();
|
||||
}
|
||||
|
||||
private:
|
||||
void prepareSingleFileLayout();
|
||||
void prepareDocumentLayout();
|
||||
void tryToReadSingleFile();
|
||||
void prepareGifPreview();
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
class AlbumPreview;
|
||||
|
||||
void updateTitleText();
|
||||
void initSendWay();
|
||||
void initPreview(rpl::producer<int> desiredPreviewHeight);
|
||||
|
||||
void setupControls();
|
||||
void setupSendWayControls();
|
||||
void setupCaption();
|
||||
void setupShadows(
|
||||
not_null<Ui::ScrollArea*> wrap,
|
||||
not_null<AlbumPreview*> content);
|
||||
|
||||
void refreshAlbumMediaCount();
|
||||
void preparePreview();
|
||||
void prepareSingleFilePreview();
|
||||
void prepareAlbumPreview();
|
||||
void applyAlbumOrder();
|
||||
|
||||
void send(bool ctrlShiftEnter = false);
|
||||
void captionResized();
|
||||
|
||||
void setupTitleText();
|
||||
void updateBoxSize();
|
||||
void updateControlsGeometry();
|
||||
base::lambda<QString()> getSendButtonText() const;
|
||||
|
||||
bool canAddFiles(not_null<const QMimeData*> data) const;
|
||||
bool addFiles(not_null<const QMimeData*> data);
|
||||
|
||||
QString _titleText;
|
||||
QStringList _files;
|
||||
QImage _image;
|
||||
std::unique_ptr<FileLoadTask::MediaInformation> _information;
|
||||
int _titleHeight = 0;
|
||||
|
||||
Storage::PreparedList _list;
|
||||
|
||||
CompressConfirm _compressConfirmInitial = CompressConfirm::None;
|
||||
CompressConfirm _compressConfirm = CompressConfirm::None;
|
||||
bool _animated = false;
|
||||
|
||||
QPixmap _preview;
|
||||
int _previewLeft = 0;
|
||||
int _previewWidth = 0;
|
||||
int _previewHeight = 0;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
|
||||
QPixmap _fileThumb;
|
||||
Text _nameText;
|
||||
bool _fileIsAudio = false;
|
||||
bool _fileIsImage = false;
|
||||
QString _statusText;
|
||||
int _statusWidth = 0;
|
||||
|
||||
QString _contactPhone;
|
||||
QString _contactFirstName;
|
||||
QString _contactLastName;
|
||||
std::unique_ptr<Ui::EmptyUserpic> _contactPhotoEmpty;
|
||||
|
||||
base::lambda<void(const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter)> _confirmedCallback;
|
||||
base::lambda<void(
|
||||
Storage::PreparedList &&list,
|
||||
SendFilesWay way,
|
||||
const QString &caption,
|
||||
bool ctrlShiftEnter)> _confirmedCallback;
|
||||
base::lambda<void()> _cancelledCallback;
|
||||
bool _confirmed = false;
|
||||
|
||||
object_ptr<Ui::InputArea> _caption = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _compressed = { nullptr };
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> _sendAlbum = { nullptr };
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> _sendPhotos = { nullptr };
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> _sendFiles = { nullptr };
|
||||
std::shared_ptr<Ui::RadioenumGroup<SendFilesWay>> _sendWay;
|
||||
|
||||
rpl::variable<int> _footerHeight = 0;
|
||||
|
||||
QWidget *_preview = nullptr;
|
||||
AlbumPreview *_albumPreview = nullptr;
|
||||
int _albumVideosCount = 0;
|
||||
int _albumPhotosCount = 0;
|
||||
|
||||
QPointer<Ui::RoundButton> _send;
|
||||
|
||||
};
|
||||
|
||||
class EditCaptionBox : public BoxContent, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId);
|
||||
|
||||
public slots:
|
||||
void onCaptionResized();
|
||||
void onSave(bool ctrlShiftEnter = false);
|
||||
void onClose() {
|
||||
closeBox();
|
||||
}
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateBoxSize();
|
||||
void prepareGifPreview(DocumentData *document);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
void saveDone(const MTPUpdates &updates);
|
||||
bool saveFail(const RPCError &error);
|
||||
|
||||
int errorTopSkip() const;
|
||||
|
||||
FullMsgId _msgId;
|
||||
bool _animated = false;
|
||||
bool _photo = false;
|
||||
bool _doc = false;
|
||||
|
||||
QPixmap _thumb;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
|
||||
object_ptr<Ui::InputArea> _field = { nullptr };
|
||||
|
||||
int _thumbx = 0;
|
||||
int _thumby = 0;
|
||||
int _thumbw = 0;
|
||||
int _thumbh = 0;
|
||||
Text _name;
|
||||
QString _status;
|
||||
int _statusw = 0;
|
||||
bool _isAudio = false;
|
||||
bool _isImage = false;
|
||||
|
||||
bool _previewCancelled = false;
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
QString _error;
|
||||
|
||||
};
|
||||
|
||||
@@ -33,10 +33,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "auth_session.h"
|
||||
@@ -52,7 +53,7 @@ ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submi
|
||||
|
||||
void ShareBox::prepare() {
|
||||
_select->resizeToWidth(st::boxWideWidth);
|
||||
myEnsureResized(_select);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
|
||||
setTitle(langFactory(lng_share_title));
|
||||
|
||||
@@ -377,7 +378,7 @@ void ShareBox::Inner::updateChatName(
|
||||
not_null<Chat*> chat,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto text = peer->isSelf() ? lang(lng_saved_messages) : peer->name;
|
||||
chat->name.setText(st::shareNameStyle, text, _textNameOptions);
|
||||
chat->name.setText(st::shareNameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void ShareBox::Inner::repaintChatAtIndex(int index) {
|
||||
|
||||
@@ -50,21 +50,21 @@ void StickerSetBox::prepare() {
|
||||
setTitle(langFactory(lng_contacts_loading));
|
||||
|
||||
_inner = setInnerWidget(object_ptr<Inner>(this, _set), st::stickersScroll);
|
||||
Auth().data().stickersUpdated()
|
||||
| rpl::start_with_next(
|
||||
[this] { updateButtons(); },
|
||||
lifetime());
|
||||
Auth().data().stickersUpdated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { updateButtons(); },
|
||||
lifetime());
|
||||
|
||||
setDimensions(st::boxWideWidth, st::stickersMaxHeight);
|
||||
|
||||
onUpdateButtons();
|
||||
|
||||
connect(_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons()));
|
||||
_inner->setInstalled()
|
||||
| rpl::start_with_next([this](auto &&setId) {
|
||||
Auth().api().stickerSetInstalled(setId);
|
||||
this->closeBox();
|
||||
}, lifetime());
|
||||
_inner->setInstalled(
|
||||
) | rpl::start_with_next([this](auto &&setId) {
|
||||
Auth().api().stickerSetInstalled(setId);
|
||||
this->closeBox();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickerSetBox::onAddStickers() {
|
||||
|
||||
@@ -90,11 +90,11 @@ StickersBox::CounterWidget::CounterWidget(QWidget *parent)
|
||||
_st.padding = st::stickersFeaturedBadgePadding;
|
||||
_st.font = st::stickersFeaturedBadgeFont;
|
||||
|
||||
Auth().data().featuredStickerSetsUnreadCountValue()
|
||||
| rpl::start_with_next([this](int count) {
|
||||
setCounter(count);
|
||||
update();
|
||||
}, lifetime());
|
||||
Auth().data().featuredStickerSetsUnreadCountValue(
|
||||
) | rpl::start_with_next([this](int count) {
|
||||
setCounter(count);
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
@@ -242,10 +242,10 @@ void StickersBox::prepare() {
|
||||
preloadArchivedSets();
|
||||
}
|
||||
setNoContentMargin(true);
|
||||
_tabs->sectionActivated()
|
||||
| rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
_tabs->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
refreshTabs();
|
||||
}
|
||||
if (_installed.widget() && _section != Section::Installed) _installed.widget()->hide();
|
||||
@@ -277,10 +277,10 @@ void StickersBox::prepare() {
|
||||
setInnerWidget(_tab->takeWidget(), getTopSkip());
|
||||
setDimensions(st::boxWideWidth, st::boxMaxListHeight);
|
||||
|
||||
Auth().data().stickersUpdated()
|
||||
| rpl::start_with_next(
|
||||
[this] { handleStickersUpdated(); },
|
||||
lifetime());
|
||||
Auth().data().stickersUpdated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { handleStickersUpdated(); },
|
||||
lifetime());
|
||||
Auth().api().updateStickers();
|
||||
|
||||
if (_installed.widget()) {
|
||||
@@ -1551,13 +1551,13 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
|
||||
auto customIt = Auth().data().stickerSets().constFind(Stickers::CustomSetId);
|
||||
if (customIt != Auth().data().stickerSets().cend()) {
|
||||
added = customIt->stickers.size();
|
||||
for_const (auto &sticker, cGetRecentStickers()) {
|
||||
for_const (auto &sticker, Stickers::GetRecentPack()) {
|
||||
if (customIt->stickers.indexOf(sticker.first) < 0) {
|
||||
++added;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
added = cGetRecentStickers().size();
|
||||
added = Stickers::GetRecentPack().size();
|
||||
}
|
||||
}
|
||||
return result + added;
|
||||
|
||||
@@ -214,19 +214,19 @@ void BoxController::Row::stopLastActionRipple() {
|
||||
}
|
||||
|
||||
void BoxController::prepare() {
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::start_with_next([this](auto item) {
|
||||
if (auto row = rowForItem(item)) {
|
||||
row->itemRemoved(item);
|
||||
if (!row->hasItems()) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
if (!delegate()->peerListFullRowsCount()) {
|
||||
refreshAbout();
|
||||
}
|
||||
Auth().data().itemRemoved(
|
||||
) | rpl::start_with_next([this](auto item) {
|
||||
if (auto row = rowForItem(item)) {
|
||||
row->itemRemoved(item);
|
||||
if (!row->hasItems()) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
if (!delegate()->peerListFullRowsCount()) {
|
||||
refreshAbout();
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}, lifetime());
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}, lifetime());
|
||||
subscribe(Current().newServiceMessage(), [this](const FullMsgId &msgId) {
|
||||
if (auto item = App::histItemById(msgId)) {
|
||||
insertRow(item, InsertWay::Prepend);
|
||||
|
||||
@@ -365,10 +365,12 @@ void Panel::initLayout() {
|
||||
|
||||
initGeometry();
|
||||
|
||||
Notify::PeerUpdateValue(_user, Notify::PeerUpdate::Flag::PhotoChanged)
|
||||
| rpl::start_with_next(
|
||||
[this] { processUserPhoto(); },
|
||||
lifetime());
|
||||
Notify::PeerUpdateValue(
|
||||
_user,
|
||||
Notify::PeerUpdate::Flag::PhotoChanged
|
||||
) | rpl::start_with_next(
|
||||
[this] { processUserPhoto(); },
|
||||
lifetime());
|
||||
subscribe(Auth().downloaderTaskFinished(), [this] {
|
||||
refreshUserPhoto();
|
||||
});
|
||||
@@ -386,10 +388,15 @@ void Panel::toggleOpacityAnimation(bool visible) {
|
||||
if (_useTransparency) {
|
||||
if (_animationCache.isNull()) {
|
||||
showControls();
|
||||
_animationCache = myGrab(this);
|
||||
_animationCache = Ui::GrabWidget(this);
|
||||
hideChildren();
|
||||
}
|
||||
_opacityAnimation.start([this] { update(); }, _visible ? 0. : 1., _visible ? 1. : 0., st::callPanelDuration, _visible ? anim::easeOutCirc : anim::easeInCirc);
|
||||
_opacityAnimation.start(
|
||||
[this] { update(); },
|
||||
_visible ? 0. : 1.,
|
||||
_visible ? 1. : 0.,
|
||||
st::callPanelDuration,
|
||||
_visible ? anim::easeOutCirc : anim::easeInCirc);
|
||||
}
|
||||
if (isHidden() && _visible) {
|
||||
show();
|
||||
@@ -490,7 +497,7 @@ void Panel::createUserpicCache(ImagePtr image) {
|
||||
_user->name
|
||||
).paintSquare(p, 0, 0, st::callWidth, st::callWidth);
|
||||
}
|
||||
Images::prepareRound(filled, ImageRoundRadius::Large, ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight);
|
||||
Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight);
|
||||
_userPhoto = App::pixmapFromImageInPlace(std::move(filled));
|
||||
}
|
||||
refreshCacheImageUserPhoto();
|
||||
|
||||
@@ -20,9 +20,91 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "chat_helpers/bot_keyboard.h"
|
||||
|
||||
#include "history/history_item_components.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Style : public ReplyKeyboard::Style {
|
||||
public:
|
||||
Style(
|
||||
not_null<BotKeyboard*> parent,
|
||||
const style::BotKeyboardButton &st);
|
||||
|
||||
int buttonRadius() const override;
|
||||
|
||||
void startPaint(Painter &p) const override;
|
||||
const style::TextStyle &textStyle() const override;
|
||||
void repaint(not_null<const HistoryItem*> item) const override;
|
||||
|
||||
protected:
|
||||
void paintButtonBg(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
float64 howMuchOver) const override;
|
||||
void paintButtonIcon(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
int outerWidth,
|
||||
HistoryMessageMarkupButton::Type type) const override;
|
||||
void paintButtonLoading(Painter &p, const QRect &rect) const override;
|
||||
int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
|
||||
|
||||
private:
|
||||
not_null<BotKeyboard*> _parent;
|
||||
|
||||
};
|
||||
|
||||
Style::Style(
|
||||
not_null<BotKeyboard*> parent,
|
||||
const style::BotKeyboardButton &st)
|
||||
: ReplyKeyboard::Style(st), _parent(parent) {
|
||||
}
|
||||
|
||||
void Style::startPaint(Painter &p) const {
|
||||
p.setPen(st::botKbColor);
|
||||
p.setFont(st::botKbStyle.font);
|
||||
}
|
||||
|
||||
const style::TextStyle &Style::textStyle() const {
|
||||
return st::botKbStyle;
|
||||
}
|
||||
|
||||
void Style::repaint(not_null<const HistoryItem*> item) const {
|
||||
_parent->update();
|
||||
}
|
||||
|
||||
int Style::buttonRadius() const {
|
||||
return st::buttonRadius;
|
||||
}
|
||||
|
||||
void Style::paintButtonBg(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
float64 howMuchOver) const {
|
||||
App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
|
||||
}
|
||||
|
||||
void Style::paintButtonIcon(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
int outerWidth,
|
||||
HistoryMessageMarkupButton::Type type) const {
|
||||
// Buttons with icons should not appear here.
|
||||
}
|
||||
|
||||
void Style::paintButtonLoading(Painter &p, const QRect &rect) const {
|
||||
// Buttons with loading progress should not appear here.
|
||||
}
|
||||
|
||||
int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const {
|
||||
int result = 2 * buttonPadding();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BotKeyboard::BotKeyboard(QWidget *parent) : TWidget(parent)
|
||||
, _st(&st::botKbButton) {
|
||||
setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
|
||||
@@ -43,40 +125,6 @@ void BotKeyboard::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::startPaint(Painter &p) const {
|
||||
p.setPen(st::botKbColor);
|
||||
p.setFont(st::botKbStyle.font);
|
||||
}
|
||||
|
||||
const style::TextStyle &BotKeyboard::Style::textStyle() const {
|
||||
return st::botKbStyle;
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::repaint(not_null<const HistoryItem*> item) const {
|
||||
_parent->update();
|
||||
}
|
||||
|
||||
int BotKeyboard::Style::buttonRadius() const {
|
||||
return st::buttonRadius;
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const {
|
||||
App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const {
|
||||
// Buttons with icons should not appear here.
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::paintButtonLoading(Painter &p, const QRect &rect) const {
|
||||
// Buttons with loading progress should not appear here.
|
||||
}
|
||||
|
||||
int BotKeyboard::Style::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const {
|
||||
int result = 2 * buttonPadding();
|
||||
return result;
|
||||
}
|
||||
|
||||
void BotKeyboard::mousePressEvent(QMouseEvent *e) {
|
||||
_lastMousePos = e->globalPos();
|
||||
updateSelected();
|
||||
@@ -108,16 +156,18 @@ void BotKeyboard::leaveEventHook(QEvent *e) {
|
||||
}
|
||||
|
||||
bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
if (auto item = App::histItemById(_wasForMsgId)) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (const auto item = App::histItemById(_wasForMsgId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (key >= Qt::Key_1 && key <= Qt::Key_9) {
|
||||
int index = (key - Qt::Key_1);
|
||||
if (!markup->rows.isEmpty() && index >= 0 && index < markup->rows.front().size()) {
|
||||
const auto index = int(key - Qt::Key_1);
|
||||
if (!markup->rows.empty()
|
||||
&& index >= 0
|
||||
&& index < int(markup->rows.front().size())) {
|
||||
App::activateBotCommand(item, 0, index);
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Q) {
|
||||
if (auto user = item->history()->peer->asUser()) {
|
||||
if (const auto user = item->history()->peer->asUser()) {
|
||||
if (user->botInfo && item->from() == user) {
|
||||
App::sendBotCommand(user, user, qsl("/translate"));
|
||||
return true;
|
||||
@@ -163,8 +213,10 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
|
||||
_impl = nullptr;
|
||||
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (!markup->rows.isEmpty()) {
|
||||
_impl.reset(new ReplyKeyboard(to, std::make_unique<Style>(this, *_st)));
|
||||
if (!markup->rows.empty()) {
|
||||
_impl = std::make_unique<ReplyKeyboard>(
|
||||
to,
|
||||
std::make_unique<Style>(this, *_st));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,3 +298,5 @@ void BotKeyboard::updateSelected() {
|
||||
setCursor(link ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
}
|
||||
|
||||
BotKeyboard::~BotKeyboard() = default;
|
||||
|
||||
@@ -22,7 +22,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "ui/widgets/tooltip.h"
|
||||
|
||||
class BotKeyboard : public TWidget, public Ui::AbstractTooltipShower, public ClickHandlerHost {
|
||||
class ReplyKeyboard;
|
||||
|
||||
class BotKeyboard
|
||||
: public TWidget
|
||||
, public Ui::AbstractTooltipShower
|
||||
, public ClickHandlerHost {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -57,6 +62,8 @@ public:
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
~BotKeyboard();
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
@@ -83,27 +90,6 @@ private:
|
||||
QPoint _lastMousePos;
|
||||
std::unique_ptr<ReplyKeyboard> _impl;
|
||||
|
||||
class Style : public ReplyKeyboard::Style {
|
||||
public:
|
||||
Style(BotKeyboard *parent, const style::BotKeyboardButton &st) : ReplyKeyboard::Style(st), _parent(parent) {
|
||||
}
|
||||
|
||||
int buttonRadius() const override;
|
||||
|
||||
void startPaint(Painter &p) const override;
|
||||
const style::TextStyle &textStyle() const override;
|
||||
void repaint(not_null<const HistoryItem*> item) const override;
|
||||
|
||||
protected:
|
||||
void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const override;
|
||||
void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const override;
|
||||
void paintButtonLoading(Painter &p, const QRect &rect) const override;
|
||||
int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override;
|
||||
|
||||
private:
|
||||
BotKeyboard *_parent;
|
||||
|
||||
};
|
||||
const style::BotKeyboardButton *_st = nullptr;
|
||||
|
||||
};
|
||||
|
||||
@@ -250,7 +250,7 @@ void EmojiColorPicker::hideFast() {
|
||||
|
||||
void EmojiColorPicker::hideAnimated() {
|
||||
if (_cache.isNull()) {
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
clearSelection();
|
||||
}
|
||||
_hiding = true;
|
||||
@@ -265,7 +265,7 @@ void EmojiColorPicker::showAnimated() {
|
||||
}
|
||||
_hiding = false;
|
||||
if (_cache.isNull()) {
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
clearSelection();
|
||||
}
|
||||
show();
|
||||
|
||||
@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/localstorage.h"
|
||||
@@ -191,7 +192,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
}
|
||||
}
|
||||
if (_chat) {
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
|
||||
const auto byOnline = [&](not_null<UserData*> user) {
|
||||
return Data::SortByOnlineValue(user, now);
|
||||
};
|
||||
mrows.reserve(mrows.size() + (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size()));
|
||||
if (_chat->noParticipantInfo()) {
|
||||
Auth().api().requestFullPeer(_chat);
|
||||
@@ -200,7 +204,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (user->isInaccessible()) continue;
|
||||
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||
ordered.insertMulti(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
for (const auto user : _chat->lastAuthors) {
|
||||
@@ -209,7 +213,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
mrows.push_back(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(App::onlineForSort(user, now), user);
|
||||
ordered.remove(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
if (!ordered.isEmpty()) {
|
||||
@@ -404,7 +408,7 @@ void FieldAutocomplete::hideAnimated() {
|
||||
|
||||
if (_cache.isNull()) {
|
||||
_scroll->show();
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
}
|
||||
_scroll->hide();
|
||||
_hiding = true;
|
||||
@@ -425,7 +429,7 @@ void FieldAutocomplete::showAnimated() {
|
||||
}
|
||||
if (_cache.isNull()) {
|
||||
_scroll->show();
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
}
|
||||
_scroll->hide();
|
||||
_hiding = false;
|
||||
|
||||
@@ -148,10 +148,10 @@ GifsListWidget::GifsListWidget(
|
||||
_inlineRequestTimer.setSingleShot(true);
|
||||
connect(&_inlineRequestTimer, &QTimer::timeout, this, [this] { sendInlineRequest(); });
|
||||
|
||||
Auth().data().savedGifsUpdated()
|
||||
| rpl::start_with_next([this] {
|
||||
refreshSavedGifs();
|
||||
}, lifetime());
|
||||
Auth().data().savedGifsUpdated(
|
||||
) | rpl::start_with_next([this] {
|
||||
refreshSavedGifs();
|
||||
}, lifetime());
|
||||
subscribe(Auth().downloaderTaskFinished(), [this] {
|
||||
update();
|
||||
});
|
||||
@@ -335,7 +335,7 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.data())) {
|
||||
if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.get())) {
|
||||
int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift;
|
||||
selectInlineResult(row, column);
|
||||
} else {
|
||||
@@ -596,12 +596,13 @@ void GifsListWidget::layoutInlineRow(Row &row, int fullWidth) {
|
||||
row.height = 0;
|
||||
int availw = fullWidth - (st::inlineResultsLeft - st::buttonRadius);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int index = indices[i];
|
||||
int w = desiredWidth
|
||||
? (row.items[index]->maxWidth() * availw / desiredWidth)
|
||||
: row.items[index]->maxWidth();
|
||||
int actualw = qMax(w, int(st::inlineResultsMinWidth));
|
||||
row.height = qMax(row.height, row.items[index]->resizeGetHeight(actualw));
|
||||
const auto index = indices[i];
|
||||
const auto &item = row.items[index];
|
||||
const auto w = desiredWidth
|
||||
? (item->maxWidth() * availw / desiredWidth)
|
||||
: item->maxWidth();
|
||||
auto actualw = qMax(w, st::inlineResultsMinWidth);
|
||||
row.height = qMax(row.height, item->resizeGetHeight(actualw));
|
||||
if (desiredWidth) {
|
||||
availw -= actualw;
|
||||
desiredWidth -= row.items[index]->maxWidth();
|
||||
|
||||
@@ -347,7 +347,7 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
}
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto it = sets.begin(), e = sets.end(); it != e;) {
|
||||
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
|
||||
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
@@ -449,7 +449,7 @@ void SpecialSetReceived(uint64 setId, const QString &setTitle, const QVector<MTP
|
||||
}
|
||||
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
i = recent.erase(i);
|
||||
@@ -748,7 +748,7 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
|
||||
}
|
||||
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
i = recent.erase(i);
|
||||
@@ -816,4 +816,56 @@ QString GetSetTitle(const MTPDstickerSet &s) {
|
||||
return title;
|
||||
}
|
||||
|
||||
RecentStickerPack &GetRecentPack() {
|
||||
if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) {
|
||||
const auto p = cRecentStickersPreload();
|
||||
cSetRecentStickersPreload(RecentStickerPreload());
|
||||
|
||||
auto &recent = cRefRecentStickers();
|
||||
recent.reserve(p.size());
|
||||
for (const auto &preloaded : p) {
|
||||
const auto document = App::document(preloaded.first);
|
||||
if (!document || !document->sticker()) continue;
|
||||
|
||||
recent.push_back(qMakePair(document, preloaded.second));
|
||||
}
|
||||
}
|
||||
return cRefRecentStickers();
|
||||
}
|
||||
|
||||
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
|
||||
auto i = recent.begin(), e = recent.end();
|
||||
for (; i != e; ++i) {
|
||||
if (i->first == tag) {
|
||||
++i->second;
|
||||
if (qAbs(i->second) > 0x4000) {
|
||||
for (auto j = recent.begin(); j != e; ++j) {
|
||||
if (j->second > 1) {
|
||||
j->second /= 2;
|
||||
} else if (j->second > 0) {
|
||||
j->second = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i != recent.begin(); --i) {
|
||||
if (qAbs((i - 1)->second) > qAbs(i->second)) {
|
||||
break;
|
||||
}
|
||||
qSwap(*i, *(i - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == e) {
|
||||
while (recent.size() >= 64) recent.pop_back();
|
||||
recent.push_back(qMakePair(tag, 1));
|
||||
for (i = recent.end() - 1; i != recent.begin(); --i) {
|
||||
if ((i - 1)->second > i->second) {
|
||||
break;
|
||||
}
|
||||
qSwap(*i, *(i - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -85,4 +85,8 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data);
|
||||
|
||||
QString GetSetTitle(const MTPDstickerSet &s);
|
||||
|
||||
RecentStickerPack &GetRecentPack();
|
||||
|
||||
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag);
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -113,6 +113,23 @@ private:
|
||||
|
||||
};
|
||||
|
||||
StickersListWidget::Set::Set(
|
||||
uint64 id,
|
||||
MTPDstickerSet::Flags flags,
|
||||
const QString &title,
|
||||
int hoversSize,
|
||||
const Stickers::Pack &pack)
|
||||
: id(id)
|
||||
, flags(flags)
|
||||
, title(title)
|
||||
, pack(pack) {
|
||||
}
|
||||
|
||||
StickersListWidget::Set::Set(Set &&other) = default;
|
||||
StickersListWidget::Set &StickersListWidget::Set::operator=(
|
||||
Set &&other) = default;
|
||||
StickersListWidget::Set::~Set() = default;
|
||||
|
||||
StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent) : InnerFooter(parent)
|
||||
, _pan(parent)
|
||||
, _a_icons(animation(this, &Footer::step_icons)) {
|
||||
@@ -605,10 +622,12 @@ int StickersListWidget::countDesiredHeight(int newWidth) {
|
||||
auto minimalLastHeight = (visibleHeight - st::stickerPanPadding);
|
||||
auto countResult = [this, minimalLastHeight] {
|
||||
if (_section == Section::Featured) {
|
||||
return st::stickerPanPadding + shownSets().size() * featuredRowHeight();
|
||||
return st::stickerPanPadding
|
||||
+ int(shownSets().size()) * featuredRowHeight();
|
||||
} else if (!shownSets().empty()) {
|
||||
auto info = sectionInfo(shownSets().size() - 1);
|
||||
return info.top + qMax(info.rowsBottom - info.top, minimalLastHeight);
|
||||
const auto info = sectionInfo(shownSets().size() - 1);
|
||||
return info.top
|
||||
+ qMax(info.rowsBottom - info.top, minimalLastHeight);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
@@ -679,7 +698,7 @@ void StickersListWidget::paintFeaturedStickers(Painter &p, QRect clip) {
|
||||
|
||||
auto tilly = st::stickerPanPadding;
|
||||
auto ms = getms();
|
||||
for (auto c = 0, l = sets.size(); c != l; ++c) {
|
||||
for (auto c = 0, l = int(sets.size()); c != l; ++c) {
|
||||
auto y = tilly;
|
||||
auto &set = sets[c];
|
||||
tilly = y + featuredRowHeight();
|
||||
@@ -1005,19 +1024,21 @@ QRect StickersListWidget::megagroupSetButtonRectFinal() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
QSharedPointer<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int section) {
|
||||
std::unique_ptr<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int section) {
|
||||
if (_section == Section::Featured) {
|
||||
auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
|
||||
auto mask = Ui::RippleAnimation::roundRectMask(maskSize, st::buttonRadius);
|
||||
return MakeShared<Ui::RippleAnimation>(st::stickersTrendingAdd.ripple, std::move(mask), [this, section] {
|
||||
rtlupdate(featuredAddRect(section));
|
||||
});
|
||||
return std::make_unique<Ui::RippleAnimation>(
|
||||
st::stickersTrendingAdd.ripple,
|
||||
std::move(mask),
|
||||
[this, section] { rtlupdate(featuredAddRect(section)); });
|
||||
}
|
||||
auto maskSize = QSize(st::stickerPanRemoveSet.rippleAreaSize, st::stickerPanRemoveSet.rippleAreaSize);
|
||||
auto mask = Ui::RippleAnimation::ellipseMask(maskSize);
|
||||
return MakeShared<Ui::RippleAnimation>(st::stickerPanRemoveSet.ripple, std::move(mask), [this, section] {
|
||||
rtlupdate(removeButtonRect(section));
|
||||
});
|
||||
return std::make_unique<Ui::RippleAnimation>(
|
||||
st::stickerPanRemoveSet.ripple,
|
||||
std::move(mask),
|
||||
[this, section] { rtlupdate(removeButtonRect(section)); });
|
||||
}
|
||||
|
||||
QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
|
||||
@@ -1083,14 +1104,16 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void StickersListWidget::removeRecentSticker(int section, int index) {
|
||||
if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::RecentSetId) {
|
||||
if ((_section != Section::Stickers)
|
||||
|| (section >= int(_mySets.size()))
|
||||
|| (_mySets[section].id != Stickers::RecentSetId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearSelection();
|
||||
bool refresh = false;
|
||||
auto sticker = _mySets[section].pack[index];
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
for (int32 i = 0, l = recent.size(); i < l; ++i) {
|
||||
if (recent.at(i).first == sticker) {
|
||||
recent.removeAt(i);
|
||||
@@ -1122,7 +1145,9 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
|
||||
}
|
||||
|
||||
void StickersListWidget::removeFavedSticker(int section, int index) {
|
||||
if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::FavedSetId) {
|
||||
if ((_section != Section::Stickers)
|
||||
|| (section >= int(_mySets.size()))
|
||||
|| (_mySets[section].id != Stickers::FavedSetId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1229,8 +1254,7 @@ void StickersListWidget::refreshStickers() {
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshSettingsVisibility() {
|
||||
auto visible = (_section == Section::Stickers)
|
||||
&& _mySets.isEmpty();
|
||||
const auto visible = (_section == Section::Stickers) && _mySets.empty();
|
||||
_settings->setVisible(visible);
|
||||
}
|
||||
|
||||
@@ -1251,7 +1275,7 @@ void StickersListWidget::preloadImages() {
|
||||
for (int j = 0; j != count; ++j) {
|
||||
if (++k > _columnCount * (_columnCount + 1)) break;
|
||||
|
||||
auto sticker = sets.at(i).pack.at(j);
|
||||
auto sticker = sets[i].pack.at(j);
|
||||
if (!sticker || !sticker->sticker()) continue;
|
||||
|
||||
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
|
||||
@@ -1272,10 +1296,15 @@ uint64 StickersListWidget::currentSet(int yOffset) const {
|
||||
if (_section == Section::Featured) {
|
||||
return Stickers::FeaturedSetId;
|
||||
}
|
||||
return _mySets.isEmpty() ? Stickers::RecentSetId : _mySets[sectionInfoByOffset(yOffset).section].id;
|
||||
return _mySets.empty()
|
||||
? Stickers::RecentSetId
|
||||
: _mySets[sectionInfoByOffset(yOffset).section].id;
|
||||
}
|
||||
|
||||
void StickersListWidget::appendSet(Sets &to, uint64 setId, AppendSkip skip) {
|
||||
void StickersListWidget::appendSet(
|
||||
std::vector<Set> &to,
|
||||
uint64 setId,
|
||||
AppendSkip skip) {
|
||||
auto &sets = Auth().data().stickerSets();
|
||||
auto it = sets.constFind(setId);
|
||||
if (it == sets.cend() || it->stickers.isEmpty()) return;
|
||||
@@ -1302,7 +1331,7 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
|
||||
_custom.clear();
|
||||
clearSelection();
|
||||
auto &sets = Auth().data().stickerSets();
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
auto customIt = sets.constFind(Stickers::CustomSetId);
|
||||
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
|
||||
|
||||
@@ -1423,7 +1452,7 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
|
||||
void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
|
||||
icons.clear();
|
||||
icons.reserve(_mySets.size() + 1);
|
||||
if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
|
||||
if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.empty()) {
|
||||
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
|
||||
}
|
||||
|
||||
@@ -1457,7 +1486,8 @@ void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
|
||||
icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh));
|
||||
}
|
||||
|
||||
if (!Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
|
||||
if (!Auth().data().featuredStickerSetsUnreadCount()
|
||||
&& !_featuredSets.empty()) {
|
||||
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
|
||||
}
|
||||
}
|
||||
@@ -1773,7 +1803,7 @@ void StickersListWidget::removeSet(uint64 setId) {
|
||||
request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(it->shortName)))).send();
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
|
||||
@@ -138,15 +138,22 @@ private:
|
||||
};
|
||||
|
||||
struct Set {
|
||||
Set(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const Stickers::Pack &pack = Stickers::Pack()) : id(id), flags(flags), title(title), pack(pack) {
|
||||
}
|
||||
Set(
|
||||
uint64 id,
|
||||
MTPDstickerSet::Flags flags,
|
||||
const QString &title,
|
||||
int hoversSize,
|
||||
const Stickers::Pack &pack = Stickers::Pack());
|
||||
Set(Set &&other);
|
||||
Set &operator=(Set &&other);
|
||||
~Set();
|
||||
|
||||
uint64 id;
|
||||
MTPDstickerSet::Flags flags;
|
||||
QString title;
|
||||
Stickers::Pack pack;
|
||||
QSharedPointer<Ui::RippleAnimation> ripple;
|
||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
};
|
||||
using Sets = QList<Set>;
|
||||
|
||||
template <typename Callback>
|
||||
bool enumerateSections(Callback callback) const;
|
||||
@@ -172,7 +179,7 @@ private:
|
||||
void updateSelected();
|
||||
void setSelected(OverState newSelected);
|
||||
void setPressed(OverState newPressed);
|
||||
QSharedPointer<Ui::RippleAnimation> createButtonRipple(int section);
|
||||
std::unique_ptr<Ui::RippleAnimation> createButtonRipple(int section);
|
||||
QPoint buttonRippleTopLeft(int section) const;
|
||||
|
||||
enum class ValidateIconAnimations {
|
||||
@@ -182,10 +189,10 @@ private:
|
||||
};
|
||||
void validateSelectedIcon(ValidateIconAnimations animations);
|
||||
|
||||
Sets &shownSets() {
|
||||
std::vector<Set> &shownSets() {
|
||||
return (_section == Section::Featured) ? _featuredSets : _mySets;
|
||||
}
|
||||
const Sets &shownSets() const {
|
||||
const std::vector<Set> &shownSets() const {
|
||||
return (_section == Section::Featured) ? _featuredSets : _mySets;
|
||||
}
|
||||
int featuredRowHeight() const;
|
||||
@@ -210,7 +217,10 @@ private:
|
||||
Archived,
|
||||
Installed,
|
||||
};
|
||||
void appendSet(Sets &to, uint64 setId, AppendSkip skip = AppendSkip::None);
|
||||
void appendSet(
|
||||
std::vector<Set> &to,
|
||||
uint64 setId,
|
||||
AppendSkip skip = AppendSkip::None);
|
||||
|
||||
void selectEmoji(EmojiPtr emoji);
|
||||
int stickersLeft() const;
|
||||
@@ -222,10 +232,10 @@ private:
|
||||
void refreshFooterIcons();
|
||||
|
||||
ChannelData *_megagroupSet = nullptr;
|
||||
Sets _mySets;
|
||||
Sets _featuredSets;
|
||||
OrderedSet<uint64> _installedLocallySets;
|
||||
QList<bool> _custom;
|
||||
std::vector<Set> _mySets;
|
||||
std::vector<Set> _featuredSets;
|
||||
base::flat_set<uint64> _installedLocallySets;
|
||||
std::vector<bool> _custom;
|
||||
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
|
||||
|
||||
Section _section = Section::Stickers;
|
||||
|
||||
@@ -63,10 +63,10 @@ TabbedPanel::TabbedPanel(
|
||||
_controller->disableGifPauseReason(Window::GifPauseReason::SavedGifs);
|
||||
}
|
||||
});
|
||||
_selector->showRequests()
|
||||
| rpl::start_with_next([this] {
|
||||
this->showFromSelector();
|
||||
}, lifetime());
|
||||
_selector->showRequests(
|
||||
) | rpl::start_with_next([this] {
|
||||
this->showFromSelector();
|
||||
}, lifetime());
|
||||
|
||||
resize(QRect(0, 0, st::emojiPanWidth, st::emojiPanMaxHeight).marginsAdded(innerPadding()).size());
|
||||
|
||||
@@ -255,7 +255,7 @@ void TabbedPanel::prepareCache() {
|
||||
auto showAnimation = base::take(_a_show);
|
||||
auto showAnimationData = base::take(_showAnimation);
|
||||
showChildren();
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
_showAnimation = base::take(showAnimationData);
|
||||
_a_show = base::take(showAnimation);
|
||||
if (_a_show.animating()) {
|
||||
@@ -296,7 +296,7 @@ QImage TabbedPanel::grabForAnimation() {
|
||||
auto showAnimation = base::take(_a_show);
|
||||
|
||||
showChildren();
|
||||
myEnsureResized(this);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
@@ -339,13 +339,13 @@ TabbedSelector::TabbedSelector(QWidget *parent, not_null<Window::Controller*> co
|
||||
}
|
||||
}));
|
||||
|
||||
Auth().api().stickerSetInstalled()
|
||||
| rpl::start_with_next([this](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(
|
||||
static_cast<int>(SelectorTab::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
_showRequests.fire({});
|
||||
}, lifetime());
|
||||
Auth().api().stickerSetInstalled(
|
||||
) | rpl::start_with_next([this](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(
|
||||
static_cast<int>(SelectorTab::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
_showRequests.fire({});
|
||||
}, lifetime());
|
||||
|
||||
// setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
@@ -489,7 +489,7 @@ QImage TabbedSelector::grabForAnimation() {
|
||||
showAll();
|
||||
_topShadow->hide();
|
||||
_tabsSlider->hide();
|
||||
myEnsureResized(this);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
@@ -638,10 +638,10 @@ void TabbedSelector::createTabsSlider() {
|
||||
_tabsSlider->setSections(sections);
|
||||
|
||||
_tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType));
|
||||
_tabsSlider->sectionActivated()
|
||||
| rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
_tabsSlider->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasSectionIcons() const {
|
||||
|
||||
@@ -1191,9 +1191,11 @@ QByteArray iconMaskValueSize(int width, int height) {
|
||||
QByteArray iconMaskValuePng(QString filepath) {
|
||||
QByteArray result;
|
||||
|
||||
auto pathAndModifiers = filepath.split('-');
|
||||
filepath = pathAndModifiers[0];
|
||||
auto modifiers = pathAndModifiers.mid(1);
|
||||
QFileInfo fileInfo(filepath);
|
||||
auto directory = fileInfo.dir();
|
||||
auto nameAndModifiers = fileInfo.fileName().split('-');
|
||||
filepath = directory.filePath(nameAndModifiers[0]);
|
||||
auto modifiers = nameAndModifiers.mid(1);
|
||||
|
||||
QImage png100x(filepath + ".png");
|
||||
QImage png200x(filepath + "@2x.png");
|
||||
|
||||
@@ -285,8 +285,6 @@ enum {
|
||||
DialogsFirstLoad = 20, // first dialogs part size requested
|
||||
DialogsPerPage = 500, // next dialogs part size
|
||||
|
||||
FileLoaderQueueStopTimeout = 5000,
|
||||
|
||||
UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb
|
||||
|
||||
UploadPartSize = 32 * 1024, // 32kb for photo
|
||||
|
||||
190
Telegram/SourceFiles/core/changelogs.cpp
Normal file
190
Telegram/SourceFiles/core/changelogs.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/changelogs.h"
|
||||
|
||||
#include "storage/localstorage.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
std::map<int, const char*> AlphaLogs() {
|
||||
return {
|
||||
{
|
||||
1001024,
|
||||
"\xE2\x80\x94 Radically improved navigation. "
|
||||
"New side panel on the right with quick access to "
|
||||
"shared media and group members.\n"
|
||||
|
||||
"\xE2\x80\x94 Pinned Messages. If you are a channel admin, "
|
||||
"pin messages to focus your subscribers\xE2\x80\x99 attention "
|
||||
"on important announcements.\n"
|
||||
|
||||
"\xE2\x80\x94 Also supported clearing history in supergroups "
|
||||
"and added a host of minor improvements."
|
||||
},
|
||||
{
|
||||
1001026,
|
||||
"\xE2\x80\x94 Admin badges in supergroup messages.\n"
|
||||
"\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n"
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1001027,
|
||||
"\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them "
|
||||
"to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. "
|
||||
"Access them from the Chats list or from the side menu."
|
||||
},
|
||||
{
|
||||
1002002,
|
||||
"\xE2\x80\x94 Grouped photos and videos are displayed as albums."
|
||||
},
|
||||
{
|
||||
1002004,
|
||||
"\xE2\x80\x94 Group media into an album "
|
||||
"when sharing multiple photos and videos.\n"
|
||||
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1002005,
|
||||
"\xE2\x80\x94 When viewing a photo from an album, "
|
||||
"you'll see other pictures from the same group "
|
||||
"as thumbnails in the lower part of the screen.\n"
|
||||
|
||||
"\xE2\x80\x94 When composing an album paste "
|
||||
"additional media from the clipboard.\n"
|
||||
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
QString FormatVersionDisplay(int version) {
|
||||
return QString::number(version / 1000000)
|
||||
+ '.' + QString::number((version % 1000000) / 1000)
|
||||
+ ((version % 1000)
|
||||
? ('.' + QString::number(version % 1000))
|
||||
: QString());
|
||||
}
|
||||
|
||||
QString FormatVersionPrecise(int version) {
|
||||
return QString::number(version / 1000000)
|
||||
+ '.' + QString::number((version % 1000000) / 1000)
|
||||
+ '.' + QString::number(version % 1000);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Changelogs::Changelogs(not_null<AuthSession*> session, int oldVersion)
|
||||
: _session(session)
|
||||
, _oldVersion(oldVersion) {
|
||||
_chatsSubscription = subscribe(
|
||||
_session->data().moreChatsLoaded(),
|
||||
[this] { requestCloudLogs(); });
|
||||
}
|
||||
|
||||
std::unique_ptr<Changelogs> Changelogs::Create(
|
||||
not_null<AuthSession*> session) {
|
||||
const auto oldVersion = Local::oldMapVersion();
|
||||
return (oldVersion > 0 && oldVersion < AppVersion)
|
||||
? std::make_unique<Changelogs>(session, oldVersion)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void Changelogs::requestCloudLogs() {
|
||||
unsubscribe(base::take(_chatsSubscription));
|
||||
|
||||
const auto callback = [this](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
|
||||
auto resultEmpty = true;
|
||||
switch (result.type()) {
|
||||
case mtpc_updateShortMessage:
|
||||
case mtpc_updateShortChatMessage:
|
||||
case mtpc_updateShort:
|
||||
resultEmpty = false;
|
||||
break;
|
||||
case mtpc_updatesCombined:
|
||||
resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty();
|
||||
break;
|
||||
case mtpc_updates:
|
||||
resultEmpty = result.c_updates().vupdates.v.isEmpty();
|
||||
break;
|
||||
case mtpc_updatesTooLong:
|
||||
case mtpc_updateShortSentMessage:
|
||||
LOG(("API Error: Bad updates type in app changelog."));
|
||||
break;
|
||||
}
|
||||
if (resultEmpty) {
|
||||
addLocalLogs();
|
||||
}
|
||||
};
|
||||
_session->api().requestChangelog(
|
||||
FormatVersionPrecise(_oldVersion),
|
||||
base::lambda_guarded(this, callback));
|
||||
}
|
||||
|
||||
void Changelogs::addLocalLogs() {
|
||||
if (cAlphaVersion() || cBetaVersion()) {
|
||||
addAlphaLogs();
|
||||
}
|
||||
if (!_addedSomeLocal) {
|
||||
const auto text = lng_new_version_wrap(
|
||||
lt_version,
|
||||
str_const_toString(AppVersionStr),
|
||||
lt_changes,
|
||||
lang(lng_new_version_minor),
|
||||
lt_link,
|
||||
qsl("https://desktop.telegram.org/changelog"));
|
||||
addLocalLog(text.trimmed());
|
||||
}
|
||||
}
|
||||
|
||||
void Changelogs::addLocalLog(const QString &text) {
|
||||
auto textWithEntities = TextWithEntities{ text };
|
||||
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
|
||||
App::wnd()->serviceNotification(
|
||||
textWithEntities,
|
||||
MTP_messageMediaEmpty(),
|
||||
unixtime());
|
||||
_addedSomeLocal = true;
|
||||
};
|
||||
|
||||
void Changelogs::addAlphaLogs() {
|
||||
for (const auto[version, changes] : AlphaLogs()) {
|
||||
addAlphaLog(version, changes);
|
||||
}
|
||||
}
|
||||
|
||||
void Changelogs::addAlphaLog(int changeVersion, const char *changes) {
|
||||
if (_oldVersion >= changeVersion) {
|
||||
return;
|
||||
}
|
||||
const auto version = FormatVersionDisplay(changeVersion);
|
||||
const auto text = qsl("New in version %1:\n\n").arg(version)
|
||||
+ QString::fromUtf8(changes).trimmed();
|
||||
addLocalLog(text);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
50
Telegram/SourceFiles/core/changelogs.h
Normal file
50
Telegram/SourceFiles/core/changelogs.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
class AuthSession;
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Changelogs : public base::has_weak_ptr, private base::Subscriber {
|
||||
public:
|
||||
Changelogs(not_null<AuthSession*> session, int oldVersion);
|
||||
|
||||
static std::unique_ptr<Changelogs> Create(
|
||||
not_null<AuthSession*> session);
|
||||
|
||||
private:
|
||||
void requestCloudLogs();
|
||||
void addLocalLogs();
|
||||
void addLocalLog(const QString &text);
|
||||
void addAlphaLogs();
|
||||
void addAlphaLog(int changeVersion, const char *changes);
|
||||
|
||||
const not_null<AuthSession*> _session;
|
||||
const int _oldVersion = 0;
|
||||
int _chatsSubscription = 0;
|
||||
bool _addedSomeLocal = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -38,9 +38,11 @@ bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) {
|
||||
// other pressed click handler currently, if there is
|
||||
// this method will be called when it is unpressed
|
||||
if (_active && *_active) {
|
||||
auto emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active);
|
||||
auto wasactive = *_active;
|
||||
(*_active).clear();
|
||||
const auto emitClickHandlerActiveChanged = false
|
||||
|| !_pressed
|
||||
|| !*_pressed
|
||||
|| (*_pressed == *_active);
|
||||
const auto wasactive = base::take(*_active);
|
||||
if (_activeHost) {
|
||||
if (emitClickHandlerActiveChanged) {
|
||||
_activeHost->clickHandlerActiveChanged(wasactive, false);
|
||||
|
||||
@@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#pragma once
|
||||
|
||||
class ClickHandler;
|
||||
using ClickHandlerPtr = QSharedPointer<ClickHandler>;
|
||||
using ClickHandlerPtr = std::shared_ptr<ClickHandler>;
|
||||
|
||||
enum ExpandLinksMode {
|
||||
ExpandLinksNone,
|
||||
@@ -103,9 +103,8 @@ public:
|
||||
// The activated click handler (if any) is returned.
|
||||
static ClickHandlerPtr unpressed() {
|
||||
if (_pressed && *_pressed) {
|
||||
bool activated = (_active && *_active == *_pressed);
|
||||
ClickHandlerPtr waspressed = *_pressed;
|
||||
(*_pressed).clear();
|
||||
const auto activated = (_active && *_active == *_pressed);
|
||||
const auto waspressed = base::take(*_pressed);
|
||||
if (_pressedHost) {
|
||||
_pressedHost->clickHandlerPressedChanged(waspressed, false);
|
||||
_pressedHost = nullptr;
|
||||
@@ -144,11 +143,15 @@ public:
|
||||
}
|
||||
static void hostDestroyed(ClickHandlerHost *host) {
|
||||
if (_activeHost == host) {
|
||||
if (_active) (*_active).clear();
|
||||
if (_active) {
|
||||
*_active = nullptr;
|
||||
}
|
||||
_activeHost = nullptr;
|
||||
}
|
||||
if (_pressedHost == host) {
|
||||
if (_pressed) (*_pressed).clear();
|
||||
if (_pressed) {
|
||||
*_pressed = nullptr;
|
||||
}
|
||||
_pressedHost = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ void HiddenUrlClickHandler::doOpen(QString url) {
|
||||
Ui::show(Box<ConfirmBox>(lang(lng_open_this_link) + qsl("\n\n") + displayUrl, lang(lng_open_link), [urlText] {
|
||||
Ui::hideLayer();
|
||||
UrlClickHandler::doOpen(urlText);
|
||||
}));
|
||||
}), LayerOption::KeepOther);
|
||||
} else {
|
||||
UrlClickHandler::doOpen(urlText);
|
||||
}
|
||||
|
||||
@@ -97,14 +97,15 @@ private:
|
||||
QString _originalUrl, _readable;
|
||||
|
||||
};
|
||||
typedef QSharedPointer<TextClickHandler> TextClickHandlerPtr;
|
||||
|
||||
class HiddenUrlClickHandler : public UrlClickHandler {
|
||||
public:
|
||||
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
|
||||
}
|
||||
QString copyToClipboardContextItemText() const override {
|
||||
return url().isEmpty() ? QString() : UrlClickHandler::copyToClipboardContextItemText();
|
||||
return url().isEmpty()
|
||||
? QString()
|
||||
: UrlClickHandler::copyToClipboardContextItemText();
|
||||
}
|
||||
|
||||
static void doOpen(QString url);
|
||||
|
||||
1080
Telegram/SourceFiles/core/crash_report_window.cpp
Normal file
1080
Telegram/SourceFiles/core/crash_report_window.cpp
Normal file
File diff suppressed because it is too large
Load Diff
226
Telegram/SourceFiles/core/crash_report_window.h
Normal file
226
Telegram/SourceFiles/core/crash_report_window.h
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PreLaunchWindow : public QWidget {
|
||||
public:
|
||||
PreLaunchWindow(QString title = QString());
|
||||
void activate();
|
||||
int basicSize() const {
|
||||
return _size;
|
||||
}
|
||||
~PreLaunchWindow();
|
||||
|
||||
static PreLaunchWindow *instance();
|
||||
|
||||
protected:
|
||||
|
||||
int _size;
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchLabel : public QLabel {
|
||||
public:
|
||||
PreLaunchLabel(QWidget *parent);
|
||||
void setText(const QString &text);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchInput : public QLineEdit {
|
||||
public:
|
||||
PreLaunchInput(QWidget *parent, bool password = false);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchLog : public QTextEdit {
|
||||
public:
|
||||
PreLaunchLog(QWidget *parent);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchButton : public QPushButton {
|
||||
public:
|
||||
PreLaunchButton(QWidget *parent, bool confirm = true);
|
||||
void setText(const QString &text);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchCheckbox : public QCheckBox {
|
||||
public:
|
||||
PreLaunchCheckbox(QWidget *parent);
|
||||
void setText(const QString &text);
|
||||
|
||||
};
|
||||
|
||||
class NotStartedWindow : public PreLaunchWindow {
|
||||
public:
|
||||
NotStartedWindow();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
private:
|
||||
void updateControls();
|
||||
|
||||
PreLaunchLabel _label;
|
||||
PreLaunchLog _log;
|
||||
PreLaunchButton _close;
|
||||
|
||||
};
|
||||
|
||||
class LastCrashedWindow : public PreLaunchWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LastCrashedWindow();
|
||||
|
||||
public slots:
|
||||
void onViewReport();
|
||||
void onSaveReport();
|
||||
void onSendReport();
|
||||
void onGetApp();
|
||||
|
||||
void onNetworkSettings();
|
||||
void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password);
|
||||
void onContinue();
|
||||
|
||||
void onCheckingFinished();
|
||||
void onSendingError(QNetworkReply::NetworkError e);
|
||||
void onSendingFinished();
|
||||
void onSendingProgress(qint64 uploaded, qint64 total);
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
void onUpdateRetry();
|
||||
void onUpdateSkip();
|
||||
|
||||
void onUpdateChecking();
|
||||
void onUpdateLatest();
|
||||
void onUpdateDownloading(qint64 ready, qint64 total);
|
||||
void onUpdateReady();
|
||||
void onUpdateFailed();
|
||||
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
private:
|
||||
QString minidumpFileName();
|
||||
void updateControls();
|
||||
|
||||
QString _host, _username, _password;
|
||||
quint32 _port;
|
||||
|
||||
PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
|
||||
PreLaunchLog _report;
|
||||
PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
|
||||
PreLaunchCheckbox _includeUsername;
|
||||
|
||||
QString _minidumpName, _minidumpFull, _reportText;
|
||||
QString _reportUsername, _reportTextNoUsername;
|
||||
QByteArray getCrashReportRaw() const;
|
||||
|
||||
bool _reportShown, _reportSaved;
|
||||
|
||||
void excludeReportUsername();
|
||||
|
||||
enum SendingState {
|
||||
SendingNoReport,
|
||||
SendingUpdateCheck,
|
||||
SendingNone,
|
||||
SendingTooOld,
|
||||
SendingTooMany,
|
||||
SendingUnofficial,
|
||||
SendingProgress,
|
||||
SendingUploading,
|
||||
SendingFail,
|
||||
SendingDone,
|
||||
};
|
||||
SendingState _sendingState;
|
||||
|
||||
PreLaunchLabel _updating;
|
||||
qint64 _sendingProgress, _sendingTotal;
|
||||
|
||||
QNetworkAccessManager _sendManager;
|
||||
QNetworkReply *_checkReply, *_sendReply;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
PreLaunchButton _updatingCheck, _updatingSkip;
|
||||
enum UpdatingState {
|
||||
UpdatingNone,
|
||||
UpdatingCheck,
|
||||
UpdatingLatest,
|
||||
UpdatingDownload,
|
||||
UpdatingFail,
|
||||
UpdatingReady
|
||||
};
|
||||
UpdatingState _updatingState;
|
||||
QString _newVersionDownload;
|
||||
|
||||
void setUpdatingState(UpdatingState state, bool force = false);
|
||||
void setDownloadProgress(qint64 ready, qint64 total);
|
||||
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
|
||||
|
||||
QString getReportField(const QLatin1String &name, const QLatin1String &prefix);
|
||||
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
|
||||
|
||||
};
|
||||
|
||||
class NetworkSettingsWindow : public PreLaunchWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password);
|
||||
|
||||
signals:
|
||||
void saved(QString host, quint32 port, QString username, QString password);
|
||||
|
||||
public slots:
|
||||
void onSave();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
private:
|
||||
void updateControls();
|
||||
|
||||
PreLaunchLabel _hostLabel, _portLabel, _usernameLabel, _passwordLabel;
|
||||
PreLaunchInput _hostInput, _portInput, _usernameInput, _passwordInput;
|
||||
PreLaunchButton _save, _cancel;
|
||||
|
||||
QWidget *_parent;
|
||||
|
||||
};
|
||||
|
||||
class ShowCrashReportWindow : public PreLaunchWindow {
|
||||
public:
|
||||
ShowCrashReportWindow(const QString &text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void closeEvent(QCloseEvent *e);
|
||||
|
||||
private:
|
||||
PreLaunchLog _log;
|
||||
|
||||
};
|
||||
580
Telegram/SourceFiles/core/crash_reports.cpp
Normal file
580
Telegram/SourceFiles/core/crash_reports.cpp
Normal file
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
#include "platform/platform_specific.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <new>
|
||||
#include <mutex>
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4091)
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef MAC_USE_BREAKPAD
|
||||
#include "client/mac/handler/exception_handler.h"
|
||||
#else // MAC_USE_BREAKPAD
|
||||
#include "client/crashpad_client.h"
|
||||
#endif // else for MAC_USE_BREAKPAD
|
||||
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
namespace CrashReports {
|
||||
namespace {
|
||||
|
||||
using Annotations = std::map<std::string, std::string>;
|
||||
using AnnotationRefs = std::map<std::string, const QString*>;
|
||||
|
||||
Annotations ProcessAnnotations;
|
||||
AnnotationRefs ProcessAnnotationRefs;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
QString ReportPath;
|
||||
FILE *ReportFile = nullptr;
|
||||
int ReportFileNo = 0;
|
||||
char LaunchedDateTimeStr[32] = { 0 };
|
||||
char LaunchedBinaryName[256] = { 0 };
|
||||
|
||||
void SafeWriteChar(char ch) {
|
||||
fwrite(&ch, 1, 1, ReportFile);
|
||||
}
|
||||
|
||||
template <bool Unsigned, typename Type>
|
||||
struct writeNumberSignAndRemoveIt {
|
||||
static void call(Type &number) {
|
||||
if (number < 0) {
|
||||
SafeWriteChar('-');
|
||||
number = -number;
|
||||
}
|
||||
}
|
||||
};
|
||||
template <typename Type>
|
||||
struct writeNumberSignAndRemoveIt<true, Type> {
|
||||
static void call(Type &number) {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
const dump &SafeWriteNumber(const dump &stream, Type number) {
|
||||
if (!ReportFile) return stream;
|
||||
|
||||
writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number);
|
||||
Type upper = 1, prev = number / 10;
|
||||
while (prev >= upper) {
|
||||
upper *= 10;
|
||||
}
|
||||
while (upper > 0) {
|
||||
int digit = (number / upper);
|
||||
SafeWriteChar('0' + digit);
|
||||
number -= digit * upper;
|
||||
upper /= 10;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
|
||||
std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
|
||||
|
||||
void InstallOperatorNewHandler() {
|
||||
ReservedMemory = std::make_unique<ReservedMemoryChunk>();
|
||||
std::set_new_handler([] {
|
||||
std::set_new_handler(nullptr);
|
||||
ReservedMemory.reset();
|
||||
Unexpected("Could not allocate!");
|
||||
});
|
||||
}
|
||||
|
||||
Qt::HANDLE ReportingThreadId = nullptr;
|
||||
bool ReportingHeaderWritten = false;
|
||||
QMutex ReportingMutex;
|
||||
|
||||
const char *BreakpadDumpPath = nullptr;
|
||||
const wchar_t *BreakpadDumpPathW = nullptr;
|
||||
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
||||
struct sigaction SIG_def[32];
|
||||
|
||||
void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
|
||||
if (signum > 0) {
|
||||
sigaction(signum, &SIG_def[signum], 0);
|
||||
}
|
||||
|
||||
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
void SignalHandler(int signum) {
|
||||
#endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64
|
||||
|
||||
const char* name = 0;
|
||||
switch (signum) {
|
||||
case SIGABRT: name = "SIGABRT"; break;
|
||||
case SIGSEGV: name = "SIGSEGV"; break;
|
||||
case SIGILL: name = "SIGILL"; break;
|
||||
case SIGFPE: name = "SIGFPE"; break;
|
||||
#ifndef Q_OS_WIN
|
||||
case SIGBUS: name = "SIGBUS"; break;
|
||||
case SIGSYS: name = "SIGSYS"; break;
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
|
||||
Qt::HANDLE thread = QThread::currentThreadId();
|
||||
if (thread == ReportingThreadId) return;
|
||||
|
||||
QMutexLocker lock(&ReportingMutex);
|
||||
ReportingThreadId = thread;
|
||||
|
||||
if (!ReportingHeaderWritten) {
|
||||
ReportingHeaderWritten = true;
|
||||
auto dec2hex = [](int value) -> char {
|
||||
if (value >= 0 && value < 10) {
|
||||
return '0' + value;
|
||||
} else if (value >= 10 && value < 16) {
|
||||
return 'a' + (value - 10);
|
||||
}
|
||||
return '#';
|
||||
};
|
||||
|
||||
for (const auto &i : ProcessAnnotationRefs) {
|
||||
QByteArray utf8 = i.second->toUtf8();
|
||||
std::string wrapped;
|
||||
wrapped.reserve(4 * utf8.size());
|
||||
for (auto ch : utf8) {
|
||||
auto uch = static_cast<uchar>(ch);
|
||||
wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F));
|
||||
}
|
||||
ProcessAnnotations[i.first] = wrapped;
|
||||
}
|
||||
|
||||
const Annotations c_ProcessAnnotations(ProcessAnnotations);
|
||||
for (const auto &i : c_ProcessAnnotations) {
|
||||
dump() << i.first.c_str() << ": " << i.second.c_str() << "\n";
|
||||
}
|
||||
psWriteDump();
|
||||
dump() << "\n";
|
||||
}
|
||||
if (name) {
|
||||
dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
|
||||
} else if (signum == -1) {
|
||||
dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
|
||||
if (BreakpadDumpPath) {
|
||||
dump() << "Minidump: " << BreakpadDumpPath << "\n";
|
||||
} else if (BreakpadDumpPathW) {
|
||||
dump() << "Minidump: " << BreakpadDumpPathW << "\n";
|
||||
}
|
||||
} else {
|
||||
dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
|
||||
}
|
||||
|
||||
// see https://github.com/benbjohnson/bandicoot
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
||||
ucontext_t *uc = (ucontext_t*)ucontext;
|
||||
|
||||
void *caller = 0;
|
||||
if (uc) {
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
/* OSX < 10.6 */
|
||||
#if defined(__x86_64__)
|
||||
caller = (void*)uc->uc_mcontext->__ss.__rip;
|
||||
#elif defined(__i386__)
|
||||
caller = (void*)uc->uc_mcontext->__ss.__eip;
|
||||
#else
|
||||
caller = (void*)uc->uc_mcontext->__ss.__srr0;
|
||||
#endif
|
||||
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
|
||||
/* OSX >= 10.6 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
caller = (void*)uc->uc_mcontext->__ss.__rip;
|
||||
#else
|
||||
caller = (void*)uc->uc_mcontext->__ss.__eip;
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__)
|
||||
caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
#elif defined(__ia64__) /* Linux IA64 */
|
||||
caller = (void*)uc->uc_mcontext.sc_ip;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void *addresses[132] = { 0 };
|
||||
size_t size = backtrace(addresses, 128);
|
||||
|
||||
/* overwrite sigaction with caller's address */
|
||||
if (caller) {
|
||||
for (int i = size; i > 1; --i) {
|
||||
addresses[i + 3] = addresses[i];
|
||||
}
|
||||
addresses[2] = (void*)0x1;
|
||||
addresses[3] = caller;
|
||||
addresses[4] = (void*)0x1;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
dump() << "\nBase image addresses:\n";
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
Dl_info info;
|
||||
dump() << i << " ";
|
||||
if (dladdr(addresses[i], &info)) {
|
||||
dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n";
|
||||
} else {
|
||||
dump() << "_unknown_module_\n";
|
||||
}
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
dump() << "\nBacktrace:\n";
|
||||
|
||||
backtrace_symbols_fd(addresses, size, ReportFileNo);
|
||||
|
||||
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
dump() << "\nBacktrace:\n";
|
||||
|
||||
psWriteStackTrace();
|
||||
#endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
|
||||
dump() << "\n";
|
||||
|
||||
ReportingThreadId = nullptr;
|
||||
}
|
||||
|
||||
bool SetSignalHandlers = true;
|
||||
bool CrashLogged = false;
|
||||
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
|
||||
google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
|
||||
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
{
|
||||
if (CrashLogged) return success;
|
||||
CrashLogged = true;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
BreakpadDumpPathW = _minidump_id;
|
||||
SignalHandler(-1);
|
||||
#else // Q_OS_WIN
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
BreakpadDumpPath = _minidump_id;
|
||||
#else // Q_OS_MAC
|
||||
BreakpadDumpPath = md.path();
|
||||
#endif // else for Q_OS_MAC
|
||||
SignalHandler(-1, 0, 0);
|
||||
#endif // else for Q_OS_WIN
|
||||
return success;
|
||||
}
|
||||
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
} // namespace
|
||||
|
||||
void StartCatching() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
|
||||
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
|
||||
ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cAlphaVersion() ? qsl("%1 alpha") : qsl("%1")).arg(AppVersion)).toUtf8().constData();
|
||||
ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData();
|
||||
ProcessAnnotations["Platform"] = cPlatformString().toUtf8().constData();
|
||||
ProcessAnnotations["UserTag"] = QString::number(Sandbox::UserTag(), 16).toUtf8().constData();
|
||||
|
||||
QString dumpspath = cWorkingDir() + qsl("tdata/dumps");
|
||||
QDir().mkpath(dumpspath);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
dumpspath.toStdWString(),
|
||||
google_breakpad::ExceptionHandler::FilterCallback(nullptr),
|
||||
DumpCallback,
|
||||
(void*)nullptr, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL,
|
||||
MINIDUMP_TYPE(MiniDumpNormal),
|
||||
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
|
||||
(const wchar_t*)nullptr, // pipe_name
|
||||
(const google_breakpad::CustomClientInfo*)nullptr
|
||||
);
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
|
||||
#ifdef MAC_USE_BREAKPAD
|
||||
#ifndef _DEBUG
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
QFile::encodeName(dumpspath).toStdString(),
|
||||
/*FilterCallback*/ 0,
|
||||
DumpCallback,
|
||||
/*context*/ 0,
|
||||
true,
|
||||
0
|
||||
);
|
||||
#endif // !_DEBUG
|
||||
SetSignalHandlers = false;
|
||||
#else // MAC_USE_BREAKPAD
|
||||
crashpad::CrashpadClient crashpad_client;
|
||||
std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData();
|
||||
std::string database = QFile::encodeName(dumpspath).constData();
|
||||
if (crashpad_client.StartHandler(base::FilePath(handler),
|
||||
base::FilePath(database),
|
||||
std::string(),
|
||||
ProcessAnnotations,
|
||||
std::vector<std::string>(),
|
||||
false)) {
|
||||
crashpad_client.UseHandler();
|
||||
}
|
||||
#endif // else for MAC_USE_BREAKPAD
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
|
||||
/*FilterCallback*/ 0,
|
||||
DumpCallback,
|
||||
/*context*/ 0,
|
||||
true,
|
||||
-1
|
||||
);
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void FinishCatching() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
|
||||
|
||||
delete base::take(BreakpadExceptionHandler);
|
||||
|
||||
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
Status Start() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
ReportPath = cWorkingDir() + qsl("tdata/working");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
FILE *f = nullptr;
|
||||
if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) {
|
||||
f = nullptr;
|
||||
} else {
|
||||
#else // !Q_OS_WIN
|
||||
if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) {
|
||||
#endif // else for !Q_OS_WIN
|
||||
QByteArray lastdump;
|
||||
char buffer[256 * 1024] = { 0 };
|
||||
int32 read = fread(buffer, 1, 256 * 1024, f);
|
||||
if (read > 0) {
|
||||
lastdump.append(buffer, read);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
Sandbox::SetLastCrashDump(lastdump);
|
||||
|
||||
LOG(("Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2").arg(ReportPath).arg(lastdump.size()));
|
||||
|
||||
return LastCrashed;
|
||||
}
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
return Restart();
|
||||
}
|
||||
|
||||
Status Restart() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
if (ReportFile) {
|
||||
return Started;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (_wfopen_s(&ReportFile, ReportPath.toStdWString().c_str(), L"wb") != 0) {
|
||||
ReportFile = nullptr;
|
||||
}
|
||||
#else // Q_OS_WIN
|
||||
ReportFile = fopen(QFile::encodeName(ReportPath).constData(), "wb");
|
||||
#endif // else for Q_OS_WIN
|
||||
if (ReportFile) {
|
||||
#ifdef Q_OS_WIN
|
||||
ReportFileNo = _fileno(ReportFile);
|
||||
#else // Q_OS_WIN
|
||||
ReportFileNo = fileno(ReportFile);
|
||||
#endif // else for Q_OS_WIN
|
||||
if (SetSignalHandlers) {
|
||||
#ifndef Q_OS_WIN
|
||||
struct sigaction sigact;
|
||||
|
||||
sigact.sa_sigaction = SignalHandler;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
|
||||
sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
|
||||
sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
|
||||
sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
|
||||
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
|
||||
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
|
||||
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
|
||||
#else // !Q_OS_WIN
|
||||
signal(SIGABRT, SignalHandler);
|
||||
signal(SIGSEGV, SignalHandler);
|
||||
signal(SIGILL, SignalHandler);
|
||||
signal(SIGFPE, SignalHandler);
|
||||
#endif // else for !Q_OS_WIN
|
||||
}
|
||||
|
||||
InstallOperatorNewHandler();
|
||||
|
||||
return Started;
|
||||
}
|
||||
|
||||
LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath));
|
||||
|
||||
return CantOpen;
|
||||
#else // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
return Started;
|
||||
#endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
FinishCatching();
|
||||
|
||||
if (ReportFile) {
|
||||
fclose(ReportFile);
|
||||
ReportFile = nullptr;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_wunlink(ReportPath.toStdWString().c_str());
|
||||
#else // Q_OS_WIN
|
||||
unlink(ReportPath.toUtf8().constData());
|
||||
#endif // else for Q_OS_WIN
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void SetAnnotation(const std::string &key, const QString &value) {
|
||||
static QMutex mutex;
|
||||
QMutexLocker lock(&mutex);
|
||||
|
||||
if (!value.trimmed().isEmpty()) {
|
||||
ProcessAnnotations[key] = value.toUtf8().constData();
|
||||
} else {
|
||||
ProcessAnnotations.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void SetAnnotationRef(const std::string &key, const QString *valuePtr) {
|
||||
if (valuePtr) {
|
||||
ProcessAnnotationRefs[key] = valuePtr;
|
||||
} else {
|
||||
ProcessAnnotationRefs.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
dump::~dump() {
|
||||
if (ReportFile) {
|
||||
fflush(ReportFile);
|
||||
}
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, const char *str) {
|
||||
if (!ReportFile) return stream;
|
||||
|
||||
fwrite(str, 1, strlen(str), ReportFile);
|
||||
return stream;
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, const wchar_t *str) {
|
||||
if (!ReportFile) return stream;
|
||||
|
||||
for (int i = 0, l = wcslen(str); i < l; ++i) {
|
||||
if (str[i] >= 0 && str[i] < 128) {
|
||||
SafeWriteChar(char(str[i]));
|
||||
} else {
|
||||
SafeWriteChar('?');
|
||||
}
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, int num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, unsigned int num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, unsigned long num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, unsigned long long num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, double num) {
|
||||
if (num < 0) {
|
||||
SafeWriteChar('-');
|
||||
num = -num;
|
||||
}
|
||||
SafeWriteNumber(stream, uint64(floor(num)));
|
||||
SafeWriteChar('.');
|
||||
num -= floor(num);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
num *= 10;
|
||||
int digit = int(floor(num));
|
||||
SafeWriteChar('0' + digit);
|
||||
num -= digit;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
} // namespace CrashReports
|
||||
85
Telegram/SourceFiles/core/crash_reports.h
Normal file
85
Telegram/SourceFiles/core/crash_reports.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace CrashReports {
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
struct dump {
|
||||
~dump();
|
||||
};
|
||||
const dump &operator<<(const dump &stream, const char *str);
|
||||
const dump &operator<<(const dump &stream, const wchar_t *str);
|
||||
const dump &operator<<(const dump &stream, int num);
|
||||
const dump &operator<<(const dump &stream, unsigned int num);
|
||||
const dump &operator<<(const dump &stream, unsigned long num);
|
||||
const dump &operator<<(const dump &stream, unsigned long long num);
|
||||
const dump &operator<<(const dump &stream, double num);
|
||||
|
||||
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
enum Status {
|
||||
CantOpen,
|
||||
LastCrashed,
|
||||
Started
|
||||
};
|
||||
Status Start();
|
||||
Status Restart(); // can be only CantOpen or Started
|
||||
void Finish();
|
||||
|
||||
void SetAnnotation(const std::string &key, const QString &value);
|
||||
inline void ClearAnnotation(const std::string &key) {
|
||||
SetAnnotation(key, QString());
|
||||
}
|
||||
|
||||
// Remembers value pointer and tries to add the value to the crash report.
|
||||
// Attention! You should call clearCrashAnnotationRef(key) before destroying value.
|
||||
void SetAnnotationRef(const std::string &key, const QString *valuePtr);
|
||||
inline void ClearAnnotationRef(const std::string &key) {
|
||||
SetAnnotationRef(key, nullptr);
|
||||
}
|
||||
|
||||
void StartCatching();
|
||||
void FinishCatching();
|
||||
|
||||
} // namespace CrashReports
|
||||
|
||||
namespace base {
|
||||
namespace assertion {
|
||||
|
||||
inline void log(const char *message, const char *file, int line) {
|
||||
const auto info = QStringLiteral("%1 %2:%3"
|
||||
).arg(message
|
||||
).arg(file
|
||||
).arg(line
|
||||
);
|
||||
const auto entry = QStringLiteral("Assertion Failed! ") + info;
|
||||
|
||||
#ifdef LOG
|
||||
LOG((entry));
|
||||
#endif // LOG
|
||||
|
||||
CrashReports::SetAnnotation("Assertion", info);
|
||||
}
|
||||
|
||||
} // namespace assertion
|
||||
} // namespace base
|
||||
@@ -26,7 +26,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/task_queue.h"
|
||||
#include "messenger.h"
|
||||
|
||||
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath) {
|
||||
bool filedialogGetSaveFile(
|
||||
QString &file,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath) {
|
||||
QStringList files;
|
||||
QByteArray remoteContent;
|
||||
bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, initialPath);
|
||||
@@ -34,7 +38,12 @@ bool filedialogGetSaveFile(QString &file, const QString &caption, const QString
|
||||
return result;
|
||||
}
|
||||
|
||||
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path, bool skipExistance, int fileTime) {
|
||||
QString filedialogDefaultName(
|
||||
const QString &prefix,
|
||||
const QString &extension,
|
||||
const QString &path,
|
||||
bool skipExistance,
|
||||
int fileTime) {
|
||||
auto directoryPath = path;
|
||||
if (directoryPath.isEmpty()) {
|
||||
if (cDialogLastPath().isEmpty()) {
|
||||
@@ -69,7 +78,10 @@ QString filedialogDefaultName(const QString &prefix, const QString &extension, c
|
||||
return name;
|
||||
}
|
||||
|
||||
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path) {
|
||||
QString filedialogNextFilename(
|
||||
const QString &name,
|
||||
const QString &cur,
|
||||
const QString &path) {
|
||||
QDir dir(path.isEmpty() ? cDialogLastPath() : path);
|
||||
int32 extIndex = name.lastIndexOf('.');
|
||||
QString prefix = name, extension;
|
||||
@@ -130,19 +142,30 @@ void UnsafeLaunchDefault(const QString &filepath) {
|
||||
|
||||
namespace FileDialog {
|
||||
|
||||
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetOpenPath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
if (Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile)
|
||||
&& ((!files.isEmpty() && !files[0].isEmpty()) || !remoteContent.isEmpty())) {
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
FileDialog::internal::Type::ReadFile);
|
||||
if (success
|
||||
&& ((!files.isEmpty() && !files[0].isEmpty())
|
||||
|| !remoteContent.isEmpty())) {
|
||||
if (callback) {
|
||||
auto result = OpenResult();
|
||||
if (!files.isEmpty() && !files[0].isEmpty()) {
|
||||
result.paths.push_back(files[0]);
|
||||
}
|
||||
result.remoteContent = remoteContent;
|
||||
callback(result);
|
||||
callback(std::move(result));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
@@ -150,17 +173,26 @@ void GetOpenPath(const QString &caption, const QString &filter, base::lambda<voi
|
||||
});
|
||||
}
|
||||
|
||||
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetOpenPaths(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
if (Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles)
|
||||
&& (!files.isEmpty() || !remoteContent.isEmpty())) {
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
FileDialog::internal::Type::ReadFiles);
|
||||
if (success && (!files.isEmpty() || !remoteContent.isEmpty())) {
|
||||
if (callback) {
|
||||
auto result = OpenResult();
|
||||
result.paths = files;
|
||||
result.remoteContent = remoteContent;
|
||||
callback(result);
|
||||
callback(std::move(result));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
@@ -168,12 +200,17 @@ void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<vo
|
||||
});
|
||||
}
|
||||
|
||||
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetWritePath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
auto file = QString();
|
||||
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
|
||||
if (callback) {
|
||||
callback(file);
|
||||
callback(std::move(file));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
@@ -181,14 +218,24 @@ void GetWritePath(const QString &caption, const QString &filter, const QString &
|
||||
});
|
||||
}
|
||||
|
||||
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, initialPath, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetFolder(
|
||||
const QString &caption,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
if (Platform::FileDialog::Get(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder, initialPath)
|
||||
&& !files.isEmpty() && !files[0].isEmpty()) {
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
QString(),
|
||||
FileDialog::internal::Type::ReadFolder,
|
||||
initialPath);
|
||||
if (success && !files.isEmpty() && !files[0].isEmpty()) {
|
||||
if (callback) {
|
||||
callback(files[0]);
|
||||
callback(std::move(files[0]));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
|
||||
@@ -23,10 +23,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/observer.h"
|
||||
|
||||
// legacy
|
||||
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath);
|
||||
bool filedialogGetSaveFile(
|
||||
QString &file,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath);
|
||||
|
||||
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false, int fileTime = 0);
|
||||
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path = QString());
|
||||
QString filedialogDefaultName(
|
||||
const QString &prefix,
|
||||
const QString &extension,
|
||||
const QString &path = QString(),
|
||||
bool skipExistance = false,
|
||||
int fileTime = 0);
|
||||
QString filedialogNextFilename(
|
||||
const QString &name,
|
||||
const QString &cur,
|
||||
const QString &path = QString());
|
||||
|
||||
namespace File {
|
||||
|
||||
@@ -54,10 +66,27 @@ struct OpenResult {
|
||||
QStringList paths;
|
||||
QByteArray remoteContent;
|
||||
};
|
||||
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetOpenPath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetOpenPaths(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetWritePath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetFolder(
|
||||
const QString &caption,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
|
||||
QString AllFilesFilter();
|
||||
|
||||
@@ -72,7 +101,13 @@ enum class Type {
|
||||
|
||||
void InitLastPathDefault();
|
||||
|
||||
bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile);
|
||||
bool GetDefault(
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace FileDialog
|
||||
|
||||
246
Telegram/SourceFiles/core/launcher.cpp
Normal file
246
Telegram/SourceFiles/core/launcher.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/launcher.h"
|
||||
|
||||
#include "platform/platform_launcher.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/main_queue_processor.h"
|
||||
#include "application.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
return std::make_unique<Platform::Launcher>(argc, argv);
|
||||
}
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: _argc(argc)
|
||||
, _argv(argv) {
|
||||
}
|
||||
|
||||
void Launcher::init() {
|
||||
_arguments = readArguments(_argc, _argv);
|
||||
|
||||
prepareSettings();
|
||||
|
||||
QCoreApplication::setApplicationName(qsl("TelegramDesktop"));
|
||||
|
||||
#ifndef OS_MAC_OLD
|
||||
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||
#endif // OS_MAC_OLD
|
||||
|
||||
initHook();
|
||||
}
|
||||
|
||||
int Launcher::exec() {
|
||||
init();
|
||||
|
||||
if (cLaunchMode() == LaunchModeFixPrevious) {
|
||||
return psFixPrevious();
|
||||
} else if (cLaunchMode() == LaunchModeCleanup) {
|
||||
return psCleanup();
|
||||
}
|
||||
|
||||
// both are finished in Application::closeApplication
|
||||
Logs::start(this); // must be started before Platform is started
|
||||
Platform::start(); // must be started before QApplication is created
|
||||
|
||||
auto result = executeApplication();
|
||||
|
||||
DEBUG_LOG(("Telegram finished, result: %1").arg(result));
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
if (cRestartingUpdate()) {
|
||||
DEBUG_LOG(("Application Info: executing updater to install update..."));
|
||||
if (!launchUpdater(UpdaterLaunch::PerformUpdate)) {
|
||||
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
|
||||
}
|
||||
} else
|
||||
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
|
||||
if (cRestarting()) {
|
||||
DEBUG_LOG(("Application Info: executing Telegram, because of restart..."));
|
||||
launchUpdater(UpdaterLaunch::JustRelaunch);
|
||||
}
|
||||
|
||||
CrashReports::Finish();
|
||||
Platform::finish();
|
||||
Logs::finish();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList Launcher::readArguments(int argc, char *argv[]) const {
|
||||
Expects(argc >= 0);
|
||||
|
||||
if (const auto native = readArgumentsHook(argc, argv)) {
|
||||
return *native;
|
||||
}
|
||||
|
||||
auto result = QStringList();
|
||||
result.reserve(argc);
|
||||
for (auto i = 0; i != argc; ++i) {
|
||||
result.push_back(fromUtf8Safe(argv[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Launcher::argumentsString() const {
|
||||
return _arguments.join(' ');
|
||||
}
|
||||
|
||||
void Launcher::prepareSettings() {
|
||||
#ifdef Q_OS_MAC
|
||||
#ifndef OS_MAC_OLD
|
||||
if (QSysInfo::macVersion() >= QSysInfo::MV_10_11) {
|
||||
gIsElCapitan = true;
|
||||
}
|
||||
#else // OS_MAC_OLD
|
||||
if (QSysInfo::macVersion() < QSysInfo::MV_10_7) {
|
||||
gIsSnowLeopard = true;
|
||||
}
|
||||
#endif // OS_MAC_OLD
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
switch (cPlatform()) {
|
||||
case dbipWindows:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/win/tupdates/current"));
|
||||
#ifndef OS_WIN_STORE
|
||||
gPlatformString = qsl("Windows");
|
||||
#else // OS_WIN_STORE
|
||||
gPlatformString = qsl("WinStore");
|
||||
#endif // OS_WIN_STORE
|
||||
break;
|
||||
case dbipMac:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac/tupdates/current"));
|
||||
#ifndef OS_MAC_STORE
|
||||
gPlatformString = qsl("MacOS");
|
||||
#else // OS_MAC_STORE
|
||||
gPlatformString = qsl("MacAppStore");
|
||||
#endif // OS_MAC_STORE
|
||||
break;
|
||||
case dbipMacOld:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac32/tupdates/current"));
|
||||
gPlatformString = qsl("MacOSold");
|
||||
break;
|
||||
case dbipLinux64:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
|
||||
gPlatformString = qsl("Linux64bit");
|
||||
break;
|
||||
case dbipLinux32:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux32/tupdates/current"));
|
||||
gPlatformString = qsl("Linux32bit");
|
||||
break;
|
||||
}
|
||||
|
||||
auto path = Platform::CurrentExecutablePath(_argc, _argv);
|
||||
LOG(("Executable path before check: %1").arg(path));
|
||||
if (!path.isEmpty()) {
|
||||
auto info = QFileInfo(path);
|
||||
if (info.isSymLink()) {
|
||||
info = info.symLinkTarget();
|
||||
}
|
||||
if (info.exists()) {
|
||||
gExeDir = info.absoluteDir().absolutePath() + '/';
|
||||
gExeName = info.fileName();
|
||||
}
|
||||
}
|
||||
if (cExeName().isEmpty()) {
|
||||
LOG(("WARNING: Could not compute executable path, some features will be disabled."));
|
||||
}
|
||||
|
||||
processArguments();
|
||||
}
|
||||
|
||||
void Launcher::processArguments() {
|
||||
enum class KeyFormat {
|
||||
NoValues,
|
||||
OneValue,
|
||||
AllLeftValues,
|
||||
};
|
||||
auto parseMap = std::map<QByteArray, KeyFormat> {
|
||||
{ "-testmode" , KeyFormat::NoValues },
|
||||
{ "-debug" , KeyFormat::NoValues },
|
||||
{ "-many" , KeyFormat::NoValues },
|
||||
{ "-key" , KeyFormat::OneValue },
|
||||
{ "-autostart" , KeyFormat::NoValues },
|
||||
{ "-fixprevious", KeyFormat::NoValues },
|
||||
{ "-cleanup" , KeyFormat::NoValues },
|
||||
{ "-noupdate" , KeyFormat::NoValues },
|
||||
{ "-tosettings" , KeyFormat::NoValues },
|
||||
{ "-startintray", KeyFormat::NoValues },
|
||||
{ "-sendpath" , KeyFormat::AllLeftValues },
|
||||
{ "-workdir" , KeyFormat::OneValue },
|
||||
{ "--" , KeyFormat::OneValue },
|
||||
};
|
||||
auto parseResult = QMap<QByteArray, QStringList>();
|
||||
auto parsingKey = QByteArray();
|
||||
auto parsingFormat = KeyFormat::NoValues;
|
||||
for (const auto &argument : _arguments) {
|
||||
switch (parsingFormat) {
|
||||
case KeyFormat::OneValue: {
|
||||
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
|
||||
parsingFormat = KeyFormat::NoValues;
|
||||
} break;
|
||||
case KeyFormat::AllLeftValues: {
|
||||
parseResult[parsingKey].push_back(argument.mid(0, 8192));
|
||||
} break;
|
||||
case KeyFormat::NoValues: {
|
||||
parsingKey = argument.toLatin1();
|
||||
auto it = parseMap.find(parsingKey);
|
||||
if (it != parseMap.end()) {
|
||||
parsingFormat = it->second;
|
||||
parseResult[parsingKey] = QStringList();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
gTestMode = parseResult.contains("-testmode");
|
||||
gDebug = parseResult.contains("-debug");
|
||||
gManyInstance = parseResult.contains("-many");
|
||||
gKeyFile = parseResult.value("-key", QStringList()).join(QString());
|
||||
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
|
||||
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
|
||||
: parseResult.contains("-cleanup") ? LaunchModeCleanup
|
||||
: LaunchModeNormal;
|
||||
gNoStartUpdate = parseResult.contains("-noupdate");
|
||||
gStartToSettings = parseResult.contains("-tosettings");
|
||||
gStartInTray = parseResult.contains("-startintray");
|
||||
gSendPaths = parseResult.value("-sendpath", QStringList());
|
||||
gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString());
|
||||
if (!gWorkingDir.isEmpty()) {
|
||||
if (QDir().exists(gWorkingDir)) {
|
||||
_customWorkingDir = true;
|
||||
} else {
|
||||
gWorkingDir = QString();
|
||||
}
|
||||
}
|
||||
gStartUrl = parseResult.value("--", QStringList()).join(QString());
|
||||
}
|
||||
|
||||
int Launcher::executeApplication() {
|
||||
MainQueueProcessor processor;
|
||||
Application app(this, _argc, _argv);
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
71
Telegram/SourceFiles/core/launcher.h
Normal file
71
Telegram/SourceFiles/core/launcher.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Launcher {
|
||||
public:
|
||||
Launcher(int argc, char *argv[]);
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
int exec();
|
||||
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const {
|
||||
return _customWorkingDir;
|
||||
}
|
||||
|
||||
protected:
|
||||
enum class UpdaterLaunch {
|
||||
PerformUpdate,
|
||||
JustRelaunch,
|
||||
};
|
||||
|
||||
private:
|
||||
void prepareSettings();
|
||||
void processArguments();
|
||||
|
||||
QStringList readArguments(int argc, char *argv[]) const;
|
||||
virtual base::optional<QStringList> readArgumentsHook(
|
||||
int argc,
|
||||
char *argv[]) const {
|
||||
return base::none;
|
||||
}
|
||||
|
||||
void init();
|
||||
virtual void initHook() {
|
||||
}
|
||||
|
||||
virtual bool launchUpdater(UpdaterLaunch action) = 0;
|
||||
|
||||
int executeApplication();
|
||||
|
||||
int _argc;
|
||||
char **_argv;
|
||||
QStringList _arguments;
|
||||
|
||||
bool _customWorkingDir = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
96
Telegram/SourceFiles/core/main_queue_processor.cpp
Normal file
96
Telegram/SourceFiles/core/main_queue_processor.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/main_queue_processor.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
QMutex ProcessorMutex;
|
||||
MainQueueProcessor *ProcessorInstance/* = nullptr*/;
|
||||
|
||||
constexpr auto kProcessorEvent = QEvent::Type(QEvent::User + 1);
|
||||
static_assert(kProcessorEvent < QEvent::MaxUser);
|
||||
|
||||
class ProcessorEvent : public QEvent {
|
||||
public:
|
||||
ProcessorEvent(void (*callable)(void*), void *argument);
|
||||
|
||||
void process();
|
||||
|
||||
private:
|
||||
void (*_callable)(void*) = nullptr;
|
||||
void *_argument = nullptr;
|
||||
|
||||
};
|
||||
|
||||
ProcessorEvent::ProcessorEvent(void (*callable)(void*), void *argument)
|
||||
: QEvent(kProcessorEvent)
|
||||
, _callable(callable)
|
||||
, _argument(argument) {
|
||||
}
|
||||
|
||||
void ProcessorEvent::process() {
|
||||
_callable(_argument);
|
||||
}
|
||||
|
||||
void ProcessMainQueue(void (*callable)(void*), void *argument) {
|
||||
QMutexLocker lock(&ProcessorMutex);
|
||||
|
||||
if (ProcessorInstance) {
|
||||
const auto event = new ProcessorEvent(callable, argument);
|
||||
QCoreApplication::postEvent(ProcessorInstance, event);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MainQueueProcessor::MainQueueProcessor() {
|
||||
acquire();
|
||||
crl::init_main_queue(ProcessMainQueue);
|
||||
}
|
||||
|
||||
bool MainQueueProcessor::event(QEvent *event) {
|
||||
if (event->type() == kProcessorEvent) {
|
||||
static_cast<ProcessorEvent*>(event)->process();
|
||||
return true;
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
void MainQueueProcessor::acquire() {
|
||||
Expects(ProcessorInstance == nullptr);
|
||||
|
||||
QMutexLocker lock(&ProcessorMutex);
|
||||
ProcessorInstance = this;
|
||||
}
|
||||
|
||||
void MainQueueProcessor::release() {
|
||||
Expects(ProcessorInstance == this);
|
||||
|
||||
QMutexLocker lock(&ProcessorMutex);
|
||||
ProcessorInstance = nullptr;
|
||||
}
|
||||
|
||||
MainQueueProcessor::~MainQueueProcessor() {
|
||||
release();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
40
Telegram/SourceFiles/core/main_queue_processor.h
Normal file
40
Telegram/SourceFiles/core/main_queue_processor.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
class MainQueueProcessor : public QObject {
|
||||
public:
|
||||
MainQueueProcessor();
|
||||
|
||||
~MainQueueProcessor();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void acquire();
|
||||
void release();
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -545,11 +545,6 @@ static int32 QuarterArcLength = (FullArcLength / 4);
|
||||
static int32 MinArcLength = (FullArcLength / 360);
|
||||
static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline QSharedPointer<T> MakeShared(Args&&... args) {
|
||||
return QSharedPointer<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// This pointer is used for global non-POD variables that are allocated
|
||||
// on demand by createIfNull(lambda) and are never automatically freed.
|
||||
template <typename T>
|
||||
|
||||
@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#define BETA_VERSION_MACRO (0ULL)
|
||||
|
||||
constexpr int AppVersion = 1002000;
|
||||
constexpr str_const AppVersionStr = "1.2";
|
||||
constexpr int AppVersion = 1002006;
|
||||
constexpr str_const AppVersionStr = "1.2.6";
|
||||
constexpr bool AppAlphaVersion = false;
|
||||
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;
|
||||
|
||||
@@ -224,7 +224,10 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals
|
||||
return saveFileName(caption, filter, prefix, name, forceSavingAs, dir);
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) {
|
||||
void DocumentOpenClickHandler::doOpen(
|
||||
not_null<DocumentData*> data,
|
||||
HistoryItem *context,
|
||||
ActionOnLoad action) {
|
||||
if (!data->date) return;
|
||||
|
||||
auto msgId = context ? context->fullId() : FullMsgId();
|
||||
@@ -329,23 +332,20 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::onClickImpl() const {
|
||||
const auto item = App::hoveredLinkItem()
|
||||
? App::hoveredLinkItem()
|
||||
: (App::contextItem() ? App::contextItem() : nullptr);
|
||||
const auto action = document()->isVoiceMessage()
|
||||
const auto data = document();
|
||||
const auto action = data->isVoiceMessage()
|
||||
? ActionOnLoadNone
|
||||
: ActionOnLoadOpen;
|
||||
doOpen(document(), item, action);
|
||||
doOpen(data, getActionItem(), action);
|
||||
}
|
||||
|
||||
void GifOpenClickHandler::onClickImpl() const {
|
||||
const auto item = App::hoveredLinkItem()
|
||||
? App::hoveredLinkItem()
|
||||
: (App::contextItem() ? App::contextItem() : nullptr);
|
||||
doOpen(document(), item, ActionOnLoadPlayInline);
|
||||
doOpen(document(), getActionItem(), ActionOnLoadPlayInline);
|
||||
}
|
||||
|
||||
void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) {
|
||||
void DocumentSaveClickHandler::doSave(
|
||||
not_null<DocumentData*> data,
|
||||
bool forceSavingAs) {
|
||||
if (!data->date) return;
|
||||
|
||||
auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs);
|
||||
@@ -369,17 +369,13 @@ void DocumentSaveClickHandler::onClickImpl() const {
|
||||
}
|
||||
|
||||
void DocumentCancelClickHandler::onClickImpl() const {
|
||||
auto data = document();
|
||||
const auto data = document();
|
||||
if (!data->date) return;
|
||||
|
||||
if (data->uploading()) {
|
||||
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
|
||||
if (auto media = item->getMedia()) {
|
||||
if (media->getDocument() == data) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
}
|
||||
if (const auto item = App::histItemById(context())) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
} else {
|
||||
data->cancel();
|
||||
@@ -672,12 +668,19 @@ QString DocumentData::loadingFilePath() const {
|
||||
}
|
||||
|
||||
bool DocumentData::displayLoading() const {
|
||||
return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading();
|
||||
return loading()
|
||||
? (!_loader->loadingLocal() || !_loader->autoLoading())
|
||||
: (uploading() && !waitingForAlbum());
|
||||
}
|
||||
|
||||
float64 DocumentData::progress() const {
|
||||
if (uploading()) {
|
||||
return snap((size > 0) ? float64(uploadOffset) / size : 0., 0., 1.);
|
||||
if (uploadingData->size > 0) {
|
||||
const auto result = float64(uploadingData->offset)
|
||||
/ uploadingData->size;
|
||||
return snap(result, 0., 1.);
|
||||
}
|
||||
return 0.;
|
||||
}
|
||||
return loading() ? _loader->currentProgress() : (loaded() ? 1. : 0.);
|
||||
}
|
||||
@@ -687,7 +690,17 @@ int32 DocumentData::loadOffset() const {
|
||||
}
|
||||
|
||||
bool DocumentData::uploading() const {
|
||||
return status == FileUploading;
|
||||
return (uploadingData != nullptr);
|
||||
}
|
||||
|
||||
void DocumentData::setWaitingForAlbum() {
|
||||
if (uploading()) {
|
||||
uploadingData->waitingForAlbum = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::waitingForAlbum() const {
|
||||
return uploading() && uploadingData->waitingForAlbum;
|
||||
}
|
||||
|
||||
void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) {
|
||||
|
||||
@@ -125,6 +125,9 @@ public:
|
||||
int32 loadOffset() const;
|
||||
bool uploading() const;
|
||||
|
||||
void setWaitingForAlbum();
|
||||
bool waitingForAlbum() const;
|
||||
|
||||
QByteArray data() const;
|
||||
const FileLocation &location(bool check = false) const;
|
||||
void setLocation(const FileLocation &loc);
|
||||
@@ -241,7 +244,8 @@ public:
|
||||
int32 size = 0;
|
||||
|
||||
FileStatus status = FileReady;
|
||||
int32 uploadOffset = 0;
|
||||
|
||||
std::unique_ptr<Data::UploadState> uploadingData;
|
||||
|
||||
int32 md5[8];
|
||||
|
||||
@@ -309,17 +313,20 @@ private:
|
||||
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
|
||||
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
|
||||
|
||||
class DocumentClickHandler : public LeftButtonClickHandler {
|
||||
class DocumentClickHandler : public FileClickHandler {
|
||||
public:
|
||||
DocumentClickHandler(DocumentData *document)
|
||||
: _document(document) {
|
||||
DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context = FullMsgId())
|
||||
: FileClickHandler(context)
|
||||
, _document(document) {
|
||||
}
|
||||
DocumentData *document() const {
|
||||
not_null<DocumentData*> document() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
private:
|
||||
DocumentData *_document;
|
||||
not_null<DocumentData*> _document;
|
||||
|
||||
};
|
||||
|
||||
@@ -327,7 +334,7 @@ class DocumentSaveClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void doSave(
|
||||
DocumentData *document,
|
||||
not_null<DocumentData*> document,
|
||||
bool forceSavingAs = false);
|
||||
|
||||
protected:
|
||||
@@ -339,7 +346,7 @@ class DocumentOpenClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void doOpen(
|
||||
DocumentData *document,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *context,
|
||||
ActionOnLoad action = ActionOnLoadOpen);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwindow.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -100,7 +101,7 @@ void PeerClickHandler::onClick(Qt::MouseButton button) const {
|
||||
PeerData::PeerData(const PeerId &id)
|
||||
: id(id)
|
||||
, _userpicEmpty(createEmptyUserpic()) {
|
||||
nameText.setText(st::msgNameStyle, QString(), _textNameOptions);
|
||||
nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void PeerData::updateNameDelayed(
|
||||
@@ -124,7 +125,7 @@ void PeerData::updateNameDelayed(
|
||||
|
||||
++nameVersion;
|
||||
name = newName;
|
||||
nameText.setText(st::msgNameStyle, name, _textNameOptions);
|
||||
nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
|
||||
refreshEmptyUserpic();
|
||||
|
||||
Notify::PeerUpdate update(this);
|
||||
@@ -164,7 +165,7 @@ void PeerData::refreshEmptyUserpic() const {
|
||||
}
|
||||
|
||||
ClickHandlerPtr PeerData::createOpenLink() {
|
||||
return MakeShared<PeerClickHandler>(this);
|
||||
return std::make_shared<PeerClickHandler>(this);
|
||||
}
|
||||
|
||||
void PeerData::setUserpic(
|
||||
@@ -277,12 +278,7 @@ void PeerData::updateUserpic(
|
||||
const auto size = kUserpicSize;
|
||||
const auto loc = StorageImageLocation::FromMTP(size, size, location);
|
||||
const auto photo = loc.isNull() ? ImagePtr() : ImagePtr(loc);
|
||||
if (_userpicPhotoId != photoId
|
||||
|| _userpic.v() != photo.v()
|
||||
|| _userpicLocation != loc) {
|
||||
setUserpic(photoId, loc, photo);
|
||||
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
|
||||
}
|
||||
setUserpicChecked(photoId, loc, photo);
|
||||
}
|
||||
|
||||
void PeerData::clearUserpic() {
|
||||
@@ -300,6 +296,19 @@ void PeerData::clearUserpic() {
|
||||
}
|
||||
return ImagePtr();
|
||||
}();
|
||||
setUserpicChecked(photoId, loc, photo);
|
||||
}
|
||||
|
||||
void PeerData::setUserpicChecked(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic) {
|
||||
if (_userpicPhotoId != photoId
|
||||
|| _userpic.v() != userpic.v()
|
||||
|| _userpicLocation != location) {
|
||||
setUserpic(photoId, location, userpic);
|
||||
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::fillNames() {
|
||||
@@ -343,7 +352,10 @@ PeerData::~PeerData() = default;
|
||||
|
||||
const Text &BotCommand::descriptionText() const {
|
||||
if (_descriptionText.isEmpty() && !_description.isEmpty()) {
|
||||
_descriptionText.setText(st::defaultTextStyle, _description, _textNameOptions);
|
||||
_descriptionText.setText(
|
||||
st::defaultTextStyle,
|
||||
_description,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
return _descriptionText;
|
||||
}
|
||||
@@ -483,7 +495,10 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
void UserData::setNameOrPhone(const QString &newNameOrPhone) {
|
||||
if (nameOrPhone != newNameOrPhone) {
|
||||
nameOrPhone = newNameOrPhone;
|
||||
phoneText.setText(st::msgNameStyle, nameOrPhone, _textNameOptions);
|
||||
phoneText.setText(
|
||||
st::msgNameStyle,
|
||||
nameOrPhone,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,16 +583,18 @@ void ChatData::setInviteLink(const QString &newInviteLink) {
|
||||
ChannelData::ChannelData(const PeerId &id)
|
||||
: PeerData(id)
|
||||
, inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) {
|
||||
Data::PeerFlagValue(this, MTPDchannel::Flag::f_megagroup)
|
||||
| rpl::start_with_next([this](bool megagroup) {
|
||||
if (megagroup) {
|
||||
if (!mgInfo) {
|
||||
mgInfo = std::make_unique<MegagroupInfo>();
|
||||
}
|
||||
} else if (mgInfo) {
|
||||
mgInfo = nullptr;
|
||||
Data::PeerFlagValue(
|
||||
this,
|
||||
MTPDchannel::Flag::f_megagroup
|
||||
) | rpl::start_with_next([this](bool megagroup) {
|
||||
if (megagroup) {
|
||||
if (!mgInfo) {
|
||||
mgInfo = std::make_unique<MegagroupInfo>();
|
||||
}
|
||||
}, _lifetime);
|
||||
} else if (mgInfo) {
|
||||
mgInfo = nullptr;
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void ChannelData::setPhoto(const MTPChatPhoto &photo) {
|
||||
@@ -784,9 +801,12 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
|
||||
}
|
||||
Data::ChannelAdminChanges(this).feed(peerToUser(user->id), false);
|
||||
} else {
|
||||
if (isKicked && membersCount() > 1) {
|
||||
setMembersCount(membersCount() - 1);
|
||||
flags |= Notify::PeerUpdate::Flag::MembersChanged;
|
||||
if (isKicked) {
|
||||
if (membersCount() > 1) {
|
||||
setMembersCount(membersCount() - 1);
|
||||
flags |= Notify::PeerUpdate::Flag::MembersChanged;
|
||||
}
|
||||
setKickedCount(kickedCount() + 1);
|
||||
}
|
||||
}
|
||||
Notify::peerUpdatedDelayed(this, flags);
|
||||
|
||||
@@ -238,6 +238,11 @@ private:
|
||||
std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const;
|
||||
void refreshEmptyUserpic() const;
|
||||
|
||||
void setUserpicChecked(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic);
|
||||
|
||||
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
ImagePtr _userpic;
|
||||
|
||||
@@ -20,7 +20,67 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "data/data_peer_values.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinOnlineChangeTimeout = TimeMs(1000);
|
||||
constexpr auto kMaxOnlineChangeTimeout = 86400 * TimeMs(1000);
|
||||
|
||||
int OnlinePhraseChangeInSeconds(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
if (-online > now) {
|
||||
return (-online - now);
|
||||
}
|
||||
return std::numeric_limits<int32>::max();
|
||||
}
|
||||
if (online > now) {
|
||||
return online - now;
|
||||
}
|
||||
const auto minutes = (now - online) / 60;
|
||||
if (minutes < 60) {
|
||||
return (minutes + 1) * 60 - (now - online);
|
||||
}
|
||||
const auto hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return (hours + 1) * 3600 - (now - online);
|
||||
}
|
||||
const auto nowFull = ::date(now);
|
||||
const auto tomorrow = QDateTime(nowFull.date().addDays(1));
|
||||
return static_cast<int32>(nowFull.secsTo(tomorrow));
|
||||
}
|
||||
|
||||
base::optional<QString> OnlineTextSpecial(not_null<UserData*> user) {
|
||||
if (isNotificationsUser(user->id)) {
|
||||
return lang(lng_status_service_notifications);
|
||||
} else if (user->botInfo) {
|
||||
return lang(lng_status_bot);
|
||||
} else if (isServiceUser(user->id)) {
|
||||
return lang(lng_status_support);
|
||||
}
|
||||
return base::none;
|
||||
}
|
||||
|
||||
base::optional<QString> OnlineTextCommon(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return lang(lng_status_offline);
|
||||
case -2: return lang(lng_status_recently);
|
||||
case -3: return lang(lng_status_last_week);
|
||||
case -4: return lang(lng_status_last_month);
|
||||
}
|
||||
return (-online > now)
|
||||
? lang(lng_status_online)
|
||||
: lang(lng_status_recently);
|
||||
} else if (online > now) {
|
||||
return lang(lng_status_online);
|
||||
}
|
||||
return base::none;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
inline auto AdminRightsValue(not_null<ChannelData*> channel) {
|
||||
return channel->adminRightsValue();
|
||||
@@ -123,4 +183,129 @@ rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer) {
|
||||
Unexpected("Bad peer value in CanWriteValue()");
|
||||
}
|
||||
|
||||
TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return -1;
|
||||
}
|
||||
const auto online = user->onlineTill;
|
||||
const auto fromDate = [](const QDate &date) {
|
||||
const auto shift = (unixtime() - myunixtime());
|
||||
return static_cast<TimeId>(QDateTime(date).toTime_t()) + shift;
|
||||
};
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return online;
|
||||
|
||||
case -2: {
|
||||
const auto recently = date(now).date().addDays(-3);
|
||||
return fromDate(recently);
|
||||
} break;
|
||||
|
||||
case -3: {
|
||||
const auto weekago = date(now).date().addDays(-7);
|
||||
return fromDate(weekago);
|
||||
} break;
|
||||
|
||||
case -4: {
|
||||
const auto monthago = date(now).date().addDays(-30);
|
||||
return fromDate(monthago);
|
||||
} break;
|
||||
}
|
||||
return -online;
|
||||
}
|
||||
return online;
|
||||
}
|
||||
|
||||
TimeMs OnlineChangeTimeout(TimeId online, TimeId now) {
|
||||
const auto result = OnlinePhraseChangeInSeconds(online, now);
|
||||
Assert(result >= 0);
|
||||
return snap(
|
||||
result * TimeMs(1000),
|
||||
kMinOnlineChangeTimeout,
|
||||
kMaxOnlineChangeTimeout);
|
||||
}
|
||||
|
||||
TimeMs OnlineChangeTimeout(not_null<UserData*> user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return kMaxOnlineChangeTimeout;
|
||||
}
|
||||
return OnlineChangeTimeout(user->onlineTill, now);
|
||||
}
|
||||
|
||||
QString OnlineText(TimeId online, TimeId now) {
|
||||
if (const auto common = OnlineTextCommon(online, now)) {
|
||||
return *common;
|
||||
}
|
||||
const auto minutes = (now - online) / 60;
|
||||
if (!minutes) {
|
||||
return lang(lng_status_lastseen_now);
|
||||
} else if (minutes < 60) {
|
||||
return lng_status_lastseen_minutes(lt_count, minutes);
|
||||
}
|
||||
const auto hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return lng_status_lastseen_hours(lt_count, hours);
|
||||
}
|
||||
const auto onlineFull = ::date(online);
|
||||
const auto nowFull = ::date(now);
|
||||
if (onlineFull.date() == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_today(lt_time, onlineTime);
|
||||
} else if (onlineFull.date().addDays(1) == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_yesterday(lt_time, onlineTime);
|
||||
}
|
||||
const auto date = onlineFull.date().toString(qsl("dd.MM.yy"));
|
||||
return lng_status_lastseen_date(lt_date, date);
|
||||
}
|
||||
|
||||
QString OnlineText(not_null<UserData*> user, TimeId now) {
|
||||
if (const auto special = OnlineTextSpecial(user)) {
|
||||
return *special;
|
||||
}
|
||||
return OnlineText(user->onlineTill, now);
|
||||
}
|
||||
|
||||
QString OnlineTextFull(not_null<UserData*> user, TimeId now) {
|
||||
if (const auto special = OnlineTextSpecial(user)) {
|
||||
return *special;
|
||||
} else if (const auto common = OnlineTextCommon(user->onlineTill, now)) {
|
||||
return *common;
|
||||
}
|
||||
const auto onlineFull = ::date(user->onlineTill);
|
||||
const auto nowFull = ::date(now);
|
||||
if (onlineFull.date() == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_today(lt_time, onlineTime);
|
||||
} else if (onlineFull.date().addDays(1) == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_yesterday(lt_time, onlineTime);
|
||||
}
|
||||
const auto date = onlineFull.date().toString(qsl("dd.MM.yy"));
|
||||
const auto time = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_date_time(lt_date, date, lt_time, time);
|
||||
}
|
||||
|
||||
bool OnlineTextActive(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1:
|
||||
case -2:
|
||||
case -3:
|
||||
case -4: return false;
|
||||
}
|
||||
return (-online > now);
|
||||
}
|
||||
return (online > now);
|
||||
}
|
||||
|
||||
bool OnlineTextActive(not_null<UserData*> user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return false;
|
||||
}
|
||||
return OnlineTextActive(user->onlineTill, now);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,23 +31,25 @@ template <typename ChangeType, typename Error, typename Generator>
|
||||
inline auto FlagsValueWithMask(
|
||||
rpl::producer<ChangeType, Error, Generator> &&value,
|
||||
typename ChangeType::Type mask) {
|
||||
return std::move(value)
|
||||
| rpl::filter([mask](const ChangeType &change) {
|
||||
return change.diff & mask;
|
||||
})
|
||||
| rpl::map([mask](const ChangeType &change) {
|
||||
return change.value & mask;
|
||||
});
|
||||
return std::move(
|
||||
value
|
||||
) | rpl::filter([mask](const ChangeType &change) {
|
||||
return change.diff & mask;
|
||||
}) | rpl::map([mask](const ChangeType &change) {
|
||||
return change.value & mask;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename ChangeType, typename Error, typename Generator>
|
||||
inline auto SingleFlagValue(
|
||||
rpl::producer<ChangeType, Error, Generator> &&value,
|
||||
typename ChangeType::Enum flag) {
|
||||
return FlagsValueWithMask(std::move(value), flag)
|
||||
| rpl::map([flag](typename ChangeType::Type value) {
|
||||
return !!value;
|
||||
});
|
||||
return FlagsValueWithMask(
|
||||
std::move(value),
|
||||
flag
|
||||
) | rpl::map([flag](typename ChangeType::Type value) {
|
||||
return !!value;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -121,4 +123,13 @@ rpl::producer<bool> CanWriteValue(ChatData *chat);
|
||||
rpl::producer<bool> CanWriteValue(ChannelData *channel);
|
||||
rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer);
|
||||
|
||||
TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now);
|
||||
TimeMs OnlineChangeTimeout(TimeId online, TimeId now);
|
||||
TimeMs OnlineChangeTimeout(not_null<UserData*> user, TimeId now);
|
||||
QString OnlineText(TimeId online, TimeId now);
|
||||
QString OnlineText(not_null<UserData*> user, TimeId now);
|
||||
QString OnlineTextFull(not_null<UserData*> user, TimeId now);
|
||||
bool OnlineTextActive(TimeId online, TimeId now);
|
||||
bool OnlineTextActive(not_null<UserData*> user, TimeId now);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -63,7 +63,9 @@ bool PhotoData::loading() const {
|
||||
}
|
||||
|
||||
bool PhotoData::displayLoading() const {
|
||||
return full->loading() ? full->displayLoading() : uploading();
|
||||
return full->loading()
|
||||
? full->displayLoading()
|
||||
: (uploading() && !waitingForAlbum());
|
||||
}
|
||||
|
||||
void PhotoData::cancel() {
|
||||
@@ -91,12 +93,22 @@ float64 PhotoData::progress() const {
|
||||
return full->progress();
|
||||
}
|
||||
|
||||
void PhotoData::setWaitingForAlbum() {
|
||||
if (uploading()) {
|
||||
uploadingData->waitingForAlbum = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PhotoData::waitingForAlbum() const {
|
||||
return uploading() && uploadingData->waitingForAlbum;
|
||||
}
|
||||
|
||||
int32 PhotoData::loadOffset() const {
|
||||
return full->loadOffset();
|
||||
}
|
||||
|
||||
bool PhotoData::uploading() const {
|
||||
return !!uploadingData;
|
||||
return (uploadingData != nullptr);
|
||||
}
|
||||
|
||||
void PhotoData::forget() {
|
||||
@@ -121,7 +133,7 @@ ImagePtr PhotoData::makeReplyPreview() {
|
||||
}
|
||||
|
||||
void PhotoOpenClickHandler::onClickImpl() const {
|
||||
Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
|
||||
Messenger::Instance().showPhoto(this);
|
||||
}
|
||||
|
||||
void PhotoSaveClickHandler::onClickImpl() const {
|
||||
@@ -136,13 +148,9 @@ void PhotoCancelClickHandler::onClickImpl() const {
|
||||
if (!data->date) return;
|
||||
|
||||
if (data->uploading()) {
|
||||
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
|
||||
if (auto media = item->getMedia()) {
|
||||
if (media->type() == MediaTypePhoto && static_cast<HistoryPhoto*>(media)->photo() == data) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
}
|
||||
if (const auto item = App::histItemById(context())) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
} else {
|
||||
data->cancel();
|
||||
|
||||
@@ -44,6 +44,9 @@ public:
|
||||
int32 loadOffset() const;
|
||||
bool uploading() const;
|
||||
|
||||
void setWaitingForAlbum();
|
||||
bool waitingForAlbum() const;
|
||||
|
||||
void forget();
|
||||
ImagePtr makeReplyPreview();
|
||||
|
||||
@@ -57,25 +60,22 @@ public:
|
||||
PeerData *peer = nullptr; // for chat and channel photos connection
|
||||
// geo, caption
|
||||
|
||||
struct UploadingData {
|
||||
UploadingData(int size) : size(size) {
|
||||
}
|
||||
int offset = 0;
|
||||
int size = 0;
|
||||
};
|
||||
std::unique_ptr<UploadingData> uploadingData;
|
||||
std::unique_ptr<Data::UploadState> uploadingData;
|
||||
|
||||
private:
|
||||
void notifyLayoutChanged() const;
|
||||
|
||||
};
|
||||
|
||||
class PhotoClickHandler : public LeftButtonClickHandler {
|
||||
class PhotoClickHandler : public FileClickHandler {
|
||||
public:
|
||||
PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context = FullMsgId(),
|
||||
PeerData *peer = nullptr)
|
||||
: _photo(photo), _peer(peer) {
|
||||
: FileClickHandler(context)
|
||||
, _photo(photo)
|
||||
, _peer(peer) {
|
||||
}
|
||||
not_null<PhotoData*> photo() const {
|
||||
return _photo;
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
|
||||
private:
|
||||
not_null<PhotoData*> _photo;
|
||||
PeerData *_peer;
|
||||
PeerData *_peer = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -36,13 +36,15 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction) {
|
||||
auto filter = [&] {
|
||||
const auto filter = [&] {
|
||||
using Type = Storage::SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
case Type::PhotoVideo:
|
||||
return MTP_inputMessagesFilterPhotoVideo();
|
||||
case Type::MusicFile:
|
||||
return MTP_inputMessagesFilterMusic();
|
||||
case Type::File:
|
||||
@@ -63,10 +65,10 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
|
||||
auto minId = 0;
|
||||
auto maxId = 0;
|
||||
auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
auto offsetId = [&] {
|
||||
const auto minId = 0;
|
||||
const auto maxId = 0;
|
||||
const auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
const auto offsetId = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before:
|
||||
case SparseIdsLoadDirection::Around: return messageId;
|
||||
@@ -74,7 +76,7 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
}
|
||||
Unexpected("Direction in PrepareSearchRequest");
|
||||
}();
|
||||
auto addOffset = [&] {
|
||||
const auto addOffset = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before: return 0;
|
||||
case SparseIdsLoadDirection::Around: return -limit / 2;
|
||||
@@ -130,7 +132,8 @@ SearchResult ParseSearchResult(
|
||||
if (auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts.v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when no channel was passed! (ParseSearchResult)"));
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (ParseSearchResult)"));
|
||||
}
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
@@ -139,7 +142,8 @@ SearchResult ParseSearchResult(
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesNotModified: {
|
||||
LOG(("API Error: received messages.messagesNotModified! (ParseSearchResult)"));
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(ParseSearchResult)"));
|
||||
return (const QVector<MTPMessage>*)nullptr;
|
||||
} break;
|
||||
}
|
||||
@@ -260,50 +264,46 @@ rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
builder->insufficientAround()
|
||||
| rpl::start_with_next([=](
|
||||
const SparseIdsSliceBuilder::AroundData &data) {
|
||||
requestMore(data, query, listData);
|
||||
}, lifetime);
|
||||
builder->insufficientAround(
|
||||
) | rpl::start_with_next([=](
|
||||
const SparseIdsSliceBuilder::AroundData &data) {
|
||||
requestMore(data, query, listData);
|
||||
}, lifetime);
|
||||
|
||||
auto pushNextSnapshot = [=] {
|
||||
consumer.put_next(builder->snapshot());
|
||||
};
|
||||
|
||||
listData->list.sliceUpdated()
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
listData->list.sliceUpdated(
|
||||
) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->history()->peer->id == peerId);
|
||||
})
|
||||
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return builder->removeOne(item->id);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().data().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->history()->peer->id == peerId);
|
||||
}) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return builder->removeOne(item->id);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
Auth().data().historyCleared()
|
||||
| rpl::filter([=](not_null<const History*> history) {
|
||||
return (history->peer->id == peerId);
|
||||
})
|
||||
| rpl::filter([=] { return builder->removeAll(); })
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().data().historyCleared(
|
||||
) | rpl::filter([=](not_null<const History*> history) {
|
||||
return (history->peer->id == peerId);
|
||||
}) | rpl::filter([=] {
|
||||
return builder->removeAll();
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using Result = Storage::SparseIdsListResult;
|
||||
listData->list.query(Storage::SparseIdsListQuery(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter))
|
||||
| rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
})
|
||||
| rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
limitAfter
|
||||
)) | rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
}) | rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
|
||||
@@ -93,56 +93,50 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
data.aroundId,
|
||||
data.direction);
|
||||
};
|
||||
builder->insufficientAround()
|
||||
| rpl::start_with_next(requestMediaAround, lifetime);
|
||||
builder->insufficientAround(
|
||||
) | rpl::start_with_next(requestMediaAround, lifetime);
|
||||
|
||||
auto pushNextSnapshot = [=] {
|
||||
consumer.put_next(builder->snapshot());
|
||||
};
|
||||
|
||||
using SliceUpdate = Storage::SharedMediaSliceUpdate;
|
||||
Auth().storage().sharedMediaSliceUpdated()
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& (update.type == key.type);
|
||||
})
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update.data);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().storage().sharedMediaSliceUpdated(
|
||||
) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& (update.type == key.type);
|
||||
}) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update.data);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using OneRemoved = Storage::SharedMediaRemoveOne;
|
||||
Auth().storage().sharedMediaOneRemoved()
|
||||
| rpl::filter([=](const OneRemoved &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& update.types.test(key.type);
|
||||
})
|
||||
| rpl::filter([=](const OneRemoved &update) {
|
||||
return builder->removeOne(update.messageId);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().storage().sharedMediaOneRemoved(
|
||||
) | rpl::filter([=](const OneRemoved &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& update.types.test(key.type);
|
||||
}) | rpl::filter([=](const OneRemoved &update) {
|
||||
return builder->removeOne(update.messageId);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using AllRemoved = Storage::SharedMediaRemoveAll;
|
||||
Auth().storage().sharedMediaAllRemoved()
|
||||
| rpl::filter([=](const AllRemoved &update) {
|
||||
return (update.peerId == key.peerId);
|
||||
})
|
||||
| rpl::filter([=] { return builder->removeAll(); })
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().storage().sharedMediaAllRemoved(
|
||||
) | rpl::filter([=](const AllRemoved &update) {
|
||||
return (update.peerId == key.peerId);
|
||||
}) | rpl::filter([=] {
|
||||
return builder->removeAll();
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using Result = Storage::SharedMediaResult;
|
||||
Auth().storage().query(
|
||||
Storage::SharedMediaQuery(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter))
|
||||
| rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
})
|
||||
| rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
Auth().storage().query(Storage::SharedMediaQuery(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter
|
||||
)) | rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
}) | rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
@@ -290,9 +284,7 @@ base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
|
||||
| [](FullMsgId msgId) { return App::histItemById(msgId); }
|
||||
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
|
||||
| [](HistoryMedia *media) {
|
||||
return (media && media->type() == MediaTypePhoto)
|
||||
? static_cast<HistoryPhoto*>(media)->photo()
|
||||
: nullptr;
|
||||
return media ? media->getPhoto() : nullptr;
|
||||
}
|
||||
| [](PhotoData *photo) { return photo ? photo->id : 0; }
|
||||
| [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };
|
||||
@@ -355,9 +347,12 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer(
|
||||
SharedMediaWithLastSlice::Key key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return SharedMediaWithLastViewer(key, limitBefore, limitAfter)
|
||||
| rpl::map([](SharedMediaWithLastSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
return SharedMediaWithLastViewer(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::map([](SharedMediaWithLastSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -415,13 +415,14 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
limitAfter
|
||||
);
|
||||
if (!key.migratedPeerId) {
|
||||
return std::move(partViewer)
|
||||
| rpl::start_with_next([=](SparseIdsSlice &&part) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key,
|
||||
std::move(part),
|
||||
base::none));
|
||||
});
|
||||
return std::move(
|
||||
partViewer
|
||||
) | rpl::start_with_next([=](SparseIdsSlice &&part) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key,
|
||||
std::move(part),
|
||||
base::none));
|
||||
});
|
||||
}
|
||||
auto migratedViewer = simpleViewer(
|
||||
key.migratedPeerId,
|
||||
|
||||
@@ -53,3 +53,13 @@ void MessageCursor::applyTo(QTextEdit *edit) {
|
||||
scrollbar->setValue(scroll);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *FileClickHandler::getActionItem() const {
|
||||
return context()
|
||||
? App::histItemById(context())
|
||||
: App::hoveredLinkItem()
|
||||
? App::hoveredLinkItem()
|
||||
: App::contextItem()
|
||||
? App::contextItem()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct UploadState {
|
||||
UploadState(int size) : size(size) {
|
||||
}
|
||||
int offset = 0;
|
||||
int size = 0;
|
||||
bool waitingForAlbum = false;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
class PeerData;
|
||||
class UserData;
|
||||
class ChatData;
|
||||
@@ -241,7 +253,6 @@ enum LocationType {
|
||||
enum FileStatus {
|
||||
FileDownloadFailed = -2,
|
||||
FileUploadFailed = -1,
|
||||
FileUploading = 0,
|
||||
FileReady = 1,
|
||||
};
|
||||
|
||||
@@ -390,3 +401,24 @@ struct SendAction {
|
||||
int progress = 0;
|
||||
|
||||
};
|
||||
|
||||
class FileClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
FileClickHandler(FullMsgId context) : _context(context) {
|
||||
}
|
||||
|
||||
void setMessageId(FullMsgId context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
FullMsgId context() const {
|
||||
return _context;
|
||||
}
|
||||
|
||||
protected:
|
||||
HistoryItem *getActionItem() const;
|
||||
|
||||
private:
|
||||
FullMsgId _context;
|
||||
|
||||
};
|
||||
|
||||
@@ -233,11 +233,11 @@ rpl::producer<UserPhotosSlice> UserPhotosViewer(
|
||||
Auth().storage().query(Storage::UserPhotosQuery(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter))
|
||||
| rpl::start_with_next_done(
|
||||
applyUpdate,
|
||||
[=] { builder->checkInsufficientPhotos(); },
|
||||
lifetime);
|
||||
limitAfter
|
||||
)) | rpl::start_with_next_done(
|
||||
applyUpdate,
|
||||
[=] { builder->checkInsufficientPhotos(); },
|
||||
lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
@@ -248,9 +248,12 @@ rpl::producer<UserPhotosSlice> UserPhotosReversedViewer(
|
||||
UserPhotosSlice::Key key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return UserPhotosViewer(key, limitBefore, limitAfter)
|
||||
| rpl::map([](UserPhotosSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
return UserPhotosViewer(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::map([](UserPhotosSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
}
|
||||
@@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_window.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -36,6 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "observer_peer.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "auth_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_controller.h"
|
||||
@@ -95,16 +97,16 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
|
||||
_cancelSearchFromUser->hide();
|
||||
|
||||
subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
lifetime());
|
||||
Auth().data().itemRepaintRequest()
|
||||
| rpl::start_with_next([this](auto item) {
|
||||
if (item->history()->lastMsg == item) {
|
||||
item->history()->updateChatListEntry();
|
||||
}
|
||||
}, lifetime());
|
||||
Auth().data().itemRemoved(
|
||||
) | rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
lifetime());
|
||||
Auth().data().itemRepaintRequest(
|
||||
) | rpl::start_with_next([this](auto item) {
|
||||
if (item->history()->lastMsg == item) {
|
||||
item->history()->updateChatListEntry();
|
||||
}
|
||||
}, lifetime());
|
||||
subscribe(App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) {
|
||||
auto updateRect = Dialogs::Layout::RowPainter::sendActionAnimationRect(update.width, update.height, getFullWidth(), update.textUpdated);
|
||||
updateDialogRow(update.history->peer, MsgId(0), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered);
|
||||
@@ -1796,9 +1798,9 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_cancelSearchInPeer->show();
|
||||
if (_searchInPeer->isSelf()) {
|
||||
_searchInSavedText.setText(
|
||||
st::dialogsSearchFromStyle,
|
||||
st::msgNameStyle,
|
||||
lang(lng_saved_messages),
|
||||
_textDlgOptions);
|
||||
Ui::DialogTextOptions());
|
||||
}
|
||||
} else {
|
||||
_cancelSearchInPeer->hide();
|
||||
@@ -1810,7 +1812,7 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_searchFromUserText.setText(
|
||||
st::dialogsSearchFromStyle,
|
||||
fromUserText,
|
||||
_textDlgOptions);
|
||||
Ui::DialogTextOptions());
|
||||
_cancelSearchFromUser->show();
|
||||
} else {
|
||||
_cancelSearchFromUser->hide();
|
||||
@@ -2123,7 +2125,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) {
|
||||
recent = cRecentSearchHashtags();
|
||||
}
|
||||
found = true;
|
||||
incrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
|
||||
Stickers::IncrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
|
||||
}
|
||||
if (found) {
|
||||
cSetRecentSearchHashtags(recent);
|
||||
|
||||
@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -156,7 +157,7 @@ void paintRow(
|
||||
if (history->cloudDraftTextCache.isEmpty()) {
|
||||
auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft)));
|
||||
auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, _textDlgOptions);
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft));
|
||||
@@ -499,7 +500,7 @@ void RowPainter::paint(
|
||||
auto item = row->item();
|
||||
auto history = item->history();
|
||||
auto cloudDraft = nullptr;
|
||||
auto from = [&] {
|
||||
const auto from = [&] {
|
||||
if (auto searchPeer = row->searchInPeer()) {
|
||||
if (searchPeer->isSelf()) {
|
||||
return item->senderOriginal();
|
||||
@@ -509,7 +510,7 @@ void RowPainter::paint(
|
||||
}
|
||||
return (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer);
|
||||
}();
|
||||
auto drawInDialogWay = [&] {
|
||||
const auto drawInDialogWay = [&] {
|
||||
if (auto searchPeer = row->searchInPeer()) {
|
||||
if (!searchPeer->isChannel() || searchPeer->isMegagroup()) {
|
||||
return HistoryItem::DrawInDialog::WithoutSender;
|
||||
@@ -517,7 +518,7 @@ void RowPainter::paint(
|
||||
}
|
||||
return HistoryItem::DrawInDialog::Normal;
|
||||
}();
|
||||
auto paintItemCallback = [&](int nameleft, int namewidth) {
|
||||
const auto paintItemCallback = [&](int nameleft, int namewidth) {
|
||||
auto lastWidth = namewidth;
|
||||
auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip;
|
||||
item->drawInDialog(
|
||||
@@ -529,11 +530,14 @@ void RowPainter::paint(
|
||||
row->_cacheFor,
|
||||
row->_cache);
|
||||
};
|
||||
auto paintCounterCallback = [] {};
|
||||
const auto paintCounterCallback = [] {};
|
||||
const auto showSavedMessages = history->peer->isSelf()
|
||||
&& !row->searchInPeer();
|
||||
const auto flags = (active ? Flag::Active : Flag(0))
|
||||
| (selected ? Flag::Selected : Flag(0))
|
||||
| (onlyBackground ? Flag::OnlyBackground : Flag(0))
|
||||
| Flag::SearchResult;
|
||||
| Flag::SearchResult
|
||||
| (showSavedMessages ? Flag::SavedMessages : Flag(0));
|
||||
paintRow(
|
||||
p,
|
||||
row,
|
||||
|
||||
@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -94,19 +95,22 @@ void ChatSearchFromController::rebuildRows() {
|
||||
auto wasEmpty = !delegate()->peerListFullRowsCount();
|
||||
|
||||
auto now = unixtime();
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
const auto byOnline = [&](not_null<UserData*> user) {
|
||||
return Data::SortByOnlineValue(user, now);
|
||||
};
|
||||
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
|
||||
if (_chat->noParticipantInfo()) {
|
||||
Auth().api().requestFullPeer(_chat);
|
||||
} else if (!_chat->participants.empty()) {
|
||||
for (const auto [user, version] : _chat->participants) {
|
||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||
ordered.insertMulti(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
for_const (auto user, _chat->lastAuthors) {
|
||||
if (user->isInaccessible()) continue;
|
||||
appendRow(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(App::onlineForSort(user, now), user);
|
||||
ordered.remove(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
if (!ordered.isEmpty()) {
|
||||
|
||||
@@ -38,6 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_slide_animation.h"
|
||||
#include "profile/profile_channel_controllers.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -228,7 +229,7 @@ void DialogsWidget::startWidthAnimation() {
|
||||
st::columnMinimalWidthLeft,
|
||||
scrollGeometry.height());
|
||||
_scroll->setGeometry(grabGeometry);
|
||||
myEnsureResized(_scroll);
|
||||
Ui::SendPendingMoveResizeEvents(_scroll);
|
||||
auto image = QImage(
|
||||
grabGeometry.size() * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
@@ -745,18 +746,22 @@ bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) {
|
||||
}
|
||||
|
||||
void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) {
|
||||
using namespace Storage;
|
||||
|
||||
if (App::main()->selectingPeer()) return;
|
||||
|
||||
const auto data = e->mimeData();
|
||||
_dragInScroll = false;
|
||||
_dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-selected"));
|
||||
if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed-link"));
|
||||
if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed"));
|
||||
if (_dragForward && Adaptive::OneColumn()) _dragForward = false;
|
||||
_dragForward = Adaptive::OneColumn()
|
||||
? false
|
||||
: (data->hasFormat(qsl("application/x-td-forward-selected"))
|
||||
|| data->hasFormat(qsl("application/x-td-forward-pressed-link"))
|
||||
|| data->hasFormat(qsl("application/x-td-forward-pressed")));
|
||||
if (_dragForward) {
|
||||
e->setDropAction(Qt::CopyAction);
|
||||
e->accept();
|
||||
updateDragInScroll(_scroll->geometry().contains(e->pos()));
|
||||
} else if (App::main() && App::main()->getDragState(e->mimeData()) != DragState::None) {
|
||||
} else if (ComputeMimeDataState(data) != MimeDataState::None) {
|
||||
e->setDropAction(Qt::CopyAction);
|
||||
e->accept();
|
||||
}
|
||||
@@ -811,6 +816,7 @@ void DialogsWidget::dropEvent(QDropEvent *e) {
|
||||
if (auto peer = _inner->updateFromParentDrag(mapToGlobal(e->pos()))) {
|
||||
e->acceptProposedAction();
|
||||
App::main()->onFilesOrForwardDrop(peer->id, e->mimeData());
|
||||
controller()->window()->activateWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "core/click_handler_types.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "observer_peer.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
@@ -69,19 +70,22 @@ bool insertBotCommand(const QString &cmd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void activateBotCommand(const HistoryItem *msg, int row, int col) {
|
||||
const HistoryMessageReplyMarkup::Button *button = nullptr;
|
||||
void activateBotCommand(
|
||||
not_null<const HistoryItem*> msg,
|
||||
int row,
|
||||
int column) {
|
||||
const HistoryMessageMarkupButton *button = nullptr;
|
||||
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (row < markup->rows.size()) {
|
||||
auto &buttonRow = markup->rows[row];
|
||||
if (col < buttonRow.size()) {
|
||||
button = &buttonRow.at(col);
|
||||
if (column < buttonRow.size()) {
|
||||
button = &buttonRow[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!button) return;
|
||||
|
||||
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
switch (button->type) {
|
||||
case ButtonType::Default: {
|
||||
// Copy string before passing it to the sending method
|
||||
@@ -93,7 +97,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) {
|
||||
case ButtonType::Callback:
|
||||
case ButtonType::Game: {
|
||||
if (auto m = main()) {
|
||||
m->app_sendBotCallback(button, msg, row, col);
|
||||
m->app_sendBotCallback(button, msg, row, column);
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
@@ -130,11 +130,21 @@ inline auto LambdaDelayedOnce(
|
||||
};
|
||||
}
|
||||
|
||||
void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0);
|
||||
void sendBotCommand(
|
||||
PeerData *peer,
|
||||
UserData *bot,
|
||||
const QString &cmd,
|
||||
MsgId replyTo = 0);
|
||||
bool insertBotCommand(const QString &cmd);
|
||||
void activateBotCommand(const HistoryItem *msg, int row, int col);
|
||||
void activateBotCommand(
|
||||
not_null<const HistoryItem*> msg,
|
||||
int row,
|
||||
int column);
|
||||
void searchByHashtag(const QString &tag, PeerData *inPeer);
|
||||
void openPeerByName(const QString &username, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString());
|
||||
void openPeerByName(
|
||||
const QString &username,
|
||||
MsgId msgId = ShowAtUnreadMsgId,
|
||||
const QString &startToken = QString());
|
||||
void joinGroupByHash(const QString &hash);
|
||||
void removeDialog(History *history);
|
||||
void showSettings();
|
||||
|
||||
@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_service.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "data/data_drafts.h"
|
||||
@@ -38,6 +39,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -60,7 +63,7 @@ auto GlobalPinnedIndex = 0;
|
||||
|
||||
HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
|
||||
auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
|
||||
TextUtilities::ParseEntities(text, _historyTextNoMonoOptions.flags);
|
||||
TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
|
||||
text.entities.push_front(EntityInText(EntityInTextItalic, 0, text.text.size()));
|
||||
flags &= ~MTPDmessage::Flag::f_post_author;
|
||||
return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, QString(), text);
|
||||
@@ -68,11 +71,6 @@ HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage
|
||||
|
||||
} // namespace
|
||||
|
||||
void HistoryInit() {
|
||||
HistoryInitMessages();
|
||||
HistoryInitMedia();
|
||||
}
|
||||
|
||||
History::History(const PeerId &peerId)
|
||||
: peer(App::peer(peerId))
|
||||
, lastItemTextCache(st::dialogsTextWidthMin)
|
||||
@@ -362,7 +360,10 @@ bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) {
|
||||
}
|
||||
if (_sendActionString != newTypingString) {
|
||||
_sendActionString = newTypingString;
|
||||
_sendActionText.setText(st::dialogsTextStyle, _sendActionString, _textNameOptions);
|
||||
_sendActionText.setText(
|
||||
st::dialogsTextStyle,
|
||||
_sendActionString,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
auto result = (!_typing.isEmpty() || !_sendActions.isEmpty());
|
||||
@@ -542,27 +543,6 @@ const QDateTime &ChannelHistory::maxReadMessageDate() {
|
||||
return _maxReadMessageDate;
|
||||
}
|
||||
|
||||
HistoryItem *ChannelHistory::addNewChannelMessage(const MTPMessage &msg, NewMessageType type) {
|
||||
if (type == NewMessageExisting) return addToHistory(msg);
|
||||
|
||||
return addNewToBlocks(msg, type);
|
||||
}
|
||||
|
||||
HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageType type) {
|
||||
if (!loadedAtBottom()) {
|
||||
HistoryItem *item = addToHistory(msg);
|
||||
if (item) {
|
||||
setLastMessage(item);
|
||||
if (type == NewMessageUnread) {
|
||||
newItemAdded(item);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
return addNewToLastBlock(msg, type);
|
||||
}
|
||||
|
||||
void ChannelHistory::cleared(bool leaveItems) {
|
||||
_joinedMessage = nullptr;
|
||||
}
|
||||
@@ -678,7 +658,7 @@ void checkForSwitchInlineButton(HistoryItem *item) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
for_const (auto &row, markup->rows) {
|
||||
for_const (auto &button, row) {
|
||||
if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) {
|
||||
if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) {
|
||||
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
|
||||
return;
|
||||
}
|
||||
@@ -795,12 +775,7 @@ void Histories::checkSelfDestructItems() {
|
||||
}
|
||||
|
||||
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
|
||||
auto msgId = MsgId(0);
|
||||
switch (msg.type()) {
|
||||
case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break;
|
||||
case mtpc_message: msgId = msg.c_message().vid.v; break;
|
||||
case mtpc_messageService: msgId = msg.c_messageService().vid.v; break;
|
||||
}
|
||||
const auto msgId = idFromMessage(msg);
|
||||
if (!msgId) return nullptr;
|
||||
|
||||
auto result = App::histItemById(channelId(), msgId);
|
||||
@@ -809,7 +784,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
||||
result->detach();
|
||||
}
|
||||
if (msg.type() == mtpc_message) {
|
||||
result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0);
|
||||
const auto media = msg.c_message().has_media()
|
||||
? &msg.c_message().vmedia
|
||||
: nullptr;
|
||||
result->updateMedia(media);
|
||||
if (applyServiceAction) {
|
||||
App::checkSavedGif(result);
|
||||
}
|
||||
@@ -1093,72 +1071,85 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
|
||||
not_null<HistoryItem*> History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
|
||||
return HistoryMessage::create(this, id, flags, date, from, postAuthor, msg);
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
|
||||
not_null<HistoryItem*> History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
|
||||
auto message = HistoryService::PreparedText { text };
|
||||
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
|
||||
if (isChannel()) return asChannelHistory()->addNewChannelMessage(msg, type);
|
||||
|
||||
if (type == NewMessageExisting) return addToHistory(msg);
|
||||
if (type == NewMessageExisting) {
|
||||
return addToHistory(msg);
|
||||
}
|
||||
if (!loadedAtBottom() || peer->migrateTo()) {
|
||||
HistoryItem *item = addToHistory(msg);
|
||||
if (item) {
|
||||
if (const auto item = addToHistory(msg)) {
|
||||
setLastMessage(item);
|
||||
if (type == NewMessageUnread) {
|
||||
newItemAdded(item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
return item;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return addNewToLastBlock(msg, type);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) {
|
||||
auto applyServiceAction = (type == NewMessageUnread);
|
||||
auto detachExistingItem = (type != NewMessageLast);
|
||||
auto item = createItem(msg, applyServiceAction, detachExistingItem);
|
||||
Expects(type != NewMessageExisting);
|
||||
|
||||
const auto applyServiceAction = (type == NewMessageUnread);
|
||||
const auto detachExistingItem = (type != NewMessageLast);
|
||||
const auto item = createItem(msg, applyServiceAction, detachExistingItem);
|
||||
if (!item || !item->detached()) {
|
||||
return item;
|
||||
}
|
||||
return addNewItem(item, (type == NewMessageUnread));
|
||||
const auto result = addNewItem(item, (type == NewMessageUnread));
|
||||
if (type == NewMessageLast) {
|
||||
// When we add just one last item, like we do while loading dialogs,
|
||||
// we want to remove a single added grouped media, otherwise it will
|
||||
// jump once we open the message history (first we show only that
|
||||
// media, then we load the rest of the group and show the group).
|
||||
//
|
||||
// That way when we open the message history we show nothing until a
|
||||
// whole history part is loaded, it certainly will contain the group.
|
||||
removeOrphanMediaGroupPart();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryItem *History::addToHistory(const MTPMessage &msg) {
|
||||
return createItem(msg, false, false);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
|
||||
not_null<HistoryItem*> History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
|
||||
return addNewItem(createItemForwarded(id, flags, date, from, postAuthor, item), true);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, postAuthor, doc, caption, markup), true);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, postAuthor, photo, caption, markup), true);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, postAuthor, game, markup), true);
|
||||
}
|
||||
|
||||
@@ -1172,15 +1163,17 @@ void History::setUnreadMentionsCount(int count) {
|
||||
|
||||
bool History::addToUnreadMentions(
|
||||
MsgId msgId,
|
||||
AddToUnreadMentionsMethod method) {
|
||||
auto allLoaded = _unreadMentionsCount ? (_unreadMentions.size() >= *_unreadMentionsCount) : false;
|
||||
UnreadMentionType type) {
|
||||
auto allLoaded = _unreadMentionsCount
|
||||
? (_unreadMentions.size() >= *_unreadMentionsCount)
|
||||
: false;
|
||||
if (allLoaded) {
|
||||
if (method == AddToUnreadMentionsMethod::New) {
|
||||
if (type == UnreadMentionType::New) {
|
||||
++*_unreadMentionsCount;
|
||||
_unreadMentions.insert(msgId);
|
||||
return true;
|
||||
}
|
||||
} else if (!_unreadMentions.empty() && method != AddToUnreadMentionsMethod::New) {
|
||||
} else if (!_unreadMentions.empty() && type != UnreadMentionType::New) {
|
||||
_unreadMentions.insert(msgId);
|
||||
return true;
|
||||
}
|
||||
@@ -1250,32 +1243,24 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
|
||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
|
||||
not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool newMsg) {
|
||||
Expects(!isBuildingFrontBlock());
|
||||
|
||||
addItemToBlock(adding);
|
||||
|
||||
setLastMessage(adding);
|
||||
if (newMsg) {
|
||||
newItemAdded(adding);
|
||||
const auto [groupFrom, groupTill] = recountGroupingFromTill(adding);
|
||||
if (groupFrom != groupTill || groupFrom->groupId()) {
|
||||
recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
|
||||
adding->addToUnreadMentions(AddToUnreadMentionsMethod::New);
|
||||
if (IsServerMsgId(adding->id)) {
|
||||
if (auto sharedMediaTypes = adding->sharedMediaTypes()) {
|
||||
if (newMsg) {
|
||||
Auth().storage().add(Storage::SharedMediaAddNew(
|
||||
peer->id,
|
||||
sharedMediaTypes,
|
||||
adding->id));
|
||||
} else {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
Auth().storage().add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
sharedMediaTypes,
|
||||
adding->id,
|
||||
{ from, till }));
|
||||
}
|
||||
if (!newMsg && IsServerMsgId(adding->id)) {
|
||||
if (const auto sharedMediaTypes = adding->sharedMediaTypes()) {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
Auth().storage().add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
sharedMediaTypes,
|
||||
adding->id,
|
||||
{ from, till }));
|
||||
}
|
||||
}
|
||||
if (adding->from()->id) {
|
||||
@@ -1358,6 +1343,11 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
|
||||
}
|
||||
}
|
||||
|
||||
setLastMessage(adding);
|
||||
if (newMsg) {
|
||||
newItemAdded(adding);
|
||||
}
|
||||
|
||||
return adding;
|
||||
}
|
||||
|
||||
@@ -1378,8 +1368,9 @@ void History::clearSendAction(not_null<UserData*> from) {
|
||||
}
|
||||
}
|
||||
|
||||
void History::newItemAdded(HistoryItem *item) {
|
||||
void History::newItemAdded(not_null<HistoryItem*> item) {
|
||||
App::checkImageCacheSize();
|
||||
item->indexAsNewItem();
|
||||
if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
|
||||
if (from == item->author()) {
|
||||
clearSendAction(from);
|
||||
@@ -1388,7 +1379,9 @@ void History::newItemAdded(HistoryItem *item) {
|
||||
from->madeAction(itemServerTime.v);
|
||||
}
|
||||
if (item->out()) {
|
||||
if (unreadBar) unreadBar->destroyUnreadBar();
|
||||
if (unreadBar) {
|
||||
unreadBar->destroyUnreadBar();
|
||||
}
|
||||
if (!item->unread()) {
|
||||
outboxRead(item);
|
||||
}
|
||||
@@ -1433,8 +1426,7 @@ HistoryBlock *History::prepareBlockForAddingItem() {
|
||||
return result;
|
||||
};
|
||||
|
||||
void History::addItemToBlock(HistoryItem *item) {
|
||||
Expects(item != nullptr);
|
||||
void History::addItemToBlock(not_null<HistoryItem*> item) {
|
||||
Expects(item->detached());
|
||||
|
||||
auto block = prepareBlockForAddingItem();
|
||||
@@ -1499,7 +1491,7 @@ void History::addToSharedMedia(std::vector<MsgId> (&medias)[kSharedMediaTypeCoun
|
||||
}
|
||||
value.push_back(indices.join(","));
|
||||
}
|
||||
SignalHandlers::setCrashAnnotation("full", value.join(";"));
|
||||
CrashReports::SetAnnotation("full", value.join(";"));
|
||||
Assert(!"History desync caught!");
|
||||
//// Logging
|
||||
|
||||
@@ -1527,6 +1519,9 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto firstAdded = (HistoryItem*)nullptr;
|
||||
auto lastAdded = (HistoryItem*)nullptr;
|
||||
|
||||
auto logged = QStringList();
|
||||
logged.push_back(QString::number(minMsgId()));
|
||||
logged.push_back(QString::number(maxMsgId()));
|
||||
@@ -1538,9 +1533,12 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
|
||||
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
||||
--i;
|
||||
auto adding = createItem(*i, false, true);
|
||||
const auto adding = createItem(*i, false, true);
|
||||
if (!adding) continue;
|
||||
|
||||
if (!firstAdded) firstAdded = adding;
|
||||
lastAdded = adding;
|
||||
|
||||
if (minAdded < 0 || minAdded > adding->id) {
|
||||
minAdded = adding->id;
|
||||
}
|
||||
@@ -1573,7 +1571,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
for (auto i = block->items.size(); i > 0; --i) {
|
||||
auto item = block->items[i - 1];
|
||||
item->addToUnreadMentions(AddToUnreadMentionsMethod::Front);
|
||||
item->addToUnreadMentions(UnreadMentionType::Existing);
|
||||
if (item->from()->id) {
|
||||
if (lastAuthors) { // chats
|
||||
if (auto user = item->from()->asUser()) {
|
||||
@@ -1631,11 +1629,16 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
|
||||
logged.push_back(QString::number(minAdded));
|
||||
logged.push_back(QString::number(maxAdded));
|
||||
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
|
||||
CrashReports::SetAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
|
||||
|
||||
addBlockToSharedMedia(block);
|
||||
|
||||
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", "");
|
||||
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
|
||||
|
||||
if (lastAdded) {
|
||||
const auto [from, till] = recountGroupingFromTill(lastAdded);
|
||||
recountGrouping(firstAdded, till);
|
||||
}
|
||||
|
||||
if (isChannel()) {
|
||||
asChannelHistory()->checkJoinedMessage();
|
||||
@@ -1654,6 +1657,9 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
}
|
||||
|
||||
auto firstAdded = (HistoryItem*)nullptr;
|
||||
auto lastAdded = (HistoryItem*)nullptr;
|
||||
|
||||
Assert(!isBuildingFrontBlock());
|
||||
if (!slice.isEmpty()) {
|
||||
auto logged = QStringList();
|
||||
@@ -1664,12 +1670,14 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
auto maxAdded = -1;
|
||||
|
||||
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
|
||||
auto atLeastOneAdded = false;
|
||||
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
||||
--i;
|
||||
auto adding = createItem(*i, false, true);
|
||||
const auto adding = createItem(*i, false, true);
|
||||
if (!adding) continue;
|
||||
|
||||
if (!firstAdded) firstAdded = adding;
|
||||
lastAdded = adding;
|
||||
|
||||
if (minAdded < 0 || minAdded > adding->id) {
|
||||
minAdded = adding->id;
|
||||
}
|
||||
@@ -1678,7 +1686,6 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
|
||||
addItemToBlock(adding);
|
||||
atLeastOneAdded = true;
|
||||
if (auto types = adding->sharedMediaTypes()) {
|
||||
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
|
||||
auto type = static_cast<Storage::SharedMediaType>(i);
|
||||
@@ -1693,21 +1700,26 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
logged.push_back(QString::number(minAdded));
|
||||
logged.push_back(QString::number(maxAdded));
|
||||
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
|
||||
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
|
||||
|
||||
if (!atLeastOneAdded) {
|
||||
if (!firstAdded) {
|
||||
newLoaded = true;
|
||||
setLastMessage(lastAvailableMessage());
|
||||
}
|
||||
addToSharedMedia(medias, wasLoadedAtBottom != loadedAtBottom());
|
||||
|
||||
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", "");
|
||||
CrashReports::ClearAnnotation("new_minmaxwas_minmaxadd");
|
||||
}
|
||||
|
||||
if (!wasLoadedAtBottom) {
|
||||
checkAddAllToUnreadMentions();
|
||||
}
|
||||
|
||||
if (firstAdded) {
|
||||
const auto [from, till] = recountGroupingFromTill(firstAdded);
|
||||
recountGrouping(from, lastAdded);
|
||||
}
|
||||
|
||||
if (isChannel()) asChannelHistory()->checkJoinedMessage();
|
||||
checkLastMsg();
|
||||
}
|
||||
@@ -1728,9 +1740,9 @@ void History::checkAddAllToUnreadMentions() {
|
||||
return;
|
||||
}
|
||||
|
||||
for_const (auto block, blocks) {
|
||||
for_const (auto item, block->items) {
|
||||
item->addToUnreadMentions(AddToUnreadMentionsMethod::Back);
|
||||
for (const auto block : blocks) {
|
||||
for (const auto item : block->items) {
|
||||
item->addToUnreadMentions(UnreadMentionType::Existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2006,7 +2018,7 @@ void History::destroyUnreadBar() {
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) {
|
||||
not_null<HistoryItem*> History::addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex) {
|
||||
Expects(blockIndex >= 0);
|
||||
Expects(blockIndex < blocks.size());
|
||||
Expects(itemIndex >= 0);
|
||||
@@ -2028,9 +2040,135 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
|
||||
newItem->nextItemChanged();
|
||||
}
|
||||
|
||||
const auto [groupFrom, groupTill] = recountGroupingFromTill(newItem);
|
||||
if (groupFrom != groupTill || groupFrom->groupId()) {
|
||||
recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
|
||||
Expects(!item->detached());
|
||||
|
||||
const auto nextBlockIndex = item->block()->indexInHistory() + 1;
|
||||
const auto nextItemIndex = item->indexInBlock() + 1;
|
||||
if (nextItemIndex < int(item->block()->items.size())) {
|
||||
return item->block()->items[nextItemIndex];
|
||||
} else if (nextBlockIndex < int(blocks.size())) {
|
||||
return blocks[nextBlockIndex]->items.front();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
|
||||
Expects(!item->detached());
|
||||
|
||||
const auto blockIndex = item->block()->indexInHistory();
|
||||
const auto itemIndex = item->indexInBlock();
|
||||
if (itemIndex > 0) {
|
||||
return item->block()->items[itemIndex - 1];
|
||||
} else if (blockIndex > 0) {
|
||||
return blocks[blockIndex - 1]->items.back();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::findGroupFirst(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto group = item->Get<HistoryMessageGroup>();
|
||||
Assert(group != nullptr);
|
||||
Assert(group->leader != nullptr);
|
||||
|
||||
const auto leaderGroup = (group->leader == item)
|
||||
? group
|
||||
: group->leader->Get<HistoryMessageGroup>();
|
||||
Assert(leaderGroup != nullptr);
|
||||
|
||||
return leaderGroup->others.empty()
|
||||
? group->leader
|
||||
: leaderGroup->others.front().get();
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::findGroupLast(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto group = item->Get<HistoryMessageGroup>();
|
||||
Assert(group != nullptr);
|
||||
|
||||
return group->leader;
|
||||
}
|
||||
|
||||
void History::recountGroupingAround(not_null<HistoryItem*> item) {
|
||||
Expects(item->history() == this);
|
||||
|
||||
if (!item->detached() && item->groupId()) {
|
||||
const auto [groupFrom, groupTill] = recountGroupingFromTill(item);
|
||||
recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
}
|
||||
|
||||
auto History::recountGroupingFromTill(not_null<HistoryItem*> item)
|
||||
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>> {
|
||||
const auto recountFromItem = [&] {
|
||||
if (const auto prev = findPreviousItem(item)) {
|
||||
if (prev->groupId()) {
|
||||
return findGroupFirst(prev);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}();
|
||||
if (recountFromItem == item && !item->groupId()) {
|
||||
return { item, item };
|
||||
}
|
||||
const auto recountTillItem = [&] {
|
||||
if (const auto next = findNextItem(item)) {
|
||||
if (next->groupId()) {
|
||||
return findGroupLast(next);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}();
|
||||
return { recountFromItem, recountTillItem };
|
||||
}
|
||||
|
||||
void History::recountGrouping(
|
||||
not_null<HistoryItem*> from,
|
||||
not_null<HistoryItem*> till) {
|
||||
Expects(!from->detached());
|
||||
Expects(!till->detached());
|
||||
|
||||
from->validateGroupId();
|
||||
auto others = std::vector<not_null<HistoryItem*>>();
|
||||
auto currentGroupId = from->groupId();
|
||||
auto prev = from;
|
||||
while (prev != till) {
|
||||
auto item = findNextItem(prev);
|
||||
item->validateGroupId();
|
||||
const auto groupId = item->groupId();
|
||||
if (currentGroupId) {
|
||||
if (groupId == currentGroupId) {
|
||||
others.push_back(prev);
|
||||
} else {
|
||||
for (const auto other : others) {
|
||||
other->makeGroupMember(prev);
|
||||
}
|
||||
prev->makeGroupLeader(base::take(others));
|
||||
currentGroupId = groupId;
|
||||
}
|
||||
} else if (groupId) {
|
||||
currentGroupId = groupId;
|
||||
}
|
||||
prev = item;
|
||||
}
|
||||
|
||||
if (currentGroupId) {
|
||||
for (const auto other : others) {
|
||||
other->makeGroupMember(prev);
|
||||
}
|
||||
till->makeGroupLeader(base::take(others));
|
||||
}
|
||||
}
|
||||
|
||||
void History::startBuildingFrontBlock(int expectedItemsCount) {
|
||||
Assert(!isBuildingFrontBlock());
|
||||
Assert(expectedItemsCount > 0);
|
||||
@@ -2251,11 +2389,11 @@ int History::resizeGetHeight(int newWidth) {
|
||||
}
|
||||
|
||||
ChannelHistory *History::asChannelHistory() {
|
||||
return isChannel() ? static_cast<ChannelHistory*>(this) : 0;
|
||||
return isChannel() ? static_cast<ChannelHistory*>(this) : nullptr;
|
||||
}
|
||||
|
||||
const ChannelHistory *History::asChannelHistory() const {
|
||||
return isChannel() ? static_cast<const ChannelHistory*>(this) : 0;
|
||||
return isChannel() ? static_cast<const ChannelHistory*>(this) : nullptr;
|
||||
}
|
||||
|
||||
not_null<History*> History::migrateToOrMe() const {
|
||||
@@ -2277,6 +2415,25 @@ bool History::isDisplayedEmpty() const {
|
||||
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty());
|
||||
}
|
||||
|
||||
bool History::hasOrphanMediaGroupPart() const {
|
||||
if (loadedAtTop() || !loadedAtBottom()) {
|
||||
return false;
|
||||
} else if (blocks.size() != 1) {
|
||||
return false;
|
||||
} else if (blocks.front()->items.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return blocks.front()->items.front()->groupId() != MessageGroupId();
|
||||
}
|
||||
|
||||
bool History::removeOrphanMediaGroupPart() {
|
||||
if (hasOrphanMediaGroupPart()) {
|
||||
clear(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void History::clear(bool leaveItems) {
|
||||
if (unreadBar) {
|
||||
unreadBar = nullptr;
|
||||
@@ -2470,7 +2627,7 @@ void History::setPinnedIndex(int pinnedIndex) {
|
||||
void History::changeMsgId(MsgId oldId, MsgId newId) {
|
||||
}
|
||||
|
||||
void History::removeBlock(HistoryBlock *block) {
|
||||
void History::removeBlock(not_null<HistoryBlock*> block) {
|
||||
Expects(block->items.empty());
|
||||
|
||||
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
|
||||
@@ -2521,9 +2678,21 @@ void HistoryBlock::clear(bool leaveItems) {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryBlock::removeItem(HistoryItem *item) {
|
||||
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
|
||||
Expects(item->block() == this);
|
||||
|
||||
auto [groupFrom, groupTill] = _history->recountGroupingFromTill(item);
|
||||
const auto groupHistory = _history;
|
||||
const auto needGroupRecount = (groupFrom != groupTill);
|
||||
if (needGroupRecount) {
|
||||
if (groupFrom == item) {
|
||||
groupFrom = groupHistory->findNextItem(groupFrom);
|
||||
}
|
||||
if (groupTill == item) {
|
||||
groupTill = groupHistory->findPreviousItem(groupTill);
|
||||
}
|
||||
}
|
||||
|
||||
auto blockIndex = indexInHistory();
|
||||
auto itemIndex = item->indexInBlock();
|
||||
if (_history->showFrom == item) {
|
||||
@@ -2557,4 +2726,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
|
||||
if (items.empty()) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
if (needGroupRecount) {
|
||||
groupHistory->recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/flat_set.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
void HistoryInit();
|
||||
|
||||
class HistoryItem;
|
||||
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
|
||||
|
||||
@@ -136,6 +134,7 @@ enum HistoryMediaType {
|
||||
MediaTypeVoiceFile,
|
||||
MediaTypeGame,
|
||||
MediaTypeInvoice,
|
||||
MediaTypeGrouped,
|
||||
|
||||
MediaTypeCount
|
||||
};
|
||||
@@ -172,10 +171,9 @@ struct Draft;
|
||||
class HistoryMedia;
|
||||
class HistoryMessage;
|
||||
|
||||
enum class AddToUnreadMentionsMethod {
|
||||
enum class UnreadMentionType {
|
||||
New, // when new message is added to history
|
||||
Front, // when old messages slice was received
|
||||
Back, // when new messages slice was received and it is the last one, we index all media
|
||||
Existing, // when some messages slice was received
|
||||
};
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -209,6 +207,8 @@ public:
|
||||
return blocks.empty();
|
||||
}
|
||||
bool isDisplayedEmpty() const;
|
||||
bool hasOrphanMediaGroupPart() const;
|
||||
bool removeOrphanMediaGroupPart();
|
||||
|
||||
void clear(bool leaveItems = false);
|
||||
void clearUpTill(MsgId availableMinId);
|
||||
@@ -217,13 +217,13 @@ public:
|
||||
|
||||
virtual ~History();
|
||||
|
||||
HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
|
||||
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
|
||||
HistoryItem *addToHistory(const MTPMessage &msg);
|
||||
HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
|
||||
HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
|
||||
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
|
||||
not_null<HistoryItem*> addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
|
||||
// Used only internally and for channel admin log.
|
||||
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
|
||||
@@ -231,7 +231,7 @@ public:
|
||||
void addOlderSlice(const QVector<MTPMessage> &slice);
|
||||
void addNewerSlice(const QVector<MTPMessage> &slice);
|
||||
|
||||
void newItemAdded(HistoryItem *item);
|
||||
void newItemAdded(not_null<HistoryItem*> item);
|
||||
|
||||
int countUnread(MsgId upTo);
|
||||
void updateShowFrom();
|
||||
@@ -366,7 +366,7 @@ public:
|
||||
return (getUnreadMentionsCount() > 0);
|
||||
}
|
||||
void setUnreadMentionsCount(int count);
|
||||
bool addToUnreadMentions(MsgId msgId, AddToUnreadMentionsMethod method);
|
||||
bool addToUnreadMentions(MsgId msgId, UnreadMentionType type);
|
||||
void eraseFromUnreadMentions(MsgId msgId);
|
||||
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
|
||||
|
||||
@@ -419,6 +419,7 @@ public:
|
||||
}
|
||||
HistoryItemsList validateForwardDraft();
|
||||
void setForwardDraft(MessageIdsList &&items);
|
||||
void recountGroupingAround(not_null<HistoryItem*> item);
|
||||
|
||||
// some fields below are a property of a currently displayed instance of this
|
||||
// conversation history not a property of the conversation history itself
|
||||
@@ -475,17 +476,17 @@ protected:
|
||||
// this method just removes a block from the blocks list
|
||||
// when the last item from this block was detached and
|
||||
// calls the required previousItemChanged()
|
||||
void removeBlock(HistoryBlock *block);
|
||||
void removeBlock(not_null<HistoryBlock*> block);
|
||||
|
||||
void clearBlocks(bool leaveItems);
|
||||
|
||||
HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
|
||||
HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
|
||||
not_null<HistoryItem*> createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
|
||||
HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
|
||||
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
|
||||
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
|
||||
not_null<HistoryItem*> addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex);
|
||||
|
||||
// All this methods add a new item to the first or last block
|
||||
// depending on if we are in isBuildingFronBlock() state.
|
||||
@@ -493,7 +494,7 @@ protected:
|
||||
|
||||
// Adds the item to the back or front block, depending on
|
||||
// isBuildingFrontBlock(), creating the block if necessary.
|
||||
void addItemToBlock(HistoryItem *item);
|
||||
void addItemToBlock(not_null<HistoryItem*> item);
|
||||
|
||||
// Usually all new items are added to the last block.
|
||||
// Only when we scroll up and add a new slice to the
|
||||
@@ -517,6 +518,18 @@ private:
|
||||
|
||||
void clearSendAction(not_null<UserData*> from);
|
||||
|
||||
HistoryItem *findPreviousItem(not_null<HistoryItem*> item) const;
|
||||
HistoryItem *findNextItem(not_null<HistoryItem*> item) const;
|
||||
not_null<HistoryItem*> findGroupFirst(
|
||||
not_null<HistoryItem*> item) const;
|
||||
not_null<HistoryItem*> findGroupLast(
|
||||
not_null<HistoryItem*> item) const;
|
||||
auto recountGroupingFromTill(not_null<HistoryItem*> item)
|
||||
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>>;
|
||||
void recountGrouping(
|
||||
not_null<HistoryItem*> from,
|
||||
not_null<HistoryItem*> till);
|
||||
|
||||
enum class Flag {
|
||||
f_has_pending_resized_items = (1 << 0),
|
||||
f_pending_resize = (1 << 1),
|
||||
@@ -593,11 +606,8 @@ public:
|
||||
|
||||
private:
|
||||
friend class History;
|
||||
HistoryItem* addNewChannelMessage(const MTPMessage &msg, NewMessageType type);
|
||||
HistoryItem *addNewToBlocks(const MTPMessage &msg, NewMessageType type);
|
||||
|
||||
void checkMaxReadMessageDate();
|
||||
|
||||
void cleared(bool leaveItems);
|
||||
|
||||
QDateTime _maxReadMessageDate;
|
||||
@@ -624,7 +634,7 @@ public:
|
||||
~HistoryBlock() {
|
||||
clear();
|
||||
}
|
||||
void removeItem(HistoryItem *item);
|
||||
void removeItem(not_null<HistoryItem*> item);
|
||||
|
||||
int resizeGetHeight(int newWidth, bool resizeAllItems);
|
||||
int y() const {
|
||||
|
||||
@@ -123,6 +123,8 @@ historyFileThumbCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }}
|
||||
historyFileThumbCancelSelected: icon {{ "history_file_cancel", historyFileThumbIconFgSelected }};
|
||||
historyFileThumbPlay: icon {{ "history_file_play", historyFileThumbIconFg }};
|
||||
historyFileThumbPlaySelected: icon {{ "history_file_play", historyFileThumbIconFgSelected }};
|
||||
historyFileThumbWaiting: icon {{ "mediaview_save_check", historyFileThumbIconFg }};
|
||||
historyFileThumbWaitingSelected: icon {{ "mediaview_save_check", historyFileThumbIconFgSelected }};
|
||||
|
||||
historySendStateSpace: 24px;
|
||||
historySendStatePosition: point(-17px, -19px);
|
||||
@@ -461,3 +463,9 @@ historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}};
|
||||
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
|
||||
|
||||
historySavedFont: font(semibold 14px);
|
||||
|
||||
historyGroupWidthMax: maxMediaSize;
|
||||
historyGroupWidthMin: minPhotoSize;
|
||||
historyGroupSkip: 4px;
|
||||
historyGroupRadialSize: 44px;
|
||||
historyGroupRadialLine: 3px;
|
||||
|
||||
@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
||||
namespace AdminLog {
|
||||
namespace {
|
||||
@@ -79,8 +80,8 @@ UserCheckbox::UserCheckbox(QWidget *parent, not_null<UserData*> user, bool check
|
||||
setChecked(!this->checked());
|
||||
});
|
||||
auto now = unixtime();
|
||||
_statusText = App::onlineText(_user, now);
|
||||
_statusOnline = App::onlineColorUse(_user, now);
|
||||
_statusText = Data::OnlineText(_user, now);
|
||||
_statusOnline = Data::OnlineTextActive(_user, now);
|
||||
auto checkSize = _check->getSize();
|
||||
_checkRect = { QPoint(_st.margin.left(), (st::contactsPhotoSize - checkSize.height()) / 2), checkSize };
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_admin_log_section.h"
|
||||
#include "history/history_admin_log_filter.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
@@ -219,12 +220,12 @@ InnerWidget::InnerWidget(
|
||||
, _emptyText(st::historyAdminLogEmptyWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.left()) {
|
||||
setMouseTracking(true);
|
||||
_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
|
||||
Auth().data().itemRepaintRequest()
|
||||
| rpl::start_with_next([this](auto item) {
|
||||
if (item->isLogEntry() && _history == item->history()) {
|
||||
repaintItem(item);
|
||||
}
|
||||
}, lifetime());
|
||||
Auth().data().itemRepaintRequest(
|
||||
) | rpl::start_with_next([this](auto item) {
|
||||
if (item->isLogEntry() && _history == item->history()) {
|
||||
repaintItem(item);
|
||||
}
|
||||
}, lifetime());
|
||||
subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); });
|
||||
subscribe(Auth().data().queryItemVisibility(), [this](const AuthSessionData::ItemVisibilityQuery &query) {
|
||||
if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) {
|
||||
@@ -424,17 +425,17 @@ void InnerWidget::updateEmptyText() {
|
||||
|
||||
QString InnerWidget::tooltipText() const {
|
||||
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
|
||||
if (auto item = App::hoveredItem()) {
|
||||
if (const auto item = App::hoveredItem()) {
|
||||
auto dateText = item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
return dateText;
|
||||
}
|
||||
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
|
||||
if (auto item = App::hoveredItem()) {
|
||||
if (auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
|
||||
if (const auto item = App::hoveredItem()) {
|
||||
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
|
||||
}
|
||||
}
|
||||
} else if (auto lnk = ClickHandler::getActive()) {
|
||||
} else if (const auto lnk = ClickHandler::getActive()) {
|
||||
return lnk->tooltip();
|
||||
}
|
||||
return QString();
|
||||
@@ -821,9 +822,9 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
|
||||
_contextMenuLink = ClickHandler::getActive();
|
||||
auto item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
|
||||
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
|
||||
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
|
||||
auto lnkPeer = dynamic_cast<PeerClickHandler*>(_contextMenuLink.data());
|
||||
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.get());
|
||||
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get());
|
||||
auto lnkPeer = dynamic_cast<PeerClickHandler*>(_contextMenuLink.get());
|
||||
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
|
||||
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
|
||||
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
|
||||
@@ -979,7 +980,7 @@ void InnerWidget::showStickerPackInfo() {
|
||||
}
|
||||
|
||||
void InnerWidget::cancelContextDownload() {
|
||||
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data())) {
|
||||
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get())) {
|
||||
lnkDocument->document()->cancel();
|
||||
} else if (auto item = App::contextItem()) {
|
||||
if (auto media = item->getMedia()) {
|
||||
@@ -992,7 +993,7 @@ void InnerWidget::cancelContextDownload() {
|
||||
|
||||
void InnerWidget::showContextInFolder() {
|
||||
QString filepath;
|
||||
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data())) {
|
||||
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get())) {
|
||||
filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked);
|
||||
} else if (auto item = App::contextItem()) {
|
||||
if (auto media = item->getMedia()) {
|
||||
@@ -1240,9 +1241,9 @@ void InnerWidget::mouseActionCancel() {
|
||||
void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
|
||||
mouseActionUpdate(screenPos);
|
||||
|
||||
ClickHandlerPtr activated = ClickHandler::unpressed();
|
||||
auto activated = ClickHandler::unpressed();
|
||||
if (_mouseAction == MouseAction::Dragging) {
|
||||
activated.clear();
|
||||
activated = nullptr;
|
||||
}
|
||||
if (App::pressedItem()) {
|
||||
repaintItem(App::pressedItem());
|
||||
|
||||
@@ -488,7 +488,7 @@ void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const
|
||||
fromLinkText,
|
||||
lt_sticker_set,
|
||||
textcmdLink(2, lang(lng_admin_log_changed_stickers_set)));
|
||||
auto setLink = MakeShared<LambdaClickHandler>([set] {
|
||||
auto setLink = std::make_shared<LambdaClickHandler>([set] {
|
||||
Ui::show(Box<StickerSetBox>(set));
|
||||
});
|
||||
auto message = HistoryService::PreparedText { text };
|
||||
|
||||
@@ -294,7 +294,7 @@ not_null<ChannelData*> Widget::channel() const {
|
||||
|
||||
QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) {
|
||||
if (params.withTopBarShadow) _fixedBarShadow->hide();
|
||||
auto result = myGrab(this);
|
||||
auto result = Ui::GrabWidget(this);
|
||||
if (params.withTopBarShadow) _fixedBarShadow->show();
|
||||
return result;
|
||||
}
|
||||
@@ -319,7 +319,7 @@ bool Widget::showInternal(
|
||||
|
||||
void Widget::setInternalState(const QRect &geometry, not_null<SectionMemento*> memento) {
|
||||
setGeometry(geometry);
|
||||
myEnsureResized(this);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
restoreState(memento);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,9 @@ void DragArea::hideStart() {
|
||||
return;
|
||||
}
|
||||
if (_cache.isNull()) {
|
||||
_cache = myGrab(this, innerRect().marginsAdded(st::boxRoundShadow.extend));
|
||||
_cache = Ui::GrabWidget(
|
||||
this,
|
||||
innerRect().marginsAdded(st::boxRoundShadow.extend));
|
||||
}
|
||||
_hiding = true;
|
||||
setIn(false);
|
||||
@@ -162,7 +164,9 @@ void DragArea::showStart() {
|
||||
}
|
||||
_hiding = false;
|
||||
if (_cache.isNull()) {
|
||||
_cache = myGrab(this, innerRect().marginsAdded(st::boxRoundShadow.extend));
|
||||
_cache = Ui::GrabWidget(
|
||||
this,
|
||||
innerRect().marginsAdded(st::boxRoundShadow.extend));
|
||||
}
|
||||
show();
|
||||
_a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::boxDuration);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user