Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a000207ee | ||
|
|
ee6edf9caa | ||
|
|
37907636e6 | ||
|
|
3f0f3a3c11 | ||
|
|
db0856f71c | ||
|
|
86cdda2277 | ||
|
|
3888e8084a | ||
|
|
517b456670 | ||
|
|
77d6e19214 | ||
|
|
fb64452495 | ||
|
|
24fabf2590 | ||
|
|
9b2847a11d | ||
|
|
c18e8fd777 | ||
|
|
f4afa762d8 | ||
|
|
5e1fb6ebbf | ||
|
|
2c7922ce7b | ||
|
|
ac78ae823c | ||
|
|
1ef6f462f6 | ||
|
|
c81f406759 | ||
|
|
889ec0c731 | ||
|
|
677fbdd84e | ||
|
|
1ebe3255e0 | ||
|
|
c70866a995 | ||
|
|
fd982b90db | ||
|
|
9461095c88 | ||
|
|
a2fa1a52e2 | ||
|
|
a847969e9c | ||
|
|
4d647e64b7 | ||
|
|
f8b756d447 | ||
|
|
484c647b5b | ||
|
|
730c968b1e | ||
|
|
8a6a749296 | ||
|
|
2f22a8f46b | ||
|
|
f123a9e16c | ||
|
|
11c45b0342 | ||
|
|
2cd6bfef06 | ||
|
|
61ca619db4 | ||
|
|
675ee9088f | ||
|
|
28a6aa45b9 | ||
|
|
08ec9e6bfd | ||
|
|
ee9f99a754 | ||
|
|
2412183b83 | ||
|
|
e83704982f | ||
|
|
6f86acf712 | ||
|
|
c22698084f | ||
|
|
8c55364afa | ||
|
|
2c3ef13b01 | ||
|
|
03454ca3b4 | ||
|
|
8a92c89f39 | ||
|
|
b83b403b75 | ||
|
|
8aac07b3c0 | ||
|
|
b4dfc25df5 | ||
|
|
917d1841c1 | ||
|
|
8ce10d5503 | ||
|
|
a130bb1be6 | ||
|
|
de52ac6b28 | ||
|
|
310837c9e1 | ||
|
|
8e6d7bb190 | ||
|
|
025ab40687 | ||
|
|
2a5071b66c | ||
|
|
2a63496054 | ||
|
|
a52d4eb4e8 | ||
|
|
4f7a124f3e | ||
|
|
4b9eb37bd5 | ||
|
|
1d5e4040f4 | ||
|
|
6818b8d8dc | ||
|
|
3f0b962ae5 | ||
|
|
8ac1ad3484 | ||
|
|
c6e1cf639e | ||
|
|
5b9278eced | ||
|
|
03d4dd00d4 | ||
|
|
f7d698b9ff | ||
|
|
46b69a938b | ||
|
|
ebba58217c | ||
|
|
94ad8f9bc3 | ||
|
|
6effac7915 |
@@ -475,6 +475,8 @@ PRIVATE
|
||||
data/business/data_shortcut_messages.h
|
||||
data/components/factchecks.cpp
|
||||
data/components/factchecks.h
|
||||
data/components/location_pickers.cpp
|
||||
data/components/location_pickers.h
|
||||
data/components/recent_peers.cpp
|
||||
data/components/recent_peers.h
|
||||
data/components/scheduled_messages.cpp
|
||||
@@ -1472,6 +1474,8 @@ PRIVATE
|
||||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/controls/location_picker.cpp
|
||||
ui/controls/location_picker.h
|
||||
ui/controls/silent_toggle.cpp
|
||||
ui/controls/silent_toggle.h
|
||||
ui/controls/userpic_button.cpp
|
||||
@@ -1493,6 +1497,10 @@ PRIVATE
|
||||
ui/image/image_location.h
|
||||
ui/image/image_location_factory.cpp
|
||||
ui/image/image_location_factory.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
@@ -1506,10 +1514,6 @@ PRIVATE
|
||||
ui/resize_area.h
|
||||
ui/search_field_controller.cpp
|
||||
ui/search_field_controller.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/unread_badge.cpp
|
||||
ui/unread_badge.h
|
||||
window/main_window.cpp
|
||||
@@ -1614,6 +1618,7 @@ PRIVATE
|
||||
qrc/telegram/animations.qrc
|
||||
qrc/telegram/export.qrc
|
||||
qrc/telegram/iv.qrc
|
||||
qrc/telegram/picker.qrc
|
||||
qrc/telegram/telegram.qrc
|
||||
qrc/telegram/sounds.qrc
|
||||
winrc/Telegram.rc
|
||||
@@ -1844,9 +1849,14 @@ if (WIN32)
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
if (QT_VERSION GREATER 6)
|
||||
if (NOT build_winarm)
|
||||
target_link_options(Telegram PRIVATE
|
||||
/DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_options(Telegram
|
||||
PRIVATE
|
||||
/DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll
|
||||
@@ -1863,7 +1873,7 @@ if (WIN32)
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll
|
||||
# /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # We shadowed GetDpiForMonitor
|
||||
/DELAYLOAD:authz.dll # Authz.lib
|
||||
/DELAYLOAD:comdlg32.dll
|
||||
/DELAYLOAD:dwrite.dll # DWrite.lib
|
||||
|
||||
BIN
Telegram/Resources/icons/chat/filled_location.png
Normal file
BIN
Telegram/Resources/icons/chat/filled_location.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 536 B |
BIN
Telegram/Resources/icons/chat/filled_location@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/filled_location@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 987 B |
BIN
Telegram/Resources/icons/chat/filled_location@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/filled_location@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1889,6 +1889,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_boost_apply#one" = "{from} boosted the group";
|
||||
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
|
||||
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
|
||||
"lng_action_payment_refunded" = "{peer} refunded back {amount}";
|
||||
|
||||
"lng_similar_channels_title" = "Similar channels";
|
||||
"lng_similar_channels_view_all" = "View all";
|
||||
@@ -2363,11 +2364,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
|
||||
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
|
||||
"lng_credits_box_history_entry_peer" = "Recipient";
|
||||
"lng_credits_box_history_entry_peer_in" = "From";
|
||||
"lng_credits_box_history_entry_via" = "Via";
|
||||
"lng_credits_box_history_entry_play_market" = "Play Market";
|
||||
"lng_credits_box_history_entry_app_store" = "App Store";
|
||||
"lng_credits_box_history_entry_fragment" = "Fragment";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
@@ -2383,6 +2387,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_location_title" = "Location";
|
||||
"lng_location_about" = "Display the location of your business on your account.";
|
||||
"lng_location_address" = "Enter Address";
|
||||
"lng_location_set_map" = "Set Location on Map";
|
||||
"lng_location_fallback" = "You can set your location on the map from your mobile device.";
|
||||
|
||||
"lng_hours_title" = "Business Hours";
|
||||
@@ -2811,12 +2816,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_prizes_badge" = "x{amount}";
|
||||
|
||||
"lng_prizes_results_title" = "Winners Selected!";
|
||||
"lng_prizes_results_title_one" = "Winner Selected!";
|
||||
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
|
||||
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
|
||||
"lng_prizes_results_link" = "Giveaway";
|
||||
"lng_prizes_results_winner" = "Winner";
|
||||
"lng_prizes_results_winners" = "Winners";
|
||||
"lng_prizes_results_more#one" = "and {count} more!";
|
||||
"lng_prizes_results_more#other" = "and {count} more!";
|
||||
"lng_prizes_results_one" = "The winner received their gift link in a private message.";
|
||||
"lng_prizes_results_all" = "All winners received gift links in private messages.";
|
||||
"lng_prizes_results_some" = "Some winners couldn't be selected.";
|
||||
|
||||
@@ -3152,6 +3160,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_close_warning_sure" = "Close anyway";
|
||||
"lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time.";
|
||||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_click_to_start" = "Click here to use this bot.";
|
||||
|
||||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
@@ -3187,6 +3197,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_maps_select_on_map" = "Select on the Map";
|
||||
"lng_maps_point_send" = "Send This Location";
|
||||
"lng_maps_point_set" = "Set This Location";
|
||||
"lng_maps_or_choose" = "Or choose a venue";
|
||||
"lng_maps_places_in_area" = "Places in this area";
|
||||
"lng_maps_no_places" = "No places found";
|
||||
"lng_maps_choose_to_search" = "Choose location to see places nearby.";
|
||||
"lng_maps_venues_source" = "Powered by Foursquare";
|
||||
"lng_live_location" = "Live Location";
|
||||
"lng_live_location_now" = "updated just now";
|
||||
"lng_live_location_minutes#one" = "updated {count} minute ago";
|
||||
@@ -3741,6 +3759,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_payments_card_declined" = "Your card was declined.";
|
||||
"lng_payments_payment_failed" = "Payment failed. Your card has not been billed.";
|
||||
"lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed.";
|
||||
"lng_payments_precheckout_timeout" = "The bot didn't respond in time. Your card has not been billed.";
|
||||
"lng_payments_precheckout_stars_failed" = "The bot couldn't process your payment.";
|
||||
"lng_payments_precheckout_stars_timeout" = "The bot didn't respond in time.";
|
||||
"lng_payments_already_paid" = "You have already paid for this item.";
|
||||
|
||||
"lng_payments_terms_title" = "Terms of Service";
|
||||
|
||||
120
Telegram/Resources/picker_html/picker.css
Normal file
120
Telegram/Resources/picker_html/picker.css
Normal file
@@ -0,0 +1,120 @@
|
||||
:root {
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
border-radius: 5px !important;
|
||||
border: 3px solid transparent !important;
|
||||
background-color: var(--td-scroll-bg) !important;
|
||||
background-clip: content-box !important;
|
||||
width: 10px !important;
|
||||
}
|
||||
html.custom_scroll ::-webkit-scrollbar:hover {
|
||||
background-color: var(--td-scroll-bg-over) !important;
|
||||
}
|
||||
html.custom_scroll ::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px !important;
|
||||
border: 3px solid transparent !important;
|
||||
background-color: var(--td-scroll-bar-bg) !important;
|
||||
background-clip: content-box !important;
|
||||
}
|
||||
html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--td-scroll-bar-bg-over) !important;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#marker {
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
#marker_drop {
|
||||
margin-bottom: 0px;
|
||||
transition: margin 160ms ease-in-out;
|
||||
}
|
||||
#marker_drop.moving {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
#marker_shadow {
|
||||
position: absolute;
|
||||
}
|
||||
#search_venues {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
top: -30px;
|
||||
transition: top 200ms ease-in-out;
|
||||
}
|
||||
#search_venues.shown {
|
||||
top: 6px;
|
||||
}
|
||||
#search_venues_inner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: var(--td-window-bg);
|
||||
color: var(--td-window-active-text-fg);
|
||||
cursor: pointer;
|
||||
border-radius: 14px;
|
||||
padding: 5px 12px 6px;
|
||||
box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow);
|
||||
}
|
||||
#search_venues_inner:hover {
|
||||
background: var(--td-window-bg-over);
|
||||
}
|
||||
#search_venues_content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
#search_venues_content:before {
|
||||
content: var(--td-lng-maps-places-in-area);
|
||||
}
|
||||
#search_venues_inner .ripple .inner {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
background-color: var(--td-window-bg-ripple);
|
||||
}
|
||||
#search_venues_inner .ripple.hiding {
|
||||
animation: fadeOut 200ms linear forwards;
|
||||
}
|
||||
@keyframes ripple {
|
||||
to {
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
@keyframes fadeOut {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
199
Telegram/Resources/picker_html/picker.js
Normal file
199
Telegram/Resources/picker_html/picker.js
Normal file
@@ -0,0 +1,199 @@
|
||||
var LocationPicker = {
|
||||
startZoom: 14,
|
||||
flySpeed: 2.4,
|
||||
notify: function(message) {
|
||||
if (window.external && window.external.invoke) {
|
||||
window.external.invoke(JSON.stringify(message));
|
||||
}
|
||||
},
|
||||
frameKeyDown: function (e) {
|
||||
const keyW = (e.key === 'w')
|
||||
|| (e.code === 'KeyW')
|
||||
|| (e.keyCode === 87);
|
||||
const keyQ = (e.key === 'q')
|
||||
|| (e.code === 'KeyQ')
|
||||
|| (e.keyCode === 81);
|
||||
const keyM = (e.key === 'm')
|
||||
|| (e.code === 'KeyM')
|
||||
|| (e.keyCode === 77);
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
|
||||
e.preventDefault();
|
||||
LocationPicker.notify({
|
||||
event: 'keydown',
|
||||
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
|
||||
key: keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
});
|
||||
} else if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
LocationPicker.notify({
|
||||
event: 'keydown',
|
||||
key: 'escape',
|
||||
});
|
||||
}
|
||||
},
|
||||
isNight: function() {
|
||||
var html = document.getElementsByTagName('html')[0];
|
||||
return html.style.getPropertyValue('--td-night') == '1';
|
||||
},
|
||||
lightPreset: function() {
|
||||
return LocationPicker.isNight() ? 'night' : 'day';
|
||||
},
|
||||
updateStyles: function (styles) {
|
||||
if (LocationPicker.styles !== styles) {
|
||||
LocationPicker.styles = styles;
|
||||
document.getElementsByTagName('html')[0].style = styles;
|
||||
|
||||
LocationPicker.map.setConfigProperty(
|
||||
'basemap',
|
||||
'lightPreset',
|
||||
LocationPicker.lightPreset());
|
||||
}
|
||||
},
|
||||
init: function (params) {
|
||||
mapboxgl.accessToken = params.token;
|
||||
if (params.protocol) {
|
||||
mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com';
|
||||
}
|
||||
|
||||
var options = { container: 'map', config: {
|
||||
basemap: { lightPreset: LocationPicker.lightPreset() }
|
||||
} };
|
||||
var center = params.center;
|
||||
if (center) {
|
||||
center = [center[1], center[0]];
|
||||
options.center = center;
|
||||
options.zoom = LocationPicker.startZoom;
|
||||
} else if (params.bounds) {
|
||||
options.bounds = params.bounds;
|
||||
center = new mapboxgl.LngLatBounds(params.bounds).getCenter();
|
||||
} else {
|
||||
center = [0, 0];
|
||||
}
|
||||
LocationPicker.map = new mapboxgl.Map(options);
|
||||
LocationPicker.createMarker(center);
|
||||
LocationPicker.trackMovement();
|
||||
LocationPicker.initSearchVenueRipple();
|
||||
},
|
||||
marker: function() {
|
||||
return document.getElementById('marker_drop');
|
||||
},
|
||||
createMarker: function(center) {
|
||||
document.getElementById('marker').style.display = 'flex';
|
||||
},
|
||||
clearMovingTimer: function() {
|
||||
if (LocationPicker.clearMovingTimeoutId) {
|
||||
clearTimeout(LocationPicker.clearMovingTimeoutId);
|
||||
LocationPicker.clearMovingTimeoutId = 0;
|
||||
}
|
||||
},
|
||||
startMovingTimer: function(done) {
|
||||
LocationPicker.clearMovingTimer();
|
||||
LocationPicker.clearMovingTimeoutId = setTimeout(done, 500);
|
||||
},
|
||||
trackMovement: function() {
|
||||
LocationPicker.map.on('movestart', function() {
|
||||
LocationPicker.marker().classList.add('moving');
|
||||
LocationPicker.clearMovingTimer();
|
||||
LocationPicker.toggleSearchVenues(false);
|
||||
LocationPicker.notify({ event: 'move_start' });
|
||||
});
|
||||
LocationPicker.map.on('moveend', function() {
|
||||
LocationPicker.startMovingTimer(function() {
|
||||
LocationPicker.marker().classList.remove('moving');
|
||||
LocationPicker.notify({
|
||||
event: 'move_end',
|
||||
latitude: LocationPicker.map.getCenter().lat,
|
||||
longitude: LocationPicker.map.getCenter().lng
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
narrowTo: function (point) {
|
||||
LocationPicker.map.flyTo({
|
||||
center: [point[1], point[0]],
|
||||
zoom: LocationPicker.startZoom,
|
||||
speed: LocationPicker.flySpeed,
|
||||
});
|
||||
},
|
||||
send: function () {
|
||||
LocationPicker.notify({
|
||||
event: 'send',
|
||||
latitude: LocationPicker.map.getCenter().lat,
|
||||
longitude: LocationPicker.map.getCenter().lng
|
||||
});
|
||||
},
|
||||
addRipple: function (button, x, y) {
|
||||
const ripple = document.createElement('span');
|
||||
ripple.classList.add('ripple');
|
||||
|
||||
const inner = document.createElement('span');
|
||||
inner.classList.add('inner');
|
||||
|
||||
var rect = button.getBoundingClientRect();
|
||||
x -= rect.x;
|
||||
y -= rect.y;
|
||||
|
||||
const mx = button.clientWidth - x;
|
||||
const my = button.clientHeight - y;
|
||||
const sq1 = x * x + y * y;
|
||||
const sq2 = mx * mx + y * y;
|
||||
const sq3 = x * x + my * my;
|
||||
const sq4 = mx * mx + my * my;
|
||||
const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4));
|
||||
|
||||
inner.style.width = inner.style.height = `${2 * radius}px`;
|
||||
inner.style.left = `${x - radius}px`;
|
||||
inner.style.top = `${y - radius}px`;
|
||||
inner.classList.add('inner');
|
||||
|
||||
ripple.addEventListener('animationend', function (e) {
|
||||
if (e.animationName === 'fadeOut') {
|
||||
ripple.remove();
|
||||
}
|
||||
});
|
||||
|
||||
ripple.appendChild(inner);
|
||||
button.appendChild(ripple);
|
||||
},
|
||||
stopRipples: function (button) {
|
||||
const id = button.id ? button.id : button;
|
||||
button = document.getElementById(id);
|
||||
const ripples = button.getElementsByClassName('ripple');
|
||||
for (var i = 0; i < ripples.length; ++i) {
|
||||
const ripple = ripples[i];
|
||||
if (!ripple.classList.contains('hiding')) {
|
||||
ripple.classList.add('hiding');
|
||||
}
|
||||
}
|
||||
},
|
||||
initSearchVenueRipple: function() {
|
||||
var button = document.getElementById('search_venues_inner');
|
||||
button.addEventListener('mousedown', function (e) {
|
||||
LocationPicker.addRipple(e.currentTarget, e.clientX, e.clientY);
|
||||
LocationPicker.searchVenuesPressed = true;
|
||||
});
|
||||
button.addEventListener('mouseup', function (e) {
|
||||
const id = e.currentTarget.id;
|
||||
setTimeout(function () {
|
||||
LocationPicker.stopRipples(id);
|
||||
}, 0);
|
||||
if (LocationPicker.searchVenuesPressed) {
|
||||
LocationPicker.searchVenuesPressed = false;
|
||||
LocationPicker.toggleSearchVenues(false);
|
||||
LocationPicker.notify({
|
||||
event: 'search_venues',
|
||||
latitude: LocationPicker.map.getCenter().lat,
|
||||
longitude: LocationPicker.map.getCenter().lng
|
||||
});
|
||||
}
|
||||
});
|
||||
button.addEventListener('mouseleave', function (e) {
|
||||
LocationPicker.stopRipples(e.currentTarget);
|
||||
LocationPicker.searchVenuesPressed = false;
|
||||
});
|
||||
},
|
||||
toggleSearchVenues: function(shown) {
|
||||
var button = document.getElementById('search_venues');
|
||||
button.classList.toggle('shown', shown);
|
||||
},
|
||||
};
|
||||
6
Telegram/Resources/qrc/telegram/picker.qrc
Normal file
6
Telegram/Resources/qrc/telegram/picker.qrc
Normal file
@@ -0,0 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="/picker">
|
||||
<file alias="picker.css">../../picker_html/picker.css</file>
|
||||
<file alias="picker.js">../../picker_html/picker.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.2.3.0" />
|
||||
Version="5.2.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,2,3,0
|
||||
PRODUCTVERSION 5,2,3,0
|
||||
FILEVERSION 5,2,5,0
|
||||
PRODUCTVERSION 5,2,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.2.3.0"
|
||||
VALUE "FileVersion", "5.2.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.2.3.0"
|
||||
VALUE "ProductVersion", "5.2.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,2,3,0
|
||||
PRODUCTVERSION 5,2,3,0
|
||||
FILEVERSION 5,2,5,0
|
||||
PRODUCTVERSION 5,2,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.2.3.0"
|
||||
VALUE "FileVersion", "5.2.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.2.3.0"
|
||||
VALUE "ProductVersion", "5.2.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -155,6 +155,7 @@ int main(int argc, char *argv[])
|
||||
QString remove;
|
||||
int version = 0;
|
||||
[[maybe_unused]] bool targetwin64 = false;
|
||||
[[maybe_unused]] bool targetwinarm = false;
|
||||
[[maybe_unused]] bool targetarmac = false;
|
||||
QFileInfoList files;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
@@ -165,6 +166,7 @@ int main(int argc, char *argv[])
|
||||
if (remove.isEmpty()) remove = info.canonicalPath() + "/";
|
||||
} else if (string("-target") == argv[i] && i + 1 < argc) {
|
||||
targetwin64 = (string("win64") == argv[i + 1]);
|
||||
targetwinarm = (string("winarm") == argv[i + 1]);
|
||||
} else if (string("-arch") == argv[i] && i + 1 < argc) {
|
||||
targetarmac = (string("arm64") == argv[i + 1]);
|
||||
if (!targetarmac && string("x86_64") != argv[i + 1]) {
|
||||
@@ -493,7 +495,7 @@ int main(int argc, char *argv[])
|
||||
cout << "Signature verified!\n";
|
||||
RSA_free(pbKey);
|
||||
#ifdef Q_OS_WIN
|
||||
QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
QString outName((targetwinarm ? QString("tarm64upd%1") : targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#elif defined Q_OS_MAC
|
||||
QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#else
|
||||
|
||||
@@ -127,11 +127,7 @@ void SendBotCallbackData(
|
||||
UrlClickHandler::Open(link);
|
||||
return;
|
||||
}
|
||||
const auto scoreLink = AppendShareGameScoreUrl(
|
||||
session,
|
||||
link,
|
||||
item->fullId());
|
||||
BotGameUrlClickHandler(bot, scoreLink).onClick({
|
||||
BotGameUrlClickHandler(bot, link).onClick({
|
||||
Qt::LeftButton,
|
||||
QVariant::fromValue(ClickHandlerContext{
|
||||
.itemId = item->fullId(),
|
||||
@@ -492,20 +488,23 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
|
||||
case ButtonType::WebView: {
|
||||
if (const auto bot = item->getMessageBot()) {
|
||||
bot->session().attachWebView().request(
|
||||
controller,
|
||||
Api::SendAction(bot->owner().history(bot)),
|
||||
bot,
|
||||
{ .text = button->text, .url = button->data });
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = { .text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = false },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::SimpleWebView: {
|
||||
if (const auto bot = item->getMessageBot()) {
|
||||
bot->session().attachWebView().requestSimple(
|
||||
controller,
|
||||
bot,
|
||||
{ .text = button->text, .url = button->data });
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = {.text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = true },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ struct SendOptions {
|
||||
bool invertCaption = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendOptions &,
|
||||
const SendOptions &) = default;
|
||||
};
|
||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||
|
||||
@@ -52,6 +56,10 @@ struct SendAction {
|
||||
MsgId replaceMediaOf = 0;
|
||||
|
||||
[[nodiscard]] MTPInputReplyTo mtpReplyTo() const;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendAction &,
|
||||
const SendAction &) = default;
|
||||
};
|
||||
|
||||
struct MessageToSend {
|
||||
|
||||
@@ -153,9 +153,7 @@ mtpRequestId EditMessage(
|
||||
const auto &text = item->originalText();
|
||||
const auto webpage = (!item->media() || !item->media()->webpage())
|
||||
? Data::WebPageDraft{ .removed = true }
|
||||
: Data::WebPageDraft{
|
||||
.id = item->media()->webpage()->id,
|
||||
};
|
||||
: Data::WebPageDraft::FromItem(item);
|
||||
return EditMessage(
|
||||
item,
|
||||
text,
|
||||
|
||||
@@ -62,6 +62,79 @@ void InnerFillMessagePostFlags(
|
||||
}
|
||||
}
|
||||
|
||||
void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
const auto session = &history->session();
|
||||
const auto api = &session->api();
|
||||
|
||||
action.clearDraft = false;
|
||||
action.generateLocal = false;
|
||||
api->sendAction(action);
|
||||
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (action.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
InnerFillMessagePostFlags(action.options, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
action.replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
std::move(inputMedia),
|
||||
MTPstring(),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
MTPvector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId);
|
||||
});
|
||||
|
||||
api->finishForwarding(action);
|
||||
}
|
||||
|
||||
template <typename MediaData>
|
||||
void SendExistingMedia(
|
||||
MessageToSend &&message,
|
||||
@@ -362,6 +435,33 @@ bool SendDice(MessageToSend &message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendLocation(SendAction action, float64 lat, float64 lon) {
|
||||
SendSimpleMedia(
|
||||
action,
|
||||
MTP_inputMediaGeoPoint(
|
||||
MTP_inputGeoPoint(
|
||||
MTP_flags(0),
|
||||
MTP_double(lat),
|
||||
MTP_double(lon),
|
||||
MTPint()))); // accuracy_radius
|
||||
}
|
||||
|
||||
void SendVenue(SendAction action, Data::InputVenue venue) {
|
||||
SendSimpleMedia(
|
||||
action,
|
||||
MTP_inputMediaVenue(
|
||||
MTP_inputGeoPoint(
|
||||
MTP_flags(0),
|
||||
MTP_double(venue.lat),
|
||||
MTP_double(venue.lon),
|
||||
MTPint()), // accuracy_radius
|
||||
MTP_string(venue.title),
|
||||
MTP_string(venue.address),
|
||||
MTP_string(venue.provider),
|
||||
MTP_string(venue.id),
|
||||
MTP_string(venue.venueType)));
|
||||
}
|
||||
|
||||
void FillMessagePostFlags(
|
||||
const SendAction &action,
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -7,15 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
class History;
|
||||
class PhotoData;
|
||||
class DocumentData;
|
||||
struct FilePrepareResult;
|
||||
|
||||
namespace Data {
|
||||
struct InputVenue;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct MessageToSend;
|
||||
@@ -33,6 +37,13 @@ void SendExistingPhoto(
|
||||
|
||||
bool SendDice(MessageToSend &message);
|
||||
|
||||
// We can't create Data::LocationPoint() and use it
|
||||
// for a local sending message, because we can't request
|
||||
// map thumbnail in messages history without access hash.
|
||||
void SendLocation(SendAction action, float64 lat, float64 lon);
|
||||
|
||||
void SendVenue(SendAction action, Data::InputVenue venue);
|
||||
|
||||
void FillMessagePostFlags(
|
||||
const SendAction &action,
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -100,6 +100,8 @@ void AboutBox::showVersionHistory() {
|
||||
url += u"win/%1.zip"_q;
|
||||
} else if (Platform::IsWindows64Bit()) {
|
||||
url += u"win64/%1.zip"_q;
|
||||
} else if (Platform::IsWindowsARM64()) {
|
||||
url += u"winarm/%1.zip"_q;
|
||||
} else if (Platform::IsMac()) {
|
||||
url += u"mac/%1.zip"_q;
|
||||
} else if (Platform::IsLinux()) {
|
||||
@@ -155,6 +157,8 @@ QString currentVersionText() {
|
||||
}
|
||||
if (Platform::IsWindows64Bit()) {
|
||||
result += " x64";
|
||||
} else if (Platform::IsWindowsARM64()) {
|
||||
result += " arm64";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ Data::ChatFilter ChangedFilter(
|
||||
filter.id(),
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
filter.colorIndex(),
|
||||
filter.flags(),
|
||||
std::move(always),
|
||||
filter.pinned(),
|
||||
@@ -58,6 +59,7 @@ Data::ChatFilter ChangedFilter(
|
||||
filter.id(),
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
filter.colorIndex(),
|
||||
filter.flags(),
|
||||
std::move(always),
|
||||
filter.pinned(),
|
||||
|
||||
@@ -83,6 +83,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
rules.iconEmoji(),
|
||||
rules.colorIndex(),
|
||||
(rules.flags() & ~flag),
|
||||
rules.always(),
|
||||
rules.pinned(),
|
||||
@@ -104,6 +105,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
rules.iconEmoji(),
|
||||
rules.colorIndex(),
|
||||
rules.flags(),
|
||||
std::move(always),
|
||||
std::move(pinned),
|
||||
@@ -170,6 +172,7 @@ void EditExceptions(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
rules.iconEmoji(),
|
||||
rules.colorIndex(),
|
||||
((rules.flags() & ~options)
|
||||
| rawController->chosenOptions()),
|
||||
include ? std::move(changed) : std::move(removeFrom),
|
||||
@@ -240,6 +243,7 @@ void CreateIconSelector(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
Ui::LookupFilterIcon(icon).emoji,
|
||||
rules.colorIndex(),
|
||||
rules.flags(),
|
||||
rules.always(),
|
||||
rules.pinned(),
|
||||
|
||||
@@ -1648,7 +1648,9 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
if (peerId) {
|
||||
auto text = tr::lng_credits_box_history_entry_peer();
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
@@ -1700,6 +1702,12 @@ void AddCreditsHistoryEntryTable(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::PremiumBot) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_via_premium_bot(
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
|
||||
@@ -418,7 +418,9 @@ void SimpleLimitBox(
|
||||
BoxShowFinishes(box),
|
||||
0,
|
||||
descriptor.current,
|
||||
descriptor.premiumLimit,
|
||||
(descriptor.complexRatio
|
||||
? descriptor.premiumLimit
|
||||
: 2 * descriptor.current),
|
||||
premiumPossible,
|
||||
descriptor.phrase,
|
||||
descriptor.icon);
|
||||
@@ -769,7 +771,7 @@ void FilterLinksLimitBox(
|
||||
premiumLimit,
|
||||
&st::premiumIconChats,
|
||||
std::nullopt,
|
||||
true });
|
||||
/*true */}); // Don't use real ratio, "Free" doesn't fit.
|
||||
}
|
||||
|
||||
|
||||
@@ -856,7 +858,7 @@ void ShareableFiltersLimitBox(
|
||||
premiumLimit,
|
||||
&st::premiumIconFolders,
|
||||
std::nullopt,
|
||||
true });
|
||||
/*true*/ }); // Don't use real ratio, "Free" doesn't fit.
|
||||
}
|
||||
|
||||
void FilterPinsLimitBox(
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_form.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
|
||||
@@ -257,6 +258,8 @@ void SendCreditsBox(
|
||||
if (state->confirmButtonBusy.current()) {
|
||||
return;
|
||||
}
|
||||
const auto show = box->uiShow();
|
||||
const auto weak = MakeWeak(box.get());
|
||||
state->confirmButtonBusy = true;
|
||||
session->api().request(
|
||||
MTPpayments_SendStarsForm(
|
||||
@@ -264,12 +267,31 @@ void SendCreditsBox(
|
||||
MTP_long(form->formId),
|
||||
form->inputInvoice)
|
||||
).done([=](auto result) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
}
|
||||
sent();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->uiShow()->showToast(error.type());
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
}
|
||||
const auto id = error.type();
|
||||
if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
|
||||
auto error = ::Ui::MakeInformBox(
|
||||
tr::lng_payments_precheckout_stars_failed(tr::now));
|
||||
error->boxClosing() | rpl::start_with_next([=] {
|
||||
if (const auto paybox = weak.data()) {
|
||||
paybox->closeBox();
|
||||
}
|
||||
}, error->lifetime());
|
||||
show->showBox(std::move(error));
|
||||
} else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) {
|
||||
show->showToast(
|
||||
tr::lng_payments_precheckout_stars_timeout(tr::now));
|
||||
} else {
|
||||
show->showToast(id);
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
{
|
||||
|
||||
@@ -1409,55 +1409,6 @@ std::vector<not_null<Data::Thread*>> ShareBox::Inner::selected() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
QString AppendShareGameScoreUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const FullMsgId &fullId) {
|
||||
auto shareHashData = QByteArray(0x20, Qt::Uninitialized);
|
||||
auto shareHashDataInts = reinterpret_cast<uint64*>(shareHashData.data());
|
||||
const auto peer = fullId.peer
|
||||
? session->data().peerLoaded(fullId.peer)
|
||||
: static_cast<PeerData*>(nullptr);
|
||||
const auto channelAccessHash = uint64((peer && peer->isChannel())
|
||||
? peer->asChannel()->access
|
||||
: 0);
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.peer.value;
|
||||
shareHashDataInts[2] = uint64(fullId.msg.bare);
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
auto key128Size = 0x10;
|
||||
auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized);
|
||||
hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data());
|
||||
|
||||
//// Mix in channel access hash to the first 64 bits of SHA1 of data.
|
||||
//*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= channelAccessHash;
|
||||
|
||||
// Encrypt data.
|
||||
if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) {
|
||||
return url;
|
||||
}
|
||||
|
||||
auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
auto shareUrl = u"tg://share_game_score?hash="_q + QString::fromLatin1(shareHash);
|
||||
|
||||
auto shareComponent = u"tgShareScoreUrl="_q + qthelp::url_encode(shareUrl);
|
||||
|
||||
auto hashPosition = url.indexOf('#');
|
||||
if (hashPosition < 0) {
|
||||
return url + '#' + shareComponent;
|
||||
}
|
||||
auto hash = url.mid(hashPosition + 1);
|
||||
if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) {
|
||||
return url + '&' + shareComponent;
|
||||
}
|
||||
if (!hash.isEmpty()) {
|
||||
return url + '?' + shareComponent;
|
||||
}
|
||||
return url + shareComponent;
|
||||
}
|
||||
|
||||
ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
||||
const std::vector<not_null<Data::Thread*>> &result,
|
||||
const MessageIdsList &msgIds) {
|
||||
@@ -1612,9 +1563,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto show = controller->uiShow();
|
||||
const auto history = item->history();
|
||||
const auto owner = &history->owner();
|
||||
const auto session = &history->session();
|
||||
@@ -1643,7 +1593,7 @@ void FastShareMessage(
|
||||
}
|
||||
if (item->hasDirectLink()) {
|
||||
using namespace HistoryView;
|
||||
CopyPostLink(controller, item->fullId(), Context::History);
|
||||
CopyPostLink(show, item->fullId(), Context::History);
|
||||
} else if (const auto bot = item->getMessageBot()) {
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto game = media->game()) {
|
||||
@@ -1675,23 +1625,27 @@ void FastShareMessage(
|
||||
auto copyLinkCallback = canCopyLink
|
||||
? Fn<void()>(std::move(copyCallback))
|
||||
: Fn<void()>();
|
||||
controller->show(
|
||||
Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyLinkCallback),
|
||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||
show,
|
||||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
.show = !hasOnlyForcedForwardedInfo,
|
||||
},
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}),
|
||||
Ui::LayerOption::CloseOther);
|
||||
show->show(Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyLinkCallback),
|
||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||
show,
|
||||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
.show = !hasOnlyForcedForwardedInfo,
|
||||
},
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}), Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
FastShareMessage(controller->uiShow(), item);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
@@ -1793,111 +1747,3 @@ auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
|
||||
return WritePremiumRequiredError;
|
||||
}
|
||||
|
||||
void ShareGameScoreByHash(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash) {
|
||||
auto &session = controller->session();
|
||||
auto key128Size = 0x10;
|
||||
|
||||
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_confirm_phone_link_invalid()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt data.
|
||||
auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized);
|
||||
if (!session.local().decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Count SHA1() of data.
|
||||
char dataSha1[20] = { 0 };
|
||||
hashSha1(hashData.constData(), hashData.size(), dataSha1);
|
||||
|
||||
//// Mix out channel access hash from the first 64 bits of SHA1 of data.
|
||||
//auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1);
|
||||
|
||||
//// Check next 64 bits of SHA1() of data.
|
||||
//auto skipSha1Part = sizeof(channelAccessHash);
|
||||
//if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
|
||||
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
// return;
|
||||
//}
|
||||
|
||||
// Check 128 bits of SHA1() of data.
|
||||
if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_share_wrong_user()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
|
||||
if (hashDataInts[0] != session.userId().bare) {
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_share_wrong_user()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto peerId = PeerId(hashDataInts[1]);
|
||||
const auto channelAccessHash = hashDataInts[3];
|
||||
if (!peerIsChannel(peerId) && channelAccessHash) {
|
||||
// If there is no channel id, there should be no channel access_hash.
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_share_wrong_user()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto msgId = MsgId(int64(hashDataInts[2]));
|
||||
if (const auto item = session.data().message(peerId, msgId)) {
|
||||
FastShareMessage(controller, item);
|
||||
} else {
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto resolveMessageAndShareScore = crl::guard(weak, [=](
|
||||
PeerData *peer) {
|
||||
auto done = crl::guard(weak, [=] {
|
||||
const auto item = weak->session().data().message(
|
||||
peerId,
|
||||
msgId);
|
||||
if (item) {
|
||||
FastShareMessage(weak.get(), item);
|
||||
} else {
|
||||
weak->show(
|
||||
Ui::MakeInformBox(tr::lng_edit_deleted()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
});
|
||||
auto &api = weak->session().api();
|
||||
api.requestMessageData(peer, msgId, std::move(done));
|
||||
});
|
||||
|
||||
const auto peer = peerIsChannel(peerId)
|
||||
? controller->session().data().peerLoaded(peerId)
|
||||
: nullptr;
|
||||
if (peer || !peerIsChannel(peerId)) {
|
||||
resolveMessageAndShareScore(peer);
|
||||
} else {
|
||||
const auto owner = &controller->session().data();
|
||||
controller->session().api().request(MTPchannels_GetChannels(
|
||||
MTP_vector<MTPInputChannel>(
|
||||
1,
|
||||
MTP_inputChannel(
|
||||
MTP_long(peerToChannel(peerId).bare),
|
||||
MTP_long(channelAccessHash)))
|
||||
)).done([=](const MTPmessages_Chats &result) {
|
||||
result.match([&](const auto &data) {
|
||||
owner->processChats(data.vchats());
|
||||
});
|
||||
if (const auto peer = owner->peerLoaded(peerId)) {
|
||||
resolveMessageAndShareScore(peer);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,13 +59,11 @@ class SlideWrap;
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
QString AppendShareGameScoreUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const FullMsgId &fullId);
|
||||
void ShareGameScoreByHash(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash);
|
||||
class ShareBox;
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item);
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace tgcalls {
|
||||
class InstanceImpl;
|
||||
class InstanceV2Impl;
|
||||
class InstanceV2ReferenceImpl;
|
||||
class InstanceV2_4_0_0Impl;
|
||||
class InstanceImplLegacy;
|
||||
void SetLegacyGlobalServerConfig(const std::string &serverConfig);
|
||||
} // namespace tgcalls
|
||||
@@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q;
|
||||
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
|
||||
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
|
||||
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
|
||||
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
|
||||
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
|
||||
|
||||
[[nodiscard]] base::flat_set<int64> CollectEndpointIds(
|
||||
|
||||
@@ -215,7 +215,7 @@ void Panel::initWindow() {
|
||||
}
|
||||
const auto shown = _layerBg->topShownLayer();
|
||||
return (!shown || !shown->geometry().contains(widgetPoint))
|
||||
? (Flag::Move | Flag::FullScreen)
|
||||
? (Flag::Move | Flag::Menu | Flag::FullScreen)
|
||||
: Flag::None;
|
||||
});
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ void Panel::initWindow() {
|
||||
}
|
||||
const auto shown = _layerBg->topShownLayer();
|
||||
return (!shown || !shown->geometry().contains(widgetPoint))
|
||||
? (Flag::Move | Flag::Maximize)
|
||||
? (Flag::Move | Flag::Menu | Flag::Maximize)
|
||||
: Flag::None;
|
||||
});
|
||||
|
||||
|
||||
@@ -1423,3 +1423,61 @@ paidTagLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
paidTagPadding: margins(16px, 6px, 16px, 6px);
|
||||
|
||||
pickLocationWindow: size(364px, 680px);
|
||||
pickLocationMapHeight: 220px;
|
||||
pickLocationCollapsedHeight: 92px;
|
||||
pickLocationRowHeight: 52px;
|
||||
pickLocationButton: FlatButton {
|
||||
height: pickLocationRowHeight;
|
||||
bgColor: contactsBg;
|
||||
overBgColor: contactsBgOver;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
pickLocationButtonText: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 20px;
|
||||
style: semiboldTextStyle;
|
||||
textFg: windowBoldFg;
|
||||
}
|
||||
pickLocationButtonStatus: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 20px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
pickLocationButtonSkip: 6px;
|
||||
pickLocationSendIcon: icon{{ "chat/filled_location", windowFgActive }};
|
||||
pickLocationVenueItem: PeerListItem(defaultPeerListItem) {
|
||||
height: pickLocationRowHeight;
|
||||
photoSize: 42px;
|
||||
photoPosition: point(18px, 5px);
|
||||
namePosition: point(70px, 7px);
|
||||
statusPosition: point(70px, 27px);
|
||||
button: OutlineButton(defaultPeerListButton) {
|
||||
textBg: contactsBg;
|
||||
textBgOver: contactsBgOver;
|
||||
font: normalFont;
|
||||
padding: margins(11px, 5px, 11px, 5px);
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
statusFg: contactsStatusFg;
|
||||
statusFgOver: contactsStatusFgOver;
|
||||
statusFgActive: contactsStatusFgOnline;
|
||||
}
|
||||
pickLocationVenueList: PeerList(defaultPeerList) {
|
||||
item: pickLocationVenueItem;
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
pickLocationIconSkip: 6px;
|
||||
pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
size: size(56px, 56px);
|
||||
color: windowSubTextFg;
|
||||
thickness: 4px;
|
||||
}
|
||||
pickLocationPromoHeight: 32px;
|
||||
pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 11px;
|
||||
width: -96px;
|
||||
font: font(15px semibold);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "menu/menu_send.h" // SendMenu::FillSendMenu
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "ui/controls/tabbed_search.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -48,7 +49,6 @@ namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kSearchBotUsername = "gif"_cs;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
@@ -864,13 +864,11 @@ void GifsListWidget::searchForGifs(const QString &query) {
|
||||
}
|
||||
|
||||
if (!_searchBot && !_searchBotRequestId) {
|
||||
auto username = kSearchBotUsername.utf16();
|
||||
const auto username = session().serverConfig().gifSearchUsername;
|
||||
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
auto &data = result.data();
|
||||
session().data().processUsers(data.vusers());
|
||||
session().data().processChats(data.vchats());
|
||||
const auto peer = session().data().peerLoaded(
|
||||
|
||||
@@ -525,7 +525,7 @@ void InitMessageFieldGeometry(not_null<Ui::InputField*> field) {
|
||||
st::historySendSize.height() - 2 * st::historySendPadding);
|
||||
field->setMaxHeight(st::historyComposeFieldMaxHeight);
|
||||
|
||||
field->document()->setDocumentMargin(4.);
|
||||
field->setDocumentMargin(4.);
|
||||
field->setAdditionalMargin(style::ConvertScale(4) - 4);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,11 @@ Application::~Application() {
|
||||
// For example Domain::removeRedundantAccounts() is called from
|
||||
// Domain::finish() and there is a violation on Ensures(started()).
|
||||
Payments::CheckoutProcess::ClearAll();
|
||||
InlineBots::AttachWebView::ClearAll();
|
||||
for (const auto &[index, account] : _domain->accounts()) {
|
||||
if (account->sessionExists()) {
|
||||
account->session().attachWebView().closeAll();
|
||||
}
|
||||
}
|
||||
_iv->closeAll();
|
||||
|
||||
_domain->finish();
|
||||
@@ -270,14 +274,9 @@ void Application::run() {
|
||||
|
||||
refreshGlobalProxy(); // Depends on app settings being read.
|
||||
|
||||
if (const auto old = Local::oldSettingsVersion()) {
|
||||
if (old < AppVersion) {
|
||||
autoRegisterUrlScheme();
|
||||
Platform::NewVersionLaunched(old);
|
||||
}
|
||||
} else {
|
||||
// Initial launch.
|
||||
if (const auto old = Local::oldSettingsVersion(); old < AppVersion) {
|
||||
autoRegisterUrlScheme();
|
||||
Platform::NewVersionLaunched(old);
|
||||
}
|
||||
|
||||
if (cAutoStart() && !Platform::AutostartSupported()) {
|
||||
|
||||
@@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "window/window_controller.h"
|
||||
@@ -171,23 +173,42 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
||||
if (Core::InternalPassportLink(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto open = [=] {
|
||||
const auto openLink = [=] {
|
||||
UrlClickHandler::Open(url, context.other);
|
||||
};
|
||||
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
|
||||
open();
|
||||
} else if (!_bot
|
||||
|| _bot->isVerified()
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
const auto weakController = my.sessionWindow;
|
||||
const auto controller = weakController.get();
|
||||
const auto item = controller
|
||||
? controller->session().data().message(my.itemId)
|
||||
: nullptr;
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto game = media ? media->game() : nullptr;
|
||||
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
|
||||
openLink();
|
||||
}
|
||||
const auto bot = _bot;
|
||||
const auto title = game->title;
|
||||
const auto itemId = my.itemId;
|
||||
const auto openGame = [=] {
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.button = {.url = url.toUtf8() },
|
||||
.source = InlineBots::WebViewSourceGame{
|
||||
.messageId = itemId,
|
||||
.title = title,
|
||||
},
|
||||
});
|
||||
};
|
||||
if (_bot->isVerified()
|
||||
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) {
|
||||
open();
|
||||
openGame();
|
||||
} else {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto callback = [=, bot = _bot](Fn<void()> close) {
|
||||
close();
|
||||
bot->session().local().markBotTrustedOpenGame(bot->id);
|
||||
open();
|
||||
openGame();
|
||||
};
|
||||
controller->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_allow_bot_pass(
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Ui {
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
namespace InlineBots {
|
||||
struct WebViewContext;
|
||||
} // namespace InlineBots
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -38,10 +42,10 @@ class SessionController;
|
||||
class PeerData;
|
||||
struct ClickHandlerContext {
|
||||
FullMsgId itemId;
|
||||
QString attachBotWebviewUrl;
|
||||
// Is filled from sections.
|
||||
Fn<HistoryView::ElementDelegate*()> elementDelegate;
|
||||
base::weak_ptr<Window::SessionController> sessionWindow;
|
||||
std::shared_ptr<InlineBots::WebViewContext> botWebviewContext;
|
||||
std::shared_ptr<Ui::Show> show;
|
||||
bool mayShowConfirmation = false;
|
||||
bool skipBotAutoLogin = false;
|
||||
|
||||
@@ -296,13 +296,17 @@ bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context,
|
||||
|
||||
QString PlatformString() {
|
||||
if (Platform::IsWindowsStoreBuild()) {
|
||||
return Platform::IsWindows64Bit()
|
||||
return Platform::IsWindowsARM64()
|
||||
? u"WinStoreARM64"_q
|
||||
: Platform::IsWindows64Bit()
|
||||
? u"WinStore64Bit"_q
|
||||
: u"WinStore32Bit"_q;
|
||||
} else if (Platform::IsWindows32Bit()) {
|
||||
return u"Windows32Bit"_q;
|
||||
} else if (Platform::IsWindows64Bit()) {
|
||||
return u"Windows64Bit"_q;
|
||||
} else if (Platform::IsWindowsARM64()) {
|
||||
return u"WindowsARM64"_q;
|
||||
} else if (Platform::IsMacStoreBuild()) {
|
||||
return u"MacAppStore"_q;
|
||||
} else if (Platform::IsMac()) {
|
||||
|
||||
243
Telegram/SourceFiles/core/current_geo_location.cpp
Normal file
243
Telegram/SourceFiles/core/current_geo_location.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
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 "core/current_geo_location.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/raw/raw_countries_bounds.h"
|
||||
#include "platform/platform_current_geo_location.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QString ChooseLanguage(const QString &language) {
|
||||
// https://docs.mapbox.com/api/search/geocoding#language-coverage
|
||||
auto result = language.toLower().replace('-', '_');
|
||||
static const auto kGood = std::array{
|
||||
// Global coverage.
|
||||
u"de"_q, u"en"_q, u"es"_q, u"fr"_q, u"it"_q, u"nl"_q, u"pl"_q,
|
||||
|
||||
// Local coverage.
|
||||
u"az"_q, u"bn"_q, u"ca"_q, u"cs"_q, u"da"_q, u"el"_q, u"fa"_q,
|
||||
u"fi"_q, u"ga"_q, u"hu"_q, u"id"_q, u"is"_q, u"ja"_q, u"ka"_q,
|
||||
u"km"_q, u"ko"_q, u"lt"_q, u"lv"_q, u"mn"_q, u"pt"_q, u"ro"_q,
|
||||
u"sk"_q, u"sq"_q, u"sv"_q, u"th"_q, u"tl"_q, u"uk"_q, u"vi"_q,
|
||||
u"zh"_q, u"zh_Hans"_q, u"zh_TW"_q,
|
||||
|
||||
// Limited coverage.
|
||||
u"ar"_q, u"bs"_q, u"gu"_q, u"he"_q, u"hi"_q, u"kk"_q, u"lo"_q,
|
||||
u"my"_q, u"nb"_q, u"ru"_q, u"sr"_q, u"te"_q, u"tk"_q, u"tr"_q,
|
||||
u"zh_Hant"_q,
|
||||
};
|
||||
for (const auto &known : kGood) {
|
||||
if (known.toLower() == result) {
|
||||
return known;
|
||||
}
|
||||
}
|
||||
if (const auto delimeter = result.indexOf('_'); delimeter > 0) {
|
||||
result = result.mid(0, delimeter);
|
||||
for (const auto &known : kGood) {
|
||||
if (known == result) {
|
||||
return known;
|
||||
}
|
||||
}
|
||||
}
|
||||
return u"en"_q;
|
||||
}
|
||||
|
||||
void ResolveLocationAddressGeneric(
|
||||
const GeoLocation &location,
|
||||
const QString &language,
|
||||
const QString &token,
|
||||
Fn<void(GeoAddress)> callback) {
|
||||
const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6"
|
||||
"/reverse?longitude=%1&latitude=%2&language=%3&access_token=%4"_q
|
||||
.arg(location.point.y())
|
||||
.arg(location.point.x())
|
||||
.arg(ChooseLanguage(language));
|
||||
static auto Cache = base::flat_map<QString, GeoAddress>();
|
||||
const auto i = Cache.find(partialUrl);
|
||||
if (i != end(Cache)) {
|
||||
callback(i->second);
|
||||
return;
|
||||
}
|
||||
const auto finishWith = [=](GeoAddress result) {
|
||||
Cache[partialUrl] = result;
|
||||
callback(result);
|
||||
};
|
||||
|
||||
struct State final : QObject {
|
||||
explicit State(QObject *parent)
|
||||
: QObject(parent)
|
||||
, manager(this)
|
||||
, destroyer([=] { if (sent.empty()) delete this; }) {
|
||||
}
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
std::vector<QPointer<QNetworkReply>> sent;
|
||||
base::Timer destroyer;
|
||||
};
|
||||
|
||||
static auto state = QPointer<State>();
|
||||
if (!state) {
|
||||
state = Ui::CreateChild<State>(qApp);
|
||||
}
|
||||
const auto destroyReplyDelayed = [](QNetworkReply *reply) {
|
||||
InvokeQueued(reply, [=] {
|
||||
for (auto i = begin(state->sent); i != end(state->sent);) {
|
||||
if (!*i || *i == reply) {
|
||||
i = state->sent.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
delete reply;
|
||||
if (state->sent.empty()) {
|
||||
state->destroyer.callOnce(kDestroyManagerTimeout);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
auto request = QNetworkRequest(partialUrl.arg(token));
|
||||
request.setRawHeader("Referer", "http://desktop-app-resource/");
|
||||
|
||||
const auto reply = state->manager.get(request);
|
||||
QObject::connect(reply, &QNetworkReply::finished, [=] {
|
||||
destroyReplyDelayed(reply);
|
||||
|
||||
const auto json = QJsonDocument::fromJson(reply->readAll());
|
||||
if (!json.isObject()) {
|
||||
finishWith({});
|
||||
return;
|
||||
}
|
||||
const auto features = json["features"].toArray();
|
||||
if (features.isEmpty()) {
|
||||
finishWith({});
|
||||
return;
|
||||
}
|
||||
const auto feature = features.at(0).toObject();
|
||||
const auto properties = feature["properties"].toObject();
|
||||
const auto context = properties["context"].toObject();
|
||||
auto names = QStringList();
|
||||
auto add = [&](std::vector<QString> keys) {
|
||||
for (const auto &key : keys) {
|
||||
const auto value = context[key];
|
||||
if (value.isObject()) {
|
||||
const auto name = value.toObject()["name"].toString();
|
||||
if (!name.isEmpty()) {
|
||||
names.push_back(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
add({ /*u"address"_q, u"street"_q, */u"neighborhood"_q });
|
||||
add({ u"place"_q, u"region"_q });
|
||||
add({ u"country"_q });
|
||||
finishWith({ .name = names.join(", ") });
|
||||
});
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred, [=] {
|
||||
destroyReplyDelayed(reply);
|
||||
|
||||
finishWith({});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GeoLocation ResolveCurrentCountryLocation() {
|
||||
const auto iso2 = Platform::SystemCountry().toUpper();
|
||||
const auto &bounds = Raw::CountryBounds();
|
||||
const auto i = bounds.find(iso2);
|
||||
if (i == end(bounds)) {
|
||||
return {
|
||||
.accuracy = GeoLocationAccuracy::Failed,
|
||||
};
|
||||
}
|
||||
return {
|
||||
.point = {
|
||||
(i->second.minLat + i->second.maxLat) / 2.,
|
||||
(i->second.minLon + i->second.maxLon) / 2.,
|
||||
},
|
||||
.bounds = {
|
||||
i->second.minLat,
|
||||
i->second.minLon,
|
||||
i->second.maxLat - i->second.minLat,
|
||||
i->second.maxLon - i->second.minLon,
|
||||
},
|
||||
.accuracy = GeoLocationAccuracy::Country,
|
||||
};
|
||||
}
|
||||
|
||||
void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) {
|
||||
using namespace Platform;
|
||||
return ResolveCurrentExactLocation([done = std::move(callback)](
|
||||
GeoLocation result) {
|
||||
done(result.accuracy != GeoLocationAccuracy::Failed
|
||||
? result
|
||||
: ResolveCurrentCountryLocation());
|
||||
});
|
||||
}
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const GeoLocation &location,
|
||||
const QString &language,
|
||||
const QString &token,
|
||||
Fn<void(GeoAddress)> callback) {
|
||||
auto done = [=, done = std::move(callback)](GeoAddress result) mutable {
|
||||
if (!result && !token.isEmpty()) {
|
||||
ResolveLocationAddressGeneric(
|
||||
location,
|
||||
language,
|
||||
token,
|
||||
std::move(done));
|
||||
} else {
|
||||
done(result);
|
||||
}
|
||||
};
|
||||
Platform::ResolveLocationAddress(location, language, std::move(done));
|
||||
}
|
||||
|
||||
bool AreTheSame(const GeoLocation &a, const GeoLocation &b) {
|
||||
if (a.accuracy != GeoLocationAccuracy::Exact
|
||||
|| b.accuracy != GeoLocationAccuracy::Exact) {
|
||||
return false;
|
||||
}
|
||||
const auto normalize = [](float64 value) {
|
||||
value = std::fmod(value + 180., 360.);
|
||||
return (value + (value < 0. ? 360. : 0.)) - 180.;
|
||||
};
|
||||
constexpr auto kEpsilon = 0.0001;
|
||||
const auto lon1 = normalize(a.point.y());
|
||||
const auto lon2 = normalize(b.point.y());
|
||||
const auto diffLat = std::abs(a.point.x() - b.point.x());
|
||||
if (std::abs(a.point.x()) >= (90. - kEpsilon)
|
||||
|| std::abs(b.point.x()) >= (90. - kEpsilon)) {
|
||||
return diffLat <= kEpsilon;
|
||||
}
|
||||
auto diffLon = std::abs(lon1 - lon2);
|
||||
if (diffLon > 180.) {
|
||||
diffLon = 360. - diffLon;
|
||||
}
|
||||
|
||||
return diffLat <= kEpsilon && diffLon <= kEpsilon;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
60
Telegram/SourceFiles/core/current_geo_location.h
Normal file
60
Telegram/SourceFiles/core/current_geo_location.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 Core {
|
||||
|
||||
enum class GeoLocationAccuracy : uchar {
|
||||
Exact,
|
||||
Country,
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct GeoLocation {
|
||||
QPointF point;
|
||||
QRectF bounds;
|
||||
GeoLocationAccuracy accuracy = GeoLocationAccuracy::Failed;
|
||||
|
||||
[[nodiscard]] bool exact() const {
|
||||
return accuracy == GeoLocationAccuracy::Exact;
|
||||
}
|
||||
[[nodiscard]] bool country() const {
|
||||
return accuracy == GeoLocationAccuracy::Country;
|
||||
}
|
||||
[[nodiscard]] bool failed() const {
|
||||
return accuracy == GeoLocationAccuracy::Failed;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !failed();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] bool AreTheSame(const GeoLocation &a, const GeoLocation &b);
|
||||
|
||||
struct GeoAddress {
|
||||
QString name;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return name.isEmpty();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] GeoLocation ResolveCurrentCountryLocation();
|
||||
void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback);
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const GeoLocation &location,
|
||||
const QString &language,
|
||||
const QString &token,
|
||||
Fn<void(GeoAddress)> callback);
|
||||
|
||||
} // namespace Core
|
||||
@@ -327,21 +327,6 @@ bool ConfirmPhone(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShareGameScore(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
ShareGameScoreByHash(controller, params.value(u"hash"_q));
|
||||
controller->window().activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplySocksProxy(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
@@ -518,7 +503,9 @@ bool ResolveUsernameOrPhone(
|
||||
return false;
|
||||
}
|
||||
using ResolveType = Window::ResolveType;
|
||||
auto resolveType = ResolveType::Default;
|
||||
auto resolveType = params.contains(u"profile"_q)
|
||||
? ResolveType::Profile
|
||||
: ResolveType::Default;
|
||||
auto startToken = params.value(u"start"_q);
|
||||
if (!startToken.isEmpty()) {
|
||||
resolveType = ResolveType::BotStart;
|
||||
@@ -588,8 +575,11 @@ bool ResolveUsernameOrPhone(
|
||||
: (appname.isEmpty() && params.contains(u"startapp"_q))
|
||||
? params.value(u"startapp"_q)
|
||||
: std::optional<QString>()),
|
||||
.attachBotMenuOpen = (appname.isEmpty()
|
||||
.attachBotMainOpen = (appname.isEmpty()
|
||||
&& params.contains(u"startapp"_q)),
|
||||
.attachBotMainCompact = (appname.isEmpty()
|
||||
&& params.contains(u"startapp"_q)
|
||||
&& (params.value(u"mode"_q) == u"compact"_q)),
|
||||
.attachBotChooseTypes = InlineBots::ParseChooseTypes(
|
||||
params.value(u"choose"_q)),
|
||||
.voicechatHash = (params.contains(u"livestream"_q)
|
||||
@@ -600,7 +590,7 @@ bool ResolveUsernameOrPhone(
|
||||
? std::make_optional(params.value(u"voicechat"_q))
|
||||
: std::nullopt),
|
||||
.clickFromMessageId = myContext.itemId,
|
||||
.clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl,
|
||||
.clickFromBotWebviewContext = myContext.botWebviewContext,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -641,7 +631,7 @@ bool ResolvePrivatePost(
|
||||
}
|
||||
: Window::RepliesByLinkInfo{ v::null },
|
||||
.clickFromMessageId = my.itemId,
|
||||
.clickFromAttachBotWebviewUrl = my.attachBotWebviewUrl,
|
||||
.clickFromBotWebviewContext = my.botWebviewContext,
|
||||
});
|
||||
controller->window().activate();
|
||||
return true;
|
||||
@@ -1193,7 +1183,7 @@ bool ResolveChatLink(
|
||||
controller->showPeerByLink(Window::PeerByLinkInfo{
|
||||
.chatLinkSlug = match->captured(1),
|
||||
.clickFromMessageId = myContext.itemId,
|
||||
.clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl,
|
||||
.clickFromBotWebviewContext = myContext.botWebviewContext,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -1230,10 +1220,6 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
u"^confirmphone/?\\?(.+)(#|$)"_q,
|
||||
ConfirmPhone
|
||||
},
|
||||
{
|
||||
u"^share_game_score/?\\?(.+)(#|$)"_q,
|
||||
ShareGameScore
|
||||
},
|
||||
{
|
||||
u"^socks/?\\?(.+)(#|$)"_q,
|
||||
ApplySocksProxy
|
||||
@@ -1287,7 +1273,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
ResolveBoost,
|
||||
},
|
||||
{
|
||||
u"^message/?\\?slug=([a-zA-Z0-9\\.\\_]+)(&|$)"_q,
|
||||
u"^message/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
|
||||
ResolveChatLink
|
||||
},
|
||||
{
|
||||
|
||||
@@ -245,6 +245,7 @@ QString FindUpdateFile() {
|
||||
"^("
|
||||
"tupdate|"
|
||||
"tx64upd|"
|
||||
"tarm64upd|"
|
||||
"tmacupd|"
|
||||
"tarmacupd|"
|
||||
"tlinuxupd|"
|
||||
|
||||
@@ -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 = 5002003;
|
||||
constexpr auto AppVersionStr = "5.2.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 5002005;
|
||||
constexpr auto AppVersionStr = "5.2.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -130,6 +130,42 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn<void(QString)> fail) {
|
||||
session->user()->setBusinessDetails(std::move(details));
|
||||
}
|
||||
|
||||
void BusinessInfo::saveLocation(
|
||||
BusinessLocation data,
|
||||
Fn<void(QString)> fail) {
|
||||
const auto session = &_owner->session();
|
||||
auto details = session->user()->businessDetails();
|
||||
const auto &was = details.location;
|
||||
if (was == data) {
|
||||
return;
|
||||
} else {
|
||||
const auto session = &_owner->session();
|
||||
using Flag = MTPaccount_UpdateBusinessLocation::Flag;
|
||||
session->api().request(MTPaccount_UpdateBusinessLocation(
|
||||
MTP_flags((data.point ? Flag::f_geo_point : Flag())
|
||||
| (data.address.isEmpty() ? Flag() : Flag::f_address)),
|
||||
(data.point
|
||||
? MTP_inputGeoPoint(
|
||||
MTP_flags(0),
|
||||
MTP_double(data.point->lat()),
|
||||
MTP_double(data.point->lon()),
|
||||
MTPint()) // accuracy_radius
|
||||
: MTP_inputGeoPointEmpty()),
|
||||
MTP_string(data.address)
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
auto details = session->user()->businessDetails();
|
||||
details.location = was;
|
||||
session->user()->setBusinessDetails(std::move(details));
|
||||
if (fail) {
|
||||
fail(error.type());
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
details.location = std::move(data);
|
||||
session->user()->setBusinessDetails(std::move(details));
|
||||
}
|
||||
|
||||
void BusinessInfo::applyAwaySettings(AwaySettings data) {
|
||||
if (_awaySettings == data) {
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
|
||||
void saveWorkingHours(WorkingHours data, Fn<void(QString)> fail);
|
||||
void saveChatIntro(ChatIntro data, Fn<void(QString)> fail);
|
||||
void saveLocation(BusinessLocation data, Fn<void(QString)> fail);
|
||||
|
||||
void saveAwaySettings(AwaySettings data, Fn<void(QString)> fail);
|
||||
void applyAwaySettings(AwaySettings data);
|
||||
|
||||
44
Telegram/SourceFiles/data/components/location_pickers.cpp
Normal file
44
Telegram/SourceFiles/data/components/location_pickers.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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/components/location_pickers.h"
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "ui/controls/location_picker.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct LocationPickers::Entry {
|
||||
Api::SendAction action;
|
||||
base::weak_ptr<Ui::LocationPicker> picker;
|
||||
};
|
||||
|
||||
LocationPickers::LocationPickers() = default;
|
||||
|
||||
LocationPickers::~LocationPickers() = default;
|
||||
|
||||
Ui::LocationPicker *LocationPickers::lookup(const Api::SendAction &action) {
|
||||
for (auto i = begin(_pickers); i != end(_pickers);) {
|
||||
if (const auto strong = i->picker.get()) {
|
||||
if (i->action == action) {
|
||||
return i->picker.get();
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
i = _pickers.erase(i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LocationPickers::emplace(
|
||||
const Api::SendAction &action,
|
||||
not_null<Ui::LocationPicker*> picker) {
|
||||
_pickers.push_back({ action, picker });
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
39
Telegram/SourceFiles/data/components/location_pickers.h
Normal file
39
Telegram/SourceFiles/data/components/location_pickers.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 "base/weak_ptr.h"
|
||||
|
||||
namespace Api {
|
||||
struct SendAction;
|
||||
} // namespace Api
|
||||
|
||||
namespace Ui {
|
||||
class LocationPicker;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
|
||||
class LocationPickers final {
|
||||
public:
|
||||
LocationPickers();
|
||||
~LocationPickers();
|
||||
|
||||
Ui::LocationPicker *lookup(const Api::SendAction &action);
|
||||
void emplace(
|
||||
const Api::SendAction &action,
|
||||
not_null<Ui::LocationPicker*> picker);
|
||||
|
||||
private:
|
||||
struct Entry;
|
||||
|
||||
std::vector<Entry> _pickers;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -1089,7 +1089,8 @@ void ApplyChannelUpdate(
|
||||
| Flag::CanGetStatistics
|
||||
| Flag::ViewAsMessages
|
||||
| Flag::CanViewRevenue
|
||||
| Flag::PaidMediaAllowed;
|
||||
| Flag::PaidMediaAllowed
|
||||
| Flag::CanViewCreditsRevenue;
|
||||
channel->setFlags((channel->flags() & ~mask)
|
||||
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
||||
| (update.is_can_view_participants()
|
||||
@@ -1107,7 +1108,10 @@ void ApplyChannelUpdate(
|
||||
? Flag::ViewAsMessages
|
||||
: Flag())
|
||||
| (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag())
|
||||
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()));
|
||||
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())
|
||||
| (update.is_can_view_stars_revenue()
|
||||
? Flag::CanViewCreditsRevenue
|
||||
: Flag()));
|
||||
channel->setUserpicPhoto(update.vchat_photo());
|
||||
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
||||
channel->addFlags(Flag::Megagroup);
|
||||
|
||||
@@ -67,6 +67,7 @@ enum class ChannelDataFlag : uint64 {
|
||||
SimilarExpanded = (1ULL << 31),
|
||||
CanViewRevenue = (1ULL << 32),
|
||||
PaidMediaAllowed = (1ULL << 33),
|
||||
CanViewCreditsRevenue = (1ULL << 34),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||
|
||||
@@ -43,6 +43,7 @@ ChatFilter::ChatFilter(
|
||||
FilterId id,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
std::optional<uint8> colorIndex,
|
||||
Flags flags,
|
||||
base::flat_set<not_null<History*>> always,
|
||||
std::vector<not_null<History*>> pinned,
|
||||
@@ -50,6 +51,7 @@ ChatFilter::ChatFilter(
|
||||
: _id(id)
|
||||
, _title(title)
|
||||
, _iconEmoji(iconEmoji)
|
||||
, _colorIndex(colorIndex)
|
||||
, _always(std::move(always))
|
||||
, _pinned(std::move(pinned))
|
||||
, _never(std::move(never))
|
||||
@@ -95,6 +97,9 @@ ChatFilter ChatFilter::FromTL(
|
||||
data.vid().v,
|
||||
qs(data.vtitle()),
|
||||
qs(data.vemoticon().value_or_empty()),
|
||||
data.vcolor()
|
||||
? std::make_optional(data.vcolor()->v)
|
||||
: std::nullopt,
|
||||
flags,
|
||||
std::move(list),
|
||||
std::move(pinned),
|
||||
@@ -140,6 +145,9 @@ ChatFilter ChatFilter::FromTL(
|
||||
data.vid().v,
|
||||
qs(data.vtitle()),
|
||||
qs(data.vemoticon().value_or_empty()),
|
||||
data.vcolor()
|
||||
? std::make_optional(data.vcolor()->v)
|
||||
: std::nullopt,
|
||||
(Flag::Chatlist
|
||||
| (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())),
|
||||
std::move(list),
|
||||
@@ -189,18 +197,20 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
||||
}
|
||||
if (_flags & Flag::Chatlist) {
|
||||
using TLFlag = MTPDdialogFilterChatlist::Flag;
|
||||
const auto flags = TLFlag::f_emoticon;
|
||||
const auto flags = TLFlag::f_emoticon
|
||||
| (_colorIndex ? TLFlag::f_color : TLFlag(0));
|
||||
return MTP_dialogFilterChatlist(
|
||||
MTP_flags(flags),
|
||||
MTP_int(replaceId ? replaceId : _id),
|
||||
MTP_string(_title),
|
||||
MTP_string(_iconEmoji),
|
||||
MTPint(), // color
|
||||
MTP_int(_colorIndex.value_or(0)),
|
||||
MTP_vector<MTPInputPeer>(pinned),
|
||||
MTP_vector<MTPInputPeer>(include));
|
||||
}
|
||||
using TLFlag = MTPDdialogFilter::Flag;
|
||||
const auto flags = TLFlag::f_emoticon
|
||||
| (_colorIndex ? TLFlag::f_color : TLFlag(0))
|
||||
| ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0))
|
||||
| ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0))
|
||||
| ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0))
|
||||
@@ -221,7 +231,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
||||
MTP_int(replaceId ? replaceId : _id),
|
||||
MTP_string(_title),
|
||||
MTP_string(_iconEmoji),
|
||||
MTPint(), // color
|
||||
MTP_int(_colorIndex.value_or(0)),
|
||||
MTP_vector<MTPInputPeer>(pinned),
|
||||
MTP_vector<MTPInputPeer>(include),
|
||||
MTP_vector<MTPInputPeer>(never));
|
||||
@@ -239,6 +249,10 @@ QString ChatFilter::iconEmoji() const {
|
||||
return _iconEmoji;
|
||||
}
|
||||
|
||||
std::optional<uint8> ChatFilter::colorIndex() const {
|
||||
return _colorIndex;
|
||||
}
|
||||
|
||||
ChatFilter::Flags ChatFilter::flags() const {
|
||||
return _flags;
|
||||
}
|
||||
@@ -555,7 +569,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) {
|
||||
|
||||
_list.insert(
|
||||
begin(_list) + position,
|
||||
ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}));
|
||||
ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}, {}));
|
||||
applyChange(*(begin(_list) + position), std::move(filter));
|
||||
}
|
||||
|
||||
@@ -582,7 +596,7 @@ void ChatFilters::applyRemove(int position) {
|
||||
Expects(position >= 0 && position < _list.size());
|
||||
|
||||
const auto i = begin(_list) + position;
|
||||
applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {}));
|
||||
applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {}, {}));
|
||||
_list.erase(i);
|
||||
}
|
||||
|
||||
@@ -711,6 +725,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned(
|
||||
id,
|
||||
i->title(),
|
||||
i->iconEmoji(),
|
||||
i->colorIndex(),
|
||||
i->flags(),
|
||||
std::move(always),
|
||||
std::move(pinned),
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
FilterId id,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
std::optional<uint8> colorIndex,
|
||||
Flags flags,
|
||||
base::flat_set<not_null<History*>> always,
|
||||
std::vector<not_null<History*>> pinned,
|
||||
@@ -71,6 +72,7 @@ public:
|
||||
[[nodiscard]] FilterId id() const;
|
||||
[[nodiscard]] QString title() const;
|
||||
[[nodiscard]] QString iconEmoji() const;
|
||||
[[nodiscard]] std::optional<uint8> colorIndex() const;
|
||||
[[nodiscard]] Flags flags() const;
|
||||
[[nodiscard]] bool chatlist() const;
|
||||
[[nodiscard]] bool hasMyLinks() const;
|
||||
@@ -84,6 +86,7 @@ private:
|
||||
FilterId _id = 0;
|
||||
QString _title;
|
||||
QString _iconEmoji;
|
||||
std::optional<uint8> _colorIndex;
|
||||
base::flat_set<not_null<History*>> _always;
|
||||
std::vector<not_null<History*>> _pinned;
|
||||
base::flat_set<not_null<History*>> _never;
|
||||
@@ -94,6 +97,7 @@ private:
|
||||
inline bool operator==(const ChatFilter &a, const ChatFilter &b) {
|
||||
return (a.title() == b.title())
|
||||
&& (a.iconEmoji() == b.iconEmoji())
|
||||
&& (a.colorIndex() == b.colorIndex())
|
||||
&& (a.flags() == b.flags())
|
||||
&& (a.always() == b.always())
|
||||
&& (a.never() == b.never());
|
||||
|
||||
@@ -26,6 +26,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point)
|
||||
, _access(point.vaccess_hash().v) {
|
||||
}
|
||||
|
||||
LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash)
|
||||
: _lat(lat)
|
||||
, _lon(lon) {
|
||||
}
|
||||
|
||||
QString LocationPoint::latAsString() const {
|
||||
return AsString(_lat);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ public:
|
||||
LocationPoint() = default;
|
||||
explicit LocationPoint(const MTPDgeoPoint &point);
|
||||
|
||||
enum IgnoreAccessHash {
|
||||
NoAccessHash,
|
||||
};
|
||||
LocationPoint(float64 lat, float64 lon, IgnoreAccessHash);
|
||||
|
||||
[[nodiscard]] QString latAsString() const;
|
||||
[[nodiscard]] QString lonAsString() const;
|
||||
[[nodiscard]] MTPGeoPoint toMTP() const;
|
||||
@@ -45,6 +50,24 @@ private:
|
||||
|
||||
};
|
||||
|
||||
struct InputVenue {
|
||||
float64 lat = 0.;
|
||||
float64 lon = 0.;
|
||||
QString title;
|
||||
QString address;
|
||||
QString provider;
|
||||
QString id;
|
||||
QString venueType;
|
||||
|
||||
[[nodiscard]] bool justLocation() const {
|
||||
return id.isEmpty();
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const InputVenue &,
|
||||
const InputVenue &) = default;
|
||||
};
|
||||
|
||||
[[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -2631,7 +2631,11 @@ const GiveawayResults *MediaGiveawayResults::giveawayResults() const {
|
||||
}
|
||||
|
||||
TextWithEntities MediaGiveawayResults::notificationText() const {
|
||||
return Ui::Text::Colorized({ tr::lng_prizes_results_title(tr::now) });
|
||||
return Ui::Text::Colorized({
|
||||
((_data.winnersCount == 1)
|
||||
? tr::lng_prizes_results_title_one
|
||||
: tr::lng_prizes_results_title)(tr::now)
|
||||
});
|
||||
}
|
||||
|
||||
QString MediaGiveawayResults::pinnedTextSubstring() const {
|
||||
|
||||
@@ -65,12 +65,14 @@ std::optional<QString> OnlineTextCommon(LastseenStatus status, TimeId now) {
|
||||
return tr::lng_status_online(tr::now);
|
||||
} else if (status.isLongAgo()) {
|
||||
return tr::lng_status_offline(tr::now);
|
||||
} else if (status.isRecently() || status.isHidden()) {
|
||||
} else if (status.isRecently()) {
|
||||
return tr::lng_status_recently(tr::now);
|
||||
} else if (status.isWithinWeek()) {
|
||||
return tr::lng_status_last_week(tr::now);
|
||||
} else if (status.isWithinMonth()) {
|
||||
return tr::lng_status_last_month(tr::now);
|
||||
} else if (status.isHidden()) {
|
||||
return tr::lng_status_recently(tr::now);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -3328,6 +3328,22 @@ void Session::documentApplyFields(
|
||||
}
|
||||
}
|
||||
|
||||
not_null<DocumentData*> Session::venueIconDocument(const QString &icon) {
|
||||
const auto i = _venueIcons.find(icon);
|
||||
if (i != end(_venueIcons)) {
|
||||
return i->second;
|
||||
}
|
||||
const auto result = documentFromWeb(MTP_webDocumentNoProxy(
|
||||
MTP_string(u"https://ss3.4sqi.net/img/categories_v2/"_q
|
||||
+ icon
|
||||
+ u"_64.png"_q),
|
||||
MTP_int(0),
|
||||
MTP_string("image/png"),
|
||||
MTP_vector<MTPDocumentAttribute>()), {}, {});
|
||||
_venueIcons.emplace(icon, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<WebPageData*> Session::webpage(WebPageId id) {
|
||||
auto i = _webpages.find(id);
|
||||
if (i == _webpages.cend()) {
|
||||
|
||||
@@ -559,6 +559,8 @@ public:
|
||||
const MTPWebDocument &data,
|
||||
const ImageLocation &thumbnailLocation,
|
||||
const ImageLocation &videoThumbnailLocation);
|
||||
[[nodiscard]] not_null<DocumentData*> venueIconDocument(
|
||||
const QString &icon);
|
||||
|
||||
[[nodiscard]] not_null<WebPageData*> webpage(WebPageId id);
|
||||
not_null<WebPageData*> processWebpage(const MTPWebPage &data);
|
||||
@@ -1002,6 +1004,7 @@ private:
|
||||
FullStoryId,
|
||||
base::flat_set<not_null<HistoryItem*>>> _storyItems;
|
||||
base::flat_map<uint64, not_null<HistoryItem*>> _highlightings;
|
||||
base::flat_map<QString, not_null<DocumentData*>> _venueIcons;
|
||||
|
||||
base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
|
||||
base::flat_set<not_null<GameData*>> _gamesUpdated;
|
||||
|
||||
193
Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp
Normal file
193
Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
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/raw/raw_countries_bounds.h"
|
||||
|
||||
// Source: https://github.com/sandstrom/country-bounding-boxes
|
||||
|
||||
namespace Raw {
|
||||
|
||||
const base::flat_map<QString, GeoBounds> &CountryBounds() {
|
||||
static const auto result = base::flat_map<QString, GeoBounds>{
|
||||
{ u"AF"_q, GeoBounds{ 60.53, 29.32, 75.16, 38.49 } },
|
||||
{ u"AO"_q, GeoBounds{ 11.64, -17.93, 24.08, -4.44 } },
|
||||
{ u"AL"_q, GeoBounds{ 19.3, 39.62, 21.02, 42.69 } },
|
||||
{ u"AE"_q, GeoBounds{ 51.58, 22.5, 56.4, 26.06 } },
|
||||
{ u"AR"_q, GeoBounds{ -73.42, -55.25, -53.63, -21.83 } },
|
||||
{ u"AM"_q, GeoBounds{ 43.58, 38.74, 46.51, 41.25 } },
|
||||
{ u"AQ"_q, GeoBounds{ -180.0, -90.0, 180.0, -63.27 } },
|
||||
{ u"TF"_q, GeoBounds{ 68.72, -49.78, 70.56, -48.63 } },
|
||||
{ u"AU"_q, GeoBounds{ 113.34, -43.63, 153.57, -10.67 } },
|
||||
{ u"AT"_q, GeoBounds{ 9.48, 46.43, 16.98, 49.04 } },
|
||||
{ u"AZ"_q, GeoBounds{ 44.79, 38.27, 50.39, 41.86 } },
|
||||
{ u"BI"_q, GeoBounds{ 29.02, -4.5, 30.75, -2.35 } },
|
||||
{ u"BE"_q, GeoBounds{ 2.51, 49.53, 6.16, 51.48 } },
|
||||
{ u"BJ"_q, GeoBounds{ 0.77, 6.14, 3.8, 12.24 } },
|
||||
{ u"BF"_q, GeoBounds{ -5.47, 9.61, 2.18, 15.12 } },
|
||||
{ u"BD"_q, GeoBounds{ 88.08, 20.67, 92.67, 26.45 } },
|
||||
{ u"BG"_q, GeoBounds{ 22.38, 41.23, 28.56, 44.23 } },
|
||||
{ u"BS"_q, GeoBounds{ -78.98, 23.71, -77.0, 27.04 } },
|
||||
{ u"BA"_q, GeoBounds{ 15.75, 42.65, 19.6, 45.23 } },
|
||||
{ u"BY"_q, GeoBounds{ 23.2, 51.32, 32.69, 56.17 } },
|
||||
{ u"BZ"_q, GeoBounds{ -89.23, 15.89, -88.11, 18.5 } },
|
||||
{ u"BO"_q, GeoBounds{ -69.59, -22.87, -57.5, -9.76 } },
|
||||
{ u"BR"_q, GeoBounds{ -73.99, -33.77, -34.73, 5.24 } },
|
||||
{ u"BN"_q, GeoBounds{ 114.2, 4.01, 115.45, 5.45 } },
|
||||
{ u"BT"_q, GeoBounds{ 88.81, 26.72, 92.1, 28.3 } },
|
||||
{ u"BW"_q, GeoBounds{ 19.9, -26.83, 29.43, -17.66 } },
|
||||
{ u"CF"_q, GeoBounds{ 14.46, 2.27, 27.37, 11.14 } },
|
||||
{ u"CA"_q, GeoBounds{ -141.0, 41.68, -52.65, 73.23 } },
|
||||
{ u"CH"_q, GeoBounds{ 6.02, 45.78, 10.44, 47.83 } },
|
||||
{ u"CL"_q, GeoBounds{ -75.64, -55.61, -66.96, -17.58 } },
|
||||
{ u"CN"_q, GeoBounds{ 73.68, 18.2, 135.03, 53.46 } },
|
||||
{ u"CI"_q, GeoBounds{ -8.6, 4.34, -2.56, 10.52 } },
|
||||
{ u"CM"_q, GeoBounds{ 8.49, 1.73, 16.01, 12.86 } },
|
||||
{ u"CD"_q, GeoBounds{ 12.18, -13.26, 31.17, 5.26 } },
|
||||
{ u"CG"_q, GeoBounds{ 11.09, -5.04, 18.45, 3.73 } },
|
||||
{ u"CO"_q, GeoBounds{ -78.99, -4.3, -66.88, 12.44 } },
|
||||
{ u"CR"_q, GeoBounds{ -85.94, 8.23, -82.55, 11.22 } },
|
||||
{ u"CU"_q, GeoBounds{ -84.97, 19.86, -74.18, 23.19 } },
|
||||
{ u"CY"_q, GeoBounds{ 32.26, 34.57, 34.0, 35.17 } },
|
||||
{ u"CZ"_q, GeoBounds{ 12.24, 48.56, 18.85, 51.12 } },
|
||||
{ u"DE"_q, GeoBounds{ 5.99, 47.3, 15.02, 54.98 } },
|
||||
{ u"DJ"_q, GeoBounds{ 41.66, 10.93, 43.32, 12.7 } },
|
||||
{ u"DK"_q, GeoBounds{ 8.09, 54.8, 12.69, 57.73 } },
|
||||
{ u"DO"_q, GeoBounds{ -71.95, 17.6, -68.32, 19.88 } },
|
||||
{ u"DZ"_q, GeoBounds{ -8.68, 19.06, 12.0, 37.12 } },
|
||||
{ u"EC"_q, GeoBounds{ -80.97, -4.96, -75.23, 1.38 } },
|
||||
{ u"EG"_q, GeoBounds{ 24.7, 22.0, 36.87, 31.59 } },
|
||||
{ u"ER"_q, GeoBounds{ 36.32, 12.46, 43.08, 18.0 } },
|
||||
{ u"ES"_q, GeoBounds{ -9.39, 35.95, 3.04, 43.75 } },
|
||||
{ u"EE"_q, GeoBounds{ 23.34, 57.47, 28.13, 59.61 } },
|
||||
{ u"ET"_q, GeoBounds{ 32.95, 3.42, 47.79, 14.96 } },
|
||||
{ u"FI"_q, GeoBounds{ 20.65, 59.85, 31.52, 70.16 } },
|
||||
{ u"FJ"_q, GeoBounds{ -180.0, -18.29, 180.0, -16.02 } },
|
||||
{ u"FK"_q, GeoBounds{ -61.2, -52.3, -57.75, -51.1 } },
|
||||
{ u"FR"_q, GeoBounds{ -5.0, 42.5, 9.56, 51.15 } },
|
||||
{ u"GA"_q, GeoBounds{ 8.8, -3.98, 14.43, 2.33 } },
|
||||
{ u"GB"_q, GeoBounds{ -7.57, 49.96, 1.68, 58.64 } },
|
||||
{ u"GE"_q, GeoBounds{ 39.96, 41.06, 46.64, 43.55 } },
|
||||
{ u"GH"_q, GeoBounds{ -3.24, 4.71, 1.06, 11.1 } },
|
||||
{ u"GN"_q, GeoBounds{ -15.13, 7.31, -7.83, 12.59 } },
|
||||
{ u"GM"_q, GeoBounds{ -16.84, 13.13, -13.84, 13.88 } },
|
||||
{ u"GW"_q, GeoBounds{ -16.68, 11.04, -13.7, 12.63 } },
|
||||
{ u"GQ"_q, GeoBounds{ 9.31, 1.01, 11.29, 2.28 } },
|
||||
{ u"GR"_q, GeoBounds{ 20.15, 34.92, 26.6, 41.83 } },
|
||||
{ u"GL"_q, GeoBounds{ -73.3, 60.04, -12.21, 83.65 } },
|
||||
{ u"GT"_q, GeoBounds{ -92.23, 13.74, -88.23, 17.82 } },
|
||||
{ u"GY"_q, GeoBounds{ -61.41, 1.27, -56.54, 8.37 } },
|
||||
{ u"HN"_q, GeoBounds{ -89.35, 12.98, -83.15, 16.01 } },
|
||||
{ u"HR"_q, GeoBounds{ 13.66, 42.48, 19.39, 46.5 } },
|
||||
{ u"HT"_q, GeoBounds{ -74.46, 18.03, -71.62, 19.92 } },
|
||||
{ u"HU"_q, GeoBounds{ 16.2, 45.76, 22.71, 48.62 } },
|
||||
{ u"ID"_q, GeoBounds{ 95.29, -10.36, 141.03, 5.48 } },
|
||||
{ u"IN"_q, GeoBounds{ 68.18, 7.97, 97.4, 35.49 } },
|
||||
{ u"IE"_q, GeoBounds{ -9.98, 51.67, -6.03, 55.13 } },
|
||||
{ u"IR"_q, GeoBounds{ 44.11, 25.08, 63.32, 39.71 } },
|
||||
{ u"IQ"_q, GeoBounds{ 38.79, 29.1, 48.57, 37.39 } },
|
||||
{ u"IS"_q, GeoBounds{ -24.33, 63.5, -13.61, 66.53 } },
|
||||
{ u"IL"_q, GeoBounds{ 34.27, 29.5, 35.84, 33.28 } },
|
||||
{ u"IT"_q, GeoBounds{ 6.75, 36.62, 18.48, 47.12 } },
|
||||
{ u"JM"_q, GeoBounds{ -78.34, 17.7, -76.2, 18.52 } },
|
||||
{ u"JO"_q, GeoBounds{ 34.92, 29.2, 39.2, 33.38 } },
|
||||
{ u"JP"_q, GeoBounds{ 129.41, 31.03, 145.54, 45.55 } },
|
||||
{ u"KZ"_q, GeoBounds{ 46.47, 40.66, 87.36, 55.39 } },
|
||||
{ u"KE"_q, GeoBounds{ 33.89, -4.68, 41.86, 5.51 } },
|
||||
{ u"KG"_q, GeoBounds{ 69.46, 39.28, 80.26, 43.3 } },
|
||||
{ u"KH"_q, GeoBounds{ 102.35, 10.49, 107.61, 14.57 } },
|
||||
{ u"KR"_q, GeoBounds{ 126.12, 34.39, 129.47, 38.61 } },
|
||||
{ u"KW"_q, GeoBounds{ 46.57, 28.53, 48.42, 30.06 } },
|
||||
{ u"LA"_q, GeoBounds{ 100.12, 13.88, 107.56, 22.46 } },
|
||||
{ u"LB"_q, GeoBounds{ 35.13, 33.09, 36.61, 34.64 } },
|
||||
{ u"LR"_q, GeoBounds{ -11.44, 4.36, -7.54, 8.54 } },
|
||||
{ u"LY"_q, GeoBounds{ 9.32, 19.58, 25.16, 33.14 } },
|
||||
{ u"LK"_q, GeoBounds{ 79.7, 5.97, 81.79, 9.82 } },
|
||||
{ u"LS"_q, GeoBounds{ 27.0, -30.65, 29.33, -28.65 } },
|
||||
{ u"LT"_q, GeoBounds{ 21.06, 53.91, 26.59, 56.37 } },
|
||||
{ u"LU"_q, GeoBounds{ 5.67, 49.44, 6.24, 50.13 } },
|
||||
{ u"LV"_q, GeoBounds{ 21.06, 55.62, 28.18, 57.97 } },
|
||||
{ u"MA"_q, GeoBounds{ -17.02, 21.42, -1.12, 35.76 } },
|
||||
{ u"MD"_q, GeoBounds{ 26.62, 45.49, 30.02, 48.47 } },
|
||||
{ u"MG"_q, GeoBounds{ 43.25, -25.6, 50.48, -12.04 } },
|
||||
{ u"MX"_q, GeoBounds{ -117.13, 14.54, -86.81, 32.72 } },
|
||||
{ u"MK"_q, GeoBounds{ 20.46, 40.84, 22.95, 42.32 } },
|
||||
{ u"ML"_q, GeoBounds{ -12.17, 10.1, 4.27, 24.97 } },
|
||||
{ u"MM"_q, GeoBounds{ 92.3, 9.93, 101.18, 28.34 } },
|
||||
{ u"ME"_q, GeoBounds{ 18.45, 41.88, 20.34, 43.52 } },
|
||||
{ u"MN"_q, GeoBounds{ 87.75, 41.6, 119.77, 52.05 } },
|
||||
{ u"MZ"_q, GeoBounds{ 30.18, -26.74, 40.78, -10.32 } },
|
||||
{ u"MR"_q, GeoBounds{ -17.06, 14.62, -4.92, 27.4 } },
|
||||
{ u"MW"_q, GeoBounds{ 32.69, -16.8, 35.77, -9.23 } },
|
||||
{ u"MY"_q, GeoBounds{ 100.09, 0.77, 119.18, 6.93 } },
|
||||
{ u"NA"_q, GeoBounds{ 11.73, -29.05, 25.08, -16.94 } },
|
||||
{ u"NC"_q, GeoBounds{ 164.03, -22.4, 167.12, -20.11 } },
|
||||
{ u"NE"_q, GeoBounds{ 0.3, 11.66, 15.9, 23.47 } },
|
||||
{ u"NG"_q, GeoBounds{ 2.69, 4.24, 14.58, 13.87 } },
|
||||
{ u"NI"_q, GeoBounds{ -87.67, 10.73, -83.15, 15.02 } },
|
||||
{ u"NL"_q, GeoBounds{ 3.31, 50.8, 7.09, 53.51 } },
|
||||
{ u"NO"_q, GeoBounds{ 4.99, 58.08, 31.29, 70.92 } },
|
||||
{ u"NP"_q, GeoBounds{ 80.09, 26.4, 88.17, 30.42 } },
|
||||
{ u"NZ"_q, GeoBounds{ 166.51, -46.64, 178.52, -34.45 } },
|
||||
{ u"OM"_q, GeoBounds{ 52.0, 16.65, 59.81, 26.4 } },
|
||||
{ u"PK"_q, GeoBounds{ 60.87, 23.69, 77.84, 37.13 } },
|
||||
{ u"PA"_q, GeoBounds{ -82.97, 7.22, -77.24, 9.61 } },
|
||||
{ u"PE"_q, GeoBounds{ -81.41, -18.35, -68.67, -0.06 } },
|
||||
{ u"PH"_q, GeoBounds{ 117.17, 5.58, 126.54, 18.51 } },
|
||||
{ u"PG"_q, GeoBounds{ 141.0, -10.65, 156.02, -2.5 } },
|
||||
{ u"PL"_q, GeoBounds{ 14.07, 49.03, 24.03, 54.85 } },
|
||||
{ u"PR"_q, GeoBounds{ -67.24, 17.95, -65.59, 18.52 } },
|
||||
{ u"KP"_q, GeoBounds{ 124.27, 37.67, 130.78, 42.99 } },
|
||||
{ u"PT"_q, GeoBounds{ -9.53, 36.84, -6.39, 42.28 } },
|
||||
{ u"PY"_q, GeoBounds{ -62.69, -27.55, -54.29, -19.34 } },
|
||||
{ u"QA"_q, GeoBounds{ 50.74, 24.56, 51.61, 26.11 } },
|
||||
{ u"RO"_q, GeoBounds{ 20.22, 43.69, 29.63, 48.22 } },
|
||||
{ u"RU"_q, GeoBounds{ -180.0, 41.15, 180.0, 81.25 } },
|
||||
{ u"RW"_q, GeoBounds{ 29.02, -2.92, 30.82, -1.13 } },
|
||||
{ u"SA"_q, GeoBounds{ 34.63, 16.35, 55.67, 32.16 } },
|
||||
{ u"SD"_q, GeoBounds{ 21.94, 8.62, 38.41, 22.0 } },
|
||||
{ u"SS"_q, GeoBounds{ 23.89, 3.51, 35.3, 12.25 } },
|
||||
{ u"SN"_q, GeoBounds{ -17.63, 12.33, -11.47, 16.6 } },
|
||||
{ u"SB"_q, GeoBounds{ 156.49, -10.83, 162.4, -6.6 } },
|
||||
{ u"SL"_q, GeoBounds{ -13.25, 6.79, -10.23, 10.05 } },
|
||||
{ u"SV"_q, GeoBounds{ -90.1, 13.15, -87.72, 14.42 } },
|
||||
{ u"SO"_q, GeoBounds{ 40.98, -1.68, 51.13, 12.02 } },
|
||||
{ u"RS"_q, GeoBounds{ 18.83, 42.25, 22.99, 46.17 } },
|
||||
{ u"SR"_q, GeoBounds{ -58.04, 1.82, -53.96, 6.03 } },
|
||||
{ u"SK"_q, GeoBounds{ 16.88, 47.76, 22.56, 49.57 } },
|
||||
{ u"SI"_q, GeoBounds{ 13.7, 45.45, 16.56, 46.85 } },
|
||||
{ u"SE"_q, GeoBounds{ 11.03, 55.36, 23.9, 69.11 } },
|
||||
{ u"SZ"_q, GeoBounds{ 30.68, -27.29, 32.07, -25.66 } },
|
||||
{ u"SY"_q, GeoBounds{ 35.7, 32.31, 42.35, 37.23 } },
|
||||
{ u"TD"_q, GeoBounds{ 13.54, 7.42, 23.89, 23.41 } },
|
||||
{ u"TG"_q, GeoBounds{ -0.05, 5.93, 1.87, 11.02 } },
|
||||
{ u"TH"_q, GeoBounds{ 97.38, 5.69, 105.59, 20.42 } },
|
||||
{ u"TJ"_q, GeoBounds{ 67.44, 36.74, 74.98, 40.96 } },
|
||||
{ u"TM"_q, GeoBounds{ 52.5, 35.27, 66.55, 42.75 } },
|
||||
{ u"TL"_q, GeoBounds{ 124.97, -9.39, 127.34, -8.27 } },
|
||||
{ u"TT"_q, GeoBounds{ -61.95, 10.0, -60.9, 10.89 } },
|
||||
{ u"TN"_q, GeoBounds{ 7.52, 30.31, 11.49, 37.35 } },
|
||||
{ u"TR"_q, GeoBounds{ 26.04, 35.82, 44.79, 42.14 } },
|
||||
{ u"TW"_q, GeoBounds{ 120.11, 21.97, 121.95, 25.3 } },
|
||||
{ u"TZ"_q, GeoBounds{ 29.34, -11.72, 40.32, -0.95 } },
|
||||
{ u"UG"_q, GeoBounds{ 29.58, -1.44, 35.04, 4.25 } },
|
||||
{ u"UA"_q, GeoBounds{ 22.09, 44.36, 40.08, 52.34 } },
|
||||
{ u"UY"_q, GeoBounds{ -58.43, -34.95, -53.21, -30.11 } },
|
||||
{ u"US"_q, GeoBounds{ -125.0, 25.0, -66.96, 49.5 } },
|
||||
{ u"UZ"_q, GeoBounds{ 55.93, 37.14, 73.06, 45.59 } },
|
||||
{ u"VE"_q, GeoBounds{ -73.3, 0.72, -59.76, 12.16 } },
|
||||
{ u"VN"_q, GeoBounds{ 102.17, 8.6, 109.34, 23.35 } },
|
||||
{ u"VU"_q, GeoBounds{ 166.63, -16.6, 167.84, -14.63 } },
|
||||
{ u"PS"_q, GeoBounds{ 34.93, 31.35, 35.55, 32.53 } },
|
||||
{ u"YE"_q, GeoBounds{ 42.6, 12.59, 53.11, 19.0 } },
|
||||
{ u"ZA"_q, GeoBounds{ 16.34, -34.82, 32.83, -22.09 } },
|
||||
{ u"ZM"_q, GeoBounds{ 21.89, -17.96, 33.49, -8.24 } },
|
||||
{ u"ZW"_q, GeoBounds{ 25.26, -22.27, 32.85, -15.51 } }
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Raw
|
||||
23
Telegram/SourceFiles/data/raw/raw_countries_bounds.h
Normal file
23
Telegram/SourceFiles/data/raw/raw_countries_bounds.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
|
||||
namespace Raw {
|
||||
|
||||
struct GeoBounds {
|
||||
double minLat = 0.;
|
||||
double minLon = 0.;
|
||||
double maxLat = 0.;
|
||||
double maxLon = 0.;
|
||||
};
|
||||
|
||||
[[nodiscard]] const base::flat_map<QString, GeoBounds> &CountryBounds();
|
||||
|
||||
} // namespace Raw
|
||||
@@ -3814,7 +3814,9 @@ ChosenRow InnerWidget::computeChosenRow() const {
|
||||
|
||||
bool InnerWidget::isUserpicPress() const {
|
||||
return (_lastRowLocalMouseX >= 0)
|
||||
&& (_lastRowLocalMouseX < _st->nameLeft);
|
||||
&& (_lastRowLocalMouseX < _st->nameLeft)
|
||||
&& (_collapsedSelected < 0
|
||||
|| _collapsedSelected >= _collapsedRows.size());
|
||||
}
|
||||
|
||||
bool InnerWidget::isUserpicPressOnWide() const {
|
||||
|
||||
@@ -1515,6 +1515,13 @@ ServiceAction ParseServiceAction(
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionRequestedPeerSentMe &data) {
|
||||
// Should not be in user inbox.
|
||||
}, [&](const MTPDmessageActionPaymentRefunded &data) {
|
||||
auto content = ActionPaymentRefunded();
|
||||
content.currency = ParseString(data.vcurrency());
|
||||
content.amount = data.vtotal_amount().v;
|
||||
content.peerId = ParsePeerId(data.vpeer());
|
||||
content.transactionId = data.vcharge().data().vid().v;
|
||||
result.content = content;
|
||||
}, [](const MTPDmessageActionEmpty &data) {});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -576,6 +576,13 @@ struct ActionBoostApply {
|
||||
int boosts = 0;
|
||||
};
|
||||
|
||||
struct ActionPaymentRefunded {
|
||||
PeerId peerId = 0;
|
||||
Utf8String currency;
|
||||
uint64 amount = 0;
|
||||
Utf8String transactionId;
|
||||
};
|
||||
|
||||
struct ServiceAction {
|
||||
std::variant<
|
||||
v::null_t,
|
||||
@@ -617,7 +624,8 @@ struct ServiceAction {
|
||||
ActionGiftCode,
|
||||
ActionGiveawayLaunch,
|
||||
ActionGiveawayResults,
|
||||
ActionBoostApply> content;
|
||||
ActionBoostApply,
|
||||
ActionPaymentRefunded> content;
|
||||
};
|
||||
|
||||
ServiceAction ParseServiceAction(
|
||||
|
||||
@@ -1315,6 +1315,12 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
+ " boosted the group "
|
||||
+ QByteArray::number(data.boosts)
|
||||
+ (data.boosts > 1 ? " times" : " time");
|
||||
}, [&](const ActionPaymentRefunded &data) {
|
||||
const auto amount = FormatMoneyAmount(data.amount, data.currency);
|
||||
auto result = peers.wrapPeerName(data.peerId)
|
||||
+ " refunded back "
|
||||
+ amount;
|
||||
return result;
|
||||
}, [](v::null_t) { return QByteArray(); });
|
||||
|
||||
if (!serviceText.isEmpty()) {
|
||||
|
||||
@@ -625,6 +625,13 @@ QByteArray SerializeMessage(
|
||||
pushActor();
|
||||
pushAction("boost_apply");
|
||||
push("boosts", data.boosts);
|
||||
}, [&](const ActionPaymentRefunded &data) {
|
||||
pushAction("refunded_payment");
|
||||
push("amount", data.amount);
|
||||
push("currency", data.currency);
|
||||
pushBare("peer_name", wrapPeerName(data.peerId));
|
||||
push("peer_id", data.peerId);
|
||||
push("charge_id", data.transactionId);
|
||||
}, [](v::null_t) {});
|
||||
|
||||
if (v::is_null(message.action.content)) {
|
||||
|
||||
@@ -2227,8 +2227,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
if (!item->isService()
|
||||
&& peerIsChannel(itemId.peer)
|
||||
&& !_peer->isMegagroup()) {
|
||||
constexpr auto kMinViewsCount = 10;
|
||||
if (const auto channel = _peer->asChannel()) {
|
||||
if (channel->flags() & ChannelDataFlag::CanGetStatistics) {
|
||||
if ((channel->flags() & ChannelDataFlag::CanGetStatistics)
|
||||
|| (channel->canPostMessages()
|
||||
&& item->viewsCount() >= kMinViewsCount)) {
|
||||
auto callback = crl::guard(controller, [=] {
|
||||
controller->showSection(
|
||||
Info::Statistics::Make(channel, itemId, {}));
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "settings/settings_credits_graphics.h" // ShowRefundInfoBox.
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "main/main_account.h"
|
||||
@@ -4028,6 +4029,22 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
||||
}
|
||||
} else if (type == mtpc_messageActionGiveawayResults) {
|
||||
UpdateComponents(HistoryServiceGiveawayResults::Bit());
|
||||
} else if (type == mtpc_messageActionPaymentRefunded) {
|
||||
const auto &data = action.c_messageActionPaymentRefunded();
|
||||
UpdateComponents(HistoryServicePaymentRefund::Bit());
|
||||
const auto refund = Get<HistoryServicePaymentRefund>();
|
||||
refund->peer = _history->owner().peer(peerFromMTP(data.vpeer()));
|
||||
refund->amount = data.vtotal_amount().v;
|
||||
refund->currency = qs(data.vcurrency());
|
||||
refund->transactionId = qs(data.vcharge().data().vid());
|
||||
const auto id = fullId();
|
||||
refund->link = std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto window = my.sessionWindow.get()) {
|
||||
Settings::ShowRefundInfoBox(window, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (const auto replyTo = message.vreply_to()) {
|
||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||
@@ -4941,6 +4958,25 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
return result;
|
||||
};
|
||||
|
||||
auto preparePaymentRefunded = [&](const MTPDmessageActionPaymentRefunded &action) {
|
||||
auto result = PreparedServiceText();
|
||||
const auto refund = Get<HistoryServicePaymentRefund>();
|
||||
Assert(refund != nullptr);
|
||||
Assert(refund->peer != nullptr);
|
||||
|
||||
const auto amount = refund->amount;
|
||||
const auto currency = refund->currency;
|
||||
result.links.push_back(refund->peer->createOpenLink());
|
||||
result.text = tr::lng_action_payment_refunded(
|
||||
tr::now,
|
||||
lt_peer,
|
||||
Ui::Text::Link(refund->peer->name(), 1), // Link 1.
|
||||
lt_amount,
|
||||
{ Ui::FillAmountAndCurrency(amount, currency) },
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
};
|
||||
|
||||
setServiceText(action.match(
|
||||
prepareChatAddUserText,
|
||||
prepareChatJoinedByLink,
|
||||
@@ -4983,6 +5019,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
prepareGiveawayLaunch,
|
||||
prepareGiveawayResults,
|
||||
prepareBoostApply,
|
||||
preparePaymentRefunded,
|
||||
PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>,
|
||||
PrepareErrorText<MTPDmessageActionEmpty>));
|
||||
|
||||
|
||||
@@ -671,6 +671,15 @@ struct HistoryServiceCustomLink
|
||||
ClickHandlerPtr link;
|
||||
};
|
||||
|
||||
struct HistoryServicePaymentRefund
|
||||
: public RuntimeComponent<HistoryServicePaymentRefund, HistoryItem> {
|
||||
ClickHandlerPtr link;
|
||||
PeerData *peer = nullptr;
|
||||
QString transactionId;
|
||||
QString currency;
|
||||
uint64 amount = 0;
|
||||
};
|
||||
|
||||
enum class HistorySelfDestructType {
|
||||
Photo,
|
||||
Video,
|
||||
|
||||
@@ -4940,7 +4940,14 @@ bool HistoryWidget::updateCmdStartShown() {
|
||||
const auto user = _peer ? _peer->asUser() : nullptr;
|
||||
const auto bot = (user && user->isBot()) ? user : nullptr;
|
||||
if (bot && !bot->botInfo->botMenuButtonUrl.isEmpty()) {
|
||||
session().attachWebView().requestMenu(controller(), bot);
|
||||
session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller() },
|
||||
.button = {
|
||||
.url = bot->botInfo->botMenuButtonUrl.toUtf8(),
|
||||
},
|
||||
.source = InlineBots::WebViewSourceBotMenu(),
|
||||
});
|
||||
} else if (!_fieldAutocomplete->isHidden()) {
|
||||
_fieldAutocomplete->hideAnimated();
|
||||
} else {
|
||||
|
||||
@@ -257,6 +257,7 @@ void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) {
|
||||
const auto was = _link;
|
||||
if (draft.removed) {
|
||||
_draft = draft;
|
||||
_parsedLinks = _parser.list().current();
|
||||
if (_parsedLinks.empty()) {
|
||||
_draft.removed = false;
|
||||
}
|
||||
|
||||
@@ -1284,7 +1284,14 @@ void CopyPostLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
Context context) {
|
||||
const auto item = controller->session().data().message(itemId);
|
||||
CopyPostLink(controller->uiShow(), itemId, context);
|
||||
}
|
||||
|
||||
void CopyPostLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
FullMsgId itemId,
|
||||
Context context) {
|
||||
const auto item = show->session().data().message(itemId);
|
||||
if (!item || !item->hasDirectLink()) {
|
||||
return;
|
||||
}
|
||||
@@ -1311,7 +1318,7 @@ void CopyPostLink(
|
||||
return channel->hasUsername();
|
||||
}();
|
||||
|
||||
controller->showToast(isPublicLink
|
||||
show->showToast(isPublicLink
|
||||
? tr::lng_channel_public_link_copied(tr::now)
|
||||
: tr::lng_context_about_private_link(tr::now));
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ void CopyPostLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
Context context);
|
||||
void CopyPostLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
FullMsgId itemId,
|
||||
Context context);
|
||||
void CopyStoryLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
FullStoryId storyId);
|
||||
|
||||
@@ -929,9 +929,6 @@ QSize Message::performCountOptimalSize() {
|
||||
- st::msgPadding.left()
|
||||
- st::msgPadding.right();
|
||||
if (withVisibleText) {
|
||||
if (botTop) {
|
||||
minHeight += botTop->height;
|
||||
}
|
||||
if (maxWidth < textualWidth) {
|
||||
minHeight -= text().minHeight();
|
||||
minHeight += text().countHeight(innerWidth);
|
||||
|
||||
@@ -679,6 +679,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
|
||||
result.link = results->lnk;
|
||||
} else if (const auto custom = item->Get<HistoryServiceCustomLink>()) {
|
||||
result.link = custom->link;
|
||||
} else if (const auto payment = item->Get<HistoryServicePaymentRefund>()) {
|
||||
result.link = payment->link;
|
||||
} else if (media && data()->showSimilarChannels()) {
|
||||
result = media->textState(mediaPoint, request);
|
||||
}
|
||||
|
||||
@@ -200,9 +200,11 @@ auto GenerateGiveawayResults(
|
||||
margins,
|
||||
links));
|
||||
};
|
||||
const auto isSingleWinner = (data->winnersCount == 1);
|
||||
pushText(
|
||||
Ui::Text::Bold(
|
||||
tr::lng_prizes_results_title(tr::now)),
|
||||
(isSingleWinner
|
||||
? tr::lng_prizes_results_title_one
|
||||
: tr::lng_prizes_results_title)(tr::now, Ui::Text::Bold),
|
||||
st::chatGiveawayPrizesTitleMargin);
|
||||
const auto showGiveawayHandler = JumpToMessageClickHandler(
|
||||
data->channel,
|
||||
@@ -219,7 +221,9 @@ auto GenerateGiveawayResults(
|
||||
st::chatGiveawayPrizesMargin,
|
||||
{ { 1, showGiveawayHandler } });
|
||||
pushText(
|
||||
Ui::Text::Bold(tr::lng_prizes_results_winners(tr::now)),
|
||||
(isSingleWinner
|
||||
? tr::lng_prizes_results_winner
|
||||
: tr::lng_prizes_results_winners)(tr::now, Ui::Text::Bold),
|
||||
st::chatGiveawayPrizesTitleMargin);
|
||||
|
||||
push(std::make_unique<PeerBubbleListPart>(
|
||||
@@ -235,6 +239,8 @@ auto GenerateGiveawayResults(
|
||||
}
|
||||
pushText({ data->unclaimedCount
|
||||
? tr::lng_prizes_results_some(tr::now)
|
||||
: isSingleWinner
|
||||
? tr::lng_prizes_results_one(tr::now)
|
||||
: tr::lng_prizes_results_all(tr::now)
|
||||
}, st::chatGiveawayEndDateMargin);
|
||||
};
|
||||
|
||||
@@ -241,19 +241,20 @@ ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item) {
|
||||
}
|
||||
}
|
||||
});
|
||||
const auto reactivate = controller
|
||||
? crl::guard(
|
||||
controller,
|
||||
[=](auto) { controller->widget()->activate(); })
|
||||
: Fn<void(Payments::CheckoutResult)>();
|
||||
const auto credits = Payments::IsCreditsInvoice(item);
|
||||
const auto nonPanelPaymentFormProcess = (controller && credits)
|
||||
? Payments::ProcessNonPanelPaymentFormFactory(controller, done)
|
||||
: nullptr;
|
||||
Payments::CheckoutProcess::Start(
|
||||
item,
|
||||
Payments::Mode::Payment,
|
||||
(controller
|
||||
? crl::guard(
|
||||
controller,
|
||||
[=](auto) { controller->widget()->activate(); })
|
||||
: Fn<void(Payments::CheckoutResult)>()),
|
||||
((controller && Payments::IsCreditsInvoice(item))
|
||||
? Payments::ProcessNonPanelPaymentFormFactory(
|
||||
controller,
|
||||
done)
|
||||
: nullptr));
|
||||
reactivate,
|
||||
nonPanelPaymentFormProcess);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -125,9 +125,6 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) {
|
||||
if (via) {
|
||||
via->resize(availw);
|
||||
}
|
||||
if (reply) {
|
||||
[[maybe_unused]] int height = reply->resizeToWidth(availw);
|
||||
}
|
||||
}
|
||||
return { newWidth, newHeight };
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ void InnerWidget::fillHistory() {
|
||||
|
||||
const auto sectionIndex = history->lifetime().make_state<int>(0);
|
||||
|
||||
const auto fill = [=](
|
||||
const auto fill = [=, peer = _peer](
|
||||
not_null<PeerData*> premiumBot,
|
||||
const Data::CreditsStatusSlice &fullSlice,
|
||||
const Data::CreditsStatusSlice &inSlice,
|
||||
@@ -368,7 +368,7 @@ void InnerWidget::fillHistory() {
|
||||
fullSlice,
|
||||
fullWrap->entity(),
|
||||
entryClicked,
|
||||
premiumBot,
|
||||
peer,
|
||||
star,
|
||||
true,
|
||||
true);
|
||||
@@ -377,7 +377,7 @@ void InnerWidget::fillHistory() {
|
||||
inSlice,
|
||||
inWrap->entity(),
|
||||
entryClicked,
|
||||
premiumBot,
|
||||
peer,
|
||||
star,
|
||||
true,
|
||||
false);
|
||||
@@ -386,7 +386,7 @@ void InnerWidget::fillHistory() {
|
||||
outSlice,
|
||||
outWrap->entity(),
|
||||
std::move(entryClicked),
|
||||
premiumBot,
|
||||
peer,
|
||||
star,
|
||||
false,
|
||||
true);
|
||||
@@ -398,11 +398,11 @@ void InnerWidget::fillHistory() {
|
||||
const auto apiLifetime = history->lifetime().make_state<rpl::lifetime>();
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
_stateUpdated.events()
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=, peer = _peer] {
|
||||
using Api = Api::CreditsHistory;
|
||||
const auto apiFull = apiLifetime->make_state<Api>(_peer, true, true);
|
||||
const auto apiIn = apiLifetime->make_state<Api>(_peer, true, false);
|
||||
const auto apiOut = apiLifetime->make_state<Api>(_peer, false, true);
|
||||
const auto apiFull = apiLifetime->make_state<Api>(peer, true, true);
|
||||
const auto apiIn = apiLifetime->make_state<Api>(peer, true, false);
|
||||
const auto apiOut = apiLifetime->make_state<Api>(peer, false, true);
|
||||
apiFull->request({}, [=](Data::CreditsStatusSlice fullSlice) {
|
||||
apiIn->request({}, [=](Data::CreditsStatusSlice inSlice) {
|
||||
apiOut->request({}, [=](Data::CreditsStatusSlice outSlice) {
|
||||
|
||||
@@ -281,6 +281,9 @@ void InnerWidget::load() {
|
||||
rpl::lifetime apiPremiumBotLifetime;
|
||||
};
|
||||
const auto state = lifetime().make_state<State>(_peer);
|
||||
using ChannelFlag = ChannelDataFlag;
|
||||
const auto canViewCredits = !_peer->isChannel()
|
||||
|| (_peer->asChannel()->flags() & ChannelFlag::CanViewCreditsRevenue);
|
||||
|
||||
Info::Statistics::FillLoading(
|
||||
this,
|
||||
@@ -363,7 +366,11 @@ void InnerWidget::load() {
|
||||
_state.premiumBotId = bot->id;
|
||||
state->apiCredits.request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
fail(error);
|
||||
if (canViewCredits) {
|
||||
fail(error);
|
||||
} else {
|
||||
_state.creditsEarn = {};
|
||||
}
|
||||
finish();
|
||||
}, [=] {
|
||||
_state.creditsEarn = state->apiCredits.data();
|
||||
@@ -1412,7 +1419,7 @@ void InnerWidget::fill() {
|
||||
data.creditsStatusSlice,
|
||||
tabCreditsList->entity(),
|
||||
entryClicked,
|
||||
premiumBot,
|
||||
_peer,
|
||||
star,
|
||||
true,
|
||||
true);
|
||||
|
||||
@@ -131,7 +131,7 @@ struct BoostsDescriptor final {
|
||||
struct CreditsDescriptor final {
|
||||
Data::CreditsStatusSlice firstSlice;
|
||||
Fn<void(const Data::CreditsHistoryEntry &)> entryClickedCallback;
|
||||
not_null<PeerData*> premiumBot;
|
||||
not_null<PeerData*> peer;
|
||||
not_null<QImage*> creditIcon;
|
||||
bool in = false;
|
||||
bool out = false;
|
||||
@@ -889,7 +889,6 @@ private:
|
||||
void applySlice(const Data::CreditsStatusSlice &slice);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<PeerData*> _premiumBot;
|
||||
Fn<void(const Data::CreditsHistoryEntry &)> _entryClickedCallback;
|
||||
not_null<QImage*> const _creditIcon;
|
||||
|
||||
@@ -903,11 +902,10 @@ private:
|
||||
};
|
||||
|
||||
CreditsController::CreditsController(CreditsDescriptor d)
|
||||
: _session(&d.premiumBot->session())
|
||||
, _premiumBot(d.premiumBot)
|
||||
: _session(&d.peer->session())
|
||||
, _entryClickedCallback(std::move(d.entryClickedCallback))
|
||||
, _creditIcon(d.creditIcon)
|
||||
, _api(d.premiumBot->session().user(), d.in, d.out)
|
||||
, _api(d.peer, d.in, d.out)
|
||||
, _firstSlice(std::move(d.firstSlice)) {
|
||||
PeerListController::setStyleOverrides(&st::boostsListBox);
|
||||
}
|
||||
@@ -950,12 +948,9 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) {
|
||||
delegate()->peerListUpdateRow(row);
|
||||
},
|
||||
};
|
||||
using Type = Data::CreditsHistoryEntry::PeerType;
|
||||
if (const auto peerId = PeerId(item.barePeerId)) {
|
||||
const auto peer = session().data().peer(peerId);
|
||||
return std::make_unique<CreditsRow>(peer, descriptor);
|
||||
} else if (item.peerType == Type::PremiumBot) {
|
||||
return std::make_unique<CreditsRow>(_premiumBot, descriptor);
|
||||
} else {
|
||||
return std::make_unique<CreditsRow>(descriptor);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ void AddCreditsHistoryList(
|
||||
const Data::CreditsStatusSlice &firstSlice,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Fn<void(const Data::CreditsHistoryEntry &)> entryClickedCallback,
|
||||
not_null<PeerData*> premiumBot,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<QImage*> creditIcon,
|
||||
bool in,
|
||||
bool out);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,15 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/flags.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "api/api_common.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace Api {
|
||||
struct SendAction;
|
||||
} // namespace Api
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
class GenericBox;
|
||||
class DropdownMenu;
|
||||
} // namespace Ui
|
||||
@@ -29,6 +32,7 @@ class Panel;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
class SessionShow;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
@@ -39,8 +43,15 @@ namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Payments {
|
||||
struct NonPanelPaymentForm;
|
||||
enum class CheckoutResult;
|
||||
} // namespace Payments
|
||||
|
||||
namespace InlineBots {
|
||||
|
||||
class WebViewInstance;
|
||||
|
||||
enum class PeerType : uint8 {
|
||||
SameBot = 0x01,
|
||||
Bot = 0x02,
|
||||
@@ -80,50 +91,227 @@ struct AddToMenuOpenApp {
|
||||
not_null<BotAppData*> app;
|
||||
QString startCommand;
|
||||
};
|
||||
using AddToMenuOpen = std::variant<
|
||||
struct AddToMenuOpen : std::variant<
|
||||
AddToMenuOpenAttach,
|
||||
AddToMenuOpenMenu,
|
||||
AddToMenuOpenApp>;
|
||||
AddToMenuOpenApp> {
|
||||
using variant::variant;
|
||||
};
|
||||
|
||||
class AttachWebView final
|
||||
struct WebViewSourceButton {
|
||||
bool simple = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
WebViewSourceButton,
|
||||
WebViewSourceButton) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceSwitch {
|
||||
friend inline bool operator==(
|
||||
const WebViewSourceSwitch &,
|
||||
const WebViewSourceSwitch &) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceLinkApp { // t.me/botusername/appname
|
||||
base::weak_ptr<WebViewInstance> from;
|
||||
QString appname;
|
||||
QString token;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WebViewSourceLinkApp &,
|
||||
const WebViewSourceLinkApp &) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceLinkAttachMenu { // ?startattach
|
||||
base::weak_ptr<WebViewInstance> from;
|
||||
base::weak_ptr<Data::Thread> thread;
|
||||
PeerTypes choose;
|
||||
QString token;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WebViewSourceLinkAttachMenu &,
|
||||
const WebViewSourceLinkAttachMenu &) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceLinkBotProfile { // t.me/botusername?startapp
|
||||
base::weak_ptr<WebViewInstance> from;
|
||||
QString token;
|
||||
bool compact = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WebViewSourceLinkBotProfile &,
|
||||
const WebViewSourceLinkBotProfile &) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceMainMenu {
|
||||
friend inline bool operator==(
|
||||
WebViewSourceMainMenu,
|
||||
WebViewSourceMainMenu) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceAttachMenu {
|
||||
base::weak_ptr<Data::Thread> thread;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WebViewSourceAttachMenu &,
|
||||
const WebViewSourceAttachMenu &) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceBotMenu {
|
||||
friend inline bool operator==(
|
||||
WebViewSourceBotMenu,
|
||||
WebViewSourceBotMenu) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceGame {
|
||||
FullMsgId messageId;
|
||||
QString title;
|
||||
|
||||
friend inline bool operator==(
|
||||
WebViewSourceGame,
|
||||
WebViewSourceGame) = default;
|
||||
};
|
||||
|
||||
struct WebViewSourceBotProfile {
|
||||
friend inline bool operator==(
|
||||
WebViewSourceBotProfile,
|
||||
WebViewSourceBotProfile) = default;
|
||||
};
|
||||
|
||||
struct WebViewSource : std::variant<
|
||||
WebViewSourceButton,
|
||||
WebViewSourceSwitch,
|
||||
WebViewSourceLinkApp,
|
||||
WebViewSourceLinkAttachMenu,
|
||||
WebViewSourceLinkBotProfile,
|
||||
WebViewSourceMainMenu,
|
||||
WebViewSourceAttachMenu,
|
||||
WebViewSourceBotMenu,
|
||||
WebViewSourceGame,
|
||||
WebViewSourceBotProfile> {
|
||||
using variant::variant;
|
||||
};
|
||||
|
||||
struct WebViewButton {
|
||||
QString text;
|
||||
QString startCommand;
|
||||
QByteArray url;
|
||||
bool fromAttachMenu = false;
|
||||
bool fromMainMenu = false;
|
||||
bool fromSwitch = false;
|
||||
};
|
||||
|
||||
struct WebViewContext {
|
||||
base::weak_ptr<Window::SessionController> controller;
|
||||
Dialogs::EntryState dialogsEntryState;
|
||||
std::optional<Api::SendAction> action;
|
||||
bool maySkipConfirmation = false;
|
||||
};
|
||||
|
||||
struct WebViewDescriptor {
|
||||
not_null<UserData*> bot;
|
||||
std::shared_ptr<Ui::Show> parentShow;
|
||||
WebViewContext context;
|
||||
WebViewButton button;
|
||||
WebViewSource source;
|
||||
};
|
||||
|
||||
class WebViewInstance final
|
||||
: public base::has_weak_ptr
|
||||
, public Ui::BotWebView::Delegate {
|
||||
public:
|
||||
explicit WebViewInstance(WebViewDescriptor &&descriptor);
|
||||
~WebViewInstance();
|
||||
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
[[nodiscard]] not_null<UserData*> bot() const;
|
||||
[[nodiscard]] WebViewSource source() const;
|
||||
|
||||
void activate();
|
||||
void close();
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Main::SessionShow> uiShow();
|
||||
|
||||
private:
|
||||
void resolve();
|
||||
|
||||
bool openAppFromBotMenuLink();
|
||||
|
||||
void requestButton();
|
||||
void requestSimple();
|
||||
void requestApp(bool allowWrite);
|
||||
void requestWithMainMenuDisclaimer();
|
||||
void requestWithMenuAdd();
|
||||
void maybeChooseAndRequestButton(PeerTypes supported);
|
||||
|
||||
void resolveApp(
|
||||
const QString &appname,
|
||||
const QString &startparam,
|
||||
bool forceConfirmation);
|
||||
void confirmOpen(Fn<void()> done);
|
||||
void confirmAppOpen(bool writeAccess, Fn<void(bool allowWrite)> done);
|
||||
|
||||
void show(const QString &url, uint64 queryId = 0);
|
||||
void showGame();
|
||||
void started(uint64 queryId);
|
||||
|
||||
[[nodiscard]] Window::SessionController *windowForThread(
|
||||
not_null<Data::Thread*> thread);
|
||||
|
||||
auto nonPanelPaymentFormFactory(
|
||||
Fn<void(Payments::CheckoutResult)> reactivate)
|
||||
-> Fn<void(Payments::NonPanelPaymentForm)>;
|
||||
|
||||
Webview::ThemeParams botThemeParams() override;
|
||||
bool botHandleLocalUri(QString uri, bool keepOpen) override;
|
||||
void botHandleInvoice(QString slug) override;
|
||||
void botHandleMenuButton(Ui::BotWebView::MenuButton button) override;
|
||||
bool botValidateExternalLink(QString uri) override;
|
||||
void botOpenIvLink(QString uri) override;
|
||||
void botSendData(QByteArray data) override;
|
||||
void botSwitchInlineQuery(
|
||||
std::vector<QString> chatTypes,
|
||||
QString query) override;
|
||||
void botCheckWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||
void botAllowWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||
void botSharePhone(Fn<void(bool shared)> callback) override;
|
||||
void botInvokeCustomMethod(
|
||||
Ui::BotWebView::CustomMethodRequest request) override;
|
||||
void botShareGameScore() override;
|
||||
void botClose() override;
|
||||
|
||||
const std::shared_ptr<Ui::Show> _parentShow;
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<UserData*> _bot;
|
||||
const WebViewContext _context;
|
||||
const WebViewButton _button;
|
||||
const WebViewSource _source;
|
||||
|
||||
BotAppData *_app = nullptr;
|
||||
QString _appStartParam;
|
||||
bool _dataSent = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
mtpRequestId _prolongId = 0;
|
||||
|
||||
QString _panelUrl;
|
||||
std::unique_ptr<Ui::BotWebView::Panel> _panel;
|
||||
|
||||
static base::weak_ptr<WebViewInstance> PendingActivation;
|
||||
|
||||
};
|
||||
|
||||
class AttachWebView final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit AttachWebView(not_null<Main::Session*> session);
|
||||
~AttachWebView();
|
||||
|
||||
struct WebViewButton {
|
||||
QString text;
|
||||
QString startCommand;
|
||||
QByteArray url;
|
||||
bool fromAttachMenu = false;
|
||||
bool fromMainMenu = false;
|
||||
bool fromSwitch = false;
|
||||
};
|
||||
void request(
|
||||
void open(WebViewDescriptor &&descriptor);
|
||||
void openByUsername(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Api::SendAction &action,
|
||||
const QString &botUsername,
|
||||
const QString &startCommand);
|
||||
void request(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Api::SendAction &action,
|
||||
not_null<UserData*> bot,
|
||||
const WebViewButton &button);
|
||||
void requestSimple(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<UserData*> bot,
|
||||
const WebViewButton &button);
|
||||
void requestMenu(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<UserData*> bot);
|
||||
void requestApp(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Api::SendAction &action,
|
||||
not_null<UserData*> bot,
|
||||
const QString &appName,
|
||||
const QString &startParam,
|
||||
bool forceConfirmation);
|
||||
|
||||
void cancel();
|
||||
|
||||
@@ -142,72 +330,32 @@ public:
|
||||
[[nodiscard]] bool showMainMenuNewBadge(
|
||||
const AttachWebViewBot &bot) const;
|
||||
|
||||
void removeFromMenu(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<UserData*> bot);
|
||||
|
||||
enum class AddToMenuResult {
|
||||
AlreadyInMenu,
|
||||
Added,
|
||||
Unsupported,
|
||||
Cancelled,
|
||||
};
|
||||
void requestAddToMenu(
|
||||
not_null<UserData*> bot,
|
||||
AddToMenuOpen open);
|
||||
void requestAddToMenu(
|
||||
Fn<void(AddToMenuResult, PeerTypes supported)> done);
|
||||
void acceptMainMenuDisclaimer(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<UserData*> bot,
|
||||
AddToMenuOpen open,
|
||||
Window::SessionController *controller,
|
||||
std::optional<Api::SendAction> action);
|
||||
void removeFromMenu(not_null<UserData*> bot);
|
||||
Fn<void(AddToMenuResult, PeerTypes supported)> done);
|
||||
|
||||
[[nodiscard]] std::optional<Api::SendAction> lookupLastAction(
|
||||
const QString &url) const;
|
||||
|
||||
static void ClearAll();
|
||||
void close(not_null<WebViewInstance*> instance);
|
||||
void closeAll();
|
||||
|
||||
private:
|
||||
struct Context;
|
||||
|
||||
|
||||
Webview::ThemeParams botThemeParams() override;
|
||||
bool botHandleLocalUri(QString uri, bool keepOpen) override;
|
||||
void botHandleInvoice(QString slug) override;
|
||||
void botHandleMenuButton(Ui::BotWebView::MenuButton button) override;
|
||||
bool botValidateExternalLink(QString uri) override;
|
||||
void botOpenIvLink(QString uri) override;
|
||||
void botSendData(QByteArray data) override;
|
||||
void botSwitchInlineQuery(
|
||||
std::vector<QString> chatTypes,
|
||||
QString query) override;
|
||||
void botCheckWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||
void botAllowWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||
void botSharePhone(Fn<void(bool shared)> callback) override;
|
||||
void botInvokeCustomMethod(
|
||||
Ui::BotWebView::CustomMethodRequest request) override;
|
||||
void botClose() override;
|
||||
|
||||
[[nodiscard]] static Context LookupContext(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Api::SendAction &action);
|
||||
[[nodiscard]] static bool IsSame(
|
||||
const std::unique_ptr<Context> &a,
|
||||
const Context &b);
|
||||
|
||||
bool openAppFromMenuLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<UserData*> bot);
|
||||
void requestWithOptionalConfirm(
|
||||
not_null<UserData*> bot,
|
||||
const WebViewButton &button,
|
||||
const Context &context,
|
||||
Window::SessionController *controllerForConfirm = nullptr);
|
||||
|
||||
void resolve();
|
||||
void request(const WebViewButton &button);
|
||||
void requestSimple(const WebViewButton &button);
|
||||
void resolveUsername(
|
||||
const QString &username,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
Fn<void(not_null<PeerData*>)> done);
|
||||
|
||||
void confirmOpen(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> done);
|
||||
void acceptMainMenuDisclaimer(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const WebViewButton &button);
|
||||
|
||||
enum class ToggledState {
|
||||
Removed,
|
||||
Added,
|
||||
@@ -216,63 +364,35 @@ private:
|
||||
void toggleInMenu(
|
||||
not_null<UserData*> bot,
|
||||
ToggledState state,
|
||||
Fn<void()> callback = nullptr);
|
||||
|
||||
void show(
|
||||
uint64 queryId,
|
||||
const QString &url,
|
||||
const QString &buttonText = QString(),
|
||||
bool allowClipboardRead = false,
|
||||
const BotAppData *app = nullptr,
|
||||
bool fromMainMenu = false);
|
||||
Fn<void(bool success)> callback = nullptr);
|
||||
void confirmAddToMenu(
|
||||
AttachWebViewBot bot,
|
||||
Fn<void()> callback = nullptr);
|
||||
void confirmAppOpen(bool requestWriteAccess);
|
||||
void requestAppView(bool allowWrite);
|
||||
void started(uint64 queryId);
|
||||
|
||||
void showToast(
|
||||
const QString &text,
|
||||
Window::SessionController *controller = nullptr);
|
||||
Fn<void(bool added)> callback = nullptr);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _refreshTimer;
|
||||
|
||||
std::unique_ptr<Context> _context;
|
||||
std::unique_ptr<Context> _lastShownContext;
|
||||
QString _lastShownUrl;
|
||||
uint64 _lastShownQueryId = 0;
|
||||
QString _lastShownButtonText;
|
||||
UserData *_bot = nullptr;
|
||||
QString _botUsername;
|
||||
QString _botAppName;
|
||||
QString _startCommand;
|
||||
BotAppData *_app = nullptr;
|
||||
QPointer<Ui::GenericBox> _confirmAddBox;
|
||||
bool _appConfirmationRequired = false;
|
||||
bool _appRequestWriteAccess = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
mtpRequestId _prolongId = 0;
|
||||
|
||||
uint64 _botsHash = 0;
|
||||
mtpRequestId _botsRequestId = 0;
|
||||
std::vector<Fn<void()>> _botsRequestCallbacks;
|
||||
|
||||
std::unique_ptr<Context> _addToMenuContext;
|
||||
UserData *_addToMenuBot = nullptr;
|
||||
mtpRequestId _addToMenuId = 0;
|
||||
AddToMenuOpen _addToMenuOpen;
|
||||
base::weak_ptr<Window::SessionController> _addToMenuChooseController;
|
||||
struct AddToMenuProcess {
|
||||
mtpRequestId requestId = 0;
|
||||
std::vector<Fn<void(AddToMenuResult, PeerTypes supported)>> done;
|
||||
};
|
||||
base::flat_map<not_null<UserData*>, AddToMenuProcess> _addToMenu;
|
||||
|
||||
std::vector<AttachWebViewBot> _attachBots;
|
||||
rpl::event_stream<> _attachBotsUpdates;
|
||||
base::flat_set<not_null<UserData*>> _disclaimerAccepted;
|
||||
|
||||
std::unique_ptr<Ui::BotWebView::Panel> _panel;
|
||||
bool _catchingCancelInShowCall = false;
|
||||
std::vector<std::unique_ptr<WebViewInstance>> _instances;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/painter.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/history.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
@@ -677,10 +678,13 @@ void Inner::switchPm() {
|
||||
if (!_inlineBot || !_inlineBot->isBot()) {
|
||||
return;
|
||||
} else if (!_switchPmUrl.isEmpty()) {
|
||||
_inlineBot->session().attachWebView().requestSimple(
|
||||
_controller,
|
||||
_inlineBot,
|
||||
{ .url = _switchPmUrl, .fromSwitch = true });
|
||||
const auto bot = _inlineBot;
|
||||
_inlineBot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = _controller },
|
||||
.button = { .url = _switchPmUrl },
|
||||
.source = InlineBots::WebViewSourceSwitch(),
|
||||
});
|
||||
} else {
|
||||
_inlineBot->botInfo->startToken = _switchPmStartToken;
|
||||
_inlineBot->botInfo->inlineReturnTo
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/webview_helpers.h"
|
||||
#include "webview/webview_data_stream_memory.h"
|
||||
#include "webview/webview_embed.h"
|
||||
#include "webview/webview_interface.h"
|
||||
@@ -35,12 +36,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QWindow>
|
||||
#include <charconv>
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace Iv {
|
||||
namespace {
|
||||
|
||||
@@ -79,67 +79,7 @@ namespace {
|
||||
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
|
||||
{ "iv-join-channel", tr::lng_iv_join_channel },
|
||||
};
|
||||
static const auto serialize = [](const style::color *color) {
|
||||
const auto qt = (*color)->c;
|
||||
if (qt.alpha() == 255) {
|
||||
return '#'
|
||||
+ QByteArray::number(qt.red(), 16).right(2)
|
||||
+ QByteArray::number(qt.green(), 16).right(2)
|
||||
+ QByteArray::number(qt.blue(), 16).right(2);
|
||||
}
|
||||
return "rgba("
|
||||
+ QByteArray::number(qt.red()) + ","
|
||||
+ QByteArray::number(qt.green()) + ","
|
||||
+ QByteArray::number(qt.blue()) + ","
|
||||
+ QByteArray::number(qt.alpha() / 255.) + ")";
|
||||
};
|
||||
static const auto escape = [](tr::phrase<> phrase) {
|
||||
const auto text = phrase(tr::now);
|
||||
|
||||
auto result = QByteArray();
|
||||
for (auto i = 0; i != text.size(); ++i) {
|
||||
uint ucs4 = text[i].unicode();
|
||||
if (QChar::isHighSurrogate(ucs4) && i + 1 != text.size()) {
|
||||
ushort low = text[i + 1].unicode();
|
||||
if (QChar::isLowSurrogate(low)) {
|
||||
ucs4 = QChar::surrogateToUcs4(ucs4, low);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (ucs4 == '\'' || ucs4 == '\"' || ucs4 == '\\') {
|
||||
result.append('\\').append(char(ucs4));
|
||||
} else if (ucs4 < 32 || ucs4 > 127) {
|
||||
result.append('\\' + QByteArray::number(ucs4, 16) + ' ');
|
||||
} else {
|
||||
result.append(char(ucs4));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto result = QByteArray();
|
||||
for (const auto &[name, phrase] : phrases) {
|
||||
result += "--td-lng-" + name + ":'" + escape(phrase) + "'; ";
|
||||
}
|
||||
for (const auto &[name, color] : map) {
|
||||
result += "--td-" + name + ':' + serialize(color) + ';';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) {
|
||||
return value
|
||||
.replace('&', "&")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">");
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) {
|
||||
return value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('\'', "\\\'");
|
||||
return Ui::ComputeStyles(map, phrases);
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray WrapPage(const Prepared &page) {
|
||||
@@ -159,7 +99,7 @@ namespace {
|
||||
<html)"_q
|
||||
+ classAttribute
|
||||
+ R"( style=")"
|
||||
+ EscapeForAttribute(ComputeStyles())
|
||||
+ Ui::EscapeForAttribute(ComputeStyles())
|
||||
+ R"(">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -194,7 +134,7 @@ Controller::Controller(
|
||||
Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox)
|
||||
: _delegate(delegate)
|
||||
, _updateStyles([=] {
|
||||
const auto str = EscapeForScriptString(ComputeStyles());
|
||||
const auto str = Ui::EscapeForScriptString(ComputeStyles());
|
||||
if (_webview) {
|
||||
_webview->eval("IV.updateStyles('" + str + "');");
|
||||
}
|
||||
@@ -343,10 +283,11 @@ void Controller::createWindow() {
|
||||
const auto window = _window.get();
|
||||
|
||||
base::qt_signal_producer(
|
||||
window->window()->windowHandle(),
|
||||
&QWindow::activeChanged
|
||||
) | rpl::filter([=] {
|
||||
return _webview && window->window()->windowHandle()->isActive();
|
||||
qApp,
|
||||
&QGuiApplication::focusWindowChanged
|
||||
) | rpl::filter([=](QWindow *focused) {
|
||||
const auto handle = window->window()->windowHandle();
|
||||
return _webview && handle && (focused == handle);
|
||||
}) | rpl::start_with_next([=] {
|
||||
setInnerFocus();
|
||||
}, window->lifetime());
|
||||
@@ -612,7 +553,7 @@ QByteArray Controller::navigateScript(int index, const QString &hash) {
|
||||
return "IV.navigateTo("
|
||||
+ QByteArray::number(index)
|
||||
+ ", '"
|
||||
+ EscapeForScriptString(qthelp::url_decode(hash).toUtf8())
|
||||
+ Ui::EscapeForScriptString(qthelp::url_decode(hash).toUtf8())
|
||||
+ "');";
|
||||
}
|
||||
|
||||
@@ -679,7 +620,7 @@ bool Controller::active() const {
|
||||
void Controller::showJoinedTooltip() {
|
||||
if (_webview && _ready) {
|
||||
_webview->eval("IV.showTooltip('"
|
||||
+ EscapeForScriptString(
|
||||
+ Ui::EscapeForScriptString(
|
||||
tr::lng_action_you_joined(tr::now).toUtf8())
|
||||
+ "');");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/file_utilities.h"
|
||||
@@ -49,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller_link_info.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Iv {
|
||||
namespace {
|
||||
@@ -298,12 +300,33 @@ ShareBoxResult Shown::shareBox(ShareBoxDescriptor &&descriptor) {
|
||||
state->destroyRequests.fire({});
|
||||
}, wrap->lifetime());
|
||||
|
||||
const auto waiting = layer->lifetime().make_state<rpl::lifetime>();
|
||||
const auto focus = crl::guard(layer, [=] {
|
||||
if (!layer->window()->isActiveWindow()) {
|
||||
layer->window()->activateWindow();
|
||||
const auto set = [=] {
|
||||
layer->window()->setFocus();
|
||||
layer->setInnerFocus();
|
||||
};
|
||||
|
||||
const auto handle = layer->window()->windowHandle();
|
||||
if (!handle) {
|
||||
waiting->destroy();
|
||||
return;
|
||||
} else if (QGuiApplication::focusWindow() == handle) {
|
||||
waiting->destroy();
|
||||
set();
|
||||
} else {
|
||||
*waiting = base::qt_signal_producer(
|
||||
qApp,
|
||||
&QGuiApplication::focusWindowChanged
|
||||
) | rpl::filter([=](QWindow *focused) {
|
||||
const auto handle = layer->window()->windowHandle();
|
||||
return handle && (focused == handle);
|
||||
}) | rpl::start_with_next([=] {
|
||||
waiting->destroy();
|
||||
set();
|
||||
});
|
||||
layer->window()->activateWindow();
|
||||
}
|
||||
layer->setInnerFocus();
|
||||
});
|
||||
auto result = ShareBoxResult{
|
||||
.focus = focus,
|
||||
|
||||
@@ -144,28 +144,22 @@ std::vector<QString> AppConfig::getStringArray(
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::map<QString, QString>> AppConfig::getStringMapArray(
|
||||
base::flat_map<QString, QString> AppConfig::getStringMap(
|
||||
const QString &key,
|
||||
std::vector<std::map<QString, QString>> &&fallback) const {
|
||||
base::flat_map<QString, QString> &&fallback) const {
|
||||
return getValue(key, [&](const MTPJSONValue &value) {
|
||||
return value.match([&](const MTPDjsonArray &data) {
|
||||
auto result = std::vector<std::map<QString, QString>>();
|
||||
return value.match([&](const MTPDjsonObject &data) {
|
||||
auto result = base::flat_map<QString, QString>();
|
||||
result.reserve(data.vvalue().v.size());
|
||||
for (const auto &entry : data.vvalue().v) {
|
||||
if (entry.type() != mtpc_jsonObject) {
|
||||
const auto &data = entry.data();
|
||||
const auto &value = data.vvalue();
|
||||
if (value.type() != mtpc_jsonString) {
|
||||
return std::move(fallback);
|
||||
}
|
||||
auto element = std::map<QString, QString>();
|
||||
for (const auto &field : entry.c_jsonObject().vvalue().v) {
|
||||
const auto &data = field.c_jsonObjectValue();
|
||||
if (data.vvalue().type() != mtpc_jsonString) {
|
||||
return std::move(fallback);
|
||||
}
|
||||
element.emplace(
|
||||
qs(data.vkey()),
|
||||
qs(data.vvalue().c_jsonString().vvalue()));
|
||||
}
|
||||
result.push_back(std::move(element));
|
||||
result.emplace(
|
||||
qs(data.vkey()),
|
||||
qs(value.c_jsonString().vvalue()));
|
||||
}
|
||||
return result;
|
||||
}, [&](const auto &data) {
|
||||
|
||||
@@ -35,12 +35,11 @@ public:
|
||||
return getString(key, fallback);
|
||||
} else if constexpr (std::is_same_v<Type, std::vector<QString>>) {
|
||||
return getStringArray(key, std::move(fallback));
|
||||
} else if constexpr (
|
||||
std::is_same_v<Type, base::flat_map<QString, QString>>) {
|
||||
return getStringMap(key, std::move(fallback));
|
||||
} else if constexpr (std::is_same_v<Type, std::vector<int>>) {
|
||||
return getIntArray(key, std::move(fallback));
|
||||
} else if constexpr (std::is_same_v<
|
||||
Type,
|
||||
std::vector<std::map<QString, QString>>>) {
|
||||
return getStringMapArray(key, std::move(fallback));
|
||||
} else if constexpr (std::is_same_v<Type, bool>) {
|
||||
return getBool(key, fallback);
|
||||
}
|
||||
@@ -78,9 +77,9 @@ private:
|
||||
[[nodiscard]] std::vector<QString> getStringArray(
|
||||
const QString &key,
|
||||
std::vector<QString> &&fallback) const;
|
||||
[[nodiscard]] std::vector<std::map<QString, QString>> getStringMapArray(
|
||||
[[nodiscard]] base::flat_map<QString, QString> getStringMap(
|
||||
const QString &key,
|
||||
std::vector<std::map<QString, QString>> &&fallback) const;
|
||||
base::flat_map<QString, QString> &&fallback) const;
|
||||
[[nodiscard]] std::vector<int> getIntArray(
|
||||
const QString &key,
|
||||
std::vector<int> &&fallback) const;
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "data/components/factchecks.h"
|
||||
#include "data/components/location_pickers.h"
|
||||
#include "data/components/recent_peers.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
@@ -73,10 +74,14 @@ constexpr auto kTmpPasswordReserveTime = TimeId(10);
|
||||
if (domain.startsWith(prefix, Qt::CaseInsensitive)) {
|
||||
return domain.endsWith('/')
|
||||
? domain
|
||||
: MTP::ConfigFields().internalLinksDomain;
|
||||
: MTP::ConfigFields(
|
||||
session->mtp().environment()
|
||||
).internalLinksDomain;
|
||||
}
|
||||
}
|
||||
return MTP::ConfigFields().internalLinksDomain;
|
||||
return MTP::ConfigFields(
|
||||
session->mtp().environment()
|
||||
).internalLinksDomain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -107,6 +112,7 @@ Session::Session(
|
||||
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
||||
, _topPeers(std::make_unique<Data::TopPeers>(this))
|
||||
, _factchecks(std::make_unique<Data::Factchecks>(this))
|
||||
, _locationPickers(std::make_unique<Data::LocationPickers>())
|
||||
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
|
||||
, _supportHelper(Support::Helper::Create(this))
|
||||
, _saveSettingsTimer([=] { saveSettings(); }) {
|
||||
|
||||
@@ -36,6 +36,7 @@ class ScheduledMessages;
|
||||
class SponsoredMessages;
|
||||
class TopPeers;
|
||||
class Factchecks;
|
||||
class LocationPickers;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
@@ -131,6 +132,9 @@ public:
|
||||
[[nodiscard]] Data::Factchecks &factchecks() const {
|
||||
return *_factchecks;
|
||||
}
|
||||
[[nodiscard]] Data::LocationPickers &locationPickers() const {
|
||||
return *_locationPickers;
|
||||
}
|
||||
[[nodiscard]] Api::Updates &updates() const {
|
||||
return *_updates;
|
||||
}
|
||||
@@ -259,6 +263,7 @@ private:
|
||||
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
|
||||
const std::unique_ptr<Data::TopPeers> _topPeers;
|
||||
const std::unique_ptr<Data::Factchecks> _factchecks;
|
||||
const std::unique_ptr<Data::LocationPickers> _locationPickers;
|
||||
|
||||
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
|
||||
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
|
||||
|
||||
@@ -193,7 +193,7 @@ void MainWindow::setupPasscodeLock() {
|
||||
setInnerFocus();
|
||||
}
|
||||
if (const auto sessionController = controller().sessionController()) {
|
||||
sessionController->session().attachWebView().cancel();
|
||||
sessionController->session().attachWebView().closeAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -729,25 +729,27 @@ void OverlayWidget::orderWidgets() {
|
||||
void OverlayWidget::setupWindow() {
|
||||
_window->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
if (!_windowed
|
||||
|| !_widget->rect().contains(widgetPoint)
|
||||
Ui::WindowTitleHitTestFlags result;
|
||||
if (!_widget->rect().contains(widgetPoint)
|
||||
|| _helper->skipTitleHitTest(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
return result;
|
||||
}
|
||||
const auto inControls = (_over != Over::None) && (_over != Over::Video);
|
||||
if (widgetPoint.y() <= st::mediaviewTitleButton.height) {
|
||||
result |= Flag::Menu;
|
||||
}
|
||||
const auto inControls = ((_over != Over::None) && (_over != Over::Video));
|
||||
if (inControls
|
||||
|| (_streamed
|
||||
&& _streamed->controls
|
||||
&& _streamed->controls->dragging())) {
|
||||
return Flag::None | Flag(0);
|
||||
} else if ((_w > _widget->width() || _h > _maxUsedHeight)
|
||||
&& (widgetPoint.y() > st::mediaviewHeaderTop)
|
||||
&& QRect(_x, _y, _w, _h).contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
} else if (_stories && _stories->ignoreWindowMove(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
} else if (_windowed) {
|
||||
result |= Flag::Move;
|
||||
}
|
||||
return Flag::Move | Flag(0);
|
||||
return result;
|
||||
});
|
||||
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
@@ -5926,8 +5928,11 @@ void OverlayWidget::handleMouseRelease(
|
||||
}
|
||||
|
||||
bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) {
|
||||
if (position && !QRect(_x, _y, _w, _h).contains(*position)) {
|
||||
return false;
|
||||
if (position) {
|
||||
if (!QRect(_x, _y, _w, _h).contains(*position)
|
||||
|| position->y() <= st::mediaviewTitleButton.height) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
_window,
|
||||
|
||||
@@ -16,18 +16,30 @@ namespace {
|
||||
|
||||
constexpr auto kVersion = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
QString ConfigDefaultReactionEmoji() {
|
||||
[[nodiscard]] QString ConfigDefaultReactionEmoji() {
|
||||
static const auto result = QString::fromUtf8("\xf0\x9f\x91\x8d");
|
||||
return result;
|
||||
}
|
||||
|
||||
Config::Config(Environment environment) : _dcOptions(environment) {
|
||||
_fields.webFileDcId = _dcOptions.isTestMode() ? 2 : 4;
|
||||
_fields.txtDomainString = _dcOptions.isTestMode()
|
||||
? u"tapv3.stel.com"_q
|
||||
: u"apv3.stel.com"_q;
|
||||
} // namespace
|
||||
|
||||
ConfigFields::ConfigFields(Environment environment)
|
||||
: webFileDcId(environment == Environment::Test ? 2 : 4)
|
||||
, txtDomainString(environment == Environment::Test
|
||||
? u"tapv3.stel.com"_q
|
||||
: u"apv3.stel.com"_q)
|
||||
, reactionDefaultEmoji(ConfigDefaultReactionEmoji())
|
||||
, gifSearchUsername(environment == Environment::Test
|
||||
? u"izgifbot"_q
|
||||
: u"gif"_q)
|
||||
, venueSearchUsername(environment == Environment::Test
|
||||
? u"foursquarebot"_q
|
||||
: u"foursquare"_q) {
|
||||
}
|
||||
|
||||
Config::Config(Environment environment)
|
||||
: _dcOptions(environment)
|
||||
, _fields(environment) {
|
||||
}
|
||||
|
||||
Config::Config(const Config &other)
|
||||
@@ -46,7 +58,9 @@ QByteArray Config::serialize() const {
|
||||
+ 3 * sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.reactionDefaultEmoji)
|
||||
+ sizeof(quint64)
|
||||
+ sizeof(qint32);
|
||||
+ sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.gifSearchUsername)
|
||||
+ Serialize::stringSize(_fields.venueSearchUsername);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -91,7 +105,9 @@ QByteArray Config::serialize() const {
|
||||
<< qint32(_fields.captionLengthMax)
|
||||
<< _fields.reactionDefaultEmoji
|
||||
<< quint64(_fields.reactionDefaultCustom)
|
||||
<< qint32(_fields.ratingDecay);
|
||||
<< qint32(_fields.ratingDecay)
|
||||
<< _fields.gifSearchUsername
|
||||
<< _fields.venueSearchUsername;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -190,6 +206,10 @@ std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.ratingDecay);
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.gifSearchUsername);
|
||||
read(raw->_fields.venueSearchUsername);
|
||||
}
|
||||
|
||||
if (stream.status() != QDataStream::Ok
|
||||
|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
|
||||
@@ -256,8 +276,12 @@ void Config::apply(const MTPDconfig &data) {
|
||||
_fields.autologinToken = qs(data.vautologin_token().value_or_empty());
|
||||
_fields.ratingDecay = data.vrating_e_decay().v;
|
||||
if (_fields.ratingDecay <= 0) {
|
||||
_fields.ratingDecay = ConfigFields().ratingDecay;
|
||||
_fields.ratingDecay = ConfigFields(
|
||||
_dcOptions.environment()
|
||||
).ratingDecay;
|
||||
}
|
||||
_fields.gifSearchUsername = qs(data.vgif_search_username().value_or_empty());
|
||||
_fields.venueSearchUsername = qs(data.vvenue_search_username().value_or_empty());
|
||||
|
||||
if (data.vdc_options().v.empty()) {
|
||||
LOG(("MTP Error: config with empty dc_options received!"));
|
||||
|
||||
@@ -11,9 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace MTP {
|
||||
|
||||
[[nodiscard]] QString ConfigDefaultReactionEmoji();
|
||||
|
||||
struct ConfigFields {
|
||||
explicit ConfigFields(Environment environment);
|
||||
|
||||
int chatSizeMax = 200;
|
||||
int megagroupSizeMax = 10000;
|
||||
int forwardedCountMax = 100;
|
||||
@@ -40,9 +40,12 @@ struct ConfigFields {
|
||||
bool blockedMode = false;
|
||||
int captionLengthMax = 1024;
|
||||
int ratingDecay = 2419200;
|
||||
QString reactionDefaultEmoji = ConfigDefaultReactionEmoji();
|
||||
uint64 reactionDefaultCustom;
|
||||
QString reactionDefaultEmoji;
|
||||
uint64 reactionDefaultCustom = 0;
|
||||
QString autologinToken;
|
||||
|
||||
QString gifSearchUsername;
|
||||
QString venueSearchUsername;
|
||||
};
|
||||
|
||||
class Config final {
|
||||
|
||||
@@ -102,7 +102,7 @@ channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5
|
||||
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
|
||||
chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull;
|
||||
channelFull#bbab348d 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 flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true id:long 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?long 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?long 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 requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull;
|
||||
channelFull#bbab348d 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 flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true id:long 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?long 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?long 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 requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull;
|
||||
|
||||
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
|
||||
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
|
||||
@@ -179,6 +179,7 @@ messageActionGiveawayLaunch#332ba9ed = MessageAction;
|
||||
messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction;
|
||||
messageActionBoostApply#cc02aa6d boosts:int = MessageAction;
|
||||
messageActionRequestedPeerSentMe#93b31848 button_id:int peers:Vector<RequestedPeer> = MessageAction;
|
||||
messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_amount:long payload:flags.0?bytes charge:PaymentCharge = MessageAction;
|
||||
|
||||
dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?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;
|
||||
@@ -2493,4 +2494,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
|
||||
|
||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
||||
|
||||
// LAYER 183
|
||||
// LAYER 184
|
||||
|
||||
@@ -536,6 +536,8 @@ void CheckoutProcess::handleError(const Error &error) {
|
||||
showToast({ tr::lng_payments_payment_failed(tr::now) });
|
||||
} else if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
|
||||
showToast({ tr::lng_payments_precheckout_failed(tr::now) });
|
||||
} else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) {
|
||||
showToast({ tr::lng_payments_precheckout_timeout(tr::now) });
|
||||
} else if (id == u"REQUESTED_INFO_INVALID"_q
|
||||
|| id == u"SHIPPING_OPTION_INVALID"_q
|
||||
|| id == u"PAYMENT_CREDENTIALS_INVALID"_q
|
||||
@@ -764,6 +766,14 @@ void CheckoutProcess::showForm() {
|
||||
_form->information(),
|
||||
_form->paymentMethod().ui,
|
||||
_form->shippingOptions());
|
||||
if (_nonPanelPaymentFormProcess && !_realFormNotified) {
|
||||
_realFormNotified = true;
|
||||
const auto weak = base::make_weak(_panel.get());
|
||||
_nonPanelPaymentFormProcess(RealFormPresentedNotification());
|
||||
if (weak) {
|
||||
requestActivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckoutProcess::showEditInformation(Ui::InformationField field) {
|
||||
|
||||
@@ -55,9 +55,13 @@ enum class CheckoutResult {
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct NonPanelPaymentForm : std::variant<
|
||||
std::shared_ptr<CreditsFormData>,
|
||||
std::shared_ptr<CreditsReceiptData>> {
|
||||
struct RealFormPresentedNotification {
|
||||
};
|
||||
struct NonPanelPaymentForm
|
||||
: std::variant<
|
||||
std::shared_ptr<CreditsFormData>,
|
||||
std::shared_ptr<CreditsReceiptData>,
|
||||
RealFormPresentedNotification> {
|
||||
using variant::variant;
|
||||
};
|
||||
|
||||
@@ -183,6 +187,7 @@ private:
|
||||
Fn<void(NonPanelPaymentForm)> _nonPanelPaymentFormProcess;
|
||||
SubmitState _submitState = SubmitState::None;
|
||||
bool _initialSilentValidation = false;
|
||||
bool _realFormNotified = false;
|
||||
bool _sendFormPending = false;
|
||||
bool _sendFormFailed = false;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/boost_box.h" // Ui::StartFireworks.
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Payments {
|
||||
@@ -37,84 +38,98 @@ bool IsCreditsInvoice(not_null<HistoryItem*> item) {
|
||||
return invoice && (invoice->currency == Ui::kCreditsCurrency);
|
||||
}
|
||||
|
||||
void ProcessCreditsPayment(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
QPointer<QWidget> fireworks,
|
||||
std::shared_ptr<CreditsFormData> form,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
const auto api = lifetime->make_state<Api::CreditsStatus>(
|
||||
show->session().user());
|
||||
const auto sendBox = [=] {
|
||||
const auto unsuccessful = std::make_shared<bool>(true);
|
||||
const auto box = show->show(Box(
|
||||
Ui::SendCreditsBox,
|
||||
form,
|
||||
[=] {
|
||||
*unsuccessful = false;
|
||||
if (const auto widget = fireworks.data()) {
|
||||
Ui::StartFireworks(widget);
|
||||
}
|
||||
if (maybeReturnToBot) {
|
||||
maybeReturnToBot(CheckoutResult::Paid);
|
||||
}
|
||||
}));
|
||||
box->boxClosing() | rpl::start_with_next([=] {
|
||||
crl::on_main([=] {
|
||||
if ((*unsuccessful) && maybeReturnToBot) {
|
||||
maybeReturnToBot(CheckoutResult::Cancelled);
|
||||
}
|
||||
});
|
||||
}, box->lifetime());
|
||||
};
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
show->session().setCredits(slice.balance);
|
||||
const auto creditsNeeded = int64(form->invoice.credits)
|
||||
- int64(slice.balance);
|
||||
if (creditsNeeded <= 0) {
|
||||
sendBox();
|
||||
} else if (show->session().premiumPossible()) {
|
||||
show->show(Box(
|
||||
Settings::SmallBalanceBox,
|
||||
show,
|
||||
creditsNeeded,
|
||||
form->botId,
|
||||
sendBox));
|
||||
} else {
|
||||
show->showToast(
|
||||
tr::lng_credits_purchase_blocked(tr::now));
|
||||
if (maybeReturnToBot) {
|
||||
maybeReturnToBot(CheckoutResult::Failed);
|
||||
}
|
||||
}
|
||||
lifetime->destroy();
|
||||
});
|
||||
}
|
||||
|
||||
void ProcessCreditsReceipt(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<CreditsReceiptData> receipt,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
||||
const auto entry = Data::CreditsHistoryEntry{
|
||||
.id = receipt->id,
|
||||
.title = receipt->title,
|
||||
.description = receipt->description,
|
||||
.date = base::unixtime::parse(receipt->date),
|
||||
.photoId = receipt->photo ? receipt->photo->id : 0,
|
||||
.credits = receipt->credits,
|
||||
.bareMsgId = uint64(),
|
||||
.barePeerId = receipt->peerId.value,
|
||||
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
|
||||
};
|
||||
controller->uiShow()->show(Box(
|
||||
Settings::ReceiptCreditsBox,
|
||||
controller,
|
||||
nullptr,
|
||||
entry));
|
||||
controller->window().activate();
|
||||
}
|
||||
|
||||
Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
||||
return [=](NonPanelPaymentForm form) {
|
||||
using CreditsFormDataPtr = std::shared_ptr<CreditsFormData>;
|
||||
using CreditsReceiptPtr = std::shared_ptr<CreditsReceiptData>;
|
||||
if (const auto creditsData = std::get_if<CreditsFormDataPtr>(&form)) {
|
||||
const auto form = *creditsData;
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
const auto api = lifetime->make_state<Api::CreditsStatus>(
|
||||
controller->session().user());
|
||||
const auto sendBox = [=, weak = base::make_weak(controller)] {
|
||||
if (const auto strong = weak.get()) {
|
||||
const auto unsuccessful = std::make_shared<bool>(true);
|
||||
const auto box = controller->uiShow()->show(Box(
|
||||
Ui::SendCreditsBox,
|
||||
form,
|
||||
crl::guard(strong, [=] {
|
||||
*unsuccessful = false;
|
||||
Ui::StartFireworks(strong->content());
|
||||
if (maybeReturnToBot) {
|
||||
maybeReturnToBot(CheckoutResult::Paid);
|
||||
}
|
||||
})));
|
||||
box->boxClosing() | rpl::start_with_next([=] {
|
||||
crl::on_main([=] {
|
||||
if ((*unsuccessful) && maybeReturnToBot) {
|
||||
maybeReturnToBot(CheckoutResult::Cancelled);
|
||||
}
|
||||
});
|
||||
}, box->lifetime());
|
||||
}
|
||||
};
|
||||
const auto weak = base::make_weak(controller);
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->session().setCredits(slice.balance);
|
||||
const auto creditsNeeded = int64(form->invoice.credits)
|
||||
- int64(slice.balance);
|
||||
if (creditsNeeded <= 0) {
|
||||
sendBox();
|
||||
} else if (strong->session().premiumPossible()) {
|
||||
strong->uiShow()->show(Box(
|
||||
Settings::SmallBalanceBox,
|
||||
strong,
|
||||
creditsNeeded,
|
||||
form->botId,
|
||||
sendBox));
|
||||
} else {
|
||||
strong->uiShow()->showToast(
|
||||
tr::lng_credits_purchase_blocked(tr::now));
|
||||
if (maybeReturnToBot) {
|
||||
maybeReturnToBot(CheckoutResult::Failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
lifetime->destroy();
|
||||
});
|
||||
}
|
||||
if (const auto r = std::get_if<CreditsReceiptPtr>(&form)) {
|
||||
const auto receipt = *r;
|
||||
const auto entry = Data::CreditsHistoryEntry{
|
||||
.id = receipt->id,
|
||||
.title = receipt->title,
|
||||
.description = receipt->description,
|
||||
.date = base::unixtime::parse(receipt->date),
|
||||
.photoId = receipt->photo ? receipt->photo->id : 0,
|
||||
.credits = receipt->credits,
|
||||
.bareMsgId = uint64(),
|
||||
.barePeerId = receipt->peerId.value,
|
||||
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
|
||||
};
|
||||
controller->uiShow()->show(Box(
|
||||
Settings::ReceiptCreditsBox,
|
||||
controller,
|
||||
nullptr,
|
||||
entry));
|
||||
}
|
||||
v::match(form, [&](const CreditsFormDataPtr &form) {
|
||||
ProcessCreditsPayment(
|
||||
controller->uiShow(),
|
||||
controller->content().get(),
|
||||
form,
|
||||
maybeReturnToBot);
|
||||
}, [&](const CreditsReceiptPtr &receipt) {
|
||||
ProcessCreditsReceipt(controller, receipt, maybeReturnToBot);
|
||||
}, [](RealFormPresentedNotification) {});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Main {
|
||||
class SessionShow;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -16,10 +20,23 @@ class SessionController;
|
||||
namespace Payments {
|
||||
|
||||
enum class CheckoutResult;
|
||||
struct CreditsFormData;
|
||||
struct CreditsReceiptData;
|
||||
struct NonPanelPaymentForm;
|
||||
|
||||
[[nodiscard]] bool IsCreditsInvoice(not_null<HistoryItem*> item);
|
||||
|
||||
void ProcessCreditsPayment(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
QPointer<QWidget> fireworks,
|
||||
std::shared_ptr<CreditsFormData> form,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot = nullptr);
|
||||
|
||||
void ProcessCreditsReceipt(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<CreditsReceiptData> receipt,
|
||||
Fn<void(CheckoutResult)> maybeReturnToBot = nullptr);
|
||||
|
||||
Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void(Payments::CheckoutResult)> maybeReturnToBot = nullptr);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 "platform/linux/current_geo_location_linux.h"
|
||||
|
||||
#include "core/current_geo_location.h"
|
||||
|
||||
namespace Platform {
|
||||
|
||||
void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
|
||||
callback({});
|
||||
}
|
||||
void ResolveLocationAddress(
|
||||
const Core::GeoLocation &location,
|
||||
const QString &language,
|
||||
Fn<void(Core::GeoAddress)> callback) {
|
||||
callback({});
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
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 "platform/platform_current_geo_location.h"
|
||||
@@ -18,8 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "core/launcher.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "webview/platform/linux/webview_linux_webkitgtk.h"
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
@@ -92,10 +94,23 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) {
|
||||
uniqueName.erase(0, 1);
|
||||
uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
|
||||
|
||||
const auto window = std::make_shared<QWidget>();
|
||||
window->setAttribute(Qt::WA_DontShowOnScreen);
|
||||
window->setWindowModality(Qt::ApplicationModal);
|
||||
window->show();
|
||||
const auto parent = []() -> QPointer<QWidget> {
|
||||
const auto active = Core::App().activeWindow();
|
||||
if (!active) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return active->widget().get();
|
||||
}();
|
||||
|
||||
const auto window = std::make_shared<base::unique_qptr<QWidget>>(
|
||||
std::in_place,
|
||||
parent);
|
||||
|
||||
auto &raw = **window;
|
||||
raw.setAttribute(Qt::WA_DontShowOnScreen);
|
||||
raw.setWindowModality(Qt::WindowModal);
|
||||
raw.show();
|
||||
|
||||
XdpRequest::RequestProxy::new_(
|
||||
proxy->get_connection(),
|
||||
@@ -146,7 +161,6 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
std::vector<std::string> commandline;
|
||||
commandline.push_back(executable.toStdString());
|
||||
if (Core::Launcher::Instance().customWorkingDir()) {
|
||||
@@ -156,7 +170,9 @@ void PortalAutostart(bool enabled, Fn<void(bool)> done) {
|
||||
commandline.push_back("-autostart");
|
||||
|
||||
interface.call_request_background(
|
||||
base::Platform::XDP::ParentWindowID(),
|
||||
base::Platform::XDP::ParentWindowID(parent
|
||||
? parent->windowHandle()
|
||||
: nullptr),
|
||||
GLib::Variant::new_array({
|
||||
GLib::Variant::new_dict_entry(
|
||||
GLib::Variant::new_string("handle_token"),
|
||||
|
||||
10
Telegram/SourceFiles/platform/mac/current_geo_location_mac.h
Normal file
10
Telegram/SourceFiles/platform/mac/current_geo_location_mac.h
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
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 "platform/platform_current_geo_location.h"
|
||||
154
Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm
Normal file
154
Telegram/SourceFiles/platform/mac/current_geo_location_mac.mm
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
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 "platform/mac/current_geo_location_mac.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
#include "core/current_geo_location.h"
|
||||
|
||||
#include <CoreLocation/CoreLocation.h>
|
||||
|
||||
@interface LocationDelegate : NSObject<CLLocationManagerDelegate>
|
||||
|
||||
- (id) initWithCallback:(Fn<void(Core::GeoLocation)>)callback;
|
||||
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations;
|
||||
- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
|
||||
- (void) locationManager:(CLLocationManager *) manager didChangeAuthorizationStatus:(CLAuthorizationStatus) status;
|
||||
- (void) dealloc;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LocationDelegate {
|
||||
CLLocationManager *_manager;
|
||||
Fn<void(Core::GeoLocation)> _callback;
|
||||
}
|
||||
|
||||
- (void) fail {
|
||||
[_manager stopUpdatingLocation];
|
||||
|
||||
const auto onstack = _callback;
|
||||
[self release];
|
||||
|
||||
onstack({});
|
||||
}
|
||||
|
||||
- (void) processWithStatus:(CLAuthorizationStatus)status {
|
||||
switch (status) {
|
||||
case kCLAuthorizationStatusNotDetermined:
|
||||
if (@available(macOS 10.15, *)) {
|
||||
[_manager requestWhenInUseAuthorization];
|
||||
} else {
|
||||
[_manager startUpdatingLocation];
|
||||
}
|
||||
break;
|
||||
case kCLAuthorizationStatusAuthorizedAlways:
|
||||
[_manager startUpdatingLocation];
|
||||
return;
|
||||
case kCLAuthorizationStatusRestricted:
|
||||
case kCLAuthorizationStatusDenied:
|
||||
default:
|
||||
[self fail];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (id) initWithCallback:(Fn<void(Core::GeoLocation)>)callback {
|
||||
if (self = [super init]) {
|
||||
_callback = std::move(callback);
|
||||
_manager = [[CLLocationManager alloc] init];
|
||||
_manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
|
||||
_manager.delegate = self;
|
||||
if ([CLLocationManager locationServicesEnabled]) {
|
||||
if (@available(macOS 11, *)) {
|
||||
[self processWithStatus:[_manager authorizationStatus]];
|
||||
} else {
|
||||
[self processWithStatus:[CLLocationManager authorizationStatus]];
|
||||
}
|
||||
} else {
|
||||
[self fail];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation*>*)locations {
|
||||
[_manager stopUpdatingLocation];
|
||||
|
||||
auto result = Core::GeoLocation();
|
||||
if ([locations count] > 0) {
|
||||
const auto coordinate = [locations lastObject].coordinate;
|
||||
result.accuracy = Core::GeoLocationAccuracy::Exact;
|
||||
result.point = QPointF(coordinate.latitude, coordinate.longitude);
|
||||
}
|
||||
|
||||
const auto onstack = _callback;
|
||||
[self release];
|
||||
|
||||
onstack(result);
|
||||
}
|
||||
|
||||
- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
|
||||
if (error.code != kCLErrorLocationUnknown) {
|
||||
[self fail];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) locationManager:(CLLocationManager *) manager didChangeAuthorizationStatus:(CLAuthorizationStatus) status {
|
||||
[self processWithStatus:status];
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
if (_manager) {
|
||||
_manager.delegate = nil;
|
||||
[_manager release];
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace Platform {
|
||||
|
||||
void ResolveCurrentExactLocation(Fn<void(Core::GeoLocation)> callback) {
|
||||
[[LocationDelegate alloc] initWithCallback:std::move(callback)];
|
||||
}
|
||||
|
||||
void ResolveLocationAddress(
|
||||
const Core::GeoLocation &location,
|
||||
const QString &language,
|
||||
Fn<void(Core::GeoAddress)> callback) {
|
||||
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
|
||||
CLLocation *request = [[CLLocation alloc]
|
||||
initWithLatitude:location.point.x()
|
||||
longitude:location.point.y()];
|
||||
[geocoder reverseGeocodeLocation:request completionHandler:^(
|
||||
NSArray<CLPlacemark*> * __nullable placemarks,
|
||||
NSError * __nullable error) {
|
||||
if (placemarks && [placemarks count] > 0) {
|
||||
CLPlacemark *placemark = [placemarks firstObject];
|
||||
auto list = QStringList();
|
||||
const auto push = [&](NSString *text) {
|
||||
if (text) {
|
||||
const auto qt = NS2QString(text);
|
||||
if (!qt.isEmpty()) {
|
||||
list.push_back(qt);
|
||||
}
|
||||
}
|
||||
};
|
||||
push([placemark thoroughfare]);
|
||||
push([placemark locality]);
|
||||
push([placemark country]);
|
||||
callback({ .name = list.join(u", "_q) });
|
||||
} else {
|
||||
callback({});
|
||||
}
|
||||
[geocoder release];
|
||||
}];
|
||||
[request release];
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user