Compare commits

...

73 Commits

Author SHA1 Message Date
John Preston
0f28069881 Beta version 2.9.13: Fix clean build. 2021-08-31 13:35:12 +03:00
John Preston
cbe38af427 Beta version 2.9.13: Mark 'isPrefix' as '[[maybe_unused]]'. 2021-08-31 12:45:37 +03:00
John Preston
4598ce2671 Beta version 2.9.13: Update lib_webview. 2021-08-31 11:13:48 +03:00
John Preston
244ccba871 Beta version 2.9.13.
- See unread comments count when scrolling discussions in channels.
2021-08-31 10:37:31 +03:00
23rd
dc8eb79295 Moved code of data send actions from Data::Session to separated file. 2021-08-30 23:08:57 +03:00
23rd
f7abd85761 Slightly optimized replacement in send action text. 2021-08-30 21:45:56 +03:00
23rd
436212bb88 Added animation of send action for stickers to middle of text. 2021-08-30 20:50:36 +03:00
23rd
8c17e3e578 Added sending action when choosing sticker. 2021-08-30 20:50:32 +03:00
23rd
0852dbc40f Formatted classes for send actions. 2021-08-30 20:50:28 +03:00
23rd
ccea6ce492 Added animated indicator of choosing sticker action to chats list. 2021-08-30 20:50:24 +03:00
John Preston
2d0bcf7dca Resend non-authorization requests on main DC change. 2021-08-30 20:32:58 +03:00
John Preston
ae40ea9336 Always start with QR-code login. 2021-08-30 20:32:58 +03:00
John Preston
52a6282eb9 Count unread replies locally when possible. 2021-08-30 20:32:58 +03:00
John Preston
c39024c7fd Track and display unread count in discussions. 2021-08-30 20:32:58 +03:00
John Preston
85e4c8527b Always write local drafts the same way. 2021-08-30 20:32:58 +03:00
John Preston
f2da34c9f5 Fix jump to message / to end in discussions. 2021-08-30 20:32:58 +03:00
John Preston
9709297713 Remove unread bar in not-seen post comments opening. 2021-08-30 20:32:58 +03:00
John Preston
809b0e9fe0 Fix build for Linux. 2021-08-30 20:32:58 +03:00
John Preston
d684c8057c Fix build for macOS. 2021-08-30 20:32:57 +03:00
John Preston
d5820e7a5a Closed alpha version 2.9.12.1. 2021-08-30 20:32:57 +03:00
John Preston
beff635e45 Colorize bubbles according to a custom chat theme. 2021-08-30 20:32:57 +03:00
John Preston
5de83ef30c Fix assertion violation in profile video with zero file size. 2021-08-30 20:32:57 +03:00
John Preston
f5a323e40a Move all background helper methods to chat_theme module. 2021-08-30 20:32:57 +03:00
John Preston
0a1e84ddb2 Move ChatTheme to td_ui. 2021-08-30 20:32:57 +03:00
John Preston
3cd0f9d189 Start non-unique ChatTheme. 2021-08-30 20:32:57 +03:00
John Preston
f3dd8c68b3 Load chat cloud themes list. 2021-08-30 20:32:57 +03:00
John Preston
70808dfa7d Show chat theme changing service messages. 2021-08-30 20:32:57 +03:00
John Preston
0821c21285 Support mono playback in OpenAL ADM. 2021-08-30 20:32:57 +03:00
John Preston
29c0956d61 Always try to play video in voice chats. 2021-08-30 20:32:57 +03:00
John Preston
0cfede984c Update API scheme to layer 132. 2021-08-30 20:32:56 +03:00
23rd
558e1d96fd Simplified extracting of calling code from phone number. 2021-08-30 20:32:56 +03:00
23rd
44c188024e Fixed updating of masked input field in intro. 2021-08-30 20:32:56 +03:00
23rd
3acbcc6247 Updated fallback countries list. 2021-08-30 20:32:56 +03:00
23rd
6caddb5141 Removed old code entry from country info. 2021-08-30 20:32:56 +03:00
23rd
963fda69a8 Removed old formatting of phone numbers. 2021-08-30 20:32:56 +03:00
23rd
6ff0cb853d Added ability to extract pattern groups from incomplete phone number. 2021-08-29 23:31:12 +03:00
23rd
10c8162575 Added ability to extract pattern groups from phone number in modern way. 2021-08-29 23:31:12 +03:00
23rd
e4640590d0 Added ability to format phone numbers in modern way. 2021-08-29 23:31:12 +03:00
23rd
e5b85bbaf1 Improved country caching. 2021-08-29 23:31:12 +03:00
23rd
8310d7e47c Improved parsing of countries data from server. 2021-08-29 23:31:12 +03:00
23rd
cf523953ad Added initial manager of countries. 2021-08-29 23:29:39 +03:00
23rd
3e80c04da7 Replaced const char ptr with QString in country structs. 2021-08-29 21:01:30 +03:00
23rd
c593f43629 Made list of countries mutable. 2021-08-29 21:01:30 +03:00
23rd
86aaa9673d Moved countries to singleton. 2021-08-29 21:01:10 +03:00
23rd
a230e83778 Simplified some names in countries. 2021-08-29 21:00:48 +03:00
23rd
df02bbb0a3 Moved countries from Data to Countries namespace. 2021-08-29 20:39:43 +03:00
John Preston
1b3075ac2e Fix pattern background tiling on Retina screens. 2021-08-25 17:15:07 +03:00
John Preston
d45c530db0 Fix CDN downloads after authorization. 2021-08-25 16:24:20 +03:00
John Preston
2671e67119 Fix crash on cancelled Qr login attempt. 2021-08-25 16:24:15 +03:00
John Preston
bdc275a927 Don't crash on bad userId in local storage. 2021-08-25 16:24:09 +03:00
Ilya Fedin
6192413f0b Split webview initialization from GtkIntegration class 2021-08-25 12:52:43 +03:00
Ilya Fedin
51df482571 Get rid of gtk open with dialog
Portal open with dialog works just fine and is a more universal solution... That allows to get rid of an additional process.
2021-08-25 12:52:43 +03:00
John Preston
2694cb76a7 Remove crash debug information from streaming. 2021-08-25 11:24:32 +03:00
John Preston
b5ae492f5e Beta version 2.9.12: Fix build on Windows. 2021-08-24 21:08:35 +03:00
John Preston
018ee0564f Beta version 2.9.12.
- Disable floating point exceptions in 32 bit Windows version.
2021-08-24 20:34:54 +03:00
John Preston
156eb69d38 Disable fp exceptions in Windows 32 bit build. 2021-08-24 20:32:26 +03:00
23rd
df0229cffd Fixed render of text selection in sections.
Regression was introduced in f4fdadd3b0.
2021-08-24 20:02:36 +03:00
23rd
b3f8d0e81a Removed dcId from passport FileKey. 2021-08-24 20:02:36 +03:00
23rd
559d488b0b Moved load status of files in passport to separated class. 2021-08-24 20:02:36 +03:00
John Preston
401529e7d1 Fix possible crash in media viewer hide workaround. 2021-08-24 19:25:31 +03:00
John Preston
a6fb0e372e Beta version 2.9.11.
- Resolve (again) a video playback crash in 32 bit Windows version.
2021-08-24 18:12:26 +03:00
John Preston
7948fc509e Remove (incorrect) checks for double casts. 2021-08-24 18:11:47 +03:00
John Preston
2d6155fc85 Throw fp exceptions in Windows 32 bit build. 2021-08-24 17:43:31 +03:00
John Preston
c7e60ef723 Beta version 2.9.10.
- Resolve (hopefully) a video playback crash in 32 bit Windows version.
2021-08-24 12:42:58 +03:00
John Preston
8f5830d520 Workaround both std::round-s in video streaming. 2021-08-24 12:42:27 +03:00
John Preston
f21d7821e7 Beta version 2.9.9.
- Still(3) debugging a video playback crash in 32 bit Windows version.
2021-08-23 20:34:38 +03:00
John Preston
e8f1373edc Add some checks for NAN in video playback. 2021-08-23 20:29:40 +03:00
John Preston
c8d1e01159 Beta version 2.9.8.
- And still debugging a video playback crash in 32 bit Windows version.
2021-08-23 18:30:14 +03:00
John Preston
7e6f24552a Add basic OpenGL info to crash annotations. 2021-08-23 18:29:20 +03:00
John Preston
27d58ba07b Try to do a non-failed double->crl::time cast.
A crash on some old CPUs show, that in video frame processing
sometimes a cast from double to crl::time fails, writing to
the resulting crl::time value INT64_MIN (0x8000000000000000).

This is shown in crash logs, with lines like:

...,rounded:104,casted:-9223372036854775808,...

where logs are written like:

...
).arg(std::round(adjust * _options.speed)
).arg(crl::time(std::round(adjust * _options.speed))
...

I don't know what to do and how to workaround this. Trying other casts.
2021-08-23 17:58:59 +03:00
23rd
3a92a181a1 Fixed editing on Up arrow in sections with non-empty input field. 2021-08-23 13:57:35 +03:00
23rd
ddd5617043 Fixed processing GIF images as non-album files.
Fixed #16844.
2021-08-23 13:57:31 +03:00
John Preston
70b3e414ce Fix crash in Update-requested-by-tg://-link. 2021-08-23 13:54:49 +03:00
201 changed files with 4968 additions and 3390 deletions

View File

@@ -345,6 +345,8 @@ PRIVATE
core/utils.cpp
core/utils.h
core/version.h
countries/countries_manager.cpp
countries/countries_manager.h
data/stickers/data_stickers_set.cpp
data/stickers/data_stickers_set.h
data/stickers/data_stickers.cpp
@@ -420,6 +422,8 @@ PRIVATE
data/data_reply_preview.h
data/data_search_controller.cpp
data/data_search_controller.h
data/data_send_action.cpp
data/data_send_action.h
data/data_session.cpp
data/data_session.h
data/data_scheduled_messages.cpp
@@ -854,14 +858,9 @@ PRIVATE
payments/payments_form.h
platform/linux/linux_desktop_environment.cpp
platform/linux/linux_desktop_environment.h
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_gtk_integration_dummy.cpp
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_gtk_integration.h
platform/linux/linux_gtk_open_with_dialog.cpp
platform/linux/linux_gtk_open_with_dialog.h
platform/linux/linux_wayland_integration_dummy.cpp
platform/linux/linux_wayland_integration.cpp
platform/linux/linux_wayland_integration.h
@@ -1166,12 +1165,7 @@ endif()
if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_gtk_open_with_dialog.cpp
platform/linux/linux_gtk_open_with_dialog.h
)
else()
remove_target_sources(Telegram ${src_loc}
@@ -1288,17 +1282,6 @@ else()
desktop-app::external_kwayland
)
endif()
if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED gtk+-3.0)
target_include_directories(Telegram SYSTEM PRIVATE ${GTK_INCLUDE_DIRS})
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
target_link_libraries(Telegram PRIVATE X11)
endif()
endif()
endif()
if (build_macstore)

View File

@@ -1209,6 +1209,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_proximity_reached" = "{from} is now within {distance} from {user}";
"lng_action_proximity_reached_you" = "{from} is now within {distance} from you";
"lng_action_you_proximity_reached" = "You are now within {distance} from {user}";
"lng_action_you_theme_changed" = "You changed chat theme to {emoji}";
"lng_action_theme_changed" = "{from} changed chat theme to {emoji}";
"lng_action_theme_changed_channel" = "Channel theme changed to {emoji}";
"lng_action_you_theme_disabled" = "You disabled chat theme";
"lng_action_theme_disabled" = "{from} disabled chat theme";
"lng_action_theme_disabled_channel" = "Channel theme disabled";
"lng_action_proximity_distance_m#one" = "{count} meter";
"lng_action_proximity_distance_m#other" = "{count} metres";
"lng_action_proximity_distance_km#one" = "{count} km";
@@ -1609,6 +1615,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_user_action_upload_photo" = "{user} is sending a photo";
"lng_send_action_upload_file" = "sending a file";
"lng_user_action_upload_file" = "{user} is sending a file";
"lng_send_action_choose_sticker" = "choosing a sticker";
"lng_user_action_choose_sticker" = "{user} is choosing a sticker";
"lng_unread_bar#one" = "{count} unread message";
"lng_unread_bar#other" = "{count} unread messages";
"lng_unread_bar_some" = "Unread messages";
@@ -2103,8 +2111,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_ptt_delay" = "Push to Talk release delay: {delay}";
"lng_group_call_share" = "Share Invite Link";
"lng_group_call_noise_suppression" = "Enable Noise Suppression";
"lng_group_call_limit#one" = "Video is only available\nfor the first {count} member";
"lng_group_call_limit#other" = "Video is only available\nfor the first {count} members";
"lng_group_call_over_limit#one" = "The voice chat is over {count} member.\nNew participants only have access to audio stream.";
"lng_group_call_over_limit#other" = "The voice chat is over {count} members.\nNew participants only have access to audio stream.";
"lng_group_call_video_paused" = "Video is paused";

View File

@@ -0,0 +1,17 @@
import os, sys, requests, re
os.chdir()
keys = []
with open('lang.strings') as f:
for line in f:
m = re.match(r'\"(lng_[a-z_]+)(\#[a-z]+)?\"', line)
if m:
keys.append(m.group(1))
elif not re.match(r'^\s*$', line):
print('Bad line: ' + line)
sys.exit(1)
print('Keys: ' + str(len(keys)))
sys.exit()

View File

@@ -92,7 +92,7 @@ inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes th
inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation;
inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer photo_id:long = InputFileLocation;
inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation;
inputGroupCallStream#bba51639 call:InputGroupCall time_ms:long scale:int = InputFileLocation;
inputGroupCallStream#598a92a flags:# call:InputGroupCall time_ms:long scale:int video_channel:flags.0?int video_quality:flags.0?int = InputFileLocation;
peerUser#9db1bc6d user_id:int = Peer;
peerChat#bad0e5bb chat_id:int = Peer;
@@ -128,8 +128,8 @@ chatForbidden#7328bdb id:int title:string = Chat;
channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#8a1e2983 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer = ChatFull;
channelFull#548c3f93 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer = ChatFull;
chatFull#49a0a5d9 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull;
channelFull#2f532f3c flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
@@ -187,6 +187,7 @@ messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int
messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector<int> = MessageAction;
messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@@ -234,7 +235,7 @@ inputReportReasonCopyright#9b89f93a = ReportReason;
inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
inputReportReasonFake#f5ddd6e7 = ReportReason;
userFull#139a9a77 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int = UserFull;
userFull#d697ff05 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact;
@@ -465,6 +466,7 @@ sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
speakingInGroupCallAction#d92c2285 = SendMessageAction;
sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction;
sendMessageChooseStickerAction#b05ac6b1 = SendMessageAction;
contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
@@ -1116,7 +1118,7 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti
inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
inputThemeSlug#f5890df1 slug:string = InputTheme;
theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme;
theme#e802b8dc flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:flags.4?int = Theme;
account.themesNotModified#f41eb622 = account.Themes;
account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
@@ -1135,9 +1137,9 @@ baseThemeNight#b7b31ea8 = BaseTheme;
baseThemeTinted#6d5f77ee = BaseTheme;
baseThemeArctic#5b11125a = BaseTheme;
inputThemeSettings#bd507cd1 flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings;
inputThemeSettings#ff38f912 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int message_colors:flags.0?Vector<int> wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings;
themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?WallPaper = ThemeSettings;
themeSettings#8db4e76c flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int message_colors:flags.0?Vector<int> wallpaper:flags.1?WallPaper = ThemeSettings;
webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
@@ -1195,7 +1197,7 @@ messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:fla
messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> users:Vector<User> = messages.MessageViews;
messages.discussionMessage#f5dd8f9d flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
@@ -1206,7 +1208,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall;
groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall;
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
@@ -1265,6 +1267,15 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
chatTheme#ed0b5c33 emoticon:string theme:Theme dark_theme:Theme = ChatTheme;
account.chatThemesNotModified#e011e1c4 = account.ChatThemes;
account.chatThemes#fe4cbebd hash:int themes:Vector<ChatTheme> = account.ChatThemes;
sponsoredMessage#f671f0d1 flags:# random_id:bytes peer_id:Peer from_id:Peer message:string media:flags.0?MessageMedia entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
messages.sponsoredMessages#65a4c7d5 messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1365,6 +1376,7 @@ account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = Globa
account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
account.resetPassword#9308ce1b = account.ResetPasswordResult;
account.declinePasswordReset#4c9409f6 = Bool;
account.getChatThemes#d6d71d7b hash:int = account.ChatThemes;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
@@ -1402,7 +1414,7 @@ messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int = Updates;
messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int = Updates;
messages.reportSpam#cf1592db peer:InputPeer = Bool;
messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings;
messages.report#8953ab4e peer:InputPeer id:Vector<int> reason:ReportReason message:string = Bool;
@@ -1533,6 +1545,7 @@ messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithI
messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates;
messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer;
messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1611,6 +1624,8 @@ channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint addr
channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates;
channels.getInactiveChannels#11e831ee = messages.InactiveChats;
channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool;
channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@@ -1649,10 +1664,10 @@ phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
phone.discardGroupCall#7a777135 call:InputGroupCall = Updates;
phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates;
phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall;
phone.getGroupCall#41845db call:InputGroupCall limit:int = phone.GroupCall;
phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector<InputPeer> sources:Vector<int> offset:string limit:int = phone.GroupParticipants;
phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector<int> = Vector<int>;
phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates;
phone.toggleGroupCallRecord#f128c708 flags:# start:flags.0?true video:flags.2?true call:InputGroupCall title:flags.1?string video_portrait:flags.2?Bool = Updates;
phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates;
phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates;
phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers;
@@ -1678,4 +1693,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 131
// LAYER 132

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.9.7.0" />
Version="2.9.13.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,9,7,0
PRODUCTVERSION 2,9,7,0
FILEVERSION 2,9,13,0
PRODUCTVERSION 2,9,13,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.9.7.0"
VALUE "FileVersion", "2.9.13.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.9.7.0"
VALUE "ProductVersion", "2.9.13.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,9,7,0
PRODUCTVERSION 2,9,7,0
FILEVERSION 2,9,13,0
PRODUCTVERSION 2,9,13,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.9.7.0"
VALUE "FileVersion", "2.9.13.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.9.7.0"
VALUE "ProductVersion", "2.9.13.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -129,6 +129,7 @@ void SendProgressManager::send(const Key &key, int progress) {
case Type::ChooseContact: return MTP_sendMessageChooseContactAction();
case Type::PlayGame: return MTP_sendMessageGamePlayAction();
case Type::Speaking: return MTP_speakingInGroupCallAction();
case Type::ChooseSticker: return MTP_sendMessageChooseStickerAction();
default: return MTP_sendMessageTypingAction();
}
}();

View File

@@ -30,6 +30,7 @@ enum class SendProgressType {
UploadFile,
ChooseLocation,
ChooseContact,
ChooseSticker,
PlayGame,
Speaking,
};

View File

@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/data_folder.h"
#include "data/data_scheduled_messages.h"
#include "data/data_send_action.h"
#include "lang/lang_cloud_manager.h"
#include "history/history.h"
#include "history/history_item.h"
@@ -1017,7 +1018,7 @@ void Updates::handleSendActionUpdate(
const auto when = requestingDifference()
? 0
: base::unixtime::now();
session().data().registerSendAction(
session().data().sendActionManager().registerFor(
history,
rootId,
from->asUser(),
@@ -2094,17 +2095,20 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto msgId = d.vtop_msg_id().v;
const auto readTillId = d.vread_max_id().v;
const auto item = session().data().message(channelId, msgId);
const auto unreadCount = item
? session().data().countUnreadRepliesLocally(item, readTillId)
: std::nullopt;
if (item) {
item->setRepliesInboxReadTill(readTillId);
item->setRepliesInboxReadTill(readTillId, unreadCount);
if (const auto post = item->lookupDiscussionPostOriginal()) {
post->setRepliesInboxReadTill(readTillId);
post->setRepliesInboxReadTill(readTillId, unreadCount);
}
}
if (const auto broadcastId = d.vbroadcast_id()) {
if (const auto post = session().data().message(
broadcastId->v,
d.vbroadcast_post()->v)) {
post->setRepliesInboxReadTill(readTillId);
post->setRepliesInboxReadTill(readTillId, unreadCount);
}
}
} break;

View File

@@ -3505,7 +3505,7 @@ void ApiWrap::sharedMediaDone(
parsed.fullCount
));
if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) {
peer->setHasPinnedMessages(true);
peer->owner().history(peer)->setHasPinnedMessages(true);
}
}

View File

@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flat_set.h"
#include "base/openssl_help.h"
#include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h" // ExtractPhonePrefix.
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/add_participants_box.h"
#include "boxes/peers/edit_participant_box.h"
@@ -20,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h"
#include "core/application.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
#include "window/window_session_controller.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
@@ -267,7 +267,7 @@ AddContactBox::AddContactBox(
this,
st::defaultInputField,
tr::lng_contact_phone(),
Ui::ExtractPhonePrefix(session->user()->phone()),
Countries::ExtractPhoneCode(session->user()->phone()),
phone)
, _invertOrder(langFirstNameGoesSecond()) {
if (!phone.isEmpty()) {

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/effects/round_checkbox.h"
#include "ui/image/image.h"
#include "ui/chat/chat_theme.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "apiwrap.h"
@@ -331,7 +332,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
}
} else if (!paper.data.backgroundColors().empty()) {
paper.thumbnail = Ui::PixmapFromImage(
Data::GenerateWallPaper(
Ui::GenerateBackgroundImage(
st::backgroundSize * cIntRetinaFactor(),
paper.data.backgroundColors(),
paper.data.gradientRotation()));
@@ -346,7 +347,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
: paper.dataMedia->thumbnail();
auto original = thumbnail->original();
if (paper.data.isPattern()) {
original = Data::PreparePatternImage(
original = Ui::PreparePatternImage(
std::move(original),
paper.data.backgroundColors(),
paper.data.gradientRotation(),

View File

@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "window/themes/window_theme.h"
#include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/toast/toast.h"
#include "ui/image/image.h"
#include "ui/widgets/checkbox.h"
@@ -340,7 +342,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
Images::Option blur = Images::Option(0)) {
auto result = PrepareScaledNonPattern(image, blur);
if (isPattern) {
result = Data::PreparePatternImage(
result = Ui::PreparePatternImage(
std::move(result),
background,
gradientRotation,
@@ -364,6 +366,7 @@ BackgroundPreviewBox::BackgroundPreviewBox(
const Data::WallPaper &paper)
: SimpleElementDelegate(controller, [=] { update(); })
, _controller(controller)
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _text1(GenerateTextItem(
delegate(),
_controller->session().data().history(PeerData::kServiceNotificationsId),
@@ -377,6 +380,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
, _paper(paper)
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
_chatStyle->apply(controller->defaultChatTheme().get());
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
@@ -394,7 +399,7 @@ void BackgroundPreviewBox::generateBackground() {
const auto size = QSize(st::boxWideWidth, st::boxWideWidth)
* cIntRetinaFactor();
_generated = Ui::PixmapFromImage((_paper.patternOpacity() >= 0.)
? Data::GenerateWallPaper(
? Ui::GenerateBackgroundImage(
size,
_paper.backgroundColors(),
_paper.gradientRotation())
@@ -589,13 +594,10 @@ QRect BackgroundPreviewBox::radialRect() const {
void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
const auto height1 = _text1->height();
const auto height2 = _text2->height();
const auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = rect(),
.clip = rect(),
.selection = TextSelection(),
.now = ms,
};
const auto context = _controller->defaultChatTheme()->preparePaintContext(
_chatStyle.get(),
rect(),
rect());
p.translate(0, textsTop());
paintDate(p);
_text1->draw(p, context);
@@ -666,7 +668,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
auto blurred = (_paper.document() || _paper.isPattern())
? QImage()
: PrepareScaledNonPattern(
Data::PrepareBlurredBackground(thumbnail->original()),
Ui::PrepareBlurredBackground(thumbnail->original()),
Images::Option(0));
setScaledFromImage(std::move(scaled), std::move(blurred));
}
@@ -674,7 +676,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
void BackgroundPreviewBox::setScaledFromImage(
QImage &&image,
QImage &&blurred) {
updateServiceBg({ Window::Theme::CountAverageColor(image) });
updateServiceBg({ Ui::CountAverageColor(image) });
if (!_full.isNull()) {
startFadeInFrom(std::move(_scaled));
}
@@ -712,7 +714,7 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
green += color.green();
blue += color.blue();
}
_serviceBg = Window::Theme::AdjustedColor(
_serviceBg = Ui::ThemeAdjustedColor(
st::msgServiceBg->c,
QColor(red / count, green / count, blue / count));
}
@@ -746,7 +748,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
patternOpacity);
auto blurred = !isPattern
? PrepareScaledNonPattern(
Data::PrepareBlurredBackground(image),
Ui::PrepareBlurredBackground(image),
Images::Option(0))
: QImage();
crl::on_main(std::move(guard), [
@@ -761,9 +763,9 @@ void BackgroundPreviewBox::checkLoadedDocument() {
});
});
};
_generating = Data::ReadImageAsync(
_generating = Data::ReadBackgroundImageAsync(
_media.get(),
Window::Theme::PreprocessBackgroundImage,
Ui::PreprocessBackgroundImage,
generateCallback);
}

View File

@@ -25,6 +25,7 @@ class SessionController;
namespace Ui {
class Checkbox;
class ChatStyle;
} // namespace Ui
class BackgroundPreviewBox
@@ -71,6 +72,7 @@ private:
void checkBlurAnimationStart();
const not_null<Window::SessionController*> _controller;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
AdminLog::OwnedItem _text1;
AdminLog::OwnedItem _text2;
Data::WallPaper _paper;

View File

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/special_fields.h"
#include "boxes/confirm_phone_box.h"
#include "boxes/confirm_box.h"
#include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
@@ -151,7 +152,7 @@ void ChangePhoneBox::EnterPhone::prepare() {
this,
st::defaultInputField,
tr::lng_change_phone_new_title(),
Ui::ExtractPhonePrefix(_session->user()->phone()),
Countries::ExtractPhoneCode(_session->user()->phone()),
phoneValue);
_phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height());

View File

@@ -281,7 +281,8 @@ void ConfirmBox::confirmed() {
}
} else if (const auto callbackPtr = std::get_if<2>(confirmed)) {
if (auto callback = base::take(*callbackPtr)) {
callback([=] { closeBox(); });
const auto weak = Ui::MakeWeak(this);
callback(crl::guard(weak, [=] { closeBox(); }));
}
}
}

View File

@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "numbers.h"
#include "lang/lang_keys.h"
#include "mtproto/facade.h"
#include "styles/style_layers.h"

View File

@@ -126,11 +126,19 @@ using JoinClientFields = std::variant<
class GroupCall::LoadPartTask final : public tgcalls::BroadcastPartTask {
public:
using Quality = tgcalls::VideoChannelDescription::Quality;
LoadPartTask(
base::weak_ptr<GroupCall> call,
int64 time,
int64 period,
Fn<void(tgcalls::BroadcastPart&&)> done);
LoadPartTask(
base::weak_ptr<GroupCall> call,
int64 time,
int64 period,
int32 videoChannel,
Quality videoQuality,
Fn<void(tgcalls::BroadcastPart&&)> done);
[[nodiscard]] int64 time() const {
return _time;
@@ -138,6 +146,12 @@ public:
[[nodiscard]] int32 scale() const {
return _scale;
}
[[nodiscard]] int32 videoChannel() const {
return _videoChannel;
}
[[nodiscard]] Quality videoQuality() const {
return _videoQuality;
}
void done(tgcalls::BroadcastPart &&part);
void cancel() override;
@@ -146,6 +160,8 @@ private:
const base::weak_ptr<GroupCall> _call;
const int64 _time = 0;
const int32 _scale = 0;
const int32 _videoChannel = 0;
const Quality _videoQuality = {};
Fn<void(tgcalls::BroadcastPart &&)> _done;
QMutex _mutex;
@@ -379,7 +395,17 @@ GroupCall::LoadPartTask::LoadPartTask(
base::weak_ptr<GroupCall> call,
int64 time,
int64 period,
Fn<void(tgcalls::BroadcastPart &&)> done)
Fn<void(tgcalls::BroadcastPart&&)> done)
: LoadPartTask(std::move(call), time, period, 0, {}, std::move(done)) {
}
GroupCall::LoadPartTask::LoadPartTask(
base::weak_ptr<GroupCall> call,
int64 time,
int64 period,
int32 videoChannel,
tgcalls::VideoChannelDescription::Quality videoQuality,
Fn<void(tgcalls::BroadcastPart&&)> done)
: _call(std::move(call))
, _time(time ? time : (base::unixtime::now() * int64(1000)))
, _scale([&] {
@@ -391,6 +417,8 @@ GroupCall::LoadPartTask::LoadPartTask(
}
Unexpected("Period in LoadPartTask.");
}())
, _videoChannel(videoChannel)
, _videoQuality(videoQuality)
, _done(std::move(done)) {
}
@@ -857,9 +885,6 @@ void GroupCall::setState(State state) {
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
call->setInCall();
}
if (!videoIsWorking()) {
refreshHasNotShownVideo();
}
}
if (false
@@ -1072,9 +1097,6 @@ void GroupCall::markEndpointActive(
bool paused) {
if (!endpoint) {
return;
} else if (active && !videoIsWorking()) {
refreshHasNotShownVideo();
return;
}
const auto i = _activeVideoTracks.find(endpoint);
const auto changed = active
@@ -2190,7 +2212,8 @@ void GroupCall::toggleRecording(bool enabled, const QString &title) {
MTP_flags((enabled ? Flag::f_start : Flag(0))
| (title.isEmpty() ? Flag(0) : Flag::f_title)),
inputCall(),
MTP_string(title)
MTP_string(title),
MTPBool() // video_portrait
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
_recordingStoppedByMe = false;
@@ -2234,7 +2257,7 @@ bool GroupCall::tryCreateController() {
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
.videoCapture = _cameraCapture,
.requestBroadcastPart = [=, call = base::make_weak(this)](
.requestAudioBroadcastPart = [=, call = base::make_weak(this)](
int64_t time,
int64_t period,
std::function<void(tgcalls::BroadcastPart &&)> done) {
@@ -2248,6 +2271,24 @@ bool GroupCall::tryCreateController() {
});
return result;
},
.requestVideoBroadcastPart = [=, call = base::make_weak(this)](
int64_t time,
int64_t period,
int32_t channel,
tgcalls::VideoChannelDescription::Quality quality,
std::function<void(tgcalls::BroadcastPart &&)> done) {
auto result = std::make_shared<LoadPartTask>(
call,
time,
period,
channel,
quality,
std::move(done));
crl::on_main(weak, [=]() mutable {
broadcastPartStart(std::move(result));
});
return result;
},
.videoContentType = tgcalls::VideoContentType::Generic,
.initialEnableNoiseSuppression
= settings.groupCallNoiseSuppression(),
@@ -2326,17 +2367,30 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
const auto raw = task.get();
const auto time = raw->time();
const auto scale = raw->scale();
const auto videoChannel = raw->videoChannel();
const auto videoQuality = raw->videoQuality();
const auto finish = [=](tgcalls::BroadcastPart &&part) {
raw->done(std::move(part));
_broadcastParts.erase(raw);
};
using Status = tgcalls::BroadcastPart::Status;
using Quality = tgcalls::VideoChannelDescription::Quality;
using Flag = MTPDinputGroupCallStream::Flag;
const auto requestId = _api.request(MTPupload_GetFile(
MTP_flags(0),
MTP_inputGroupCallStream(
MTP_flags(videoChannel
? (Flag::f_video_channel | Flag::f_video_quality)
: Flag(0)),
inputCall(),
MTP_long(time),
MTP_int(scale)),
MTP_int(scale),
MTP_int(videoChannel),
MTP_int((videoQuality == Quality::Full)
? 2
: (videoQuality == Quality::Medium)
? 1
: 0)),
MTP_int(0),
MTP_int(128 * 1024)
)).done([=](
@@ -2350,7 +2404,7 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
.timestampMilliseconds = time,
.responseTimestamp = TimestampFromMsgId(response.outerMsgId),
.status = Status::Success,
.oggData = std::move(bytes),
.data = std::move(bytes),
});
}, [&](const MTPDupload_fileCdnRedirect &data) {
LOG(("Voice Chat Stream Error: fileCdnRedirect received."));
@@ -2558,22 +2612,6 @@ void GroupCall::updateRequestedVideoChannelsDelayed() {
});
}
void GroupCall::refreshHasNotShownVideo() {
if (!_joinState.ssrc || hasNotShownVideo()) {
return;
}
const auto real = lookupReal();
Assert(real != nullptr);
const auto hasVideo = [&](const Data::GroupCallParticipant &data) {
return (data.peer != _joinAs)
&& (!GetCameraEndpoint(data.videoParams).empty()
|| !GetScreenEndpoint(data.videoParams).empty());
};
_hasNotShownVideo = _joinState.ssrc
&& ranges::any_of(real->participants(), hasVideo);
}
void GroupCall::fillActiveVideoEndpoints() {
const auto real = lookupReal();
Assert(real != nullptr);
@@ -2581,9 +2619,7 @@ void GroupCall::fillActiveVideoEndpoints() {
const auto me = real->participantByPeer(_joinAs);
if (me && me->videoJoined) {
_videoIsWorking = true;
_hasNotShownVideo = false;
} else {
refreshHasNotShownVideo();
_videoIsWorking = false;
toggleVideo(false);
toggleScreenSharing(std::nullopt);
@@ -2611,30 +2647,28 @@ void GroupCall::fillActiveVideoEndpoints() {
}
};
using Type = VideoEndpointType;
if (_videoIsWorking.current()) {
for (const auto &participant : real->participants()) {
const auto camera = GetCameraEndpoint(participant.videoParams);
if (camera != _cameraEndpoint
&& camera != _screenEndpoint
&& participant.peer != _joinAs) {
const auto paused = IsCameraPaused(participant.videoParams);
feedOne({ Type::Camera, participant.peer, camera }, paused);
}
const auto screen = GetScreenEndpoint(participant.videoParams);
if (screen != _cameraEndpoint
&& screen != _screenEndpoint
&& participant.peer != _joinAs) {
const auto paused = IsScreenPaused(participant.videoParams);
feedOne({ Type::Screen, participant.peer, screen }, paused);
}
for (const auto &participant : real->participants()) {
const auto camera = GetCameraEndpoint(participant.videoParams);
if (camera != _cameraEndpoint
&& camera != _screenEndpoint
&& participant.peer != _joinAs) {
const auto paused = IsCameraPaused(participant.videoParams);
feedOne({ Type::Camera, participant.peer, camera }, paused);
}
const auto screen = GetScreenEndpoint(participant.videoParams);
if (screen != _cameraEndpoint
&& screen != _screenEndpoint
&& participant.peer != _joinAs) {
const auto paused = IsScreenPaused(participant.videoParams);
feedOne({ Type::Screen, participant.peer, screen }, paused);
}
feedOne(
{ Type::Camera, _joinAs, cameraSharingEndpoint() },
isCameraPaused());
feedOne(
{ Type::Screen, _joinAs, screenSharingEndpoint() },
isScreenPaused());
}
feedOne(
{ Type::Camera, _joinAs, cameraSharingEndpoint() },
isCameraPaused());
feedOne(
{ Type::Screen, _joinAs, screenSharingEndpoint() },
isScreenPaused());
if (large && !largeFound) {
setVideoEndpointLarge({});
}

View File

@@ -362,12 +362,6 @@ public:
[[nodiscard]] rpl::producer<bool> videoIsWorkingValue() const {
return _videoIsWorking.value();
}
[[nodiscard]] bool hasNotShownVideo() const {
return _hasNotShownVideo.current();
}
[[nodiscard]] rpl::producer<bool> hasNotShownVideoValue() const {
return _hasNotShownVideo.value();
}
void setCurrentAudioDevice(bool input, const QString &deviceId);
[[nodiscard]] bool isSharingScreen() const;
@@ -521,7 +515,6 @@ private:
void updateRequestedVideoChannels();
void updateRequestedVideoChannelsDelayed();
void fillActiveVideoEndpoints();
void refreshHasNotShownVideo();
void editParticipant(
not_null<PeerData*> participantPeer,
@@ -580,7 +573,6 @@ private:
rpl::variable<MuteState> _muted = MuteState::Muted;
rpl::variable<bool> _canManage = false;
rpl::variable<bool> _videoIsWorking = false;
rpl::variable<bool> _hasNotShownVideo = false;
bool _initialMuteStateSent = false;
bool _acceptFields = false;

View File

@@ -46,125 +46,6 @@ constexpr auto kUserpicBlurRadius = 8;
using Row = MembersRow;
[[nodiscard]] int VideoParticipantsLimit(not_null<Main::Session*> session) {
return int(session->account().appConfig().get<double>(
"groupcall_video_participants_max",
30.));
}
void SetupVideoPlaceholder(
not_null<Ui::RpWidget*> widget,
not_null<PeerData*> chat,
int limit) {
struct State {
QImage blurred;
QImage rounded;
InMemoryKey key = {};
std::shared_ptr<Data::CloudImageView> view;
qint64 blurredCacheKey = 0;
};
const auto state = widget->lifetime().make_state<State>();
const auto refreshBlurred = [=] {
const auto key = chat->userpicUniqueKey(state->view);
if (state->key == key && !state->blurred.isNull()) {
return;
}
constexpr auto size = kUserpicSizeForBlur;
state->key = key;
state->blurred = QImage(
QSize(size, size),
QImage::Format_ARGB32_Premultiplied);
{
auto p = Painter(&state->blurred);
auto hq = PainterHighQualityEnabler(p);
chat->paintUserpicSquare(p, state->view, 0, 0, size);
}
state->blurred = Images::BlurLargeImage(
std::move(state->blurred),
kUserpicBlurRadius);
widget->update();
};
const auto refreshRounded = [=](QSize size) {
refreshBlurred();
const auto key = state->blurred.cacheKey();
if (state->rounded.size() == size && state->blurredCacheKey == key) {
return;
}
state->blurredCacheKey = key;
state->rounded = Images::prepare(
state->blurred,
size.width(),
size.width(), // Square
Images::Option::Smooth,
size.width(),
size.height());
{
auto p = QPainter(&state->rounded);
p.fillRect(
0,
0,
size.width(),
size.height(),
QColor(0, 0, 0, Viewport::kShadowMaxAlpha));
}
state->rounded = Images::prepare(
std::move(state->rounded),
size.width(),
size.height(),
(Images::Option::RoundedLarge | Images::Option::RoundedAll),
size.width(),
size.height());
};
chat->loadUserpic();
refreshBlurred();
widget->paintRequest(
) | rpl::start_with_next([=] {
const auto size = QSize(
widget->width(),
widget->height() - st::groupCallVideoSmallSkip);
refreshRounded(size * cIntRetinaFactor());
auto p = QPainter(widget);
const auto inner = QRect(QPoint(), size);
p.drawImage(inner, state->rounded);
st::groupCallPaused.paint(
p,
(size.width() - st::groupCallPaused.width()) / 2,
st::groupCallVideoPlaceholderIconTop,
size.width());
const auto skip = st::groupCallVideoLargeSkip;
p.setPen(st::groupCallVideoTextFg);
const auto text = QRect(
skip,
st::groupCallVideoPlaceholderTextTop,
(size.width() - 2 * skip),
size.height() - st::groupCallVideoPlaceholderTextTop);
p.setFont(st::semiboldFont);
p.drawText(
text,
tr::lng_group_call_limit(tr::now, lt_count, int(limit)),
style::al_top);
}, widget->lifetime());
}
void SetupVideoAboutLimit(
not_null<Ui::RpWidget*> widget,
not_null<Main::Session*> session,
int limit) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(
widget.get(),
tr::lng_group_call_over_limit(lt_count, rpl::single(limit * 1.)),
st::groupCallVideoLimitLabel);
widget->widthValue(
) | rpl::start_with_next([=](int width) {
label->resizeToWidth(width);
label->moveToLeft(0, st::normalFont->height / 3);
widget->resize(width, label->height() + st::normalFont->height);
}, label->lifetime());
}
} // namespace
class Members::Controller final
@@ -1658,8 +1539,6 @@ Members::Members(
, _layout(_scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll.data())))
, _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
, _videoPlaceholder(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
, _videoAboutLimit(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
, _viewport(
std::make_unique<Viewport>(
_videoWrap.get(),
@@ -1897,61 +1776,9 @@ void Members::trackViewportGeometry() {
_scroll->scrollTopValue(
) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
const auto videoLimit = VideoParticipantsLimit(&_call->peer()->session());
rpl::combine(
_layout->widthValue(),
_call->hasNotShownVideoValue()
) | rpl::start_with_next([=](int width, bool has) {
const auto height = has ? st::groupCallVideoPlaceholderHeight : 0;
_videoPlaceholder->setGeometry(0, 0, width, height);
}, _videoPlaceholder->lifetime());
SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer(), videoLimit);
_layout->widthValue(
) | rpl::start_with_next([=](int width) {
_videoAboutLimit->resizeToWidth(width);
}, _videoAboutLimit->lifetime());
using namespace rpl::mappers;
auto aboutLimitRelevant = fullCountValue(
) | rpl::map(
_1 > videoLimit
) | rpl::distinct_until_changed();
auto aboutLimitShown = rpl::combine(
std::move(aboutLimitRelevant),
_call->canManageValue(),
_1 && _2);
SetupVideoAboutLimit(
_videoAboutLimit.get(),
&_call->peer()->session(),
videoLimit);
rpl::combine(
_videoPlaceholder->heightValue(),
_viewport->fullHeightValue(),
_videoAboutLimit->heightValue(),
std::move(aboutLimitShown)
) | rpl::start_with_next([=](
int placeholder,
int viewport,
int aboutLimit,
bool aboutLimitShown) {
if (placeholder > 0 || viewport <= 0 || !aboutLimitShown) {
aboutLimitShown = false;
}
// This call may update _videoAboutLimit->height() :(
_videoAboutLimit->setVisible(aboutLimitShown);
_videoAboutLimit->move(0, viewport);
_videoWrap->resize(
_videoWrap->width(),
std::max(
placeholder,
(viewport
+ (aboutLimitShown ? _videoAboutLimit->height() : 0))));
_viewport->fullHeightValue(
) | rpl::start_with_next([=](int viewport) {
_videoWrap->resize(_videoWrap->width(), viewport);
if (viewport > 0) {
move();
resize();

View File

@@ -101,8 +101,6 @@ private:
std::unique_ptr<Controller> _listController;
not_null<Ui::VerticalLayout*> _layout;
const not_null<Ui::RpWidget*> _videoWrap;
const std::unique_ptr<Ui::RpWidget> _videoPlaceholder;
const std::unique_ptr<Ui::RpWidget> _videoAboutLimit;
std::unique_ptr<Viewport> _viewport;
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
RpWidget *_topSkip = nullptr;

View File

@@ -970,6 +970,11 @@ StickersListWidget::StickersListWidget(
}
refreshRecent();
}, lifetime());
positionValue(
) | rpl::skip(1) | rpl::map_to(
TabbedSelector::Action::Update
) | rpl::start_to_stream(_choosingUpdated, lifetime());
}
Main::Session &StickersListWidget::session() const {
@@ -988,6 +993,11 @@ rpl::producer<> StickersListWidget::checkForHide() const {
return _checkForHide.events();
}
auto StickersListWidget::choosingUpdated() const
-> rpl::producer<TabbedSelector::Action> {
return _choosingUpdated.events();
}
object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
Expects(_footer == nullptr);
@@ -2361,6 +2371,7 @@ TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
}
void StickersListWidget::processHideFinished() {
_choosingUpdated.fire(TabbedSelector::Action::Cancel);
clearSelection();
clearHeavyData();
if (_footer) {

View File

@@ -55,6 +55,7 @@ public:
rpl::producer<TabbedSelector::FileChosen> chosen() const;
rpl::producer<> scrollUpdated() const;
rpl::producer<> checkForHide() const;
rpl::producer<TabbedSelector::Action> choosingUpdated() const;
void refreshRecent() override;
void preloadImages() override;
@@ -392,6 +393,7 @@ private:
rpl::event_stream<TabbedSelector::FileChosen> _chosen;
rpl::event_stream<> _scrollUpdated;
rpl::event_stream<> _checkForHide;
rpl::event_stream<TabbedSelector::Action> _choosingUpdated;
};

View File

@@ -26,7 +26,7 @@ object_ptr<Window::SectionWidget> TabbedMemento::createWidget(
TabbedSection::TabbedSection(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Window::SectionWidget(parent, controller, PaintedBackground::Custom)
: Window::SectionWidget(parent, controller)
, _selector(controller->tabbedSelector()) {
_selector->setParent(this);
_selector->setRoundRadius(0);

View File

@@ -479,6 +479,13 @@ auto TabbedSelector::inlineResultChosen() const
return hasGifsTab() ? gifs()->inlineResultChosen() : nullptr;
}
auto TabbedSelector::choosingStickerUpdated() const
-> rpl::producer<TabbedSelector::Action>{
return hasStickersTab()
? stickers()->choosingUpdated()
: rpl::never<Action>();
}
rpl::producer<> TabbedSelector::cancelled() const {
return hasGifsTab() ? gifs()->cancelRequests() : nullptr;
}

View File

@@ -67,6 +67,10 @@ public:
EmojiOnly,
MediaEditor,
};
enum class Action {
Update,
Cancel,
};
TabbedSelector(
QWidget *parent,
@@ -85,6 +89,7 @@ public:
rpl::producer<> checkForHide() const;
rpl::producer<> slideFinished() const;
rpl::producer<> contextMenuRequested() const;
rpl::producer<Action> choosingStickerUpdated() const;
void setRoundRadius(int radius);
void refreshStickers();

View File

@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_updates.h"
#include "calls/calls_instance.h"
#include "countries/countries_manager.h"
#include "lang/lang_file_parser.h"
#include "lang/lang_translator.h"
#include "lang/lang_cloud_manager.h"
@@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_instance.h"
#include "mainwidget.h"
#include "core/file_utilities.h"
#include "core/crash_reports.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "main/main_session.h"
@@ -94,6 +96,27 @@ constexpr auto kQuitPreventTimeoutMs = crl::time(1500);
constexpr auto kAutoLockTimeoutLateMs = crl::time(3000);
constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000);
void SetCrashAnnotationsGL() {
#ifdef Q_OS_WIN
CrashReports::SetAnnotation("OpenGL ANGLE", [] {
if (Core::App().settings().disableOpenGL()) {
return "Disabled";
} else switch (Ui::GL::CurrentANGLE()) {
case Ui::GL::ANGLE::Auto: return "Auto";
case Ui::GL::ANGLE::D3D11: return "Direct3D 11";
case Ui::GL::ANGLE::D3D9: return "Direct3D 9";
case Ui::GL::ANGLE::D3D11on12: return "D3D11on12";
case Ui::GL::ANGLE::OpenGL: return "OpenGL";
}
Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
}());
#else // Q_OS_WIN
CrashReports::SetAnnotation(
"OpenGL",
Core::App().settings().disableOpenGL() ? "Disabled" : "Enabled");
#endif // Q_OS_WIN
}
} // namespace
Application *Application::Instance = nullptr;
@@ -287,6 +310,7 @@ void Application::run() {
LOG(("Shortcuts Error: %1").arg(error));
}
SetCrashAnnotationsGL();
if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) {
showOpenGLCrashNotification();
}
@@ -297,6 +321,14 @@ void Application::run() {
_mediaView->show(std::move(request));
}
}, _window->lifetime());
{
const auto countries = std::make_shared<Countries::Manager>(
_domain.get());
countries->lifetime().add([=] {
[[maybe_unused]] const auto countriesCopy = countries;
});
}
}
void Application::showOpenGLCrashNotification() {

View File

@@ -101,7 +101,12 @@ std::map<int, const char*> BetaLogs() {
"- Fix animated chat backgrounds in Saved Messages.\n"
"- Fix \"Sorry, group is inaccessible\" message in scheduled voice chats.\n",
"- Fix \"Sorry, group is inaccessible\" message "
"in scheduled voice chats.\n",
},
{
2009013,
"- See unread comments count when scrolling discussions in channels."
},
};
};

View File

@@ -270,12 +270,12 @@ std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
return std::make_unique<Platform::Launcher>(argc, argv);
}
Launcher::Launcher(
int argc,
char *argv[])
Launcher::Launcher(int argc, char *argv[])
: _argc(argc)
, _argv(argv)
, _baseIntegration(_argc, _argv) {
crl::toggle_fp_exceptions(true);
base::Integration::Set(&_baseIntegration);
}

View File

@@ -13,9 +13,7 @@ namespace Core {
class Launcher {
public:
Launcher(
int argc,
char *argv[]);
Launcher(int argc, char *argv[]);
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2009007;
constexpr auto AppVersionStr = "2.9.7";
constexpr auto AppVersion = 2009013;
constexpr auto AppVersionStr = "2.9.13";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -0,0 +1,460 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "countries/countries_instance.h"
namespace Countries {
namespace {
auto SingleInstance = CountriesInstance();
const std::array<Info, 231> FallbackList = { {
{ "Andorra", "AD", "", { CallingCodeInfo{ "376", {}, { "XX XX XX" } } }, false },
{ "United Arab Emirates", "AE", "", { CallingCodeInfo{ "971", {}, { "XX XXX XXXX" } } }, false },
{ "Afghanistan", "AF", "", { CallingCodeInfo{ "93", {}, { "XXX XXX XXX" } } }, false },
{ "Antigua & Barbuda", "AG", "", { CallingCodeInfo{ "1268", {}, { "XXX XXXX" } } }, false },
{ "Anguilla", "AI", "", { CallingCodeInfo{ "1264", {}, { "XXX XXXX" } } }, false },
{ "Albania", "AL", "", { CallingCodeInfo{ "355", {}, { "XX XXX XXXX" } } }, false },
{ "Armenia", "AM", "", { CallingCodeInfo{ "374", {}, { "XX XXX XXX" } } }, false },
{ "Angola", "AO", "", { CallingCodeInfo{ "244", {}, { "XXX XXX XXX" } } }, false },
{ "Argentina", "AR", "", { CallingCodeInfo{ "54", {}, {} } }, false },
{ "American Samoa", "AS", "", { CallingCodeInfo{ "1684", {}, { "XXX XXXX" } } }, false },
{ "Austria", "AT", "", { CallingCodeInfo{ "43", {}, { "X XXXXXXXX" } } }, false },
{ "Australia", "AU", "", { CallingCodeInfo{ "61", {}, { "X XXXX XXXX" } } }, false },
{ "Aruba", "AW", "", { CallingCodeInfo{ "297", {}, { "XXX XXXX" } } }, false },
{ "Azerbaijan", "AZ", "", { CallingCodeInfo{ "994", {}, { "XX XXX XXXX" } } }, false },
{ "Bosnia & Herzegovina", "BA", "", { CallingCodeInfo{ "387", {}, { "XX XXX XXX" } } }, false },
{ "Barbados", "BB", "", { CallingCodeInfo{ "1246", {}, { "XXX XXXX" } } }, false },
{ "Bangladesh", "BD", "", { CallingCodeInfo{ "880", {}, { "XX XXX XXX" } } }, false },
{ "Belgium", "BE", "", { CallingCodeInfo{ "32", {}, { "XXX XX XX XX" } } }, false },
{ "Burkina Faso", "BF", "", { CallingCodeInfo{ "226", {}, { "XX XX XX XX" } } }, false },
{ "Bulgaria", "BG", "", { CallingCodeInfo{ "359", {}, {} } }, false },
{ "Bahrain", "BH", "", { CallingCodeInfo{ "973", {}, { "XXXX XXXX" } } }, false },
{ "Burundi", "BI", "", { CallingCodeInfo{ "257", {}, { "XX XX XXXX" } } }, false },
{ "Benin", "BJ", "", { CallingCodeInfo{ "229", {}, { "XX XXX XXX" } } }, false },
{ "Bermuda", "BM", "", { CallingCodeInfo{ "1441", {}, { "XXX XXXX" } } }, false },
{ "Brunei Darussalam", "BN", "", { CallingCodeInfo{ "673", {}, { "XXX XXXX" } } }, false },
{ "Bolivia", "BO", "", { CallingCodeInfo{ "591", {}, { "X XXX XXXX" } } }, false },
{ "Bonaire, Sint Eustatius & Saba", "BQ", "", { CallingCodeInfo{ "599", {}, {} } }, false },
{ "Brazil", "BR", "", { CallingCodeInfo{ "55", {}, { "XX XXXXX XXXX" } } }, false },
{ "Bahamas", "BS", "", { CallingCodeInfo{ "1242", {}, { "XXX XXXX" } } }, false },
{ "Bhutan", "BT", "", { CallingCodeInfo{ "975", {}, { "XX XXX XXX" } } }, false },
{ "Botswana", "BW", "", { CallingCodeInfo{ "267", {}, { "XX XXX XXX" } } }, false },
{ "Belarus", "BY", "", { CallingCodeInfo{ "375", {}, { "XX XXX XXXX" } } }, false },
{ "Belize", "BZ", "", { CallingCodeInfo{ "501", {}, {} } }, false },
{ "Canada", "CA", "", { CallingCodeInfo{ "1", { "403" }, { "XXX XXX XXXX" } } }, false },
{ "Congo (Dem. Rep.)", "CD", "", { CallingCodeInfo{ "243", {}, { "XX XXX XXXX" } } }, false },
{ "Central African Rep.", "CF", "", { CallingCodeInfo{ "236", {}, { "XX XX XX XX" } } }, false },
{ "Congo (Rep.)", "CG", "", { CallingCodeInfo{ "242", {}, { "XX XXX XXXX" } } }, false },
{ "Switzerland", "CH", "", { CallingCodeInfo{ "41", {}, { "XX XXX XXXX" } } }, false },
{ "Côte d'Ivoire", "CI", "", { CallingCodeInfo{ "225", {}, { "XX XX XX XXXX" } } }, false },
{ "Cook Islands", "CK", "", { CallingCodeInfo{ "682", {}, {} } }, false },
{ "Chile", "CL", "", { CallingCodeInfo{ "56", {}, { "X XXXX XXXX" } } }, false },
{ "Cameroon", "CM", "", { CallingCodeInfo{ "237", {}, { "XXXX XXXX" } } }, false },
{ "China", "CN", "", { CallingCodeInfo{ "86", {}, { "XXX XXXX XXXX" } } }, false },
{ "Colombia", "CO", "", { CallingCodeInfo{ "57", {}, { "XXX XXX XXXX" } } }, false },
{ "Costa Rica", "CR", "", { CallingCodeInfo{ "506", {}, { "XXXX XXXX" } } }, false },
{ "Cuba", "CU", "", { CallingCodeInfo{ "53", {}, { "X XXX XXXX" } } }, false },
{ "Cape Verde", "CV", "", { CallingCodeInfo{ "238", {}, { "XXX XXXX" } } }, false },
{ "Curaçao", "CW", "", { CallingCodeInfo{ "599", { "9" }, {} } }, false },
{ "Cyprus", "CY", "", { CallingCodeInfo{ "357", {}, { "XXXX XXXX" } } }, false },
{ "Czech Republic", "CZ", "", { CallingCodeInfo{ "420", {}, { "XXX XXX XXX" } } }, false },
{ "Germany", "DE", "", { CallingCodeInfo{ "49", {}, { "XXXX XXXXXXX" } } }, false },
{ "Djibouti", "DJ", "", { CallingCodeInfo{ "253", {}, { "XX XX XX XX" } } }, false },
{ "Denmark", "DK", "", { CallingCodeInfo{ "45", {}, { "XXXX XXXX" } } }, false },
{ "Dominica", "DM", "", { CallingCodeInfo{ "1767", {}, { "XXX XXXX" } } }, false },
{ "Dominican Rep.", "DO", "", { CallingCodeInfo{ "1809", {}, { "XXX XXXX" } } }, false },
{ "Algeria", "DZ", "", { CallingCodeInfo{ "213", {}, { "XXX XX XX XX" } } }, false },
{ "Ecuador", "EC", "", { CallingCodeInfo{ "593", {}, { "XX XXX XXXX" } } }, false },
{ "Estonia", "EE", "", { CallingCodeInfo{ "372", {}, { "XXXX XXXX" } } }, false },
{ "Egypt", "EG", "", { CallingCodeInfo{ "20", {}, { "XX XXXX XXXX" } } }, false },
{ "Eritrea", "ER", "", { CallingCodeInfo{ "291", {}, { "X XXX XXX" } } }, false },
{ "Spain", "ES", "", { CallingCodeInfo{ "34", {}, { "XXX XXX XXX" } } }, false },
{ "Ethiopia", "ET", "", { CallingCodeInfo{ "251", {}, { "XX XXX XXXX" } } }, false },
{ "Finland", "FI", "", { CallingCodeInfo{ "358", {}, {} } }, false },
{ "Fiji", "FJ", "", { CallingCodeInfo{ "679", {}, { "XXX XXXX" } } }, false },
{ "Falkland Islands", "FK", "", { CallingCodeInfo{ "500", {}, {} } }, false },
{ "Micronesia", "FM", "", { CallingCodeInfo{ "691", {}, {} } }, false },
{ "Faroe Islands", "FO", "", { CallingCodeInfo{ "298", {}, { "XXX XXX" } } }, false },
{ "France", "FR", "", { CallingCodeInfo{ "33", {}, { "X XX XX XX XX" } } }, false },
{ "Gabon", "GA", "", { CallingCodeInfo{ "241", {}, { "X XX XX XX" } } }, false },
{ "United Kingdom", "GB", "", { CallingCodeInfo{ "44", {}, { "XXXX XXXXXX" } } }, false },
{ "Grenada", "GD", "", { CallingCodeInfo{ "1473", {}, { "XXX XXXX" } } }, false },
{ "Georgia", "GE", "", { CallingCodeInfo{ "995", {}, { "XXX XXX XXX" } } }, false },
{ "French Guiana", "GF", "", { CallingCodeInfo{ "594", {}, {} } }, false },
{ "Ghana", "GH", "", { CallingCodeInfo{ "233", {}, { "XX XXX XXXX" } } }, false },
{ "Gibraltar", "GI", "", { CallingCodeInfo{ "350", {}, { "XXXX XXXX" } } }, false },
{ "Greenland", "GL", "", { CallingCodeInfo{ "299", {}, { "XXX XXX" } } }, false },
{ "Gambia", "GM", "", { CallingCodeInfo{ "220", {}, { "XXX XXXX" } } }, false },
{ "Guinea", "GN", "", { CallingCodeInfo{ "224", {}, { "XXX XXX XXX" } } }, false },
{ "Guadeloupe", "GP", "", { CallingCodeInfo{ "590", {}, { "XXX XX XX XX" } } }, false },
{ "Equatorial Guinea", "GQ", "", { CallingCodeInfo{ "240", {}, { "XXX XXX XXX" } } }, false },
{ "Greece", "GR", "", { CallingCodeInfo{ "30", {}, { "XXX XXX XXXX" } } }, false },
{ "Guatemala", "GT", "", { CallingCodeInfo{ "502", {}, { "X XXX XXXX" } } }, false },
{ "Guam", "GU", "", { CallingCodeInfo{ "1671", {}, { "XXX XXXX" } } }, false },
{ "Guinea-Bissau", "GW", "", { CallingCodeInfo{ "245", {}, { "XXX XXXX" } } }, false },
{ "Guyana", "GY", "", { CallingCodeInfo{ "592", {}, {} } }, false },
{ "Hong Kong", "HK", "", { CallingCodeInfo{ "852", {}, { "X XXX XXXX" } } }, false },
{ "Honduras", "HN", "", { CallingCodeInfo{ "504", {}, { "XXXX XXXX" } } }, false },
{ "Croatia", "HR", "", { CallingCodeInfo{ "385", {}, { "XX XXX XXX" } } }, false },
{ "Haiti", "HT", "", { CallingCodeInfo{ "509", {}, { "XXXX XXXX" } } }, false },
{ "Hungary", "HU", "", { CallingCodeInfo{ "36", {}, { "XXX XXX XXX" } } }, false },
{ "Indonesia", "ID", "", { CallingCodeInfo{ "62", {}, { "XXX XXXXXX" } } }, false },
{ "Ireland", "IE", "", { CallingCodeInfo{ "353", {}, { "XX XXX XXXX" } } }, false },
{ "Israel", "IL", "", { CallingCodeInfo{ "972", {}, { "XX XXX XXXX" } } }, false },
{ "India", "IN", "", { CallingCodeInfo{ "91", {}, { "XXXXX XXXXX" } } }, false },
{ "Diego Garcia", "IO", "", { CallingCodeInfo{ "246", {}, { "XXX XXXX" } } }, false },
{ "Iraq", "IQ", "", { CallingCodeInfo{ "964", {}, { "XXX XXX XXXX" } } }, false },
{ "Iran", "IR", "", { CallingCodeInfo{ "98", {}, { "XXX XXX XXXX" } } }, false },
{ "Iceland", "IS", "", { CallingCodeInfo{ "354", {}, { "XXX XXXX" } } }, false },
{ "Italy", "IT", "", { CallingCodeInfo{ "39", {}, { "XXX XXX XXX" } } }, false },
{ "Jamaica", "JM", "", { CallingCodeInfo{ "1876", {}, { "XXX XXXX" } } }, false },
{ "Jordan", "JO", "", { CallingCodeInfo{ "962", {}, { "X XXXX XXXX" } } }, false },
{ "Japan", "JP", "", { CallingCodeInfo{ "81", {}, { "XX XXXX XXXX" } } }, false },
{ "Kenya", "KE", "", { CallingCodeInfo{ "254", {}, { "XXX XXX XXX" } } }, false },
{ "Kyrgyzstan", "KG", "", { CallingCodeInfo{ "996", {}, { "XXX XXXXXX" } } }, false },
{ "Cambodia", "KH", "", { CallingCodeInfo{ "855", {}, { "XX XXX XXX" } } }, false },
{ "Kiribati", "KI", "", { CallingCodeInfo{ "686", {}, { "XXXX XXXX" } } }, false },
{ "Comoros", "KM", "", { CallingCodeInfo{ "269", {}, { "XXX XXXX" } } }, false },
{ "Saint Kitts & Nevis", "KN", "", { CallingCodeInfo{ "1869", {}, { "XXX XXXX" } } }, false },
{ "North Korea", "KP", "", { CallingCodeInfo{ "850", {}, {} } }, false },
{ "South Korea", "KR", "", { CallingCodeInfo{ "82", {}, { "XX XXXX XXX" } } }, false },
{ "Kuwait", "KW", "", { CallingCodeInfo{ "965", {}, { "XXXX XXXX" } } }, false },
{ "Cayman Islands", "KY", "", { CallingCodeInfo{ "1345", {}, { "XXX XXXX" } } }, false },
{ "Kazakhstan", "KZ", "", { CallingCodeInfo{ "7", { "6" }, { "XXX XXX XX XX" } } }, false },
{ "Laos", "LA", "", { CallingCodeInfo{ "856", {}, { "XX XX XXX XXX" } } }, false },
{ "Lebanon", "LB", "", { CallingCodeInfo{ "961", {}, { "XX XXX XXX" } } }, false },
{ "Saint Lucia", "LC", "", { CallingCodeInfo{ "1758", {}, { "XXX XXXX" } } }, false },
{ "Liechtenstein", "LI", "", { CallingCodeInfo{ "423", {}, { "XXX XXXX" } } }, false },
{ "Sri Lanka", "LK", "", { CallingCodeInfo{ "94", {}, { "XX XXX XXXX" } } }, false },
{ "Liberia", "LR", "", { CallingCodeInfo{ "231", {}, { "XX XXX XXXX" } } }, false },
{ "Lesotho", "LS", "", { CallingCodeInfo{ "266", {}, { "XX XXX XXX" } } }, false },
{ "Lithuania", "LT", "", { CallingCodeInfo{ "370", {}, { "XXX XXXXX" } } }, false },
{ "Luxembourg", "LU", "", { CallingCodeInfo{ "352", {}, { "XXX XXX XXX" } } }, false },
{ "Latvia", "LV", "", { CallingCodeInfo{ "371", {}, { "XXX XXXXX" } } }, false },
{ "Libya", "LY", "", { CallingCodeInfo{ "218", {}, { "XX XXX XXXX" } } }, false },
{ "Morocco", "MA", "", { CallingCodeInfo{ "212", {}, { "XX XXX XXXX" } } }, false },
{ "Monaco", "MC", "", { CallingCodeInfo{ "377", {}, { "XXXX XXXX" } } }, false },
{ "Moldova", "MD", "", { CallingCodeInfo{ "373", {}, { "XX XXX XXX" } } }, false },
{ "Montenegro", "ME", "", { CallingCodeInfo{ "382", {}, {} } }, false },
{ "Madagascar", "MG", "", { CallingCodeInfo{ "261", {}, { "XX XX XXX XX" } } }, false },
{ "Marshall Islands", "MH", "", { CallingCodeInfo{ "692", {}, {} } }, false },
{ "North Macedonia", "MK", "", { CallingCodeInfo{ "389", {}, { "XX XXX XXX" } } }, false },
{ "Mali", "ML", "", { CallingCodeInfo{ "223", {}, { "XXXX XXXX" } } }, false },
{ "Myanmar", "MM", "", { CallingCodeInfo{ "95", {}, {} } }, false },
{ "Mongolia", "MN", "", { CallingCodeInfo{ "976", {}, { "XX XX XXXX" } } }, false },
{ "Macau", "MO", "", { CallingCodeInfo{ "853", {}, { "XXXX XXXX" } } }, false },
{ "Northern Mariana Islands", "MP", "", { CallingCodeInfo{ "1670", {}, { "XXX XXXX" } } }, false },
{ "Martinique", "MQ", "", { CallingCodeInfo{ "596", {}, {} } }, false },
{ "Mauritania", "MR", "", { CallingCodeInfo{ "222", {}, { "XXXX XXXX" } } }, false },
{ "Montserrat", "MS", "", { CallingCodeInfo{ "1664", {}, { "XXX XXXX" } } }, false },
{ "Malta", "MT", "", { CallingCodeInfo{ "356", {}, { "XX XX XX XX" } } }, false },
{ "Mauritius", "MU", "", { CallingCodeInfo{ "230", {}, { "XXXX XXXX" } } }, false },
{ "Maldives", "MV", "", { CallingCodeInfo{ "960", {}, { "XXX XXXX" } } }, false },
{ "Malawi", "MW", "", { CallingCodeInfo{ "265", {}, { "XX XXX XXXX" } } }, false },
{ "Mexico", "MX", "", { CallingCodeInfo{ "52", {}, {} } }, false },
{ "Malaysia", "MY", "", { CallingCodeInfo{ "60", {}, { "XX XXXX XXXX" } } }, false },
{ "Mozambique", "MZ", "", { CallingCodeInfo{ "258", {}, { "XX XXX XXXX" } } }, false },
{ "Namibia", "NA", "", { CallingCodeInfo{ "264", {}, { "XX XXX XXXX" } } }, false },
{ "New Caledonia", "NC", "", { CallingCodeInfo{ "687", {}, {} } }, false },
{ "Niger", "NE", "", { CallingCodeInfo{ "227", {}, { "XX XX XX XX" } } }, false },
{ "Norfolk Island", "NF", "", { CallingCodeInfo{ "672", {}, {} } }, false },
{ "Nigeria", "NG", "", { CallingCodeInfo{ "234", {}, { "XX XXXX XXXX" } } }, false },
{ "Nicaragua", "NI", "", { CallingCodeInfo{ "505", {}, { "XXXX XXXX" } } }, false },
{ "Netherlands", "NL", "", { CallingCodeInfo{ "31", {}, { "X XX XX XX XX" } } }, false },
{ "Norway", "NO", "", { CallingCodeInfo{ "47", {}, { "XXXX XXXX" } } }, false },
{ "Nepal", "NP", "", { CallingCodeInfo{ "977", {}, { "XX XXXX XXXX" } } }, false },
{ "Nauru", "NR", "", { CallingCodeInfo{ "674", {}, {} } }, false },
{ "Niue", "NU", "", { CallingCodeInfo{ "683", {}, {} } }, false },
{ "New Zealand", "NZ", "", { CallingCodeInfo{ "64", {}, { "XXXX XXXX" } } }, false },
{ "Oman", "OM", "", { CallingCodeInfo{ "968", {}, { "XXXX XXXX" } } }, false },
{ "Panama", "PA", "", { CallingCodeInfo{ "507", {}, { "XXXX XXXX" } } }, false },
{ "Peru", "PE", "", { CallingCodeInfo{ "51", {}, { "XXX XXX XXX" } } }, false },
{ "French Polynesia", "PF", "", { CallingCodeInfo{ "689", {}, {} } }, false },
{ "Papua New Guinea", "PG", "", { CallingCodeInfo{ "675", {}, {} } }, false },
{ "Philippines", "PH", "", { CallingCodeInfo{ "63", {}, { "XXX XXX XXXX" } } }, false },
{ "Pakistan", "PK", "", { CallingCodeInfo{ "92", {}, { "XXX XXX XXXX" } } }, false },
{ "Poland", "PL", "", { CallingCodeInfo{ "48", {}, { "XXX XXX XXX" } } }, false },
{ "Saint Pierre & Miquelon", "PM", "", { CallingCodeInfo{ "508", {}, {} } }, false },
{ "Puerto Rico", "PR", "", { CallingCodeInfo{ "1787", {}, { "XXX XXXX" } } }, false },
{ "Palestine", "PS", "", { CallingCodeInfo{ "970", {}, { "XXX XX XXXX" } } }, false },
{ "Portugal", "PT", "", { CallingCodeInfo{ "351", {}, { "XXX XXX XXX" } } }, false },
{ "Palau", "PW", "", { CallingCodeInfo{ "680", {}, {} } }, false },
{ "Paraguay", "PY", "", { CallingCodeInfo{ "595", {}, { "XXX XXX XXX" } } }, false },
{ "Qatar", "QA", "", { CallingCodeInfo{ "974", {}, { "XX XXX XXX" } } }, false },
{ "Réunion", "RE", "", { CallingCodeInfo{ "262", {}, { "XXX XXX XXX" } } }, false },
{ "Romania", "RO", "", { CallingCodeInfo{ "40", {}, { "XXX XXX XXX" } } }, false },
{ "Serbia", "RS", "", { CallingCodeInfo{ "381", {}, { "XX XXX XXXX" } } }, false },
{ "Russian Federation", "RU", "", { CallingCodeInfo{ "7", {}, { "XXX XXX XXXX" } } }, false },
{ "Rwanda", "RW", "", { CallingCodeInfo{ "250", {}, { "XXX XXX XXX" } } }, false },
{ "Saudi Arabia", "SA", "", { CallingCodeInfo{ "966", {}, { "XX XXX XXXX" } } }, false },
{ "Solomon Islands", "SB", "", { CallingCodeInfo{ "677", {}, {} } }, false },
{ "Seychelles", "SC", "", { CallingCodeInfo{ "248", {}, { "X XX XX XX" } } }, false },
{ "Sudan", "SD", "", { CallingCodeInfo{ "249", {}, { "XX XXX XXXX" } } }, false },
{ "Sweden", "SE", "", { CallingCodeInfo{ "46", {}, { "XX XXX XXXX" } } }, false },
{ "Singapore", "SG", "", { CallingCodeInfo{ "65", {}, { "XXXX XXXX" } } }, false },
{ "Saint Helena", "SH", "", { CallingCodeInfo{ "247", {}, {} } }, false },
{ "Slovenia", "SI", "", { CallingCodeInfo{ "386", {}, { "XX XXX XXX" } } }, false },
{ "Slovakia", "SK", "", { CallingCodeInfo{ "421", {}, { "XXX XXX XXX" } } }, false },
{ "Sierra Leone", "SL", "", { CallingCodeInfo{ "232", {}, { "XX XXX XXX" } } }, false },
{ "San Marino", "SM", "", { CallingCodeInfo{ "378", {}, { "XXX XXX XXXX" } } }, false },
{ "Senegal", "SN", "", { CallingCodeInfo{ "221", {}, { "XX XXX XXXX" } } }, false },
{ "Somalia", "SO", "", { CallingCodeInfo{ "252", {}, { "XX XXX XXX" } } }, false },
{ "Suriname", "SR", "", { CallingCodeInfo{ "597", {}, { "XXX XXXX" } } }, false },
{ "South Sudan", "SS", "", { CallingCodeInfo{ "211", {}, { "XX XXX XXXX" } } }, false },
{ "São Tomé & Príncipe", "ST", "", { CallingCodeInfo{ "239", {}, { "XX XXXXX" } } }, false },
{ "El Salvador", "SV", "", { CallingCodeInfo{ "503", {}, { "XXXX XXXX" } } }, false },
{ "Sint Maarten", "SX", "", { CallingCodeInfo{ "1721", {}, { "XXX XXXX" } } }, false },
{ "Syria", "SY", "", { CallingCodeInfo{ "963", {}, { "XXX XXX XXX" } } }, false },
{ "Eswatini", "SZ", "", { CallingCodeInfo{ "268", {}, { "XXXX XXXX" } } }, false },
{ "Turks & Caicos Islands", "TC", "", { CallingCodeInfo{ "1649", {}, { "XXX XXXX" } } }, false },
{ "Chad", "TD", "", { CallingCodeInfo{ "235", {}, { "XX XX XX XX" } } }, false },
{ "Togo", "TG", "", { CallingCodeInfo{ "228", {}, { "XX XXX XXX" } } }, false },
{ "Thailand", "TH", "", { CallingCodeInfo{ "66", {}, { "X XXXX XXXX" } } }, false },
{ "Tajikistan", "TJ", "", { CallingCodeInfo{ "992", {}, { "XX XXX XXXX" } } }, false },
{ "Tokelau", "TK", "", { CallingCodeInfo{ "690", {}, {} } }, false },
{ "Timor-Leste", "TL", "", { CallingCodeInfo{ "670", {}, {} } }, false },
{ "Turkmenistan", "TM", "", { CallingCodeInfo{ "993", {}, { "XX XXXXXX" } } }, false },
{ "Tunisia", "TN", "", { CallingCodeInfo{ "216", {}, { "XX XXX XXX" } } }, false },
{ "Tonga", "TO", "", { CallingCodeInfo{ "676", {}, {} } }, false },
{ "Turkey", "TR", "", { CallingCodeInfo{ "90", {}, { "XXX XXX XXXX" } } }, false },
{ "Trinidad & Tobago", "TT", "", { CallingCodeInfo{ "1868", {}, { "XXX XXXX" } } }, false },
{ "Tuvalu", "TV", "", { CallingCodeInfo{ "688", {}, {} } }, false },
{ "Taiwan", "TW", "", { CallingCodeInfo{ "886", {}, { "XXX XXX XXX" } } }, false },
{ "Tanzania", "TZ", "", { CallingCodeInfo{ "255", {}, { "XX XXX XXXX" } } }, false },
{ "Ukraine", "UA", "", { CallingCodeInfo{ "380", {}, { "XX XXX XX XX" } } }, false },
{ "Uganda", "UG", "", { CallingCodeInfo{ "256", {}, { "XX XXX XXXX" } } }, false },
{ "USA", "US", "United States of America", { CallingCodeInfo{ "1", {}, { "XXX XXX XXXX" } } }, false },
{ "Uruguay", "UY", "", { CallingCodeInfo{ "598", {}, { "X XXX XXXX" } } }, false },
{ "Uzbekistan", "UZ", "", { CallingCodeInfo{ "998", {}, { "XX XXX XX XX" } } }, false },
{ "Saint Vincent & the Grenadines", "VC", "", { CallingCodeInfo{ "1784", {}, { "XXX XXXX" } } }, false },
{ "Venezuela", "VE", "", { CallingCodeInfo{ "58", {}, { "XXX XXX XXXX" } } }, false },
{ "British Virgin Islands", "VG", "", { CallingCodeInfo{ "1284", {}, { "XXX XXXX" } } }, false },
{ "US Virgin Islands", "VI", "", { CallingCodeInfo{ "1340", {}, { "XXX XXXX" } } }, false },
{ "Vietnam", "VN", "", { CallingCodeInfo{ "84", {}, {} } }, false },
{ "Vanuatu", "VU", "", { CallingCodeInfo{ "678", {}, {} } }, false },
{ "Wallis & Futuna", "WF", "", { CallingCodeInfo{ "681", {}, {} } }, false },
{ "Samoa", "WS", "", { CallingCodeInfo{ "685", {}, {} } }, false },
{ "Kosovo", "XK", "", { CallingCodeInfo{ "383", {}, { "XXXX XXXX" } } }, false },
{ "Yemen", "YE", "", { CallingCodeInfo{ "967", {}, { "XXX XXX XXX" } } }, false },
{ "South Africa", "ZA", "", { CallingCodeInfo{ "27", {}, { "XX XXX XXXX" } } }, false },
{ "Zambia", "ZM", "", { CallingCodeInfo{ "260", {}, { "XX XXX XXXX" } } }, false },
{ "Zimbabwe", "ZW", "", { CallingCodeInfo{ "263", {}, { "XX XXX XXXX" } } }, false },
} };
} // namespace
CountriesInstance::CountriesInstance() {
}
const std::vector<Info> &CountriesInstance::list() {
if (_list.empty()) {
_list = (FallbackList | ranges::to_vector);
}
return _list;
}
void CountriesInstance::setList(std::vector<Info> &&infos) {
_list = std::move(infos);
}
const CountriesInstance::Map &CountriesInstance::byCode() {
if (_byCode.empty()) {
_byCode.reserve(list().size());
for (const auto &entry : list()) {
for (const auto &code : entry.codes) {
_byCode.insert(code.callingCode, &entry);
}
}
}
return _byCode;
}
const CountriesInstance::Map &CountriesInstance::byISO2() {
if (_byISO2.empty()) {
_byISO2.reserve(list().size());
for (const auto &entry : list()) {
_byISO2.insert(entry.iso2, &entry);
}
}
return _byISO2;
}
QString CountriesInstance::validPhoneCode(QString fullCode) {
const auto &listByCode = byCode();
while (fullCode.length()) {
const auto i = listByCode.constFind(fullCode);
if (i != listByCode.cend()) {
return fullCode;
}
fullCode.chop(1);
}
return QString();
}
QString CountriesInstance::countryNameByISO2(const QString &iso) {
const auto &listByISO2 = byISO2();
const auto i = listByISO2.constFind(iso);
return (i != listByISO2.cend()) ? (*i)->name : QString();
}
QString CountriesInstance::countryISO2ByPhone(const QString &phone) {
const auto &listByCode = byCode();
const auto code = validPhoneCode(phone);
const auto i = listByCode.find(code);
return (i != listByCode.cend()) ? (*i)->iso2 : QString();
}
FormatResult CountriesInstance::format(FormatArgs args) {
// Ported from TDLib.
if (args.phone.isEmpty()) {
return FormatResult();
}
const auto &phoneNumber = args.phone;
const Info *bestCountryPtr = nullptr;
const CallingCodeInfo *bestCallingCodePtr = nullptr;
auto bestLength = size_t(0);
[[maybe_unused]] auto isPrefix = false;
for (const auto &country : list()) {
for (auto &callingCode : country.codes) {
if (phoneNumber.startsWith(callingCode.callingCode)) {
const auto codeSize = callingCode.callingCode.size();
for (const auto &prefix : callingCode.prefixes) {
if (prefix.startsWith(phoneNumber.midRef(codeSize))) {
isPrefix = true;
}
if ((codeSize + prefix.size()) > bestLength &&
phoneNumber.midRef(codeSize).startsWith(prefix)) {
bestCountryPtr = &country;
bestCallingCodePtr = &callingCode;
bestLength = codeSize + prefix.size();
}
}
}
if (callingCode.callingCode.startsWith(phoneNumber)) {
isPrefix = true;
}
}
}
if (bestCountryPtr == nullptr) {
return FormatResult{ .formatted = phoneNumber };
}
if (args.onlyCode) {
return FormatResult{ .code = bestCallingCodePtr->callingCode };
}
const auto codeSize = int(bestCallingCodePtr->callingCode.size());
if (args.onlyGroups && args.incomplete) {
auto groups = args.skipCode
? QVector<int>()
: QVector<int>{ codeSize };
auto groupSize = 0;
for (const auto &c : bestCallingCodePtr->patterns.front()) {
if (c == ' ') {
groups.push_back(base::take(groupSize));
} else {
groupSize++;
}
}
if (groupSize) {
groups.push_back(base::take(groupSize));
}
return FormatResult{ .groups = std::move(groups) };
}
const auto formattedPart = phoneNumber.mid(codeSize);
auto formattedResult = formattedPart;
auto groups = QVector<int>();
auto maxMatchedDigits = size_t(0);
for (auto &pattern : bestCallingCodePtr->patterns) {
auto resultGroups = QVector<int>();
auto result = QString();
auto currentPatternPos = int(0);
auto isFailedMatch = false;
auto matchedDigits = size_t(0);
auto groupSize = 0;
for (const auto &c : formattedPart) {
while ((currentPatternPos < pattern.size())
&& (pattern[currentPatternPos] != 'X')
&& !pattern[currentPatternPos].isDigit()) {
if (args.onlyGroups) {
resultGroups.push_back(groupSize);
groupSize = 0;
} else {
result += pattern[currentPatternPos];
}
currentPatternPos++;
}
if (!args.onlyGroups && (currentPatternPos == pattern.size())) {
result += ' ';
}
if ((currentPatternPos >= pattern.size())
|| (pattern[currentPatternPos] == 'X')) {
currentPatternPos++;
if (args.onlyGroups) {
groupSize++;
} else {
result += c;
}
} else {
if (c == pattern[currentPatternPos]) {
matchedDigits++;
currentPatternPos++;
if (args.onlyGroups) {
groupSize++;
} else {
result += c;
}
} else {
isFailedMatch = true;
break;
}
}
}
if (groupSize) {
resultGroups.push_back(groupSize);
}
if (!isFailedMatch && matchedDigits >= maxMatchedDigits) {
maxMatchedDigits = matchedDigits;
if (args.onlyGroups) {
groups = std::move(resultGroups);
} else {
formattedResult = std::move(result);
}
}
}
if (!args.skipCode) {
if (args.onlyGroups) {
groups.push_front(codeSize);
} else {
formattedResult = '+'
+ bestCallingCodePtr->callingCode
+ ' '
+ std::move(formattedResult);
}
}
return FormatResult{
.formatted = (args.onlyGroups
? QString()
: std::move(formattedResult)),
.groups = std::move(groups),
};
}
CountriesInstance &Instance() {
return SingleInstance;
}
QString ExtractPhoneCode(const QString &phone) {
return Instance().format({ .phone = phone, .onlyCode = true }).code;
}
} // namespace Countries

View File

@@ -0,0 +1,70 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Countries {
struct CallingCodeInfo {
QString callingCode;
std::vector<QString> prefixes;
std::vector<QString> patterns;
};
struct Info {
QString name;
QString iso2;
QString alternativeName;
std::vector<CallingCodeInfo> codes;
bool isHidden = false;
};
struct FormatResult {
QString formatted;
QVector<int> groups;
QString code;
};
struct FormatArgs {
QString phone;
bool onlyGroups = false;
bool skipCode = false;
bool incomplete = false;
bool onlyCode = false;
};
class CountriesInstance final {
public:
using Map = QHash<QString, const Info *>;
CountriesInstance();
[[nodiscard]] const std::vector<Info> &list();
void setList(std::vector<Info> &&infos);
[[nodiscard]] const Map &byCode();
[[nodiscard]] const Map &byISO2();
[[nodiscard]] QString validPhoneCode(QString fullCode);
[[nodiscard]] QString countryNameByISO2(const QString &iso);
[[nodiscard]] QString countryISO2ByPhone(const QString &phone);
[[nodiscard]] FormatResult format(FormatArgs args);
private:
std::vector<Info> _list;
Map _byCode;
Map _byISO2;
};
CountriesInstance &Instance();
[[nodiscard]] QString ExtractPhoneCode(const QString &phone);
} // namespace Countries

View File

@@ -0,0 +1,257 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "countries/countries_manager.h"
#include "core/application.h"
#include "countries/countries_instance.h"
#include "main/main_app_config.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "mtproto/mtp_instance.h"
#include <QtCore/QFile>
namespace Countries {
namespace {
struct FileData {
int hash = 0;
std::vector<Info> infos;
};
auto ProcessAlternativeName(Info &&info) {
if (info.name == u"USA"_q) {
info.alternativeName = u"United States of America"_q;
}
return std::move(info);
}
[[nodiscard]] QByteArray SerializeCodeInfo(const CallingCodeInfo &info) {
auto result = QByteArray();
auto stream = QDataStream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_3);
stream
<< info.callingCode
<< int(info.prefixes.size())
<< int(info.patterns.size());
for (const auto &prefix : info.prefixes) {
stream << prefix;
}
for (const auto &pattern : info.patterns) {
stream << pattern;
}
stream.device()->close();
return result;
}
[[nodiscard]] CallingCodeInfo DeserializeCodeInfo(const QByteArray &data) {
auto stream = QDataStream(data);
auto result = CallingCodeInfo();
auto prefixesCount = qint32(0);
auto patternsCount = qint32(0);
stream
>> result.callingCode
>> prefixesCount
>> patternsCount;
for (auto i = 0; i < prefixesCount; i++) {
auto prefix = QString();
stream >> prefix;
result.prefixes.push_back(std::move(prefix));
}
for (auto i = 0; i < patternsCount; i++) {
auto pattern = QString();
stream >> pattern;
result.patterns.push_back(std::move(pattern));
}
return (stream.status() != QDataStream::Ok)
? CallingCodeInfo()
: result;
}
[[nodiscard]] QByteArray SerializeInfo(const Info &info) {
auto result = QByteArray();
auto stream = QDataStream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_3);
stream
<< info.name
<< info.iso2
<< info.alternativeName
<< info.isHidden
<< int(info.codes.size());
for (const auto &code : info.codes) {
stream << SerializeCodeInfo(code);
}
stream.device()->close();
return result;
}
[[nodiscard]] Info DeserializeInfo(const QByteArray &data) {
auto stream = QDataStream(data);
auto result = Info();
auto codesCount = qint32(0);
stream
>> result.name
>> result.iso2
>> result.alternativeName
>> result.isHidden
>> codesCount;
for (auto i = 0; i < codesCount; i++) {
auto code = QByteArray();
stream >> code;
result.codes.push_back(DeserializeCodeInfo(code));
}
return (stream.status() != QDataStream::Ok)
? Info()
: result;
}
[[nodiscard]] QByteArray Serialize(const FileData &data) {
auto result = QByteArray();
auto stream = QDataStream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_3);
stream
<< data.hash
<< int(data.infos.size());
for (const auto &info : data.infos) {
stream << SerializeInfo(info);
}
stream.device()->close();
return result;
}
[[nodiscard]] FileData Deserialize(const QByteArray &data) {
auto stream = QDataStream(data);
auto hash = int(0);
auto infosCount = qint32(0);
auto infos = std::vector<Info>();
stream >> hash >> infosCount;
for (auto i = 0; i < infosCount; i++) {
auto info = QByteArray();
stream >> info;
infos.push_back(DeserializeInfo(info));
}
return (stream.status() != QDataStream::Ok)
? FileData()
: FileData{ .hash = hash, .infos = std::move(infos) };
}
} // namespace
Manager::Manager(not_null<Main::Domain*> domain)
: _path(cWorkingDir() + "tdata/countries") {
read();
domain->activeValue(
) | rpl::map([=](Main::Account *account) {
if (!account) {
_api.reset();
}
return account
? account->mtpMainSessionValue()
: rpl::never<not_null<MTP::Instance*>>();
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
_api.emplace(instance);
request();
}, _lifetime);
}
void Manager::read() {
auto file = QFile(_path);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
auto stream = QDataStream(&file);
auto data = QByteArray();
stream >> data;
auto fileData = Deserialize(data);
_hash = fileData.hash;
Instance().setList(base::take(fileData.infos));
}
void Manager::write() const {
auto file = QFile(_path);
if (!file.open(QIODevice::WriteOnly)) {
return;
}
auto stream = QDataStream(&file);
stream << Serialize({ .hash = _hash, .infos = Instance().list() });
}
void Manager::request() {
Expects(_api.has_value());
const auto convertMTP = [](const auto &vector, bool force = false) {
if (!vector) {
return std::vector<QString>(force ? 1 : 0);
}
return ranges::views::all(
vector->v
) | ranges::views::transform([](const MTPstring &s) -> QString {
return qs(s);
}) | ranges::to_vector;
};
_api->request(MTPhelp_GetCountriesList(
MTP_string(),
MTP_int(_hash)
)).done([=](const MTPhelp_CountriesList &result) {
result.match([&](const MTPDhelp_countriesList &data) {
_hash = data.vhash().v;
auto infos = std::vector<Info>();
for (const auto &country : data.vcountries().v) {
const auto &countryData = country.c_help_country();
if (countryData.is_hidden()) {
continue;
}
auto info = Info(ProcessAlternativeName({
.name = countryData.vdefault_name().v,
.iso2 = countryData.viso2().v,
.isHidden = countryData.is_hidden(),
}));
for (const auto &code : countryData.vcountry_codes().v) {
const auto &codeData = code.c_help_countryCode();
info.codes.push_back(CallingCodeInfo{
.callingCode = codeData.vcountry_code().v,
.prefixes = convertMTP(codeData.vprefixes(), true),
.patterns = convertMTP(codeData.vpatterns()),
});
}
infos.push_back(std::move(info));
}
Instance().setList(std::move(infos));
write();
}, [](const MTPDhelp_countriesListNotModified &data) {
});
_lifetime.destroy();
}).fail([=](const MTP::Error &error) {
LOG(("API Error: getting countries failed with error %1"
).arg(error.type()));
_lifetime.destroy();
}).send();
}
rpl::lifetime &Manager::lifetime() {
return _lifetime;
}
Manager::~Manager() {
}
} // namespace Countries

View File

@@ -0,0 +1,38 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/sender.h"
namespace Main {
class Domain;
} // namespace Main
namespace Countries {
class Manager final {
public:
Manager(not_null<Main::Domain*> domain);
~Manager();
void read();
void write() const;
rpl::lifetime &lifetime();
private:
void request();
std::optional<MTP::Sender> _api;
const QString _path;
int _hash = 0;
rpl::lifetime _lifetime;
};
} // namespace Countries

View File

@@ -57,7 +57,7 @@ struct PeerUpdate {
Notifications = (1U << 4),
Migration = (1U << 5),
UnavailableReason = (1U << 6),
PinnedMessages = (1U << 7),
ChatThemeEmoji = (1U << 7),
IsBlocked = (1U << 8),
MessagesTTL = (1U << 9),
@@ -118,8 +118,9 @@ struct HistoryUpdate {
BotKeyboard = (1U << 11),
CloudDraft = (1U << 12),
LocalDraftSet = (1U << 13),
PinnedMessages = (1U << 14),
LastUsedBit = (1U << 13),
LastUsedBit = (1U << 14),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
@@ -133,16 +134,17 @@ struct MessageUpdate {
enum class Flag : uint32 {
None = 0,
Edited = (1U << 0),
Destroyed = (1U << 1),
DialogRowRepaint = (1U << 2),
DialogRowRefresh = (1U << 3),
NewAdded = (1U << 4),
ReplyMarkup = (1U << 5),
BotCallbackSent = (1U << 6),
NewMaybeAdded = (1U << 7),
Edited = (1U << 0),
Destroyed = (1U << 1),
DialogRowRepaint = (1U << 2),
DialogRowRefresh = (1U << 3),
NewAdded = (1U << 4),
ReplyMarkup = (1U << 5),
BotCallbackSent = (1U << 6),
NewMaybeAdded = (1U << 7),
RepliesUnreadCount = (1U << 8),
LastUsedBit = (1U << 7),
LastUsedBit = (1U << 7),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@@ -887,6 +887,7 @@ void ApplyChannelUpdate(
session->changes().peerUpdated(channel, UpdateFlag::StickersSet);
}
}
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
channel->fullUpdated();
if (canViewAdmins != channel->canViewAdmins()

View File

@@ -428,6 +428,7 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
SetTopPinnedMessageId(chat, pinned->v);
}
chat->checkFolder(update.vfolder_id().value_or_empty());
chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
chat->fullUpdated();
chat->setAbout(qs(update.vabout()));

View File

@@ -31,21 +31,83 @@ constexpr auto kReloadTimeout = 3600 * crl::time(1000);
CloudTheme CloudTheme::Parse(
not_null<Main::Session*> session,
const MTPDtheme &data) {
const MTPDtheme &data,
bool parseSettings) {
const auto document = data.vdocument();
const auto paper = [&]() -> std::optional<WallPaper> {
if (const auto settings = data.vsettings()) {
return settings->match([&](const MTPDthemeSettings &data) {
return data.vwallpaper()
? WallPaper::Create(session, *data.vwallpaper())
: std::nullopt;
});
}
return {};
};
const auto outgoingMessagesColors = [&] {
auto result = std::vector<QColor>();
if (const auto settings = data.vsettings()) {
settings->match([&](const MTPDthemeSettings &data) {
if (const auto colors = data.vmessage_colors()) {
for (const auto color : colors->v) {
result.push_back(ColorFromSerialized(color));
}
}
});
}
return result;
};
const auto accentColor = [&]() -> std::optional<QColor> {
if (const auto settings = data.vsettings()) {
return settings->match([&](const MTPDthemeSettings &data) {
return ColorFromSerialized(data.vaccent_color().v);
});
}
return {};
};
const auto basedOnDark = [&] {
if (const auto settings = data.vsettings()) {
return settings->match([&](const MTPDthemeSettings &data) {
return data.vbase_theme().match([](
const MTPDbaseThemeNight &) {
return true;
}, [](const MTPDbaseThemeTinted &) {
return true;
}, [](const auto &) {
return false;
});
});
}
return false;
};
return {
data.vid().v,
data.vaccess_hash().v,
qs(data.vslug()),
qs(data.vtitle()),
(document
.id = data.vid().v,
.accessHash = data.vaccess_hash().v,
.slug = qs(data.vslug()),
.title = qs(data.vtitle()),
.documentId = (document
? session->data().processDocument(*document)->id
: DocumentId(0)),
data.is_creator() ? session->userId() : UserId(0),
data.vinstalls_count().v
.createdBy = data.is_creator() ? session->userId() : UserId(0),
.usersCount = data.vinstalls_count().value_or_empty(),
.paper = parseSettings ? paper() : std::nullopt,
.accentColor = parseSettings ? accentColor() : std::nullopt,
.outgoingMessagesColors = (parseSettings
? outgoingMessagesColors()
: std::vector<QColor>()),
.basedOnDark = parseSettings && basedOnDark(),
};
}
CloudTheme CloudTheme::Parse(
not_null<Main::Session*> session,
const MTPTheme &data,
bool parseSettings) {
return data.match([&](const MTPDtheme &data) {
return CloudTheme::Parse(session, data, parseSettings);
});
}
QString CloudThemes::Format() {
static const auto kResult = QString::fromLatin1("tdesktop");
return kResult;
@@ -256,14 +318,14 @@ void CloudThemes::scheduleReload() {
}
void CloudThemes::refresh() {
if (_refreshRquestId) {
if (_refreshRequestId) {
return;
}
_refreshRquestId = _session->api().request(MTPaccount_GetThemes(
_refreshRequestId = _session->api().request(MTPaccount_GetThemes(
MTP_string(Format()),
MTP_int(_hash)
)).done([=](const MTPaccount_Themes &result) {
_refreshRquestId = 0;
_refreshRequestId = 0;
result.match([&](const MTPDaccount_themes &data) {
_hash = data.vhash().v;
parseThemes(data.vthemes().v);
@@ -271,7 +333,7 @@ void CloudThemes::refresh() {
}, [](const MTPDaccount_themesNotModified &) {
});
}).fail([=](const MTP::Error &error) {
_refreshRquestId = 0;
_refreshRequestId = 0;
}).send();
}
@@ -279,13 +341,79 @@ void CloudThemes::parseThemes(const QVector<MTPTheme> &list) {
_list.clear();
_list.reserve(list.size());
for (const auto &theme : list) {
theme.match([&](const MTPDtheme &data) {
_list.push_back(CloudTheme::Parse(_session, data));
});
_list.push_back(CloudTheme::Parse(_session, theme));
}
checkCurrentTheme();
}
void CloudThemes::refreshChatThemes() {
if (_chatThemesRequestId) {
return;
}
_chatThemesRequestId = _session->api().request(MTPaccount_GetChatThemes(
MTP_int(_chatThemesHash)
)).done([=](const MTPaccount_ChatThemes &result) {
_chatThemesRequestId = 0;
result.match([&](const MTPDaccount_chatThemes &data) {
_hash = data.vhash().v;
parseChatThemes(data.vthemes().v);
_chatThemesUpdates.fire({});
}, [](const MTPDaccount_chatThemesNotModified &) {
});
}).fail([=](const MTP::Error &error) {
_chatThemesRequestId = 0;
}).send();
}
const std::vector<ChatTheme> &CloudThemes::chatThemes() const {
return _chatThemes;
}
rpl::producer<> CloudThemes::chatThemesUpdated() const {
return _chatThemesUpdates.events();
}
std::optional<ChatTheme> CloudThemes::themeForEmoji(
const QString &emoji) const {
if (emoji.isEmpty()) {
return {};
}
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
}
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
const QString &emoji) {
if (emoji.isEmpty()) {
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
} else if (auto result = themeForEmoji(emoji)) {
return rpl::single(std::move(result));
}
refreshChatThemes();
return rpl::single<std::optional<ChatTheme>>(
std::nullopt
) | rpl::then(chatThemesUpdated(
) | rpl::map([=] {
return themeForEmoji(emoji);
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
return theme.has_value();
}) | rpl::take(1));
}
void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
_chatThemes.clear();
_chatThemes.reserve(list.size());
for (const auto &theme : list) {
theme.match([&](const MTPDchatTheme &data) {
_chatThemes.push_back({
.emoji = qs(data.vemoticon()),
.light = CloudTheme::Parse(_session, data.vtheme(), true),
.dark = CloudTheme::Parse(_session, data.vdark_theme(), true),
});
});
}
}
void CloudThemes::checkCurrentTheme() {
const auto &object = Window::Theme::Background()->themeObject();
if (!object.cloud.id || !object.cloud.documentId) {

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/timer.h"
#include "data/data_wall_paper.h"
class DocumentData;
@@ -32,9 +33,25 @@ struct CloudTheme {
UserId createdBy = 0;
int usersCount = 0;
std::optional<WallPaper> paper;
std::optional<QColor> accentColor;
std::vector<QColor> outgoingMessagesColors;
bool basedOnDark = false;
static CloudTheme Parse(
not_null<Main::Session*> session,
const MTPDtheme &data);
const MTPDtheme &data,
bool parseSettings = false);
static CloudTheme Parse(
not_null<Main::Session*> session,
const MTPTheme &data,
bool parseSettings = false);
};
struct ChatTheme {
QString emoji;
CloudTheme light;
CloudTheme dark;
};
class CloudThemes final {
@@ -49,6 +66,14 @@ public:
void savedFromEditor(const CloudTheme &data);
void remove(uint64 cloudThemeId);
void refreshChatThemes();
[[nodiscard]] const std::vector<ChatTheme> &chatThemes() const;
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
const QString &emoji) const;
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
const QString &emoji);
void applyUpdate(const MTPTheme &theme);
void resolve(
@@ -90,13 +115,20 @@ private:
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback);
void invokeForLoaded(LoadingDocument &value);
void parseChatThemes(const QVector<MTPChatTheme> &list);
const not_null<Main::Session*> _session;
int32 _hash = 0;
mtpRequestId _refreshRquestId = 0;
mtpRequestId _refreshRequestId = 0;
mtpRequestId _resolveRequestId = 0;
std::vector<CloudTheme> _list;
rpl::event_stream<> _updates;
int32 _chatThemesHash = 0;
mtpRequestId _chatThemesRequestId = 0;
std::vector<ChatTheme> _chatThemes;
rpl::event_stream<> _chatThemesUpdates;
base::Timer _reloadCurrentTimer;
LoadingDocument _updatingFrom;
LoadingDocument _previewFrom;

View File

@@ -1,301 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_countries.h"
namespace Data {
namespace {
const std::array<CountryInfo, 231> List = { {
{ "Afghanistan", "AF", "93" },
{ "Albania", "AL", "355" },
{ "Algeria", "DZ", "213" },
{ "American Samoa", "AS", "1684" },
{ "Andorra", "AD", "376" },
{ "Angola", "AO", "244" },
{ "Anguilla", "AI", "1264" },
{ "Antigua & Barbuda", "AG", "1268" },
{ "Argentina", "AR", "54" },
{ "Armenia", "AM", "374" },
{ "Aruba", "AW", "297" },
{ "Australia", "AU", "61" },
{ "Austria", "AT", "43" },
{ "Azerbaijan", "AZ", "994" },
{ "Bahamas", "BS", "1242" },
{ "Bahrain", "BH", "973" },
{ "Bangladesh", "BD", "880" },
{ "Barbados", "BB", "1246" },
{ "Belarus", "BY", "375" },
{ "Belgium", "BE", "32" },
{ "Belize", "BZ", "501" },
{ "Benin", "BJ", "229" },
{ "Bermuda", "BM", "1441" },
{ "Bhutan", "BT", "975" },
{ "Bolivia", "BO", "591" },
{ "Bonaire, Sint Eustatius & Saba", "BQ", "599" },
{ "Bosnia & Herzegovina", "BA", "387" },
{ "Botswana", "BW", "267" },
{ "Brazil", "BR", "55" },
{ "British Virgin Islands", "VG", "1284" },
{ "Brunei Darussalam", "BN", "673" },
{ "Bulgaria", "BG", "359" },
{ "Burkina Faso", "BF", "226" },
{ "Burundi", "BI", "257" },
{ "Cambodia", "KH", "855" },
{ "Cameroon", "CM", "237" },
{ "Canada", "CA", "1" },
{ "Cape Verde", "CV", "238" },
{ "Cayman Islands", "KY", "1345" },
{ "Central African Republic", "CF", "236" },
{ "Chad", "TD", "235" },
{ "Chile", "CL", "56" },
{ "China", "CN", "86" },
{ "Colombia", "CO", "57" },
{ "Comoros", "KM", "269" },
{ "Congo", "CG", "242" },
{ "Congo, Democratic Republic", "CD", "243" },
{ "Cook Islands", "CK", "682" },
{ "Costa Rica", "CR", "506" },
{ "Croatia", "HR", "385" },
{ "Cuba", "CU", "53" },
{ "Curaçao", "CW", "599" },
{ "Cyprus", "CY", "357" },
{ "Czech Republic", "CZ", "420" },
{ "Côte d`Ivoire", "CI", "225" },
{ "Denmark", "DK", "45" },
{ "Diego Garcia", "IO", "246" },
{ "Djibouti", "DJ", "253" },
{ "Dominica", "DM", "1767" },
{ "Dominican Republic", "DO", "1" },
{ "Ecuador", "EC", "593" },
{ "Egypt", "EG", "20" },
{ "El Salvador", "SV", "503" },
{ "Equatorial Guinea", "GQ", "240" },
{ "Eritrea", "ER", "291" },
{ "Estonia", "EE", "372" },
{ "Ethiopia", "ET", "251" },
{ "Falkland Islands", "FK", "500" },
{ "Faroe Islands", "FO", "298" },
{ "Fiji", "FJ", "679" },
{ "Finland", "FI", "358" },
{ "France", "FR", "33" },
{ "French Guiana", "GF", "594" },
{ "French Polynesia", "PF", "689" },
{ "Gabon", "GA", "241" },
{ "Gambia", "GM", "220" },
{ "Georgia", "GE", "995" },
{ "Germany", "DE", "49" },
{ "Ghana", "GH", "233" },
{ "Gibraltar", "GI", "350" },
{ "Greece", "GR", "30" },
{ "Greenland", "GL", "299" },
{ "Grenada", "GD", "1473" },
{ "Guadeloupe", "GP", "590" },
{ "Guam", "GU", "1671" },
{ "Guatemala", "GT", "502" },
{ "Guinea", "GN", "224" },
{ "Guinea-Bissau", "GW", "245" },
{ "Guyana", "GY", "592" },
{ "Haiti", "HT", "509" },
{ "Honduras", "HN", "504" },
{ "Hong Kong", "HK", "852" },
{ "Hungary", "HU", "36" },
{ "Iceland", "IS", "354" },
{ "India", "IN", "91" },
{ "Indonesia", "ID", "62" },
{ "Iran", "IR", "98" },
{ "Iraq", "IQ", "964" },
{ "Ireland", "IE", "353" },
{ "Israel", "IL", "972" },
{ "Italy", "IT", "39" },
{ "Jamaica", "JM", "1876" },
{ "Japan", "JP", "81" },
{ "Jordan", "JO", "962" },
{ "Kazakhstan", "KZ", "7" },
{ "Kenya", "KE", "254" },
{ "Kiribati", "KI", "686" },
{ "Kuwait", "KW", "965" },
{ "Kyrgyzstan", "KG", "996" },
{ "Laos", "LA", "856" },
{ "Latvia", "LV", "371" },
{ "Lebanon", "LB", "961" },
{ "Lesotho", "LS", "266" },
{ "Liberia", "LR", "231" },
{ "Libya", "LY", "218" },
{ "Liechtenstein", "LI", "423" },
{ "Lithuania", "LT", "370" },
{ "Luxembourg", "LU", "352" },
{ "Macau", "MO", "853" },
{ "Macedonia", "MK", "389" },
{ "Madagascar", "MG", "261" },
{ "Malawi", "MW", "265" },
{ "Malaysia", "MY", "60" },
{ "Maldives", "MV", "960" },
{ "Mali", "ML", "223" },
{ "Malta", "MT", "356" },
{ "Marshall Islands", "MH", "692" },
{ "Martinique", "MQ", "596" },
{ "Mauritania", "MR", "222" },
{ "Mauritius", "MU", "230" },
{ "Mexico", "MX", "52" },
{ "Micronesia", "FM", "691" },
{ "Moldova", "MD", "373" },
{ "Monaco", "MC", "377" },
{ "Mongolia", "MN", "976" },
{ "Montenegro", "ME", "382" },
{ "Montserrat", "MS", "1664" },
{ "Morocco", "MA", "212" },
{ "Mozambique", "MZ", "258" },
{ "Myanmar", "MM", "95" },
{ "Namibia", "NA", "264" },
{ "Nauru", "NR", "674" },
{ "Nepal", "NP", "977" },
{ "Netherlands", "NL", "31" },
{ "New Caledonia", "NC", "687" },
{ "New Zealand", "NZ", "64" },
{ "Nicaragua", "NI", "505" },
{ "Niger", "NE", "227" },
{ "Nigeria", "NG", "234" },
{ "Niue", "NU", "683" },
{ "Norfolk Island", "NF", "672" },
{ "North Korea", "KP", "850" },
{ "Northern Mariana Islands", "MP", "1670" },
{ "Norway", "NO", "47" },
{ "Oman", "OM", "968" },
{ "Pakistan", "PK", "92" },
{ "Palau", "PW", "680" },
{ "Palestine", "PS", "970" },
{ "Panama", "PA", "507" },
{ "Papua New Guinea", "PG", "675" },
{ "Paraguay", "PY", "595" },
{ "Peru", "PE", "51" },
{ "Philippines", "PH", "63" },
{ "Poland", "PL", "48" },
{ "Portugal", "PT", "351" },
{ "Puerto Rico", "PR", "1" },
{ "Qatar", "QA", "974" },
{ "Romania", "RO", "40" },
{ "Russian Federation", "RU", "7" },
{ "Rwanda", "RW", "250" },
{ "Réunion", "RE", "262" },
{ "Saint Helena", "SH", "247" },
{ "Saint Helena", "SH2", "290" },
{ "Saint Kitts & Nevis", "KN", "1869" },
{ "Saint Lucia", "LC", "1758" },
{ "Saint Pierre & Miquelon", "PM", "508" },
{ "Saint Vincent & the Grenadines", "VC", "1784" },
{ "Samoa", "WS", "685" },
{ "San Marino", "SM", "378" },
{ "Saudi Arabia", "SA", "966" },
{ "Senegal", "SN", "221" },
{ "Serbia", "RS", "381" },
{ "Seychelles", "SC", "248" },
{ "Sierra Leone", "SL", "232" },
{ "Singapore", "SG", "65" },
{ "Sint Maarten", "SX", "1721" },
{ "Slovakia", "SK", "421" },
{ "Slovenia", "SI", "386" },
{ "Solomon Islands", "SB", "677" },
{ "Somalia", "SO", "252" },
{ "South Africa", "ZA", "27" },
{ "South Korea", "KR", "82" },
{ "South Sudan", "SS", "211" },
{ "Spain", "ES", "34" },
{ "Sri Lanka", "LK", "94" },
{ "Sudan", "SD", "249" },
{ "Suriname", "SR", "597" },
{ "Swaziland", "SZ", "268" },
{ "Sweden", "SE", "46" },
{ "Switzerland", "CH", "41" },
{ "Syrian Arab Republic", "SY", "963" },
{ "São Tomé & Príncipe", "ST", "239" },
{ "Taiwan", "TW", "886" },
{ "Tajikistan", "TJ", "992" },
{ "Tanzania", "TZ", "255" },
{ "Thailand", "TH", "66" },
{ "Timor-Leste", "TL", "670" },
{ "Togo", "TG", "228" },
{ "Tokelau", "TK", "690" },
{ "Tonga", "TO", "676" },
{ "Trinidad & Tobago", "TT", "1868" },
{ "Tunisia", "TN", "216" },
{ "Turkey", "TR", "90" },
{ "Turkmenistan", "TM", "993" },
{ "Turks & Caicos Islands", "TC", "1649" },
{ "Tuvalu", "TV", "688" },
{ "US Virgin Islands", "VI", "1340" },
{ "USA", "US", "1", "United States of America" },
{ "Uganda", "UG", "256" },
{ "Ukraine", "UA", "380" },
{ "United Arab Emirates", "AE", "971" },
{ "United Kingdom", "GB", "44" },
{ "Uruguay", "UY", "598" },
{ "Uzbekistan", "UZ", "998" },
{ "Vanuatu", "VU", "678" },
{ "Venezuela", "VE", "58" },
{ "Vietnam", "VN", "84" },
{ "Wallis & Futuna", "WF", "681" },
{ "Yemen", "YE", "967" },
{ "Zambia", "ZM", "260" },
{ "Zimbabwe", "ZW", "263" },
} };
QHash<QString, const CountryInfo *> ByCode;
QHash<QString, const CountryInfo *> ByISO2;
} // namespace
const std::array<CountryInfo, 231> &Countries() {
return List;
}
const QHash<QString, const CountryInfo *> &CountriesByCode() {
if (ByCode.isEmpty()) {
ByCode.reserve(List.size());
for (const auto &entry : List) {
ByCode.insert(entry.code, &entry);
}
}
return ByCode;
}
const QHash<QString, const CountryInfo *> &CountriesByISO2() {
if (ByISO2.isEmpty()) {
ByISO2.reserve(List.size());
for (const auto &entry : List) {
ByISO2.insert(entry.iso2, &entry);
}
}
return ByISO2;
}
QString ValidPhoneCode(QString fullCode) {
const auto &byCode = CountriesByCode();
while (fullCode.length()) {
const auto i = byCode.constFind(fullCode);
if (i != byCode.cend()) {
return (*i)->code;
}
fullCode.chop(1);
}
return QString();
}
QString CountryNameByISO2(const QString &iso) {
const auto &byISO2 = CountriesByISO2();
const auto i = byISO2.constFind(iso);
return (i != byISO2.cend()) ? QString::fromUtf8((*i)->name) : QString();
}
QString CountryISO2ByPhone(const QString &phone) {
const auto &byCode = Data::CountriesByCode();
const auto code = Data::ValidPhoneCode(phone);
const auto i = byCode.find(code);
return (i != byCode.cend()) ? QString::fromUtf8((*i)->iso2) : QString();
}
} // namespace Data

View File

@@ -1,29 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Data {
struct CountryInfo {
const char *name = nullptr;
const char *iso2 = nullptr;
const char *code = nullptr;
const char *alternativeName = nullptr;
};
[[nodiscard]] const std::array<CountryInfo, 231> &Countries();
[[nodiscard]] const QHash<QString, const CountryInfo *> &CountriesByCode();
[[nodiscard]] const QHash<QString, const CountryInfo *> &CountriesByISO2();
[[nodiscard]] QString ValidPhoneCode(QString fullCode);
[[nodiscard]] QString CountryNameByISO2(const QString &iso);
[[nodiscard]] QString CountryISO2ByPhone(const QString &phone);
} // namespace Data

View File

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "media/player/media_player_instance.h"
#include "platform/platform_file_utilities.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/text_utilities.h"
#include "window/window_session_controller.h"
@@ -33,8 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
namespace {
constexpr auto kMaxWallpaperSize = 3072;
void LaunchWithWarning(
// not_null<Window::Controller*> controller,
const QString &name,
@@ -175,19 +174,7 @@ bool IsIpRevealingName(const QString &filepath) {
);
}
[[nodiscard]] QImage ReadImage(
const QString &path,
const QByteArray &content,
bool gzipSvg) {
return Images::Read({
.path = path,
.content = content,
.maxSize = QSize(kMaxWallpaperSize, kMaxWallpaperSize),
.gzipSvg = gzipSvg,
}).image;
}
base::binary_guard ReadImageAsync(
base::binary_guard ReadBackgroundImageAsync(
not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,
FnMut<void(QImage&&)> done) {
@@ -201,7 +188,7 @@ base::binary_guard ReadImageAsync(
guard = result.make_guard(),
callback = std::move(done)
]() mutable {
auto image = ReadImage(path, bytes, gzipSvg);
auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
if (postprocess) {
image = postprocess(std::move(image));
}

View File

@@ -24,7 +24,7 @@ class DocumentMedia;
// [[nodiscard]] bool IsValidMediaFile(const QString &filepath);
[[nodiscard]] bool IsExecutableName(const QString &filepath);
[[nodiscard]] bool IsIpRevealingName(const QString &filepath);
base::binary_guard ReadImageAsync(
base::binary_guard ReadBackgroundImageAsync(
not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,
FnMut<void(QImage&&)> done);

View File

@@ -496,8 +496,9 @@ void GroupCall::reload() {
}
_reloadByQueuedUpdatesTimer.cancel();
const auto limit = 3;
_reloadRequestId = api().request(
MTPphone_GetGroupCall(input())
MTPphone_GetGroupCall(input(), MTP_int(limit))
).done([=](const MTPphone_GroupCall &result) {
if (requestParticipantsAfterReload(result)) {
_savedFull = result;

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "data/data_cloud_themes.h"
#include "base/unixtime.h"
#include "base/crc32hash.h"
#include "lang/lang_keys.h"
@@ -527,15 +528,6 @@ bool PeerData::canEditMessagesIndefinitely() const {
Unexpected("Peer type in PeerData::canEditMessagesIndefinitely.");
}
bool PeerData::hasPinnedMessages() const {
return _hasPinnedMessages;
}
void PeerData::setHasPinnedMessages(bool has) {
_hasPinnedMessages = has;
session().changes().peerUpdated(this, UpdateFlag::PinnedMessages);
}
bool PeerData::canExportChatHistory() const {
if (isRepliesChat()) {
return false;
@@ -1012,6 +1004,21 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
return 0;
}
void PeerData::setThemeEmoji(const QString &emoji) {
if (_themeEmoji == emoji) {
return;
}
_themeEmoji = emoji;
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
owner().cloudThemes().refreshChatThemes();
}
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
}
const QString &PeerData::themeEmoji() const {
return _themeEmoji;
}
void PeerData::setIsBlocked(bool is) {
const auto status = is
? BlockStatus::Blocked
@@ -1154,7 +1161,7 @@ void SetTopPinnedMessageId(not_null<PeerData*> peer, MsgId messageId) {
Storage::SharedMediaType::Pinned,
messageId,
{ messageId, ServerMaxMsgId }));
peer->setHasPinnedMessages(true);
peer->owner().history(peer)->setHasPinnedMessages(true);
}
FullMsgId ResolveTopPinnedId(

View File

@@ -406,9 +406,6 @@ public:
[[nodiscard]] bool canPinMessages() const;
[[nodiscard]] bool canEditMessagesIndefinitely() const;
[[nodiscard]] bool hasPinnedMessages() const;
void setHasPinnedMessages(bool has);
[[nodiscard]] bool canExportChatHistory() const;
// Returns true if about text was changed.
@@ -470,6 +467,9 @@ public:
[[nodiscard]] Data::GroupCall *groupCall() const;
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
void setThemeEmoji(const QString &emoji);
[[nodiscard]] const QString &themeEmoji() const;
const PeerId id;
QString name;
MTPinputPeer input = MTP_inputPeerEmpty();
@@ -508,13 +508,13 @@ private:
crl::time _lastFullUpdate = 0;
TimeId _ttlPeriod = 0;
bool _hasPinnedMessages = false;
Settings _settings = PeerSettings(PeerSetting::Unknown);
BlockStatus _blockStatus = BlockStatus::Unknown;
LoadedStatus _loadedStatus = LoadedStatus::Not;
QString _about;
QString _themeEmoji;
};

View File

@@ -101,6 +101,15 @@ rpl::producer<MessagesSlice> RepliesList::source(
_partLoaded.events(
) | rpl::start_with_next(pushDelayed, lifetime);
_history->session().data().channelDifferenceTooLong(
) | rpl::filter([=](not_null<ChannelData*> channel) {
if (_history->peer != channel || !_skippedAfter.has_value()) {
return false;
}
_skippedAfter = std::nullopt;
return true;
}) | rpl::start_with_next(pushDelayed, lifetime);
push();
return lifetime;
};
@@ -169,6 +178,64 @@ rpl::producer<int> RepliesList::fullCount() const {
return _fullCount.value() | rpl::filter_optional();
}
std::optional<int> RepliesList::fullUnreadCountAfter(
MsgId readTillId,
MsgId wasReadTillId,
std::optional<int> wasUnreadCountAfter) const {
Expects(readTillId >= wasReadTillId);
readTillId = std::max(readTillId, _rootId);
wasReadTillId = std::max(wasReadTillId, _rootId);
const auto backLoaded = (_skippedBefore == 0);
const auto frontLoaded = (_skippedAfter == 0);
const auto fullLoaded = backLoaded && frontLoaded;
const auto allUnread = (readTillId == _rootId)
|| (fullLoaded && _list.empty());
const auto countIncoming = [&](auto from, auto till) {
auto &owner = _history->owner();
const auto channelId = _history->channelId();
auto count = 0;
for (auto i = from; i != till; ++i) {
if (!owner.message(channelId, *i)->out()) {
++count;
}
}
return count;
};
if (allUnread && fullLoaded) {
// Should not happen too often unless the list is empty.
return countIncoming(begin(_list), end(_list));
} else if (frontLoaded && !_list.empty() && readTillId >= _list.front()) {
// Always "count by local data" if read till the end.
return 0;
} else if (wasReadTillId == readTillId) {
// Otherwise don't recount the same value over and over.
return wasUnreadCountAfter;
} else if (frontLoaded && !_list.empty() && readTillId >= _list.back()) {
// And count by local data if it is available and read-till changed.
return countIncoming(
begin(_list),
ranges::lower_bound(_list, readTillId, std::greater<>()));
} else if (_list.empty()) {
return std::nullopt;
} else if (wasUnreadCountAfter.has_value()
&& (frontLoaded || readTillId <= _list.front())
&& (backLoaded || wasReadTillId >= _list.back())) {
// Count how many were read since previous value.
const auto from = ranges::lower_bound(
_list,
readTillId,
std::greater<>());
const auto till = ranges::lower_bound(
from,
end(_list),
wasReadTillId,
std::greater<>());
return std::max(*wasUnreadCountAfter - countIncoming(from, till), 0);
}
return std::nullopt;
}
void RepliesList::injectRootMessageAndReverse(not_null<Viewer*> viewer) {
injectRootMessage(viewer);
ranges::reverse(viewer->slice.ids);

View File

@@ -31,6 +31,11 @@ public:
[[nodiscard]] rpl::producer<int> fullCount() const;
[[nodiscard]] std::optional<int> fullUnreadCountAfter(
MsgId readTillId,
MsgId wasReadTillId,
std::optional<int> wasUnreadCountAfter) const;
private:
struct Viewer;

View File

@@ -0,0 +1,153 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_send_action.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/view/history_view_send_action.h"
namespace Data {
SendActionManager::SendActionManager()
: _animation([=](crl::time now) { return callback(now); }) {
}
HistoryView::SendActionPainter *SendActionManager::lookupPainter(
not_null<History*> history,
MsgId rootId) {
if (!rootId) {
return history->sendActionPainter();
}
const auto i = _painters.find(history);
if (i == end(_painters)) {
return nullptr;
}
const auto j = i->second.find(rootId);
if (j == end(i->second)) {
return nullptr;
}
const auto result = j->second.lock();
if (!result) {
i->second.erase(j);
if (i->second.empty()) {
_painters.erase(i);
}
return nullptr;
}
crl::on_main([copy = result] {
});
return result.get();
}
void SendActionManager::registerFor(
not_null<History*> history,
MsgId rootId,
not_null<UserData*> user,
const MTPSendMessageAction &action,
TimeId when) {
if (history->peer->isSelf()) {
return;
}
const auto sendAction = lookupPainter(history, rootId);
if (!sendAction) {
return;
}
if (sendAction->updateNeedsAnimating(user, action)) {
user->madeAction(when);
if (!_sendActions.contains(std::pair{ history, rootId })) {
_sendActions.emplace(std::pair{ history, rootId }, crl::now());
_animation.start();
}
}
}
auto SendActionManager::repliesPainter(
not_null<History*> history,
MsgId rootId)
-> std::shared_ptr<SendActionPainter> {
auto &weak = _painters[history][rootId];
if (auto strong = weak.lock()) {
return strong;
}
auto result = std::make_shared<SendActionPainter>(history);
weak = result;
return result;
}
void SendActionManager::repliesPainterRemoved(
not_null<History*> history,
MsgId rootId) {
const auto i = _painters.find(history);
if (i == end(_painters)) {
return;
}
const auto j = i->second.find(rootId);
if (j == end(i->second) || j->second.lock()) {
return;
}
i->second.erase(j);
if (i->second.empty()) {
_painters.erase(i);
}
}
void SendActionManager::repliesPaintersClear(
not_null<History*> history,
not_null<UserData*> user) {
auto &map = _painters[history];
for (auto i = map.begin(); i != map.end();) {
if (auto strong = i->second.lock()) {
strong->clear(user);
++i;
} else {
i = map.erase(i);
}
}
if (map.empty()) {
_painters.erase(history);
}
}
bool SendActionManager::callback(crl::time now) {
for (auto i = begin(_sendActions); i != end(_sendActions);) {
const auto sendAction = lookupPainter(
i->first.first,
i->first.second);
if (sendAction && sendAction->updateNeedsAnimating(now)) {
++i;
} else {
i = _sendActions.erase(i);
}
}
return !_sendActions.empty();
}
auto SendActionManager::animationUpdated() const
-> rpl::producer<SendActionManager::AnimationUpdate> {
return _animationUpdate.events();
}
void SendActionManager::updateAnimation(AnimationUpdate &&update) {
_animationUpdate.fire(std::move(update));
}
auto SendActionManager::speakingAnimationUpdated() const
-> rpl::producer<not_null<History*>> {
return _speakingAnimationUpdate.events();
}
void SendActionManager::updateSpeakingAnimation(not_null<History*> history) {
_speakingAnimationUpdate.fire_copy(history);
}
void SendActionManager::clear() {
_sendActions.clear();
}
} // namespace Data

View File

@@ -0,0 +1,81 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/effects/animations.h"
class History;
namespace HistoryView {
class SendActionPainter;
} // namespace HistoryView
namespace Data {
class SendActionManager final {
public:
struct AnimationUpdate {
not_null<History*> history;
int left = 0;
int width = 0;
int height = 0;
bool textUpdated = false;
};
explicit SendActionManager();
void registerFor(
not_null<History*> history,
MsgId rootId,
not_null<UserData*> user,
const MTPSendMessageAction &action,
TimeId when);
[[nodiscard]] auto animationUpdated() const
-> rpl::producer<AnimationUpdate>;
void updateAnimation(AnimationUpdate &&update);
[[nodiscard]] auto speakingAnimationUpdated() const
-> rpl::producer<not_null<History*>>;
void updateSpeakingAnimation(not_null<History*> history);
using SendActionPainter = HistoryView::SendActionPainter;
[[nodiscard]] std::shared_ptr<SendActionPainter> repliesPainter(
not_null<History*> history,
MsgId rootId);
void repliesPainterRemoved(
not_null<History*> history,
MsgId rootId);
void repliesPaintersClear(
not_null<History*> history,
not_null<UserData*> user);
void clear();
private:
bool callback(crl::time now);
[[nodiscard]] SendActionPainter *lookupPainter(
not_null<History*> history,
MsgId rootId);
// When typing in this history started.
base::flat_map<
std::pair<not_null<History*>, MsgId>,
crl::time> _sendActions;
Ui::Animations::Basic _animation;
rpl::event_stream<AnimationUpdate> _animationUpdate;
rpl::event_stream<not_null<History*>> _speakingAnimationUpdate;
base::flat_map<
not_null<History*>,
base::flat_map<
MsgId,
std::weak_ptr<SendActionPainter>>> _painters;
};
} // namespace Data

View File

@@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
#include "history/view/media/history_view_media.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_send_action.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "storage/storage_account.h"
#include "storage/storage_encrypted_file.h"
@@ -53,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_poll.h"
#include "data/data_chat_filters.h"
#include "data/data_scheduled_messages.h"
#include "data/data_send_action.h"
#include "data/data_cloud_themes.h"
#include "data/data_streaming.h"
#include "data/data_media_rotation.h"
@@ -229,15 +229,13 @@ Session::Session(not_null<Main::Session*> session)
, _contactsNoChatsList(Dialogs::SortMode::Name)
, _ttlCheckTimer([=] { checkTTLs(); })
, _selfDestructTimer([=] { checkSelfDestructItems(); })
, _sendActionsAnimation([=](crl::time now) {
return sendActionsAnimationCallback(now);
})
, _pollsClosingTimer([=] { checkPollsClosings(); })
, _unmuteByFinishedTimer([=] { unmuteByFinished(); })
, _groups(this)
, _chatsFilters(std::make_unique<ChatFilters>(this))
, _scheduledMessages(std::make_unique<ScheduledMessages>(this))
, _cloudThemes(std::make_unique<CloudThemes>(session))
, _sendActionManager(std::make_unique<SendActionManager>())
, _streaming(std::make_unique<Streaming>(this))
, _mediaRotation(std::make_unique<MediaRotation>())
, _histories(std::make_unique<Histories>(this))
@@ -278,7 +276,7 @@ void Session::clear() {
// Optimization: clear notifications before destroying items.
Core::App().notifications().clearFromSession(_session);
_sendActions.clear();
_sendActionManager->clear();
_histories->unloadAll();
_scheduledMessages = nullptr;
@@ -1000,117 +998,6 @@ void Session::cancelForwarding(not_null<History*> history) {
Data::HistoryUpdate::Flag::ForwardDraft);
}
HistoryView::SendActionPainter *Session::lookupSendActionPainter(
not_null<History*> history,
MsgId rootId) {
if (!rootId) {
return history->sendActionPainter();
}
const auto i = _sendActionPainters.find(history);
if (i == end(_sendActionPainters)) {
return nullptr;
}
const auto j = i->second.find(rootId);
if (j == end(i->second)) {
return nullptr;
}
const auto result = j->second.lock();
if (!result) {
i->second.erase(j);
if (i->second.empty()) {
_sendActionPainters.erase(i);
}
return nullptr;
}
crl::on_main([copy = result] {
});
return result.get();
}
void Session::registerSendAction(
not_null<History*> history,
MsgId rootId,
not_null<UserData*> user,
const MTPSendMessageAction &action,
TimeId when) {
if (history->peer->isSelf()) {
return;
}
const auto sendAction = lookupSendActionPainter(history, rootId);
if (!sendAction) {
return;
}
if (sendAction->updateNeedsAnimating(user, action)) {
user->madeAction(when);
if (!_sendActions.contains(std::pair{ history, rootId })) {
_sendActions.emplace(std::pair{ history, rootId }, crl::now());
_sendActionsAnimation.start();
}
}
}
auto Session::repliesSendActionPainter(
not_null<History*> history,
MsgId rootId)
-> std::shared_ptr<SendActionPainter> {
auto &weak = _sendActionPainters[history][rootId];
if (auto strong = weak.lock()) {
return strong;
}
auto result = std::make_shared<SendActionPainter>(history);
weak = result;
return result;
}
void Session::repliesSendActionPainterRemoved(
not_null<History*> history,
MsgId rootId) {
const auto i = _sendActionPainters.find(history);
if (i == end(_sendActionPainters)) {
return;
}
const auto j = i->second.find(rootId);
if (j == end(i->second) || j->second.lock()) {
return;
}
i->second.erase(j);
if (i->second.empty()) {
_sendActionPainters.erase(i);
}
}
void Session::repliesSendActionPaintersClear(
not_null<History*> history,
not_null<UserData*> user) {
auto &map = _sendActionPainters[history];
for (auto i = map.begin(); i != map.end();) {
if (auto strong = i->second.lock()) {
strong->clear(user);
++i;
} else {
i = map.erase(i);
}
}
if (map.empty()) {
_sendActionPainters.erase(history);
}
}
bool Session::sendActionsAnimationCallback(crl::time now) {
for (auto i = begin(_sendActions); i != end(_sendActions);) {
const auto sendAction = lookupSendActionPainter(
i->first.first,
i->first.second);
if (sendAction && sendAction->updateNeedsAnimating(now)) {
++i;
} else {
i = _sendActions.erase(i);
}
}
return !_sendActions.empty();
}
bool Session::chatsListLoaded(Data::Folder *folder) {
return chatsList(folder)->loaded();
}
@@ -2301,25 +2188,6 @@ HistoryItem *Session::addNewMessage(
return result;
}
auto Session::sendActionAnimationUpdated() const
-> rpl::producer<SendActionAnimationUpdate> {
return _sendActionAnimationUpdate.events();
}
void Session::updateSendActionAnimation(
SendActionAnimationUpdate &&update) {
_sendActionAnimationUpdate.fire(std::move(update));
}
auto Session::speakingAnimationUpdated() const
-> rpl::producer<not_null<History*>> {
return _speakingAnimationUpdate.events();
}
void Session::updateSpeakingAnimation(not_null<History*> history) {
_speakingAnimationUpdate.fire_copy(history);
}
int Session::unreadBadge() const {
return computeUnreadBadge(_chatsList.unreadState());
}
@@ -2360,6 +2228,23 @@ void Session::notifyUnreadBadgeChanged() {
_unreadBadgeChanges.fire({});
}
std::optional<int> Session::countUnreadRepliesLocally(
not_null<HistoryItem*> root,
MsgId afterId) const {
auto result = std::optional<int>();
_unreadRepliesCountRequests.fire({
.root = root,
.afterId = afterId,
.result = &result,
});
return result;
}
auto Session::unreadRepliesCountRequests() const
-> rpl::producer<UnreadRepliesCountRequest> {
return _unreadRepliesCountRequests.events();
}
int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const {
const auto all = Core::App().settings().includeMutedCounter();
return std::max(state.marks - (all ? 0 : state.marksMuted), 0)
@@ -2619,13 +2504,14 @@ void Session::photoApplyFields(
}
const auto area = [](const MTPVideoSize &size) {
return size.match([](const MTPDvideoSize &data) {
return data.vw().v * data.vh().v;
return data.vsize().v ? (data.vw().v * data.vh().v) : 0;
});
};
return *ranges::max_element(
const auto result = *ranges::max_element(
sizes->v,
std::greater<>(),
area);
return (area(result) > 0) ? std::make_optional(result) : std::nullopt;
};
const auto useProgressive = (progressive != sizes.end());
const auto large = useProgressive

View File

@@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_location_manager.h"
#include "base/timer.h"
#include "base/flags.h"
#include "ui/effects/animations.h"
class Image;
class HistoryItem;
@@ -31,7 +30,6 @@ namespace HistoryView {
struct Group;
class Element;
class ElementDelegate;
class SendActionPainter;
} // namespace HistoryView
namespace Main {
@@ -52,6 +50,7 @@ class Folder;
class LocationPoint;
class WallPaper;
class ScheduledMessages;
class SendActionManager;
class ChatFilters;
class CloudThemes;
class Streaming;
@@ -92,6 +91,9 @@ public:
[[nodiscard]] ScheduledMessages &scheduledMessages() const {
return *_scheduledMessages;
}
[[nodiscard]] SendActionManager &sendActionManager() const {
return *_sendActionManager;
}
[[nodiscard]] CloudThemes &cloudThemes() const {
return *_cloudThemes;
}
@@ -192,13 +194,6 @@ public:
void cancelForwarding(not_null<History*> history);
void registerSendAction(
not_null<History*> history,
MsgId rootId,
not_null<UserData*> user,
const MTPSendMessageAction &action,
TimeId when);
[[nodiscard]] rpl::variable<bool> &contactsLoaded() {
return _contactsLoaded;
}
@@ -403,38 +398,26 @@ public:
MessageFlags localFlags,
NewMessageType type);
struct SendActionAnimationUpdate {
not_null<History*> history;
int width = 0;
int height = 0;
bool textUpdated = false;
};
[[nodiscard]] auto sendActionAnimationUpdated() const
-> rpl::producer<SendActionAnimationUpdate>;
void updateSendActionAnimation(SendActionAnimationUpdate &&update);
[[nodiscard]] auto speakingAnimationUpdated() const
-> rpl::producer<not_null<History*>>;
void updateSpeakingAnimation(not_null<History*> history);
using SendActionPainter = HistoryView::SendActionPainter;
[[nodiscard]] std::shared_ptr<SendActionPainter> repliesSendActionPainter(
not_null<History*> history,
MsgId rootId);
void repliesSendActionPainterRemoved(
not_null<History*> history,
MsgId rootId);
void repliesSendActionPaintersClear(
not_null<History*> history,
not_null<UserData*> user);
[[nodiscard]] int unreadBadge() const;
[[nodiscard]] bool unreadBadgeMuted() const;
[[nodiscard]] int unreadBadgeIgnoreOne(const Dialogs::Key &key) const;
[[nodiscard]] bool unreadBadgeMutedIgnoreOne(const Dialogs::Key &key) const;
[[nodiscard]] bool unreadBadgeMutedIgnoreOne(
const Dialogs::Key &key) const;
[[nodiscard]] int unreadOnlyMutedBadge() const;
[[nodiscard]] rpl::producer<> unreadBadgeChanges() const;
void notifyUnreadBadgeChanged();
[[nodiscard]] std::optional<int> countUnreadRepliesLocally(
not_null<HistoryItem*> root,
MsgId afterId) const;
struct UnreadRepliesCountRequest {
not_null<HistoryItem*> root;
MsgId afterId = 0;
not_null<std::optional<int>*> result;
};
[[nodiscard]] auto unreadRepliesCountRequests() const
-> rpl::producer<UnreadRepliesCountRequest>;
void selfDestructIn(not_null<HistoryItem*> item, crl::time delay);
[[nodiscard]] not_null<PhotoData*> photo(PhotoId id);
@@ -810,11 +793,6 @@ private:
const MTPMessageMedia &media,
TimeId date);
bool sendActionsAnimationCallback(crl::time now);
[[nodiscard]] SendActionPainter *lookupSendActionPainter(
not_null<History*> history,
MsgId rootId);
void setWallpapers(const QVector<MTPWallPaper> &data, int32 hash);
void checkPollsClosings();
@@ -855,6 +833,7 @@ private:
rpl::event_stream<DialogsRowReplacement> _dialogsRowReplacements;
rpl::event_stream<ChatListEntryRefresh> _chatListEntryRefreshes;
rpl::event_stream<> _unreadBadgeChanges;
rpl::event_stream<UnreadRepliesCountRequest> _unreadRepliesCountRequests;
Dialogs::MainList _chatsList;
Dialogs::IndexedList _contactsList;
@@ -875,12 +854,6 @@ private:
base::Timer _selfDestructTimer;
std::vector<FullMsgId> _selfDestructItems;
// When typing in this history started.
base::flat_map<
std::pair<not_null<History*>, MsgId>,
crl::time> _sendActions;
Ui::Animations::Basic _sendActionsAnimation;
std::unordered_map<
PhotoId,
std::unique_ptr<PhotoData>> _photos;
@@ -970,9 +943,6 @@ private:
int>;
std::unique_ptr<CredentialsWithGeneration> _passportCredentials;
rpl::event_stream<SendActionAnimationUpdate> _sendActionAnimationUpdate;
rpl::event_stream<not_null<History*>> _speakingAnimationUpdate;
std::vector<WallPaper> _wallpapers;
int32 _wallpapersHash = 0;
@@ -980,14 +950,10 @@ private:
std::unique_ptr<ChatFilters> _chatsFilters;
std::unique_ptr<ScheduledMessages> _scheduledMessages;
std::unique_ptr<CloudThemes> _cloudThemes;
std::unique_ptr<SendActionManager> _sendActionManager;
std::unique_ptr<Streaming> _streaming;
std::unique_ptr<MediaRotation> _mediaRotation;
std::unique_ptr<Histories> _histories;
base::flat_map<
not_null<History*>,
base::flat_map<
MsgId,
std::weak_ptr<SendActionPainter>>> _sendActionPainters;
std::unique_ptr<Stickers> _stickers;
MsgId _nonHistoryEntryId = ServerMaxMsgId;

View File

@@ -225,6 +225,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->setAbout(qs(update.vabout().value_or_empty()));
user->setCommonChatsCount(update.vcommon_chats_count().v);
user->checkFolder(update.vfolder_id().value_or_empty());
user->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
user->fullUpdated();
}

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "storage/serialize_common.h"
#include "ui/chat/chat_theme.h"
#include "core/application.h"
#include "main/main_session.h"
@@ -45,25 +46,6 @@ constexpr auto kVersion = 1;
return color ? SerializeColor(*color) : quint32(-1);
}
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
quint32 serialized) {
return (serialized == quint32(-1))
? std::nullopt
: std::make_optional(QColor(
int((serialized >> 16) & 0xFFU),
int((serialized >> 8) & 0xFFU),
int(serialized & 0xFFU)));
}
[[nodiscard]] QColor DefaultBackgroundColor() {
return QColor(213, 223, 233);
}
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
const tl::conditional<MTPint> &mtp) {
return mtp ? MaybeColorFromSerialized(mtp->v) : std::nullopt;
}
[[nodiscard]] std::vector<QColor> ColorsFromMTP(
const MTPDwallPaperSettings &data) {
auto result = std::vector<QColor>();
@@ -196,12 +178,6 @@ WallPaperId WallPaper::id() const {
return _id;
}
std::optional<QColor> WallPaper::backgroundColor() const {
return _backgroundColors.empty()
? std::nullopt
: std::make_optional(_backgroundColors.front());
}
const std::vector<QColor> WallPaper::backgroundColors() const {
return _backgroundColors;
}
@@ -704,93 +680,35 @@ bool IsCloudWallPaper(const WallPaper &paper) {
&& !details::IsTestingEditorWallPaper(paper);
}
QImage GenerateWallPaper(
QSize size,
const std::vector<QColor> &bg,
int gradientRotation,
float64 patternOpacity,
Fn<void(QPainter&)> drawPattern) {
auto result = bg.empty()
? Images::GenerateGradient(size, { DefaultBackgroundColor() })
: Images::GenerateGradient(size, bg, gradientRotation);
if (bg.size() > 1 && (!drawPattern || patternOpacity >= 0.)) {
result = Images::DitherImage(std::move(result));
}
if (drawPattern) {
auto p = QPainter(&result);
if (patternOpacity >= 0.) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(patternOpacity);
} else {
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
}
drawPattern(p);
if (patternOpacity < 0. && patternOpacity > -1.) {
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setOpacity(1. + patternOpacity);
p.fillRect(QRect{ QPoint(), size }, Qt::black);
}
}
return std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
QImage PreparePatternImage(
QImage pattern,
const std::vector<QColor> &bg,
int gradientRotation,
float64 patternOpacity) {
auto result = GenerateWallPaper(
pattern.size(),
bg,
gradientRotation,
patternOpacity,
[&](QPainter &p) {
p.drawImage(QRect(QPoint(), pattern.size()), pattern);
});
pattern = QImage();
return result;
}
QImage PrepareBlurredBackground(QImage image) {
constexpr auto kSize = 900;
constexpr auto kRadius = 24;
if (image.width() > kSize || image.height() > kSize) {
image = image.scaled(
kSize,
kSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
return Images::BlurLargeImage(image, kRadius);
}
QImage GenerateDitheredGradient(
const std::vector<QColor> &colors,
int rotation) {
constexpr auto kSize = 512;
const auto size = QSize(kSize, kSize);
if (colors.empty()) {
return Images::GenerateGradient(size, { DefaultBackgroundColor() });
}
auto result = Images::GenerateGradient(size, colors, rotation);
if (colors.size() > 1) {
result = Images::DitherImage(std::move(result));
}
return result;
}
QImage GenerateDitheredGradient(const Data::WallPaper &paper) {
if (paper.backgroundColors().empty()) {
return GenerateDitheredGradient({ DefaultBackgroundColor() }, 0);
}
return GenerateDitheredGradient(
return Ui::GenerateDitheredGradient(
paper.backgroundColors(),
paper.gradientRotation());
}
QColor ColorFromSerialized(quint32 serialized) {
return QColor(
int((serialized >> 16) & 0xFFU),
int((serialized >> 8) & 0xFFU),
int(serialized & 0xFFU));
}
QColor ColorFromSerialized(MTPint serialized) {
return ColorFromSerialized(serialized.v);
}
std::optional<QColor> MaybeColorFromSerialized(
quint32 serialized) {
return (serialized == quint32(-1))
? std::nullopt
: std::make_optional(ColorFromSerialized(serialized));
}
std::optional<QColor> MaybeColorFromSerialized(
const tl::conditional<MTPint> &mtp) {
return mtp ? std::make_optional(ColorFromSerialized(*mtp)) : std::nullopt;
}
namespace details {
WallPaper UninitializedWallPaper() {

View File

@@ -35,7 +35,6 @@ public:
void setLocalImageAsThumbnail(std::shared_ptr<Image> image);
[[nodiscard]] WallPaperId id() const;
[[nodiscard]] std::optional<QColor> backgroundColor() const;
[[nodiscard]] const std::vector<QColor> backgroundColors() const;
[[nodiscard]] DocumentData *document() const;
[[nodiscard]] Image *localThumbnail() const;
@@ -122,23 +121,15 @@ private:
[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
[[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper);
[[nodiscard]] QImage GenerateWallPaper(
QSize size,
const std::vector<QColor> &bg,
int gradientRotation,
float64 patternOpacity = 1.,
Fn<void(QPainter&)> drawPattern = nullptr);
[[nodiscard]] QImage PreparePatternImage(
QImage pattern,
const std::vector<QColor> &bg,
int gradientRotation,
float64 patternOpacity);
[[nodiscard]] QImage PrepareBlurredBackground(QImage image);
[[nodiscard]] QImage GenerateDitheredGradient(
const std::vector<QColor> &colors,
int rotation);
[[nodiscard]] QImage GenerateDitheredGradient(const WallPaper &paper);
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
[[nodiscard]] QColor ColorFromSerialized(MTPint serialized);
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
quint32 serialized);
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
const tl::conditional<MTPint> &mtp);
namespace details {
[[nodiscard]] WallPaper UninitializedWallPaper();

View File

@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_file.h"
#include "data/data_changes.h"
#include "data/stickers/data_stickers.h"
#include "data/data_send_action.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
@@ -166,11 +167,12 @@ InnerWidget::InnerWidget(
}
}, lifetime());
session().data().sendActionAnimationUpdated(
session().data().sendActionManager().animationUpdated(
) | rpl::start_with_next([=](
const Data::Session::SendActionAnimationUpdate &update) {
const Data::SendActionManager::AnimationUpdate &update) {
using RowPainter = Layout::RowPainter;
const auto updateRect = RowPainter::sendActionAnimationRect(
update.left,
update.width,
update.height,
width(),
@@ -181,7 +183,7 @@ InnerWidget::InnerWidget(
UpdateRowSection::Default | UpdateRowSection::Filtered);
}, lifetime());
session().data().speakingAnimationUpdated(
session().data().sendActionManager().speakingAnimationUpdated(
) | rpl::start_with_next([=](not_null<History*> history) {
updateDialogRowCornerStatus(history);
}, lifetime());

View File

@@ -943,11 +943,24 @@ void RowPainter::paint(
paintCounterCallback);
}
QRect RowPainter::sendActionAnimationRect(int animationWidth, int animationHeight, int fullWidth, bool textUpdated) {
auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding;
auto namewidth = fullWidth - nameleft - st::dialogsPadding.x();
auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip;
return QRect(nameleft, texttop, textUpdated ? namewidth : animationWidth, animationHeight);
QRect RowPainter::sendActionAnimationRect(
int animationLeft,
int animationWidth,
int animationHeight,
int fullWidth,
bool textUpdated) {
const auto nameleft = st::dialogsPadding.x()
+ st::dialogsPhotoSize
+ st::dialogsPhotoPadding;
const auto namewidth = fullWidth - nameleft - st::dialogsPadding.x();
const auto texttop = st::dialogsPadding.y()
+ st::msgNameFont->height
+ st::dialogsSkip;
return QRect(
nameleft + (textUpdated ? 0 : animationLeft),
texttop,
textUpdated ? namewidth : animationWidth,
animationHeight);
}
void PaintCollapsedRow(

View File

@@ -39,6 +39,7 @@ public:
crl::time ms,
bool displayUnreadInfo);
static QRect sendActionAnimationRect(
int animationLeft,
int animationWidth,
int animationHeight,
int fullWidth,

View File

@@ -164,7 +164,7 @@ void Widget::BottomButton::paintEvent(QPaintEvent *e) {
Widget::Widget(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Window::AbstractSectionWidget(parent, controller, PaintedBackground::Custom)
: Window::AbstractSectionWidget(parent, controller, nullptr)
, _api(&controller->session().mtp())
, _searchControls(this)
, _mainMenuToggle(_searchControls, st::dialogsMenuToggle)

View File

@@ -1123,6 +1123,10 @@ ServiceAction ParseServiceAction(
result.content = ActionGroupCallScheduled{
.date = data.vschedule_date().v,
};
}, [&](const MTPDmessageActionSetChatTheme &data) {
result.content = ActionSetChatTheme{
.emoji = qs(data.vemoticon()),
};
}, [](const MTPDmessageActionEmpty &data) {});
return result;
}

View File

@@ -475,6 +475,10 @@ struct ActionGroupCallScheduled {
TimeId date = 0;
};
struct ActionSetChatTheme {
QString emoji;
};
struct ServiceAction {
std::variant<
v::null_t,
@@ -503,7 +507,8 @@ struct ServiceAction {
ActionGroupCall,
ActionInviteToGroupCall,
ActionSetMessagesTTL,
ActionGroupCallScheduled> content;
ActionGroupCallScheduled,
ActionSetChatTheme> content;
};
ServiceAction ParseServiceAction(

View File

@@ -1095,8 +1095,17 @@ auto HtmlWriter::Wrap::pushMessage(
}, [&](const ActionGroupCallScheduled &data) {
const auto dateText = FormatDateTime(data.date);
return isChannel
? "Voice chat scheduled for " + dateText
? ("Voice chat scheduled for " + dateText)
: (serviceFrom + " scheduled a voice chat for " + dateText);
}, [&](const ActionSetChatTheme &data) {
if (data.emoji.isEmpty()) {
return isChannel
? "Channel theme was disabled"
: (serviceFrom + " disabled chat theme");
}
return isChannel
? ("Channel theme was changed to " + data.emoji).toUtf8()
: (serviceFrom + " changed chat theme to " + data.emoji).toUtf8();
}, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) {

View File

@@ -517,6 +517,12 @@ QByteArray SerializeMessage(
pushActor();
pushAction("group_call_scheduled");
push("schedule_date", data.date);
}, [&](const ActionSetChatTheme &data) {
pushActor();
pushAction("edit_chat_theme");
if (!data.emoji.isEmpty()) {
push("emoticon", data.emoji.toUtf8());
}
}, [](v::null_t) {});
if (v::is_null(message.action.content)) {

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "ui/chat/chat_theme.h"
#include "ui/widgets/popup_menu.h"
#include "ui/image/image.h"
#include "ui/text/text_utilities.h"
@@ -241,6 +242,14 @@ InnerWidget::InnerWidget(
st::historyAdminLogEmptyWidth
- st::historyAdminLogEmptyPadding.left()
- st::historyAdminLogEmptyPadding.left()) {
Window::ChatThemeValueFromPeer(
controller,
channel
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
setMouseTracking(true);
_scrollDateHideTimer.setCallback([=] { scrollDateHideByTimer(); });
session().data().viewRepaintRequest(
@@ -912,14 +921,13 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
return this->itemTop(elem) < bottom;
});
if (from != end) {
auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr,
.viewport = viewport.translated(0, -top),
.clip = clip.translated(0, -top),
.now = crl::now(),
};
auto context = _controller->preparePaintContext({
.theme = _theme.get(),
.visibleAreaTop = _visibleTop,
.visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
.clip = clip,
}).translated(0, -top);
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = i->get();

View File

@@ -58,6 +58,9 @@ public:
not_null<ChannelData*> channel);
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<Ui::ChatTheme*> theme() const {
return _theme.get();
}
[[nodiscard]] rpl::producer<> showSearchSignal() const;
[[nodiscard]] rpl::producer<int> scrollToSignal() const;
@@ -253,6 +256,7 @@ private:
MTP::Sender _api;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
std::shared_ptr<Ui::ChatTheme> _theme;
std::vector<OwnedItem> _items;
std::set<uint64> _eventIds;

View File

@@ -275,7 +275,7 @@ Widget::Widget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<ChannelData*> channel)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(channel))
, _scroll(this, st::historyScroll, false)
, _fixedBar(this, controller, channel)
, _fixedBarShadow(this)
@@ -306,11 +306,6 @@ Widget::Widget(
updateAdaptiveLayout();
}, lifetime());
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller, channel));
_inner->showSearchSignal(
) | rpl::start_with_next([=] {
@@ -475,7 +470,8 @@ void Widget::paintEvent(QPaintEvent *e) {
//auto ms = crl::now();
//_historyDownShown.step(ms);
SectionWidget::PaintBackground(controller(), this, e->rect());
const auto clip = e->rect();
SectionWidget::PaintBackground(controller(), _inner->theme(), this, clip);
}
void Widget::onScroll() {

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_chat_filters.h"
#include "data/data_scheduled_messages.h"
#include "data/data_send_action.h"
#include "data/data_folder.h"
#include "data/data_photo.h"
#include "data/data_channel.h"
@@ -458,7 +459,7 @@ void History::unpinAllMessages() {
Storage::SharedMediaRemoveAll(
peer->id,
Storage::SharedMediaType::Pinned));
peer->setHasPinnedMessages(false);
setHasPinnedMessages(false);
for (const auto &message : _messages) {
if (message->isPinned()) {
message->setIsPinned(false);
@@ -751,7 +752,7 @@ not_null<HistoryItem*> History::addNewToBack(
item->id,
{ from, till }));
if (sharedMediaTypes.test(Storage::SharedMediaType::Pinned)) {
peer->setHasPinnedMessages(true);
setHasPinnedMessages(true);
}
}
}
@@ -1023,7 +1024,7 @@ void History::applyServiceChanges(
Storage::SharedMediaType::Pinned,
{ id },
{ id, ServerMaxMsgId }));
peer->setHasPinnedMessages(true);
setHasPinnedMessages(true);
}
});
}
@@ -1061,6 +1062,8 @@ void History::applyServiceChanges(
}
}
}
}, [&](const MTPDmessageActionSetChatTheme &data) {
peer->setThemeEmoji(qs(data.vemoticon()));
}, [](const auto &) {
});
}
@@ -1086,7 +1089,7 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
if (from == item->author()) {
_sendActionPainter.clear(from);
owner().repliesSendActionPaintersClear(this, from);
owner().sendActionManager().repliesPaintersClear(this, from);
}
from->madeAction(item->date());
}
@@ -1399,7 +1402,7 @@ void History::addToSharedMedia(
std::move(medias[i]),
{ from, till }));
if (type == Storage::SharedMediaType::Pinned) {
peer->setHasPinnedMessages(true);
setHasPinnedMessages(true);
}
}
}
@@ -3121,6 +3124,15 @@ void History::removeBlock(not_null<HistoryBlock*> block) {
}
}
bool History::hasPinnedMessages() const {
return _hasPinnedMessages;
}
void History::setHasPinnedMessages(bool has) {
_hasPinnedMessages = has;
session().changes().historyUpdated(this, UpdateFlag::PinnedMessages);
}
History::~History() = default;
HistoryBlock::HistoryBlock(not_null<History*> history)

View File

@@ -412,6 +412,9 @@ public:
void setInboxReadTill(MsgId upTo);
std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
[[nodiscard]] bool hasPinnedMessages() const;
void setHasPinnedMessages(bool has);
// Still public data.
std::deque<std::unique_ptr<HistoryBlock>> blocks;
@@ -581,6 +584,7 @@ private:
bool _unreadMark = false;
bool _fakeUnreadWhileOpened = false;
bool _hasPinnedMessages = false;
// A pointer to the block that is currently being built.
// We hold this pointer so we can destroy it while building

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_context_menu.h"
#include "ui/chat/chat_theme.h"
#include "ui/widgets/popup_menu.h"
#include "ui/image/image.h"
#include "ui/toast/toast.h"
@@ -162,6 +163,14 @@ HistoryInner::HistoryInner(
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) {
Instance = this;
Window::ChatThemeValueFromPeer(
controller,
_peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
_touchSelectTimer.setSingleShot(true);
connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect()));
@@ -615,7 +624,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto item = view->data();
auto top = mtop + block->y() + view->y();
auto context = _controller->bubblesContext({
auto context = _controller->preparePaintContext({
.theme = _theme.get(),
.visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal,
.clip = clip,
@@ -663,7 +673,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto item = view->data();
auto readTill = (HistoryItem*)nullptr;
auto top = htop + block->y() + view->y();
auto context = _controller->bubblesContext({
auto context = _controller->preparePaintContext({
.theme = _theme.get(),
.visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal,
.visibleAreaWidth = width(),

View File

@@ -34,6 +34,7 @@ class SessionController;
} // namespace Window
namespace Ui {
class ChatTheme;
class PopupMenu;
enum class ReportReason;
class PathShiftGradient;
@@ -55,7 +56,10 @@ public:
not_null<Window::SessionController*> controller,
not_null<History*> history);
Main::Session &session() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<Ui::ChatTheme*> theme() const {
return _theme.get();
}
void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
@@ -344,6 +348,7 @@ private:
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme;
History *_migrated = nullptr;
int _contentWidth = 0;

View File

@@ -364,7 +364,7 @@ void HistoryItem::setIsPinned(bool pinned) {
Storage::SharedMediaType::Pinned,
id,
{ id, id }));
history()->peer->setHasPinnedMessages(true);
history()->setHasPinnedMessages(true);
} else {
_flags &= ~MessageFlag::Pinned;
history()->session().storage().remove(Storage::SharedMediaRemoveOne(
@@ -553,7 +553,7 @@ void HistoryItem::indexAsNewItem() {
types,
id));
if (types.test(Storage::SharedMediaType::Pinned)) {
_history->peer->setHasPinnedMessages(true);
_history->setHasPinnedMessages(true);
}
}
}

View File

@@ -223,7 +223,9 @@ public:
[[nodiscard]] virtual MsgId repliesInboxReadTill() const {
return MsgId(0);
}
virtual void setRepliesInboxReadTill(MsgId readTillId) {
virtual void setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) {
}
[[nodiscard]] virtual MsgId computeRepliesInboxReadTillFull() const {
return MsgId(0);
@@ -316,7 +318,10 @@ public:
}
virtual void clearReplies() {
}
virtual void changeRepliesCount(int delta, PeerId replier) {
virtual void changeRepliesCount(
int delta,
PeerId replier,
std::optional<bool> unread) {
}
virtual void setReplyToTop(MsgId replyToTop) {
}

View File

@@ -49,6 +49,7 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
MsgId repliesInboxReadTillId = 0;
MsgId repliesOutboxReadTillId = 0;
MsgId repliesMaxId = 0;
int repliesUnreadCount = -1; // unknown
ChannelId commentsMegagroupId = 0;
MsgId commentsRootId = 0;
};

View File

@@ -816,16 +816,30 @@ MsgId HistoryMessage::repliesInboxReadTill() const {
return 0;
}
void HistoryMessage::setRepliesInboxReadTill(MsgId readTillId) {
void HistoryMessage::setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) {
if (const auto views = Get<HistoryMessageViews>()) {
const auto newReadTillId = std::max(readTillId, 1);
if (newReadTillId > views->repliesInboxReadTillId) {
const auto ignore = (newReadTillId < views->repliesInboxReadTillId);
if (ignore) {
return;
}
const auto changed = (newReadTillId > views->repliesInboxReadTillId);
if (changed) {
const auto wasUnread = repliesAreComments() && areRepliesUnread();
views->repliesInboxReadTillId = newReadTillId;
if (wasUnread && !areRepliesUnread()) {
history()->owner().requestItemRepaint(this);
}
}
const auto wasUnreadCount = (views->repliesUnreadCount >= 0)
? std::make_optional(views->repliesUnreadCount)
: std::nullopt;
if (unreadCount != wasUnreadCount
&& (changed || unreadCount.has_value())) {
setUnreadRepliesCount(views, unreadCount.value_or(-1));
}
}
}
@@ -1808,10 +1822,27 @@ void HistoryMessage::refreshRepliesText(
}
}
void HistoryMessage::changeRepliesCount(int delta, PeerId replier) {
void HistoryMessage::changeRepliesCount(
int delta,
PeerId replier,
std::optional<bool> unread) {
const auto views = Get<HistoryMessageViews>();
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
if (!views || views->replies.count < 0) {
if (!views) {
return;
}
// Update unread count.
if (!unread) {
setUnreadRepliesCount(views, -1);
} else if (views->repliesUnreadCount >= 0 && *unread) {
setUnreadRepliesCount(
views,
std::max(views->repliesUnreadCount + delta, 0));
}
// Update full count.
if (views->replies.count < 0) {
return;
}
views->replies.count = std::max(views->replies.count + delta, 0);
@@ -1830,6 +1861,19 @@ void HistoryMessage::changeRepliesCount(int delta, PeerId replier) {
refreshRepliesText(views);
}
void HistoryMessage::setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count) {
// Track unread count in discussion forwards, not in the channel posts.
if (views->repliesUnreadCount == count || views->commentsMegagroupId) {
return;
}
views->repliesUnreadCount = count;
history()->session().changes().messageUpdated(
this,
Data::MessageUpdate::Flag::RepliesUnreadCount);
}
void HistoryMessage::setReplyToTop(MsgId replyToTop) {
const auto reply = Get<HistoryMessageReply>();
if (!reply
@@ -1877,19 +1921,23 @@ void HistoryMessage::changeReplyToTopCounter(
if (!top) {
return;
}
const auto changeFor = [&](not_null<HistoryItem*> item) {
if (const auto from = displayFrom()) {
item->changeRepliesCount(delta, from->id);
return;
}
item->changeRepliesCount(delta, PeerId());
};
auto unread = out() ? std::make_optional(false) : std::nullopt;
if (const auto views = top->Get<HistoryMessageViews>()) {
if (views->commentsMegagroupId) {
// This is a post in channel, we don't track its replies.
return;
}
if (views->repliesInboxReadTillId > 0) {
unread = !out() && (id > views->repliesInboxReadTillId);
}
}
const auto changeFor = [&](not_null<HistoryItem*> item) {
if (const auto from = displayFrom()) {
item->changeRepliesCount(delta, from->id, unread);
} else {
item->changeRepliesCount(delta, PeerId(), unread);
}
};
changeFor(top);
if (const auto original = top->lookupDiscussionPostOriginal()) {
changeFor(original);

View File

@@ -133,7 +133,10 @@ public:
void setForwardsCount(int count) override;
void setReplies(const MTPMessageReplies &data) override;
void clearReplies() override;
void changeRepliesCount(int delta, PeerId replier) override;
void changeRepliesCount(
int delta,
PeerId replier,
std::optional<bool> unread) override;
void setReplyToTop(MsgId replyToTop) override;
void setPostAuthor(const QString &author) override;
void setRealId(MsgId newId) override;
@@ -181,7 +184,9 @@ public:
[[nodiscard]] bool externalReply() const override;
[[nodiscard]] MsgId repliesInboxReadTill() const override;
void setRepliesInboxReadTill(MsgId readTillId) override;
void setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) override;
[[nodiscard]] MsgId computeRepliesInboxReadTillFull() const override;
[[nodiscard]] MsgId repliesOutboxReadTill() const override;
void setRepliesOutboxReadTill(MsgId readTillId) override;
@@ -250,6 +255,9 @@ private:
void refreshRepliesText(
not_null<HistoryMessageViews*> views,
bool forceResize = false);
void setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count);
static void FillForwardedInfo(
CreateConfig &config,

View File

@@ -427,6 +427,31 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result;
};
auto prepareSetChatTheme = [this](const MTPDmessageActionSetChatTheme &action) {
auto result = PreparedText{};
const auto text = qs(action.vemoticon());
if (!text.isEmpty()) {
if (isPost()) {
result.text = tr::lng_action_theme_changed_channel(tr::now, lt_emoji, text);
} else if (_from->isSelf()) {
result.text = tr::lng_action_you_theme_changed(tr::now, lt_emoji, text);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_theme_changed(tr::now, lt_from, fromLinkText(), lt_emoji, text);
}
} else {
if (isPost()) {
result.text = tr::lng_action_theme_disabled_channel(tr::now);
} else if (_from->isSelf()) {
result.text = tr::lng_action_you_theme_disabled(tr::now);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_theme_disabled(tr::now, lt_from, fromLinkText());
}
}
return result;
};
const auto messageText = action.match([&](
const MTPDmessageActionChatAddUser &data) {
return prepareChatAddUserText(data);
@@ -484,6 +509,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return prepareSetMessagesTTL(data);
}, [&](const MTPDmessageActionGroupCallScheduled &data) {
return prepareCallScheduledText(data.vschedule_date().v);
}, [&](const MTPDmessageActionSetChatTheme &data) {
return prepareSetChatTheme(data);
}, [](const MTPDmessageActionEmpty &) {
return PreparedText{ tr::lng_message_empty(tr::now) };
});

View File

@@ -105,6 +105,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/report_box.h"
#include "ui/chat/pinned_bar.h"
#include "ui/chat/group_call_bar.h"
#include "ui/chat/chat_theme.h"
#include "ui/widgets/popup_menu.h"
#include "ui/item_text_options.h"
#include "ui/unread_badge.h"
@@ -166,12 +167,24 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
crl::time(1000) * 8);
}
[[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
not_null<Window::SessionController*> controller) {
return controller->activeChatValue(
) | rpl::map([](const Dialogs::Key &key) {
const auto history = key.history();
return history ? history->peer.get() : nullptr;
});
}
} // namespace
HistoryWidget::HistoryWidget(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Window::AbstractSectionWidget(parent, controller, PaintedBackground::Section)
: Window::AbstractSectionWidget(
parent,
controller,
ActivePeerValue(controller))
, _api(&controller->session().mtp())
, _updateEditTimeLeftDisplay([=] { updateField(); })
, _fieldBarCancel(this, st::historyReplyCancel)
@@ -432,11 +445,6 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
controller->repaintBackgroundRequests(
) | rpl::start_with_next([=] {
update();
}, lifetime());
session().data().newItemAdded(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
newItemAdded(item);
@@ -504,40 +512,52 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
session().changes().historyUpdates(
Data::HistoryUpdate::Flag::MessageSent
| Data::HistoryUpdate::Flag::ForwardDraft
| Data::HistoryUpdate::Flag::BotKeyboard
| Data::HistoryUpdate::Flag::CloudDraft
| Data::HistoryUpdate::Flag::UnreadMentions
| Data::HistoryUpdate::Flag::UnreadView
| Data::HistoryUpdate::Flag::TopPromoted
| Data::HistoryUpdate::Flag::LocalMessages
HistoryUpdateFlag::MessageSent
| HistoryUpdateFlag::ForwardDraft
| HistoryUpdateFlag::BotKeyboard
| HistoryUpdateFlag::CloudDraft
| HistoryUpdateFlag::UnreadMentions
| HistoryUpdateFlag::UnreadView
| HistoryUpdateFlag::TopPromoted
| HistoryUpdateFlag::LocalMessages
| HistoryUpdateFlag::PinnedMessages
) | rpl::filter([=](const Data::HistoryUpdate &update) {
if (_migrated && update.history.get() == _migrated) {
if (_pinnedTracker
&& (update.flags & HistoryUpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
}
return (_history == update.history.get());
}) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
if (update.flags & Data::HistoryUpdate::Flag::MessageSent) {
const auto flags = update.flags;
if (flags & HistoryUpdateFlag::MessageSent) {
synteticScrollToY(_scroll->scrollTopMax());
}
if (update.flags & Data::HistoryUpdate::Flag::ForwardDraft) {
if (flags & HistoryUpdateFlag::ForwardDraft) {
updateForwarding();
}
if (update.flags & Data::HistoryUpdate::Flag::BotKeyboard) {
if (flags & HistoryUpdateFlag::BotKeyboard) {
updateBotKeyboard(update.history);
}
if (update.flags & Data::HistoryUpdate::Flag::CloudDraft) {
if (flags & HistoryUpdateFlag::CloudDraft) {
applyCloudDraft(update.history);
}
if (update.flags & Data::HistoryUpdate::Flag::LocalMessages) {
if (flags & HistoryUpdateFlag::LocalMessages) {
updateSendButtonType();
}
if (update.flags & Data::HistoryUpdate::Flag::UnreadMentions) {
if (flags & HistoryUpdateFlag::UnreadMentions) {
updateUnreadMentionsVisibility();
}
if (update.flags & Data::HistoryUpdate::Flag::UnreadView) {
if (flags & HistoryUpdateFlag::UnreadView) {
unreadCountUpdated();
}
if (update.flags & Data::HistoryUpdate::Flag::TopPromoted) {
if (_pinnedTracker && (flags & HistoryUpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
if (flags & HistoryUpdateFlag::TopPromoted) {
updateHistoryGeometry();
updateControlsVisibility();
updateControlsGeometry();
@@ -545,25 +565,27 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
using MessageUpdateFlag = Data::MessageUpdate::Flag;
session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
| Data::MessageUpdate::Flag::Edited
| Data::MessageUpdate::Flag::ReplyMarkup
| Data::MessageUpdate::Flag::BotCallbackSent
MessageUpdateFlag::Destroyed
| MessageUpdateFlag::Edited
| MessageUpdateFlag::ReplyMarkup
| MessageUpdateFlag::BotCallbackSent
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
const auto flags = update.flags;
if (flags & MessageUpdateFlag::Destroyed) {
itemRemoved(update.item);
return;
}
if (update.flags & Data::MessageUpdate::Flag::Edited) {
if (flags & MessageUpdateFlag::Edited) {
itemEdited(update.item);
}
if (update.flags & Data::MessageUpdate::Flag::ReplyMarkup) {
if (flags & MessageUpdateFlag::ReplyMarkup) {
if (_keyboard->forMsgId() == update.item->fullId()) {
updateBotKeyboard(update.item->history(), true);
}
}
if (update.flags & Data::MessageUpdate::Flag::BotCallbackSent) {
if (flags & MessageUpdateFlag::BotCallbackSent) {
botCallbackSent(update.item);
}
}, lifetime());
@@ -574,45 +596,38 @@ HistoryWidget::HistoryWidget(
}
});
using UpdateFlag = Data::PeerUpdate::Flag;
using PeerUpdateFlag = Data::PeerUpdate::Flag;
session().changes().peerUpdates(
UpdateFlag::Rights
| UpdateFlag::Migration
| UpdateFlag::UnavailableReason
| UpdateFlag::IsBlocked
| UpdateFlag::Admins
| UpdateFlag::Members
| UpdateFlag::OnlineStatus
| UpdateFlag::Notifications
| UpdateFlag::ChannelAmIn
| UpdateFlag::ChannelLinkedChat
| UpdateFlag::Slowmode
| UpdateFlag::BotStartToken
| UpdateFlag::PinnedMessages
| UpdateFlag::MessagesTTL
PeerUpdateFlag::Rights
| PeerUpdateFlag::Migration
| PeerUpdateFlag::UnavailableReason
| PeerUpdateFlag::IsBlocked
| PeerUpdateFlag::Admins
| PeerUpdateFlag::Members
| PeerUpdateFlag::OnlineStatus
| PeerUpdateFlag::Notifications
| PeerUpdateFlag::ChannelAmIn
| PeerUpdateFlag::ChannelLinkedChat
| PeerUpdateFlag::Slowmode
| PeerUpdateFlag::BotStartToken
| PeerUpdateFlag::MessagesTTL
) | rpl::filter([=](const Data::PeerUpdate &update) {
if (_migrated && update.peer.get() == _migrated->peer) {
if (_pinnedTracker
&& (update.flags & UpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
}
return (update.peer.get() == _peer);
}) | rpl::map([](const Data::PeerUpdate &update) {
return update.flags;
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
if (flags & UpdateFlag::Rights) {
if (flags & PeerUpdateFlag::Rights) {
checkPreview();
updateStickersByEmoji();
updateFieldPlaceholder();
}
if (flags & UpdateFlag::Migration) {
if (flags & PeerUpdateFlag::Migration) {
handlePeerMigration();
}
if (flags & UpdateFlag::Notifications) {
if (flags & PeerUpdateFlag::Notifications) {
updateNotifyControls();
}
if (flags & UpdateFlag::UnavailableReason) {
if (flags & PeerUpdateFlag::UnavailableReason) {
const auto unavailable = _peer->computeUnavailableReason();
if (!unavailable.isEmpty()) {
controller->showBackFromStack();
@@ -620,26 +635,23 @@ HistoryWidget::HistoryWidget(
return;
}
}
if (flags & UpdateFlag::BotStartToken) {
if (flags & PeerUpdateFlag::BotStartToken) {
updateControlsVisibility();
updateControlsGeometry();
}
if (flags & UpdateFlag::Slowmode) {
if (flags & PeerUpdateFlag::Slowmode) {
updateSendButtonType();
}
if (flags & (UpdateFlag::IsBlocked
| UpdateFlag::Admins
| UpdateFlag::Members
| UpdateFlag::OnlineStatus
| UpdateFlag::Rights
| UpdateFlag::ChannelAmIn
| UpdateFlag::ChannelLinkedChat)) {
if (flags & (PeerUpdateFlag::IsBlocked
| PeerUpdateFlag::Admins
| PeerUpdateFlag::Members
| PeerUpdateFlag::OnlineStatus
| PeerUpdateFlag::Rights
| PeerUpdateFlag::ChannelAmIn
| PeerUpdateFlag::ChannelLinkedChat)) {
handlePeerUpdate();
}
if (_pinnedTracker && (flags & UpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
if (flags & UpdateFlag::MessagesTTL) {
if (flags & PeerUpdateFlag::MessagesTTL) {
checkMessagesTTL();
}
}, lifetime());
@@ -892,6 +904,19 @@ void HistoryWidget::initTabbedSelector() {
) | filter | rpl::start_with_next([=] {
selector->showMenuWithType(sendMenuType());
}, lifetime());
selector->choosingStickerUpdated(
) | rpl::start_with_next([=](const Selector::Action &data) {
if (!_history) {
return;
}
const auto type = Api::SendProgressType::ChooseSticker;
if (data != Selector::Action::Cancel) {
session().sendProgressManager().update(_history, type);
} else {
session().sendProgressManager().cancel(_history, type);
}
}, lifetime());
}
void HistoryWidget::supportInitAutocomplete() {
@@ -1428,14 +1453,7 @@ void HistoryWidget::saveCloudDraft() {
void HistoryWidget::writeDraftTexts() {
Expects(_history != nullptr);
session().local().writeDrafts(
_history,
_editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
Storage::MessageDraft{
_editMsgId ? _editMsgId : _replyToId,
_field->getTextWithTags(),
_previewState,
});
session().local().writeDrafts(_history);
if (_migrated) {
_migrated->clearDrafts();
session().local().writeDrafts(_migrated);
@@ -1445,10 +1463,7 @@ void HistoryWidget::writeDraftTexts() {
void HistoryWidget::writeDraftCursors() {
Expects(_history != nullptr);
session().local().writeDraftCursors(
_history,
_editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
MessageCursor(_field));
session().local().writeDraftCursors(_history);
if (_migrated) {
_migrated->clearDrafts();
session().local().writeDraftCursors(_migrated);
@@ -1679,7 +1694,8 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
clearFieldText(0, fieldHistoryAction);
_field->setFocus();
_replyEditMsg = nullptr;
_editMsgId = _replyToId = 0;
_replyToId = 0;
setEditMsgId(0);
if (fieldWillBeHiddenAfterEdit) {
updateControlsVisibility();
updateControlsGeometry();
@@ -1703,11 +1719,11 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
_replyEditMsg = nullptr;
if (const auto editDraft = _history->localEditDraft()) {
_editMsgId = editDraft->msgId;
setEditMsgId(editDraft->msgId);
_replyToId = 0;
} else {
_editMsgId = 0;
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
setEditMsgId(0);
}
updateCmdStartShown();
updateControlsVisibility();
@@ -1863,7 +1879,7 @@ void HistoryWidget::showHistory(
_scrollToAnimation.stop();
clearAllLoadRequests();
_history = _migrated = nullptr;
setHistory(nullptr);
_list = nullptr;
_peer = nullptr;
_channel = NoChannel;
@@ -1932,8 +1948,7 @@ void HistoryWidget::showHistory(
_itemsRevealHeight = 0;
if (_peer) {
_history = _peer->owner().history(_peer);
_migrated = _history->migrateFrom();
setHistory(_peer->owner().history(_peer));
if (_migrated
&& !_migrated->isEmpty()
&& (!_history->loadedAtTop() || !_migrated->loadedAtBottom())) {
@@ -2060,6 +2075,56 @@ void HistoryWidget::showHistory(
crl::on_main(this, [=] { controller()->widget()->setInnerFocus(); });
}
void HistoryWidget::setHistory(History *history) {
if (_history == history) {
return;
}
unregisterDraftSources();
_history = history;
_migrated = _history ? _history->migrateFrom() : nullptr;
registerDraftSource();
}
void HistoryWidget::unregisterDraftSources() {
if (!_history) {
return;
}
session().local().unregisterDraftSource(
_history,
Data::DraftKey::Local());
session().local().unregisterDraftSource(
_history,
Data::DraftKey::LocalEdit());
}
void HistoryWidget::registerDraftSource() {
if (!_history) {
return;
}
const auto editMsgId = _editMsgId;
const auto draft = [=] {
return Storage::MessageDraft{
editMsgId ? editMsgId : _replyToId,
_field->getTextWithTags(),
_previewState,
};
};
auto draftSource = Storage::MessageDraftSource{
.draft = draft,
.cursor = [=] { return MessageCursor(_field); },
};
session().local().registerDraftSource(
_history,
editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
std::move(draftSource));
}
void HistoryWidget::setEditMsgId(MsgId msgId) {
unregisterDraftSources();
_editMsgId = msgId;
registerDraftSource();
}
void HistoryWidget::clearDelayedShowAt() {
_delayedShowAtMsgId = -1;
clearDelayedShowAtRequest();
@@ -4930,7 +4995,7 @@ void HistoryWidget::startItemRevealAnimations() {
HistoryView::ListWidget::kItemRevealDuration,
anim::easeOutCirc);
if (item->out() || _history->peer->isSelf()) {
controller()->rotateComplexGradientBackground();
_list->theme()->rotateComplexGradientBackground();
}
}
}
@@ -6110,7 +6175,7 @@ void HistoryWidget::cancelEdit() {
}
_replyEditMsg = nullptr;
_editMsgId = 0;
setEditMsgId(0);
_history->clearLocalEditDraft();
applyDraft();
@@ -6874,7 +6939,11 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
updateListSize();
}
Window::SectionWidget::PaintBackground(controller(), this, e->rect());
Window::SectionWidget::PaintBackground(
controller(),
_list ? _list->theme().get() : controller()->defaultChatTheme().get(),
this,
e->rect());
Painter p(this);
const auto clip = e->rect();
@@ -6982,6 +7051,7 @@ HistoryWidget::~HistoryWidget() {
session().api().saveDraftToCloudDelayed(_history);
clearAllLoadRequests();
unregisterDraftSources();
}
setTabbedPanel(nullptr);
}

View File

@@ -558,6 +558,11 @@ private:
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void unregisterDraftSources();
void registerDraftSource();
void setHistory(History *history);
void setEditMsgId(MsgId msgId);
HistoryItem *getItemFromHistoryOrMigrated(MsgId genericMsgId) const;
void animatedScrollToItem(MsgId msgId);
void animatedScrollToY(int scrollTo, HistoryItem *attachTo = nullptr);

View File

@@ -30,6 +30,7 @@ struct VoiceToSend {
struct SendActionUpdate {
Api::SendProgressType type = Api::SendProgressType();
int progress = 0;
bool cancel = false;
};
struct SetHistoryArgs {

View File

@@ -626,6 +626,7 @@ ComposeControls::ComposeControls(
ComposeControls::~ComposeControls() {
saveFieldToHistoryLocalDraft();
unregisterDraftSources();
setTabbedPanel(nullptr);
session().api().request(_inlineBotResolveRequestId).cancel();
}
@@ -650,7 +651,9 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
//if (_history == history) {
// return;
//}
unregisterDraftSources();
_history = history;
registerDraftSource();
_window->tabbedSelector()->setCurrentPeer(
history ? history->peer.get() : nullptr);
initWebpageProcess();
@@ -877,7 +880,11 @@ TextWithTags ComposeControls::getTextWithAppliedMarkdown() const {
}
void ComposeControls::clear() {
setText({});
// Otherwise cancelReplyMessage() will save the draft.
const auto saveTextDraft = !replyingToMessage();
setFieldText(
{},
saveTextDraft ? TextUpdateEvent::SaveDraft : TextUpdateEvent());
cancelReplyMessage();
}
@@ -993,7 +1000,9 @@ void ComposeControls::init() {
_header->editMsgId(
) | rpl::start_with_next([=](const auto &id) {
unregisterDraftSources();
updateSendButtonType();
registerDraftSource();
}, _wrap->lifetime());
_header->previewCancelled(
@@ -1094,7 +1103,7 @@ void ComposeControls::initKeyHandler() {
return;
}
if (key == Qt::Key_Up && !hasModifiers) {
if (!isEditingMessage()) {
if (!isEditingMessage() && _field->empty()) {
_editLastMessageRequests.fire(std::move(keyEvent));
return;
}
@@ -1396,23 +1405,51 @@ void ComposeControls::saveDraft(bool delayed) {
void ComposeControls::writeDraftTexts() {
Expects(_history != nullptr);
session().local().writeDrafts(
_history,
draftKeyCurrent(),
Storage::MessageDraft{
_header->getDraftMessageId(),
_field->getTextWithTags(),
_previewState,
});
session().local().writeDrafts(_history);
}
void ComposeControls::writeDraftCursors() {
Expects(_history != nullptr);
session().local().writeDraftCursors(
_history,
draftKeyCurrent(),
MessageCursor(_field));
session().local().writeDraftCursors(_history);
}
void ComposeControls::unregisterDraftSources() {
if (!_history) {
return;
}
const auto normal = draftKey(DraftType::Normal);
const auto edit = draftKey(DraftType::Edit);
if (normal != Data::DraftKey::None()) {
session().local().unregisterDraftSource(_history, normal);
}
if (edit != Data::DraftKey::None()) {
session().local().unregisterDraftSource(_history, edit);
}
}
void ComposeControls::registerDraftSource() {
if (!_history) {
return;
}
const auto key = draftKeyCurrent();
if (key != Data::DraftKey::None()) {
const auto draft = [=] {
return Storage::MessageDraft{
_header->getDraftMessageId(),
_field->getTextWithTags(),
_previewState,
};
};
auto draftSource = Storage::MessageDraftSource{
.draft = draft,
.cursor = [=] { return MessageCursor(_field); },
};
session().local().registerDraftSource(
_history,
key,
std::move(draftSource));
}
}
void ComposeControls::writeDrafts() {
@@ -1523,6 +1560,14 @@ void ComposeControls::initTabbedSelector() {
) | rpl::start_with_next([=] {
selector->showMenuWithType(sendMenuType());
}, wrap->lifetime());
selector->choosingStickerUpdated(
) | rpl::start_with_next([=](ChatHelpers::TabbedSelector::Action action) {
_sendActionUpdates.fire({
.type = Api::SendProgressType::ChooseSticker,
.cancel = (action == ChatHelpers::TabbedSelector::Action::Cancel),
});
}, wrap->lifetime());
}
void ComposeControls::initSendButton() {

View File

@@ -270,6 +270,9 @@ private:
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void saveFieldToHistoryLocalDraft();
void unregisterDraftSources();
void registerDraftSource();
const not_null<QWidget*> _parent;
const not_null<Window::SessionController*> _window;
History *_history = nullptr;

View File

@@ -25,6 +25,7 @@ class SessionController;
namespace Ui {
class PathShiftGradient;
struct BubblePattern;
struct ChatPaintContext;
} // namespace Ui
namespace HistoryView {
@@ -142,6 +143,11 @@ public:
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override;
protected:
[[nodiscard]] not_null<Window::SessionController*> controller() const {
return _controller;
}
private:
const not_null<Window::SessionController*> _controller;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
@@ -192,29 +198,7 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
struct PaintContext {
const Ui::BubblePattern *bubblesPattern = nullptr;
QRect viewport;
QRect clip;
TextSelection selection;
crl::time now = 0;
void translate(int x, int y) {
viewport.translate(x, y);
clip.translate(x, y);
}
void translate(QPoint point) {
translate(point.x(), point.y());
}
[[nodiscard]] PaintContext translated(int x, int y) const {
auto result = *this;
result.translate(x, y);
return result;
}
[[nodiscard]] PaintContext translated(QPoint point) const {
return translated(point.x(), point.y());
}
};
using PaintContext = Ui::ChatPaintContext;
class Element
: public Object

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "ui/inactive_press.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/chat/chat_theme.h"
#include "lang/lang_keys.h"
#include "boxes/peers/edit_participant_box.h"
#include "data/data_session.h"
@@ -562,10 +563,12 @@ void ListWidget::checkUnreadBarCreation() {
if (auto data = _delegate->listMessagesBar(_items); data.bar.element) {
_bar = std::move(data.bar);
_barText = std::move(data.text);
_bar.element->createUnreadBar(_barText.value());
const auto i = ranges::find(_items, not_null{ _bar.element });
Assert(i != end(_items));
refreshAttachmentsAtIndex(i - begin(_items));
if (!_bar.hidden) {
_bar.element->createUnreadBar(_barText.value());
const auto i = ranges::find(_items, not_null{ _bar.element });
Assert(i != end(_items));
refreshAttachmentsAtIndex(i - begin(_items));
}
}
}
}
@@ -582,10 +585,11 @@ void ListWidget::restoreScrollState() {
} else if (_overrideInitialScroll
&& base::take(_overrideInitialScroll)()) {
_scrollTopState = ScrollTopState();
_scrollInited = true;
return;
}
if (!_scrollTopState.item) {
if (!_bar.element || !_bar.focus || _scrollInited) {
if (!_bar.element || _bar.hidden || !_bar.focus || _scrollInited) {
return;
}
_scrollInited = true;
@@ -1445,7 +1449,7 @@ void ListWidget::startItemRevealAnimations() {
kItemRevealDuration,
anim::easeOutCirc);
if (view->data()->out()) {
controller()->rotateComplexGradientBackground();
_delegate->listChatTheme()->rotateComplexGradientBackground();
}
}
}
@@ -1602,7 +1606,6 @@ void ListWidget::paintEvent(QPaintEvent *e) {
width(),
std::min(st::msgMaxWidth / 2, width() / 2));
auto ms = crl::now();
auto clip = e->rect();
auto from = std::lower_bound(begin(_items), end(_items), clip.top(), [this](auto &elem, int top) {
@@ -1611,18 +1614,19 @@ void ListWidget::paintEvent(QPaintEvent *e) {
auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) {
return this->itemTop(elem) < bottom;
});
if (from != end(_items)) {
auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr,
.viewport = viewport.translated(0, -top),
.clip = clip.translated(0, -top),
.now = crl::now(),
};
auto context = controller()->preparePaintContext({
.theme = _delegate->listChatTheme(),
.visibleAreaTop = _visibleTop,
.visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
.clip = clip,
}).translated(0, -top);
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = *i;
context.selection = itemRenderSelection(view);
view->draw(p, context);
const auto height = view->height();
top += height;

View File

@@ -21,6 +21,7 @@ class Session;
namespace Ui {
class PopupMenu;
class ChatTheme;
} // namespace Ui
namespace Window {
@@ -52,6 +53,7 @@ struct SelectedItem {
struct MessagesBar {
Element *element = nullptr;
bool hidden = false;
bool focus = false;
};
@@ -91,6 +93,7 @@ public:
const QString &command,
const FullMsgId &context) = 0;
virtual void listHandleViaClick(not_null<UserData*> bot) = 0;
virtual not_null<Ui::ChatTheme*> listChatTheme() = 0;
};

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_web_page.h"
#include "history/view/history_view_group_call_tracker.h" // UserpicInRow.
#include "history/history.h"
#include "ui/chat/chat_theme.h"
#include "ui/effects/ripple_animation.h"
#include "base/unixtime.h"
#include "ui/chat/message_bubble.h"
@@ -558,6 +559,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
p,
Ui::ComplexBubble{
.simple = Ui::SimpleBubble{
.st = context.st,
.geometry = g,
.pattern = context.bubblesPattern,
.patternViewport = context.viewport,

View File

@@ -90,7 +90,7 @@ PinnedWidget::PinnedWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, history->peer)
, _history(history->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom())
, _topBar(this, controller)
@@ -101,6 +101,14 @@ PinnedWidget::PinnedWidget(
QString(),
st::historyComposeButton))
, _scrollDown(_scroll.get(), st::historyToDown) {
Window::ChatThemeValueFromPeer(
controller,
history->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
_topBar->setActiveChat(
TopBarWidget::ActiveChat{
.key = _history,
@@ -457,7 +465,7 @@ void PinnedWidget::paintEvent(QPaintEvent *e) {
const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground(controller(), this, bg);
SectionWidget::PaintBackground(controller(), _theme.get(), this, bg);
}
void PinnedWidget::onScroll() {
@@ -650,6 +658,10 @@ void PinnedWidget::listSendBotCommand(
void PinnedWidget::listHandleViaClick(not_null<UserData*> bot) {
}
not_null<Ui::ChatTheme*> PinnedWidget::listChatTheme() {
return _theme.get();
}
void PinnedWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View File

@@ -102,6 +102,7 @@ public:
const QString &command,
const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override;
not_null<Ui::ChatTheme*> listChatTheme() override;
protected:
void resizeEvent(QResizeEvent *e) override;
@@ -145,6 +146,7 @@ private:
void refreshClearButtonText();
const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme;
PeerData *_migratedPeer = nullptr;
QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar;
@@ -158,7 +160,6 @@ private:
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
int _messagesCount = -1;
};

View File

@@ -31,19 +31,21 @@ PinnedTracker::PinnedTracker(not_null<History*> history)
: _history(history->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom()) {
using namespace rpl::mappers;
const auto has = [&](PeerData *peer) -> rpl::producer<bool> {
const auto has = [&](History *history) -> rpl::producer<bool> {
auto &changes = _history->session().changes();
const auto flag = Data::PeerUpdate::Flag::PinnedMessages;
if (!peer) {
const auto flag = Data::HistoryUpdate::Flag::PinnedMessages;
if (!history) {
return rpl::single(false);
}
return changes.peerFlagsValue(peer, flag) | rpl::map([=] {
return peer->hasPinnedMessages();
return changes.historyFlagsValue(history, flag) | rpl::map([=] {
return history->hasPinnedMessages();
});
};
rpl::combine(
has(_history->peer),
has(_migratedPeer),
has(_history),
has(_migratedPeer
? _history->owner().history(_migratedPeer).get()
: nullptr),
_1 || _2
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool has) {
@@ -97,9 +99,10 @@ void PinnedTracker::refreshViewer() {
}
refreshCurrentFromSlice();
if (_slice.fullCount == 0) {
_history->peer->setHasPinnedMessages(false);
_history->setHasPinnedMessages(false);
if (_migratedPeer) {
_migratedPeer->setHasPinnedMessages(false);
const auto to = _history->owner().history(_migratedPeer);
to->setHasPinnedMessages(false);
}
}
}, _dataLifetime);

View File

@@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_replies_list.h"
#include "data/data_changes.h"
#include "data/data_send_action.h"
#include "storage/storage_media_prepare.h"
#include "storage/storage_account.h"
#include "inline_bots/inline_bot_result.h"
@@ -146,12 +147,14 @@ RepliesWidget::RepliesWidget(
not_null<Window::SessionController*> controller,
not_null<History*> history,
MsgId rootId)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, history->peer)
, _history(history)
, _rootId(rootId)
, _root(lookupRoot())
, _areComments(computeAreComments())
, _sendAction(history->owner().repliesSendActionPainter(history, rootId))
, _sendAction(history->owner().sendActionManager().repliesPainter(
history,
rootId))
, _topBar(this, controller)
, _topBarShadow(this)
, _composeControls(std::make_unique<ComposeControls>(
@@ -162,6 +165,14 @@ RepliesWidget::RepliesWidget(
, _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false))
, _scrollDown(_scroll.get(), st::historyToDown)
, _readRequestTimer([=] { sendReadTillRequest(); }) {
Window::ChatThemeValueFromPeer(
controller,
history->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
setupRoot();
setupRootView();
@@ -232,23 +243,38 @@ RepliesWidget::RepliesWidget(
_composeControls->sendActionUpdates(
) | rpl::start_with_next([=](ComposeControls::SendActionUpdate &&data) {
session().sendProgressManager().update(
_history,
_rootId,
data.type,
data.progress);
if (!data.cancel) {
session().sendProgressManager().update(
_history,
_rootId,
data.type,
data.progress);
} else {
session().sendProgressManager().cancel(
_history,
_rootId,
data.type);
}
}, lifetime());
using MessageUpdateFlag = Data::MessageUpdate::Flag;
_history->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
MessageUpdateFlag::Destroyed
| MessageUpdateFlag::RepliesUnreadCount
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.item == _root) {
_root = nullptr;
updatePinnedVisibility();
controller->showBackFromStack();
}
while (update.item == _replyReturn) {
calculateNextReplyReturn();
if (update.flags & MessageUpdateFlag::Destroyed) {
if (update.item == _root) {
_root = nullptr;
updatePinnedVisibility();
controller->showBackFromStack();
}
while (update.item == _replyReturn) {
calculateNextReplyReturn();
}
return;
} else if ((update.item == _root)
&& (update.flags & MessageUpdateFlag::RepliesUnreadCount)) {
refreshUnreadCountBadge();
}
}, lifetime());
@@ -259,6 +285,17 @@ RepliesWidget::RepliesWidget(
_inner->update();
}, lifetime());
_history->session().data().unreadRepliesCountRequests(
) | rpl::filter([=](
const Data::Session::UnreadRepliesCountRequest &request) {
return (request.root.get() == _root);
}) | rpl::start_with_next([=](
const Data::Session::UnreadRepliesCountRequest &request) {
if (const auto result = computeUnreadCountLocally(request.afterId)) {
*request.result = result;
}
}, lifetime());
setupScrollDownButton();
setupComposeControls();
orderWidgets();
@@ -269,7 +306,9 @@ RepliesWidget::~RepliesWidget() {
sendReadTillRequest();
}
base::take(_sendAction);
_history->owner().repliesSendActionPainterRemoved(_history, _rootId);
_history->owner().sendActionManager().repliesPainterRemoved(
_history,
_rootId);
}
void RepliesWidget::orderWidgets() {
@@ -294,12 +333,15 @@ void RepliesWidget::sendReadTillRequest() {
_readRequestPending = false;
const auto api = &_history->session().api();
api->request(base::take(_readRequestId)).cancel();
_readRequestId = api->request(MTPmessages_ReadDiscussion(
_root->history()->peer->input,
MTP_int(_root->id),
MTP_int(_root->computeRepliesInboxReadTillFull())
)).done([=](const MTPBool &) {
}).send();
)).done(crl::guard(this, [=](const MTPBool &) {
_readRequestId = 0;
reloadUnreadCountIfNeeded();
})).send();
}
void RepliesWidget::setupRoot() {
@@ -309,6 +351,7 @@ void RepliesWidget::setupRoot() {
_root = lookupRoot();
if (_root) {
_areComments = computeAreComments();
refreshUnreadCountBadge();
if (_readRequestPending) {
sendReadTillRequest();
}
@@ -362,6 +405,19 @@ bool RepliesWidget::computeAreComments() const {
return _root && _root->isDiscussionPost();
}
std::optional<int> RepliesWidget::computeUnreadCount() const {
if (!_root) {
return std::nullopt;
}
const auto views = _root->Get<HistoryMessageViews>();
if (!views) {
return std::nullopt;
}
return (views->repliesUnreadCount >= 0)
? std::make_optional(views->repliesUnreadCount)
: std::nullopt;
}
void RepliesWidget::setupComposeControls() {
auto slowmodeSecondsLeft = session().changes().peerFlagsValue(
_history->peer,
@@ -1135,6 +1191,7 @@ void RepliesWidget::setupScrollDownButton() {
_scrollDown->setClickedCallback([=] {
scrollDownClicked();
});
refreshUnreadCountBadge();
base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::Wheel) {
return base::EventFilterResult::Continue;
@@ -1146,6 +1203,56 @@ void RepliesWidget::setupScrollDownButton() {
updateScrollDownVisibility();
}
void RepliesWidget::refreshUnreadCountBadge() {
if (!_root) {
return;
} else if (const auto count = computeUnreadCount()) {
_scrollDown->setUnreadCount(*count);
} else if (!_readRequestPending
&& !_readRequestTimer.isActive()
&& !_readRequestId) {
reloadUnreadCountIfNeeded();
}
}
void RepliesWidget::reloadUnreadCountIfNeeded() {
const auto views = _root ? _root->Get<HistoryMessageViews>() : nullptr;
if (!views || views->repliesUnreadCount >= 0) {
return;
} else if (views->repliesInboxReadTillId
< _root->computeRepliesInboxReadTillFull()) {
_readRequestTimer.callOnce(0);
} else if (!_reloadUnreadCountRequestId) {
const auto session = &_history->session();
const auto fullId = _root->fullId();
const auto apply = [session, fullId](int readTill, int unreadCount) {
if (const auto root = session->data().message(fullId)) {
root->setRepliesInboxReadTill(readTill, unreadCount);
if (const auto post = root->lookupDiscussionPostOriginal()) {
post->setRepliesInboxReadTill(readTill, unreadCount);
}
}
};
const auto weak = Ui::MakeWeak(this);
_reloadUnreadCountRequestId = session->api().request(
MTPmessages_GetDiscussionMessage(
_history->peer->input,
MTP_int(_rootId))
).done([=](const MTPmessages_DiscussionMessage &result) {
if (weak) {
_reloadUnreadCountRequestId = 0;
}
result.match([&](const MTPDmessages_discussionMessage &data) {
session->data().processUsers(data.vusers());
session->data().processChats(data.vchats());
apply(
data.vread_inbox_max_id().value_or_empty(),
data.vunread_count().v);
});
}).send();
}
}
void RepliesWidget::scrollDownClicked() {
if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
showAtEnd();
@@ -1511,7 +1618,7 @@ void RepliesWidget::paintEvent(QPaintEvent *e) {
const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground(controller(), this, bg);
SectionWidget::PaintBackground(controller(), _theme.get(), this, bg);
}
void RepliesWidget::onScroll() {
@@ -1678,17 +1785,35 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) {
_topBar->showSelected(state);
}
std::optional<int> RepliesWidget::computeUnreadCountLocally(
MsgId afterId) const {
const auto views = _root ? _root->Get<HistoryMessageViews>() : nullptr;
if (!views) {
return std::nullopt;
}
const auto wasReadTillId = views->repliesInboxReadTillId;
const auto wasUnreadCount = views->repliesUnreadCount;
return _replies->fullUnreadCountAfter(
afterId,
wasReadTillId,
wasUnreadCount);
}
void RepliesWidget::readTill(not_null<HistoryItem*> item) {
if (!_root) {
return;
}
const auto was = _root->computeRepliesInboxReadTillFull();
const auto now = item->id;
const auto fast = item->out();
if (was < now) {
_root->setRepliesInboxReadTill(now);
if (now < was) {
return;
}
const auto unreadCount = computeUnreadCountLocally(now);
const auto fast = item->out() || !unreadCount.has_value();
if (was < now || (fast && now == was)) {
_root->setRepliesInboxReadTill(now, unreadCount);
if (const auto post = _root->lookupDiscussionPostOriginal()) {
post->setRepliesInboxReadTill(now);
post->setRepliesInboxReadTill(now, unreadCount);
}
if (!_readRequestTimer.isActive()) {
_readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout);
@@ -1714,22 +1839,20 @@ MessagesBarData RepliesWidget::listMessagesBar(
return {};
}
const auto till = _root->computeRepliesInboxReadTillFull();
if (till < 2) {
return {};
}
const auto hidden = (till < 2);
for (auto i = 0, count = int(elements.size()); i != count; ++i) {
const auto item = elements[i]->data();
if (IsServerMsgId(item->id) && item->id > till) {
if (item->out() || !item->replyToId()) {
readTill(item);
} else {
return MessagesBarData{
// Designated initializers here crash MSVC 16.7.3.
MessagesBar{
return {
.bar = {
.element = elements[i],
.hidden = hidden,
.focus = true,
},
tr::lng_unread_bar_some(),
.text = tr::lng_unread_bar_some(),
};
}
}
@@ -1782,6 +1905,10 @@ void RepliesWidget::listHandleViaClick(not_null<UserData*> bot) {
_composeControls->setText({ '@' + bot->username + ' ' });
}
not_null<Ui::ChatTheme*> RepliesWidget::listChatTheme() {
return _theme.get();
}
void RepliesWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View File

@@ -137,6 +137,7 @@ public:
const QString &command,
const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override;
not_null<Ui::ChatTheme*> listChatTheme() override;
protected:
void resizeEvent(QResizeEvent *e) override;
@@ -172,6 +173,8 @@ private:
void setupDragArea();
void sendReadTillRequest();
void readTill(not_null<HistoryItem*> item);
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
MsgId afterId) const;
void setupScrollDownButton();
void scrollDownClicked();
@@ -197,6 +200,7 @@ private:
[[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] bool computeAreComments() const;
[[nodiscard]] std::optional<int> computeUnreadCount() const;
void orderWidgets();
void pushReplyReturn(not_null<HistoryItem*> item);
@@ -207,6 +211,8 @@ private:
void recountChatWidth();
void replyToMessage(FullMsgId itemId);
void refreshTopBarActiveChat();
void refreshUnreadCountBadge();
void reloadUnreadCountIfNeeded();
void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles(
@@ -250,6 +256,7 @@ private:
const not_null<History*> _history;
const MsgId _rootId = 0;
std::shared_ptr<Ui::ChatTheme> _theme;
HistoryItem *_root = nullptr;
std::shared_ptr<Data::RepliesList> _replies;
rpl::variable<bool> _areComments = false;
@@ -274,13 +281,13 @@ private:
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
bool _choosingAttach = false;
base::Timer _readRequestTimer;
bool _readRequestPending = false;
mtpRequestId _readRequestId = 0;
mtpRequestId _reloadUnreadCountRequestId = 0;
bool _loaded = false;
};

View File

@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_scheduled_messages.h"
#include "data/data_user.h"
#include "storage/storage_media_prepare.h"
@@ -90,7 +89,7 @@ ScheduledWidget::ScheduledWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history)
: Window::SectionWidget(parent, controller, PaintedBackground::Section)
: Window::SectionWidget(parent, controller, history->peer)
, _history(history)
, _scroll(this, st::historyScroll, false)
, _topBar(this, controller)
@@ -101,6 +100,14 @@ ScheduledWidget::ScheduledWidget(
ComposeControls::Mode::Scheduled,
SendMenu::Type::Disabled))
, _scrollDown(_scroll, st::historyToDown) {
Window::ChatThemeValueFromPeer(
controller,
history->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme);
controller->setChatStyleTheme(_theme);
}, lifetime());
const auto state = Dialogs::EntryState{
.key = _history,
.section = Dialogs::EntryState::Section::Scheduled,
@@ -996,7 +1003,8 @@ void ScheduledWidget::paintEvent(QPaintEvent *e) {
//auto ms = crl::now();
//_historyDownShown.step(ms);
SectionWidget::PaintBackground(controller(), this, e->rect());
const auto clip = e->rect();
SectionWidget::PaintBackground(controller(), _theme.get(), this, clip);
}
void ScheduledWidget::onScroll() {
@@ -1200,6 +1208,10 @@ void ScheduledWidget::listHandleViaClick(not_null<UserData*> bot) {
_composeControls->setText({ '@' + bot->username + ' ' });
}
not_null<Ui::ChatTheme*> ScheduledWidget::listChatTheme() {
return _theme.get();
}
void ScheduledWidget::confirmSendNowSelected() {
ConfirmSendNowSelectedItems(_inner);
}

View File

@@ -118,6 +118,7 @@ public:
const QString &command,
const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override;
not_null<Ui::ChatTheme*> listChatTheme() override;
protected:
void resizeEvent(QResizeEvent *e) override;
@@ -206,6 +207,7 @@ private:
Api::SendOptions options);
const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme;
object_ptr<Ui::ScrollArea> _scroll;
QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar;

View File

@@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_send_action.h"
#include "data/data_user.h"
#include "data/data_send_action.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "history/history.h"
#include "lang/lang_instance.h" // Instance::supportChoosingStickerReplacement
#include "lang/lang_keys.h"
#include "ui/effects/animations.h"
#include "ui/text/text_options.h"
@@ -30,6 +32,7 @@ constexpr auto kStatusShowClientsideUploadPhoto = 6 * crl::time(1000);
constexpr auto kStatusShowClientsideUploadFile = 6 * crl::time(1000);
constexpr auto kStatusShowClientsideChooseLocation = 6 * crl::time(1000);
constexpr auto kStatusShowClientsideChooseContact = 6 * crl::time(1000);
constexpr auto kStatusShowClientsideChooseSticker = 6 * crl::time(1000);
constexpr auto kStatusShowClientsidePlayGame = 10 * crl::time(1000);
constexpr auto kStatusShowClientsideSpeaking = 6 * crl::time(1000);
@@ -38,6 +41,7 @@ constexpr auto kStatusShowClientsideSpeaking = 6 * crl::time(1000);
SendActionPainter::SendActionPainter(not_null<History*> history)
: _history(history)
, _weak(&_history->session())
, _st(st::dialogsTextStyle)
, _sendActionText(st::dialogsTextWidthMin) {
}
@@ -66,9 +70,13 @@ bool SendActionPainter::updateNeedsAnimating(
}, [&](const MTPDsendMessageRecordRoundAction &) {
emplaceAction(Type::RecordRound, kStatusShowClientsideRecordRound);
}, [&](const MTPDsendMessageGeoLocationAction &) {
emplaceAction(Type::ChooseLocation, kStatusShowClientsideChooseLocation);
emplaceAction(
Type::ChooseLocation,
kStatusShowClientsideChooseLocation);
}, [&](const MTPDsendMessageChooseContactAction &) {
emplaceAction(Type::ChooseContact, kStatusShowClientsideChooseContact);
emplaceAction(
Type::ChooseContact,
kStatusShowClientsideChooseContact);
}, [&](const MTPDsendMessageUploadVideoAction &data) {
emplaceAction(
Type::UploadVideo,
@@ -106,6 +114,10 @@ bool SendActionPainter::updateNeedsAnimating(
user,
now + kStatusShowClientsideSpeaking);
}, [&](const MTPDsendMessageHistoryImportAction &) {
}, [&](const MTPDsendMessageChooseStickerAction &) {
emplaceAction(
Type::ChooseSticker,
kStatusShowClientsideChooseSticker);
}, [&](const MTPDsendMessageCancelAction &) {
Unexpected("CancelAction here.");
});
@@ -121,16 +133,29 @@ bool SendActionPainter::paint(
style::color color,
crl::time ms) {
if (_sendActionAnimation) {
const auto animationWidth = _sendActionAnimation.width();
const auto extraAnimationWidth = _animationLeft
? animationWidth * 2
: 0;
const auto left =
(availableWidth < _animationLeft + extraAnimationWidth)
? 0
: _animationLeft;
_sendActionAnimation.paint(
p,
color,
x,
left + x,
y + st::normalFont->ascent,
outerWidth,
ms);
auto animationWidth = _sendActionAnimation.width();
x += animationWidth;
availableWidth -= animationWidth;
// availableWidth should be the same
// if an animation is in the middle of text.
if (!left) {
x += animationWidth;
availableWidth -= _animationLeft
? extraAnimationWidth
: animationWidth;
}
p.setPen(color);
_sendActionText.drawElided(p, x, y, availableWidth);
return true;
@@ -216,28 +241,79 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
} else if (!_sendActions.empty()) {
// Handles all actions except game playing.
using Type = Api::SendProgressType;
auto sendActionString = [](Type type, const QString &name) -> QString {
const auto sendActionString = [](
Type type,
const QString &name) -> QString {
switch (type) {
case Type::RecordVideo: return name.isEmpty() ? tr::lng_send_action_record_video(tr::now) : tr::lng_user_action_record_video(tr::now, lt_user, name);
case Type::UploadVideo: return name.isEmpty() ? tr::lng_send_action_upload_video(tr::now) : tr::lng_user_action_upload_video(tr::now, lt_user, name);
case Type::RecordVoice: return name.isEmpty() ? tr::lng_send_action_record_audio(tr::now) : tr::lng_user_action_record_audio(tr::now, lt_user, name);
case Type::UploadVoice: return name.isEmpty() ? tr::lng_send_action_upload_audio(tr::now) : tr::lng_user_action_upload_audio(tr::now, lt_user, name);
case Type::RecordRound: return name.isEmpty() ? tr::lng_send_action_record_round(tr::now) : tr::lng_user_action_record_round(tr::now, lt_user, name);
case Type::UploadRound: return name.isEmpty() ? tr::lng_send_action_upload_round(tr::now) : tr::lng_user_action_upload_round(tr::now, lt_user, name);
case Type::UploadPhoto: return name.isEmpty() ? tr::lng_send_action_upload_photo(tr::now) : tr::lng_user_action_upload_photo(tr::now, lt_user, name);
case Type::UploadFile: return name.isEmpty() ? tr::lng_send_action_upload_file(tr::now) : tr::lng_user_action_upload_file(tr::now, lt_user, name);
case Type::RecordVideo: return name.isEmpty()
? tr::lng_send_action_record_video({})
: tr::lng_user_action_record_video({}, lt_user, name);
case Type::UploadVideo: return name.isEmpty()
? tr::lng_send_action_upload_video({})
: tr::lng_user_action_upload_video({}, lt_user, name);
case Type::RecordVoice: return name.isEmpty()
? tr::lng_send_action_record_audio({})
: tr::lng_user_action_record_audio({}, lt_user, name);
case Type::UploadVoice: return name.isEmpty()
? tr::lng_send_action_upload_audio({})
: tr::lng_user_action_upload_audio({}, lt_user, name);
case Type::RecordRound: return name.isEmpty()
? tr::lng_send_action_record_round({})
: tr::lng_user_action_record_round({}, lt_user, name);
case Type::UploadRound: return name.isEmpty()
? tr::lng_send_action_upload_round({})
: tr::lng_user_action_upload_round({}, lt_user, name);
case Type::UploadPhoto: return name.isEmpty()
? tr::lng_send_action_upload_photo({})
: tr::lng_user_action_upload_photo({}, lt_user, name);
case Type::UploadFile: return name.isEmpty()
? tr::lng_send_action_upload_file({})
: tr::lng_user_action_upload_file({}, lt_user, name);
case Type::ChooseLocation:
case Type::ChooseContact: return name.isEmpty() ? tr::lng_typing(tr::now) : tr::lng_user_typing(tr::now, lt_user, name);
case Type::ChooseContact: return name.isEmpty()
? tr::lng_typing({})
: tr::lng_user_typing({}, lt_user, name);
case Type::ChooseSticker: return name.isEmpty()
? tr::lng_send_action_choose_sticker({})
: tr::lng_user_action_choose_sticker({}, lt_user, name);
default: break;
};
return QString();
};
for (const auto &[user, action] : _sendActions) {
const auto isNamed = !_history->peer->isUser();
newTypingString = sendActionString(
action.type,
_history->peer->isUser() ? QString() : user->firstName);
isNamed ? user->firstName : QString());
if (!newTypingString.isEmpty()) {
_sendActionAnimation.start(action.type);
// Add an animation to the middle of text.
const auto &lang = Lang::GetInstance();
if (lang.supportChoosingStickerReplacement()
&& (action.type == Type::ChooseSticker)) {
const auto index = newTypingString.size()
- lang.rightIndexChoosingStickerReplacement(
isNamed);
_animationLeft = _st.font->width(
newTypingString,
0,
index);
if (!_spacesCount) {
_spacesCount = std::ceil(
_sendActionAnimation.width()
/ _st.font->spacew);
}
newTypingString = newTypingString.replace(
index,
Lang::kChoosingStickerReplacement.utf8().size(),
QString().fill(' ', _spacesCount).constData(),
_spacesCount);
} else {
_animationLeft = 0;
}
break;
}
}
@@ -293,8 +369,9 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
if (force
|| sendActionChanged
|| (sendActionResult && !anim::Disabled())) {
_history->peer->owner().updateSendActionAnimation({
_history->peer->owner().sendActionManager().updateAnimation({
_history,
_animationLeft,
_sendActionAnimation.width(),
st::normalFont->height,
(force || sendActionChanged)
@@ -303,7 +380,7 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
if (force
|| speakingChanged
|| (speakingResult && !anim::Disabled())) {
_history->peer->owner().updateSpeakingAnimation({
_history->peer->owner().sendActionManager().updateSpeakingAnimation({
_history
});
}

View File

@@ -54,6 +54,7 @@ public:
private:
const not_null<History*> _history;
const base::weak_ptr<Main::Session> _weak;
const style::TextStyle &_st;
base::flat_map<not_null<UserData*>, crl::time> _typing;
base::flat_map<not_null<UserData*>, crl::time> _speaking;
base::flat_map<not_null<UserData*>, Api::SendProgress> _sendActions;
@@ -62,6 +63,9 @@ private:
Ui::SendActionAnimation _sendActionAnimation;
Ui::SendActionAnimation _speakingAnimation;
int _animationLeft = 0;
int _spacesCount = 0;
};
} // namespace HistoryView

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_abstract_structure.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/text_options.h"
#include "ui/ui_utility.h"
#include "mainwidget.h"

View File

@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_changes.h"
#include "data/data_send_action.h"
#include "base/unixtime.h"
#include "support/support_helper.h"
#include "apiwrap.h"
@@ -124,8 +125,8 @@ TopBarWidget::TopBarWidget(
refreshUnreadBadge();
{
using AnimationUpdate = Data::Session::SendActionAnimationUpdate;
session().data().sendActionAnimationUpdated(
using AnimationUpdate = Data::SendActionManager::AnimationUpdate;
session().data().sendActionManager().animationUpdated(
) | rpl::filter([=](const AnimationUpdate &update) {
return (update.history == _activeChat.key.history());
}) | rpl::start_with_next([=] {

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_call.h"
#include "lang/lang_keys.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/format_values.h"
#include "layout/layout_selection.h" // FullSelection
#include "history/history.h"

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h"
#include "window/window_session_controller.h"
#include "ui/empty_userpic.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_options.h"
#include "data/data_session.h"

Some files were not shown because too many files have changed in this diff Show More