Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5434ea491 | ||
|
|
ce4338fae4 | ||
|
|
5a1a8af222 | ||
|
|
152b49c65c | ||
|
|
5c5414b680 | ||
|
|
f99960e1f6 | ||
|
|
e363b254f6 | ||
|
|
3aea9cb3ca | ||
|
|
060fe6a928 | ||
|
|
8c45b5e0f8 | ||
|
|
0126578dbd | ||
|
|
1a9c241b96 | ||
|
|
638d4d63c5 | ||
|
|
9370e87c54 | ||
|
|
59c38df5cc | ||
|
|
5655ad25b0 | ||
|
|
c86ced8a1e | ||
|
|
511067981d | ||
|
|
9a186cd8ce | ||
|
|
385aa3eef7 | ||
|
|
e065d32d28 | ||
|
|
d4feb16378 | ||
|
|
79e6369e27 | ||
|
|
e4bd89d33e | ||
|
|
2b89700f66 | ||
|
|
ab95751a66 | ||
|
|
841908fe31 | ||
|
|
9e0b046213 | ||
|
|
d46b9d024e | ||
|
|
52cd9f8cbf | ||
|
|
25d69434ec | ||
|
|
8c4e8212cd | ||
|
|
c24da4c3df | ||
|
|
47a237c924 | ||
|
|
fc3a9d98c0 | ||
|
|
acce671eb0 | ||
|
|
67b6023b32 | ||
|
|
5a46bb1770 | ||
|
|
01fd8aded1 | ||
|
|
e0750f7b87 | ||
|
|
4eaba39a7c | ||
|
|
6ac9ef34eb | ||
|
|
42a2286230 | ||
|
|
24d02d5461 | ||
|
|
9bf2940375 | ||
|
|
d98212e8b3 | ||
|
|
3fe9c36d90 | ||
|
|
22f16caa89 | ||
|
|
46cce57f6b | ||
|
|
456244cdec | ||
|
|
69b2030c71 | ||
|
|
11018d76f1 | ||
|
|
e862215efb | ||
|
|
129de6d87f | ||
|
|
3c3ce24675 | ||
|
|
d98ac33425 | ||
|
|
76842792b8 | ||
|
|
4b01043b27 | ||
|
|
0a4f3f310c | ||
|
|
8320feea10 | ||
|
|
58281023bc | ||
|
|
0b655450bb | ||
|
|
61292557bf | ||
|
|
12ad1190ff | ||
|
|
42e0994581 | ||
|
|
69bc595e31 | ||
|
|
5c097887ef | ||
|
|
b02b690747 | ||
|
|
c52da743fd | ||
|
|
63dff9ff91 | ||
|
|
1c41808042 | ||
|
|
2ebf44c166 | ||
|
|
c46b96f252 | ||
|
|
6c89f60679 | ||
|
|
bb73687fc5 | ||
|
|
31fa2d9355 | ||
|
|
c350e33dd8 | ||
|
|
7dd9adb934 | ||
|
|
01dc3b9382 | ||
|
|
c62b39e287 | ||
|
|
04f95e905e | ||
|
|
9463bbd266 | ||
|
|
f64f1ea62e | ||
|
|
55bd469b2d | ||
|
|
ba50393e86 | ||
|
|
df155f6cb5 | ||
|
|
8aafe6ba0e | ||
|
|
9cf15da2b1 | ||
|
|
d9c566ac44 | ||
|
|
780b5555d7 | ||
|
|
87aa8a249f | ||
|
|
8de6d0b63b | ||
|
|
8dceec5a9f | ||
|
|
da690d2741 | ||
|
|
b95f5071a4 | ||
|
|
7cc55e24c0 | ||
|
|
199c746216 | ||
|
|
2b9cce2f23 | ||
|
|
ed4b90717a | ||
|
|
c50df6a6bc | ||
|
|
54149fb156 | ||
|
|
2f964d0415 | ||
|
|
0970728273 | ||
|
|
b02dd889e0 | ||
|
|
f68e4d9d59 | ||
|
|
9445ce4b09 | ||
|
|
515d8e78da | ||
|
|
f316d951ae | ||
|
|
52e780b065 | ||
|
|
900f7e1304 | ||
|
|
2f5cb33bf2 | ||
|
|
000a7ae28b | ||
|
|
00460506b2 | ||
|
|
0852e717c3 | ||
|
|
a7f9b260de | ||
|
|
7d81159ccf | ||
|
|
556aa28df6 | ||
|
|
c61cabb075 | ||
|
|
a6fe5c08ad | ||
|
|
256e976167 | ||
|
|
e081ed4b4a | ||
|
|
4623804123 | ||
|
|
93e78f1565 | ||
|
|
b587328fed | ||
|
|
2fd5771c3d | ||
|
|
59b521d666 | ||
|
|
c3f5de30be | ||
|
|
2df5972f68 | ||
|
|
c9ebe28fc1 | ||
|
|
9997af1e8b | ||
|
|
b13b4a6b5c | ||
|
|
f10b2194e6 | ||
|
|
d1050e6041 | ||
|
|
326574ab7e | ||
|
|
3822845f86 | ||
|
|
f887bf3b6a | ||
|
|
dfa4a9990d | ||
|
|
7f55fd2cad | ||
|
|
822c0434e8 | ||
|
|
76596f42c7 | ||
|
|
f2577265ee | ||
|
|
895c65d518 | ||
|
|
d8cc7a9b50 | ||
|
|
212944d89c | ||
|
|
2b5df331bd | ||
|
|
7ebebc2bc3 | ||
|
|
120ce27894 | ||
|
|
17312a1eec | ||
|
|
f1b4a82015 | ||
|
|
a6c76382e3 | ||
|
|
370ad0aa44 | ||
|
|
b1554782fb | ||
|
|
90c6ff3e41 | ||
|
|
432ce4caa4 | ||
|
|
eb1845e33b | ||
|
|
0981335ca7 | ||
|
|
1a69627102 | ||
|
|
3c0694280f | ||
|
|
e050055c1e | ||
|
|
e7c598e533 | ||
|
|
dd76d54aeb | ||
|
|
1dc31c7f2f | ||
|
|
5cc7c2b6c6 | ||
|
|
dd0e0a10cd | ||
|
|
9fb12b6093 | ||
|
|
5ea5d3c60d | ||
|
|
20ea3af2f0 | ||
|
|
41f2cc6d81 | ||
|
|
d529c60081 | ||
|
|
7d0eb3ba8e | ||
|
|
3c028590b1 | ||
|
|
47c8b852b8 | ||
|
|
c15019dee6 | ||
|
|
09aff23ac9 | ||
|
|
b7707a8a89 | ||
|
|
10ced19841 | ||
|
|
2ef47222f4 | ||
|
|
a95b756111 | ||
|
|
d2615dda63 | ||
|
|
e6f3cd1d56 | ||
|
|
5d32ba5867 | ||
|
|
4b1e9e3b9d | ||
|
|
06c9e55c26 | ||
|
|
613a2f358a | ||
|
|
c3fa300b5c | ||
|
|
0db6fc4ffb | ||
|
|
9211b4d421 | ||
|
|
ab33af3f73 | ||
|
|
dfc1712043 | ||
|
|
87eaab15b5 | ||
|
|
90af3d295b | ||
|
|
107dea085c |
14
.github/workflows/linux.yml
vendored
@@ -95,7 +95,7 @@ jobs:
|
||||
libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \
|
||||
autoconf automake build-essential libxml2-dev libass-dev libfreetype6-dev \
|
||||
libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \
|
||||
libvorbis-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \
|
||||
libvorbis-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-screensaver0-dev \
|
||||
libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libatspi2.0-dev \
|
||||
libxcb-render-util0-dev libxcb-util0-dev libxcb-xkb-dev libxrender-dev \
|
||||
libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libegl1-mesa-dev \
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/opus
|
||||
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
|
||||
@@ -202,7 +202,7 @@ jobs:
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg-cache
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
|
||||
- name: OpenSSL cache.
|
||||
id: cache-openssl
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openssl-cache
|
||||
key: ${{ runner.OS }}-${{ env.OPENSSL_VER }}-${{ env.CACHE_KEY }}
|
||||
@@ -369,7 +369,7 @@ jobs:
|
||||
git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \
|
||||
$GIT/openssl/openssl $opensslDir
|
||||
cd $opensslDir
|
||||
./config --prefix="$OPENSSL_PREFIX"
|
||||
./config --prefix="$OPENSSL_PREFIX" no-tests
|
||||
make -j$(nproc)
|
||||
sudo make DESTDIR="$LibrariesPath/openssl-cache" install_sw
|
||||
cd ..
|
||||
@@ -412,7 +412,7 @@ jobs:
|
||||
|
||||
- name: Qt 5.12.8 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/qt-cache
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_12_8/*') }}
|
||||
@@ -461,7 +461,7 @@ jobs:
|
||||
|
||||
- name: Breakpad cache.
|
||||
id: cache-breakpad
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/breakpad-cache
|
||||
key: ${{ runner.OS }}-breakpad-${{ env.CACHE_KEY }}
|
||||
|
||||
13
.github/workflows/mac.yml
vendored
@@ -133,7 +133,7 @@ jobs:
|
||||
|
||||
- name: OpenSSL cache.
|
||||
id: cache-openssl
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openssl_${{ env.OPENSSL_VER }}
|
||||
key: ${{ runner.OS }}-${{ env.OPENSSL_VER }}-${{ env.CACHE_KEY }}
|
||||
@@ -147,6 +147,7 @@ jobs:
|
||||
git checkout OpenSSL_"$OPENSSL_VER"-stable
|
||||
./Configure \
|
||||
--prefix=$PREFIX \
|
||||
no-tests \
|
||||
darwin64-x86_64-cc \
|
||||
-static \
|
||||
$MIN_MAC
|
||||
@@ -163,7 +164,7 @@ jobs:
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/opus
|
||||
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
|
||||
@@ -185,7 +186,7 @@ jobs:
|
||||
|
||||
- name: Libiconv cache.
|
||||
id: cache-libiconv
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/${{ env.LIBICONV_VER }}
|
||||
key: ${{ runner.OS }}-${{ env.LIBICONV_VER }}-${{ env.CACHE_KEY }}
|
||||
@@ -206,7 +207,7 @@ jobs:
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg-cache
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}
|
||||
@@ -362,7 +363,7 @@ jobs:
|
||||
|
||||
- name: Crashpad cache.
|
||||
id: cache-crashpad
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/crashpad
|
||||
key: ${{ runner.OS }}-crashpad-${{ env.CACHE_KEY }}-${{ hashFiles('**/crashpad.diff') }}-${{ hashFiles('**/mini_chromium.diff') }}
|
||||
@@ -401,7 +402,7 @@ jobs:
|
||||
|
||||
- name: Qt 5.12.8 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/qt-cache
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }}
|
||||
|
||||
14
.github/workflows/win.yml
vendored
@@ -133,7 +133,7 @@ jobs:
|
||||
|
||||
- name: OpenSSL cache.
|
||||
id: cache-openssl
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openssl_${{ env.OPENSSL_VER }}
|
||||
key: ${{ runner.OS }}-${{ env.CACHE_KEY }}-${{ env.OPENSSL_VER }}
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
git clone %GIT%/openssl/openssl.git openssl_%OPENSSL_VER%
|
||||
cd openssl_%OPENSSL_VER%
|
||||
git checkout OpenSSL_%OPENSSL_VER%-stable
|
||||
perl Configure no-shared debug-VC-WIN32
|
||||
perl Configure no-shared no-tests debug-VC-WIN32
|
||||
nmake
|
||||
mkdir out32.dbg
|
||||
move libcrypto.lib out32.dbg
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
|
||||
- name: OpenAL Soft cache.
|
||||
id: cache-openal
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openal-soft
|
||||
key: ${{ runner.OS }}-openal-soft-${{ env.CACHE_KEY }}
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
|
||||
- name: Breakpad cache.
|
||||
id: cache-breakpad
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/breakpad
|
||||
key: ${{ runner.OS }}-breakpad-${{ env.CACHE_KEY }}-${{ hashFiles('**/breakpad.diff') }}
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/opus
|
||||
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
|
||||
@@ -269,7 +269,7 @@ jobs:
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-2-${{ hashFiles('**/build_ffmpeg_win.sh') }}
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
|
||||
- name: Qt 5.12.8 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/Qt-5.12.8
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }}
|
||||
|
||||
@@ -72,9 +72,7 @@ if (LINUX)
|
||||
PRIVATE
|
||||
desktop-app::external_materialdecoration
|
||||
desktop-app::external_nimf_qt5
|
||||
desktop-app::external_qt5ct
|
||||
desktop-app::external_qt5ct_style
|
||||
desktop-app::external_qt5ct_qtplugin
|
||||
desktop-app::external_qt5ct_support
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
@@ -106,6 +104,7 @@ PRIVATE
|
||||
tdesktop::lib_mtproto
|
||||
tdesktop::lib_scheme
|
||||
tdesktop::lib_export
|
||||
tdesktop::lib_tgvoip
|
||||
desktop-app::lib_base
|
||||
desktop-app::lib_crl
|
||||
desktop-app::lib_ui
|
||||
@@ -122,11 +121,37 @@ PRIVATE
|
||||
desktop-app::external_qr_code_generator
|
||||
desktop-app::external_crash_reports
|
||||
desktop-app::external_auto_updates
|
||||
tdesktop::lib_tgvoip
|
||||
desktop-app::external_openssl
|
||||
desktop-app::external_openal
|
||||
)
|
||||
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
|
||||
|
||||
target_include_directories(Telegram
|
||||
PRIVATE
|
||||
${WAYLAND_CLIENT_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(XCB_SCREENSAVER REQUIRED IMPORTED_TARGET xcb-screensaver)
|
||||
pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
PkgConfig::XCB_SCREENSAVER
|
||||
PkgConfig::XCB
|
||||
)
|
||||
else()
|
||||
target_link_static_libraries(Telegram PRIVATE xcb-screensaver)
|
||||
target_link_libraries(Telegram PRIVATE xcb)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX AND NOT TDESKTOP_DISABLE_GTK_INTEGRATION)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
target_compile_options(Telegram PRIVATE -Wno-register)
|
||||
@@ -156,38 +181,30 @@ if (LINUX AND NOT TDESKTOP_DISABLE_GTK_INTEGRATION)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Telegram uses long atomic types, so on some architectures libatomic is needed.
|
||||
check_cxx_source_compiles("
|
||||
#include <atomic>
|
||||
std::atomic_int64_t foo;
|
||||
int main() {return foo;}
|
||||
" HAVE_LONG_ATOMIC_WITHOUT_LIB)
|
||||
if (NOT HAVE_LONG_ATOMIC_WITHOUT_LIB)
|
||||
target_link_libraries(Telegram PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
Threads::Threads
|
||||
)
|
||||
endif()
|
||||
|
||||
target_precompile_headers(Telegram PRIVATE ${src_loc}/stdafx.h)
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
${style_files}
|
||||
|
||||
api/api_bot.cpp
|
||||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_common.h
|
||||
api/api_editing.cpp
|
||||
api/api_editing.h
|
||||
api/api_global_privacy.cpp
|
||||
api/api_global_privacy.h
|
||||
api/api_hash.cpp
|
||||
api/api_hash.h
|
||||
api/api_media.cpp
|
||||
api/api_media.h
|
||||
api/api_self_destruct.cpp
|
||||
api/api_self_destruct.h
|
||||
api/api_send_progress.cpp
|
||||
api/api_send_progress.h
|
||||
api/api_sending.cpp
|
||||
api/api_sending.h
|
||||
api/api_sensitive_content.cpp
|
||||
@@ -569,6 +586,8 @@ PRIVATE
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
history/view/history_view_top_bar_widget.h
|
||||
history/view/history_view_webpage_preview.cpp
|
||||
history/view/history_view_webpage_preview.h
|
||||
history/history.cpp
|
||||
history/history.h
|
||||
history/history_drag_area.cpp
|
||||
@@ -723,6 +742,8 @@ PRIVATE
|
||||
media/audio/media_audio_track.h
|
||||
media/audio/media_child_ffmpeg_loader.cpp
|
||||
media/audio/media_child_ffmpeg_loader.h
|
||||
media/audio/media_openal_functions.cpp
|
||||
media/audio/media_openal_functions.h
|
||||
media/clip/media_clip_check_streaming.cpp
|
||||
media/clip/media_clip_check_streaming.h
|
||||
media/clip/media_clip_ffmpeg.cpp
|
||||
@@ -833,6 +854,8 @@ PRIVATE
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_libs.cpp
|
||||
platform/linux/linux_libs.h
|
||||
platform/linux/linux_xlib_helper.cpp
|
||||
platform/linux/linux_xlib_helper.h
|
||||
platform/linux/file_utilities_linux.cpp
|
||||
platform/linux/file_utilities_linux.h
|
||||
platform/linux/launcher_linux.cpp
|
||||
@@ -858,8 +881,24 @@ PRIVATE
|
||||
platform/mac/specific_mac_p.h
|
||||
platform/mac/window_title_mac.mm
|
||||
platform/mac/window_title_mac.h
|
||||
platform/mac/mac_touchbar.h
|
||||
platform/mac/mac_touchbar.mm
|
||||
platform/mac/touchbar/items/mac_formatter_item.h
|
||||
platform/mac/touchbar/items/mac_formatter_item.mm
|
||||
platform/mac/touchbar/items/mac_pinned_chats_item.h
|
||||
platform/mac/touchbar/items/mac_pinned_chats_item.mm
|
||||
platform/mac/touchbar/items/mac_scrubber_item.h
|
||||
platform/mac/touchbar/items/mac_scrubber_item.mm
|
||||
platform/mac/touchbar/mac_touchbar_audio.h
|
||||
platform/mac/touchbar/mac_touchbar_audio.mm
|
||||
platform/mac/touchbar/mac_touchbar_common.h
|
||||
platform/mac/touchbar/mac_touchbar_common.mm
|
||||
platform/mac/touchbar/mac_touchbar_controls.h
|
||||
platform/mac/touchbar/mac_touchbar_controls.mm
|
||||
platform/mac/touchbar/mac_touchbar_main.h
|
||||
platform/mac/touchbar/mac_touchbar_main.mm
|
||||
platform/mac/touchbar/mac_touchbar_manager.h
|
||||
platform/mac/touchbar/mac_touchbar_manager.mm
|
||||
platform/mac/touchbar/mac_touchbar_media_view.h
|
||||
platform/mac/touchbar/mac_touchbar_media_view.mm
|
||||
platform/win/audio_win.cpp
|
||||
platform/win/audio_win.h
|
||||
platform/win/file_utilities_win.cpp
|
||||
@@ -1032,6 +1071,7 @@ PRIVATE
|
||||
window/window_connecting_widget.h
|
||||
window/window_controller.cpp
|
||||
window/window_controller.h
|
||||
window/window_controls_layout.h
|
||||
window/window_filters_menu.cpp
|
||||
window/window_filters_menu.h
|
||||
window/window_history_hider.cpp
|
||||
@@ -1100,9 +1140,7 @@ if (NOT LINUX)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE qt_functions.cpp)
|
||||
else()
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
|
||||
endif()
|
||||
|
||||
@@ -1244,7 +1282,7 @@ PRIVATE
|
||||
TDESKTOP_API_HASH=${TDESKTOP_API_HASH}
|
||||
)
|
||||
|
||||
if (${CMAKE_GENERATOR} MATCHES "(Visual Studio|Xcode)")
|
||||
if (APPLE OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL "" OR NOT "${output_name}" STREQUAL "Telegram")
|
||||
set(output_folder ${CMAKE_BINARY_DIR})
|
||||
elseif (DESKTOP_APP_SPECIAL_TARGET STREQUAL "")
|
||||
set(output_folder ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
|
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 148 B After Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 350 B |
BIN
Telegram/Resources/icons/dialogs_menu_unread.png
Normal file
|
After Width: | Height: | Size: 146 B |
BIN
Telegram/Resources/icons/dialogs_menu_unread@2x.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
Telegram/Resources/icons/dialogs_menu_unread@3x.png
Normal file
|
After Width: | Height: | Size: 386 B |
BIN
Telegram/Resources/icons/dialogs_menu_unread_dot.png
Normal file
|
After Width: | Height: | Size: 170 B |
BIN
Telegram/Resources/icons/dialogs_menu_unread_dot@2x.png
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
Telegram/Resources/icons/dialogs_menu_unread_dot@3x.png
Normal file
|
After Width: | Height: | Size: 488 B |
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_menu_back" = "Back";
|
||||
"lng_menu_night_mode" = "Night Mode";
|
||||
"lng_menu_add_account" = "Add Account";
|
||||
"lng_menu_activate" = "Activate";
|
||||
|
||||
"lng_disable_notifications_from_tray" = "Disable notifications";
|
||||
"lng_enable_notifications_from_tray" = "Enable notifications";
|
||||
@@ -291,6 +292,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_empty_bio" = "None";
|
||||
|
||||
"lng_settings_section_notify" = "Notifications";
|
||||
"lng_settings_show_from" = "Show notifications from";
|
||||
"lng_settings_notify_all" = "All accounts";
|
||||
"lng_settings_notify_all_about" = "Turn this off if you want to receive notifications only from the account you are currently using.";
|
||||
"lng_settings_notify_title" = "Notifications for chats";
|
||||
"lng_settings_desktop_notify" = "Desktop notifications";
|
||||
"lng_settings_show_name" = "Show sender's name";
|
||||
@@ -334,6 +338,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_update_fail" = "Update check failed :(";
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_native_frame" = "Use system window frame";
|
||||
"lng_settings_auto_start" = "Launch Telegram when system starts";
|
||||
"lng_settings_start_min" = "Launch minimized";
|
||||
"lng_settings_add_sendto" = "Place Telegram in \"Send to\" menu";
|
||||
@@ -419,6 +424,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_passcode_title" = "Local passcode";
|
||||
"lng_settings_password_title" = "Two-step verification";
|
||||
"lng_settings_sessions_title" = "Active sessions";
|
||||
"lng_settings_new_unknown" = "New chats from unknown users";
|
||||
"lng_settings_auto_archive" = "Archive and Mute";
|
||||
"lng_settings_auto_archive_about" = "Automatically archive and mute new chats, groups and channels from non-contacts.";
|
||||
"lng_settings_destroy_title" = "Delete my account";
|
||||
"lng_settings_network_proxy" = "Network and proxy";
|
||||
"lng_settings_version_info" = "Version and updates";
|
||||
@@ -429,6 +437,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
|
||||
"lng_settings_auto_night_mode" = "Auto-Night mode";
|
||||
"lng_settings_auto_night_enabled" = "Match the system settings";
|
||||
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
|
||||
"lng_settings_auto_night_disable" = "Disable";
|
||||
|
||||
"lng_suggest_hide_new_title" = "Hide new chats?";
|
||||
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
|
||||
"lng_suggest_hide_new_to_settings" = "Go to Settings";
|
||||
|
||||
"lng_settings_spellchecker" = "Spell checker";
|
||||
"lng_settings_system_spellchecker" = "Use system spell checker";
|
||||
"lng_settings_custom_spellchecker" = "Use spell checker";
|
||||
@@ -1122,6 +1139,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_group_invite_members#one" = "{count} member, among them:";
|
||||
"lng_group_invite_members#other" = "{count} members, among them:";
|
||||
"lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content.";
|
||||
|
||||
"lng_group_invite_create" = "Create an invite link";
|
||||
"lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link.";
|
||||
@@ -1276,6 +1294,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_new_contact_share_done" = "{user} can now see your phone number.";
|
||||
"lng_new_contact_add_name" = "Add {user} to contacts";
|
||||
"lng_new_contact_add_done" = "{user} is now in your contact list.";
|
||||
"lng_new_contact_unarchive" = "Unarchive";
|
||||
"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}";
|
||||
"lng_cant_more_info" = "More info »";
|
||||
|
||||
@@ -62,10 +62,9 @@ inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector<
|
||||
inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
|
||||
inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
|
||||
inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
|
||||
inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
|
||||
inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
|
||||
inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
|
||||
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
@@ -75,7 +74,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> s
|
||||
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
|
||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
|
||||
inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto;
|
||||
inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
|
||||
|
||||
inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
|
||||
@@ -113,7 +112,7 @@ userEmpty#200250ba id:int = User;
|
||||
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
|
||||
|
||||
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
|
||||
userProfilePhoto#ecd75d8c photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
|
||||
userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
|
||||
|
||||
userStatusEmpty#9d05049 = UserStatus;
|
||||
userStatusOnline#edb93949 expires:int = UserStatus;
|
||||
@@ -139,7 +138,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?
|
||||
chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
|
||||
|
||||
chatPhotoEmpty#37c1011c = ChatPhoto;
|
||||
chatPhoto#475cdbd5 photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
|
||||
chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
|
||||
|
||||
messageEmpty#83e5de54 id:int = Message;
|
||||
message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
|
||||
@@ -187,7 +186,7 @@ dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer t
|
||||
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;
|
||||
|
||||
photoEmpty#2331b22d id:long = Photo;
|
||||
photo#d07504a5 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> dc_id:int = Photo;
|
||||
photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> video_sizes:flags.1?Vector<VideoSize> dc_id:int = Photo;
|
||||
|
||||
photoSizeEmpty#e17e23c type:string = PhotoSize;
|
||||
photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
|
||||
@@ -213,7 +212,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags
|
||||
|
||||
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
|
||||
|
||||
peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings;
|
||||
peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings;
|
||||
|
||||
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
|
||||
wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
|
||||
@@ -358,6 +357,7 @@ updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
|
||||
updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
|
||||
updateDialogFilters#3504914f = Update;
|
||||
updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update;
|
||||
updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -395,7 +395,7 @@ help.inviteText#18cb9f78 message:string = help.InviteText;
|
||||
|
||||
encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat;
|
||||
encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat;
|
||||
encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
|
||||
encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
|
||||
encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
|
||||
encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat;
|
||||
|
||||
@@ -529,6 +529,7 @@ chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
|
||||
|
||||
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
|
||||
chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
|
||||
chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
|
||||
|
||||
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
|
||||
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
|
||||
@@ -619,11 +620,6 @@ channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector
|
||||
|
||||
help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
|
||||
|
||||
foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif;
|
||||
foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif;
|
||||
|
||||
messages.foundGifs#450a1c0a next_offset:int results:Vector<FoundGif> = messages.FoundGifs;
|
||||
|
||||
messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs;
|
||||
messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
|
||||
|
||||
@@ -1141,7 +1137,17 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
|
||||
help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
|
||||
help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;
|
||||
|
||||
videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize;
|
||||
videoSize#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
|
||||
|
||||
statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster;
|
||||
|
||||
statsGroupTopAdmin#6014f412 user_id:int deleted:int kicked:int banned:int = StatsGroupTopAdmin;
|
||||
|
||||
statsGroupTopInviter#31962a4c user_id:int invitations:int = StatsGroupTopInviter;
|
||||
|
||||
stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector<StatsGroupTopPoster> top_admins:Vector<StatsGroupTopAdmin> top_inviters:Vector<StatsGroupTopInviter> users:Vector<User> = stats.MegagroupStats;
|
||||
|
||||
globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings;
|
||||
|
||||
---functions---
|
||||
|
||||
@@ -1237,6 +1243,8 @@ account.getThemes#285946f8 format:string hash:int = account.Themes;
|
||||
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
|
||||
account.getContentSettings#8b9b4dae = account.ContentSettings;
|
||||
account.getMultiWallPapers#65ad71dc wallpapers:Vector<InputWallPaper> = Vector<WallPaper>;
|
||||
account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings;
|
||||
account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings;
|
||||
|
||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||
@@ -1312,7 +1320,6 @@ messages.migrateChat#15a3b8e3 chat_id:int = Updates;
|
||||
messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector<long> = Bool;
|
||||
messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
|
||||
messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs;
|
||||
messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs;
|
||||
messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
|
||||
messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
|
||||
@@ -1392,7 +1399,7 @@ updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:
|
||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||
|
||||
photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto;
|
||||
photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo;
|
||||
photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo;
|
||||
photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
|
||||
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
|
||||
|
||||
@@ -1425,6 +1432,7 @@ help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo;
|
||||
help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo;
|
||||
help.getPromoData#c0977421 = help.PromoData;
|
||||
help.hidePromoData#1e251c95 peer:InputPeer = Bool;
|
||||
help.dismissSuggestion#77fa99f suggestion:string = Bool;
|
||||
|
||||
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
|
||||
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
|
||||
@@ -1501,5 +1509,6 @@ folders.deleteFolder#1c295881 folder_id:int = Updates;
|
||||
|
||||
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
|
||||
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
|
||||
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
|
||||
|
||||
// LAYER 114
|
||||
// LAYER 116
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.1.14.0" />
|
||||
Version="2.2.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,1,14,0
|
||||
PRODUCTVERSION 2,1,14,0
|
||||
FILEVERSION 2,2,0,0
|
||||
PRODUCTVERSION 2,2,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.1.14.0"
|
||||
VALUE "FileVersion", "2.2.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.1.14.0"
|
||||
VALUE "ProductVersion", "2.2.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,1,14,0
|
||||
PRODUCTVERSION 2,1,14,0
|
||||
FILEVERSION 2,2,0,0
|
||||
PRODUCTVERSION 2,2,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.1.14.0"
|
||||
VALUE "FileVersion", "2.2.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.1.14.0"
|
||||
VALUE "ProductVersion", "2.2.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
117
Telegram/SourceFiles/api/api_bot.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
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 "api/api_bot.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_send_progress.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/toast/toast.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
void SendBotCallbackData(
|
||||
not_null<HistoryItem*> item,
|
||||
int row,
|
||||
int column) {
|
||||
if (!IsServerMsgId(item->id)) {
|
||||
return;
|
||||
}
|
||||
const auto history = item->history();
|
||||
const auto session = &history->session();
|
||||
const auto owner = &history->owner();
|
||||
const auto api = &session->api();
|
||||
const auto bot = item->getMessageBot();
|
||||
const auto fullId = item->fullId();
|
||||
const auto getButton = [=] {
|
||||
return HistoryMessageMarkupButton::Get(
|
||||
owner,
|
||||
fullId,
|
||||
row,
|
||||
column);
|
||||
};
|
||||
const auto button = getButton();
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
const auto isGame = (button->type == ButtonType::Game);
|
||||
|
||||
auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0);
|
||||
QByteArray sendData;
|
||||
if (isGame) {
|
||||
flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_game;
|
||||
} else if (button->type == ButtonType::Callback) {
|
||||
flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_data;
|
||||
sendData = button->data;
|
||||
}
|
||||
button->requestId = api->request(MTPmessages_GetBotCallbackAnswer(
|
||||
MTP_flags(flags),
|
||||
history->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_bytes(sendData)
|
||||
)).done([=](const MTPmessages_BotCallbackAnswer &result) {
|
||||
const auto item = owner->message(fullId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (const auto button = getButton()) {
|
||||
button->requestId = 0;
|
||||
owner->requestItemRepaint(item);
|
||||
}
|
||||
result.match([&](const MTPDmessages_botCallbackAnswer &data) {
|
||||
if (const auto message = data.vmessage()) {
|
||||
if (data.is_alert()) {
|
||||
Ui::show(Box<InformBox>(qs(*message)));
|
||||
} else {
|
||||
Ui::Toast::Show(qs(*message));
|
||||
}
|
||||
} else if (const auto url = data.vurl()) {
|
||||
const auto link = qs(*url);
|
||||
if (!isGame) {
|
||||
UrlClickHandler::Open(link);
|
||||
return;
|
||||
}
|
||||
const auto scoreLink = AppendShareGameScoreUrl(
|
||||
session,
|
||||
link,
|
||||
item->fullId());
|
||||
BotGameUrlClickHandler(bot, scoreLink).onClick({});
|
||||
session->sendProgressManager().update(
|
||||
history,
|
||||
Api::SendProgressType::PlayGame);
|
||||
}
|
||||
});
|
||||
}).fail([=](const RPCError &error) {
|
||||
const auto item = owner->message(fullId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
// Show error?
|
||||
if (const auto button = getButton()) {
|
||||
button->requestId = 0;
|
||||
owner->requestItemRepaint(item);
|
||||
}
|
||||
}).send();
|
||||
|
||||
session->changes().messageUpdated(
|
||||
item,
|
||||
Data::MessageUpdate::Flag::BotCallbackSent
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
19
Telegram/SourceFiles/api/api_bot.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Api {
|
||||
|
||||
void SendBotCallbackData(
|
||||
not_null<HistoryItem*> item,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
} // namespace Api
|
||||
230
Telegram/SourceFiles/api/api_chat_invite.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
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 "api/api_chat_invite.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
void CheckChatInvite(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash,
|
||||
ChannelData *invitePeekChannel) {
|
||||
const auto session = &controller->session();
|
||||
const auto weak = base::make_weak(controller.get());
|
||||
session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
|
||||
Core::App().hideMediaView();
|
||||
result.match([=](const MTPDchatInvite &data) {
|
||||
const auto box = Ui::show(Box<ConfirmInviteBox>(
|
||||
session,
|
||||
data,
|
||||
invitePeekChannel,
|
||||
[=] { session->api().importChatInvite(hash); }));
|
||||
if (invitePeekChannel) {
|
||||
box->boxClosing(
|
||||
) | rpl::filter([=] {
|
||||
return !invitePeekChannel->amIn();
|
||||
}) | rpl::start_with_next([=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->clearSectionStack(Window::SectionShow(
|
||||
Window::SectionShow::Way::ClearStack,
|
||||
anim::type::normal,
|
||||
anim::activation::background));
|
||||
}
|
||||
}, box->lifetime());
|
||||
}
|
||||
}, [=](const MTPDchatInviteAlready &data) {
|
||||
if (const auto chat = session->data().processChat(data.vchat())) {
|
||||
if (const auto channel = chat->asChannel()) {
|
||||
channel->clearInvitePeek();
|
||||
}
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showPeerHistory(
|
||||
chat,
|
||||
Window::SectionShow::Way::Forward);
|
||||
}
|
||||
}
|
||||
}, [=](const MTPDchatInvitePeek &data) {
|
||||
if (const auto chat = session->data().processChat(data.vchat())) {
|
||||
if (const auto channel = chat->asChannel()) {
|
||||
channel->setInvitePeek(hash, data.vexpires().v);
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showPeerHistory(
|
||||
chat,
|
||||
Window::SectionShow::Way::Forward);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [=](const RPCError &error) {
|
||||
if (error.code() != 400) {
|
||||
return;
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
ConfirmInviteBox::ConfirmInviteBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data,
|
||||
ChannelData *invitePeekChannel,
|
||||
Fn<void()> submit)
|
||||
: _session(session)
|
||||
, _submit(std::move(submit))
|
||||
, _title(this, st::confirmInviteTitle)
|
||||
, _status(this, st::confirmInviteStatus)
|
||||
, _participants(GetParticipants(_session, data))
|
||||
, _isChannel(data.is_channel() && !data.is_megagroup()) {
|
||||
const auto title = qs(data.vtitle());
|
||||
const auto count = data.vparticipants_count().v;
|
||||
const auto status = [&] {
|
||||
return invitePeekChannel
|
||||
? tr::lng_channel_invite_private(tr::now)
|
||||
: (!_participants.empty() && _participants.size() < count)
|
||||
? tr::lng_group_invite_members(tr::now, lt_count, count)
|
||||
: (count > 0)
|
||||
? tr::lng_chat_status_members(tr::now, lt_count_decimal, count)
|
||||
: _isChannel
|
||||
? tr::lng_channel_status(tr::now)
|
||||
: tr::lng_group_status(tr::now);
|
||||
}();
|
||||
_title->setText(title);
|
||||
_status->setText(status);
|
||||
|
||||
const auto photo = _session->data().processPhoto(data.vphoto());
|
||||
if (!photo->isNull()) {
|
||||
_photo = photo->createMediaView();
|
||||
_photo->wanted(Data::PhotoSize::Small, Data::FileOrigin());
|
||||
if (!_photo->image(Data::PhotoSize::Small)) {
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
} else {
|
||||
_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||
Data::PeerUserpicColor(0),
|
||||
title);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmInviteBox::~ConfirmInviteBox() = default;
|
||||
|
||||
auto ConfirmInviteBox::GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data)
|
||||
-> std::vector<Participant> {
|
||||
const auto participants = data.vparticipants();
|
||||
if (!participants) {
|
||||
return {};
|
||||
}
|
||||
const auto &v = participants->v;
|
||||
auto result = std::vector<Participant>();
|
||||
result.reserve(v.size());
|
||||
for (const auto &participant : v) {
|
||||
if (const auto user = session->data().processUser(participant)) {
|
||||
result.push_back(Participant{ user });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::prepare() {
|
||||
addButton(
|
||||
(_isChannel
|
||||
? tr::lng_profile_join_channel()
|
||||
: tr::lng_profile_join_group()),
|
||||
_submit);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
while (_participants.size() > 4) {
|
||||
_participants.pop_back();
|
||||
}
|
||||
|
||||
auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom();
|
||||
if (!_participants.empty()) {
|
||||
int skip = (st::boxWideWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
|
||||
int padding = skip / 2;
|
||||
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (st::boxWideWidth - sumWidth) / 2;
|
||||
for (const auto &participant : _participants) {
|
||||
auto name = new Ui::FlatLabel(this, st::confirmInviteUserName);
|
||||
name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
|
||||
name->setText(participant.user->firstName.isEmpty()
|
||||
? participant.user->name
|
||||
: participant.user->firstName);
|
||||
name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
|
||||
left += _userWidth;
|
||||
}
|
||||
|
||||
newHeight += st::confirmInviteUserHeight;
|
||||
}
|
||||
setDimensions(st::boxWideWidth, newHeight);
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop);
|
||||
_status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop);
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
if (_photo) {
|
||||
if (const auto image = _photo->image(Data::PhotoSize::Small)) {
|
||||
p.drawPixmap(
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
image->pixCircled(
|
||||
st::confirmInvitePhotoSize,
|
||||
st::confirmInvitePhotoSize));
|
||||
}
|
||||
} else if (_photoEmpty) {
|
||||
_photoEmpty->paint(
|
||||
p,
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
width(),
|
||||
st::confirmInvitePhotoSize);
|
||||
}
|
||||
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (width() - sumWidth) / 2;
|
||||
for (auto &participant : _participants) {
|
||||
participant.user->paintUserpicLeft(
|
||||
p,
|
||||
participant.userpic,
|
||||
left + (_userWidth - st::confirmInviteUserPhotoSize) / 2,
|
||||
st::confirmInviteUserPhotoTop,
|
||||
width(),
|
||||
st::confirmInviteUserPhotoSize);
|
||||
left += _userWidth;
|
||||
}
|
||||
}
|
||||
74
Telegram/SourceFiles/api/api_chat_invite.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/box_content.h"
|
||||
|
||||
class UserData;
|
||||
class ChannelData;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Data {
|
||||
class CloudImageView;
|
||||
class PhotoMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class EmptyUserpic;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
|
||||
void CheckChatInvite(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash,
|
||||
ChannelData *invitePeekChannel = nullptr);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
class ConfirmInviteBox final : public Ui::BoxContent {
|
||||
public:
|
||||
ConfirmInviteBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data,
|
||||
ChannelData *invitePeekChannel,
|
||||
Fn<void()> submit);
|
||||
~ConfirmInviteBox();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
struct Participant {
|
||||
not_null<UserData*> user;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
static std::vector<Participant> GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
Fn<void()> _submit;
|
||||
object_ptr<Ui::FlatLabel> _title;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
|
||||
std::vector<Participant> _participants;
|
||||
bool _isChannel = false;
|
||||
|
||||
int _userWidth = 0;
|
||||
|
||||
};
|
||||
213
Telegram/SourceFiles/api/api_editing.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
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 "api/api_editing.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_media.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_rpc_sender.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
using namespace rpl::details;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto WithId =
|
||||
is_callable_plain_v<T, const MTPUpdates &, Fn<void()>, mtpRequestId>;
|
||||
template <typename T>
|
||||
constexpr auto WithoutId =
|
||||
is_callable_plain_v<T, const MTPUpdates &, Fn<void()>>;
|
||||
template <typename T>
|
||||
constexpr auto WithoutCallback =
|
||||
is_callable_plain_v<T, const MTPUpdates &>;
|
||||
|
||||
template <typename DoneCallback, typename FailCallback>
|
||||
mtpRequestId EditMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &textWithEntities,
|
||||
SendOptions options,
|
||||
DoneCallback &&done,
|
||||
FailCallback &&fail,
|
||||
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
|
||||
const auto session = &item->history()->session();
|
||||
const auto api = &session->api();
|
||||
|
||||
const auto text = textWithEntities.text;
|
||||
const auto sentEntities = EntitiesToMTP(
|
||||
session,
|
||||
textWithEntities.entities,
|
||||
ConvertOption::SkipLocal);
|
||||
const auto media = item->media();
|
||||
|
||||
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
||||
const auto flags = emptyFlag
|
||||
| (!text.isEmpty() || media
|
||||
? MTPmessages_EditMessage::Flag::f_message
|
||||
: emptyFlag)
|
||||
| ((media && inputMedia.has_value())
|
||||
? MTPmessages_EditMessage::Flag::f_media
|
||||
: emptyFlag)
|
||||
| (options.removeWebPageId
|
||||
? MTPmessages_EditMessage::Flag::f_no_webpage
|
||||
: emptyFlag)
|
||||
| (!sentEntities.v.isEmpty()
|
||||
? MTPmessages_EditMessage::Flag::f_entities
|
||||
: emptyFlag)
|
||||
| (options.scheduled
|
||||
? MTPmessages_EditMessage::Flag::f_schedule_date
|
||||
: emptyFlag);
|
||||
|
||||
const auto id = item->isScheduled()
|
||||
? session->data().scheduledMessages().lookupId(item)
|
||||
: item->id;
|
||||
return api->request(MTPmessages_EditMessage(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_string(text),
|
||||
inputMedia.value_or(MTPInputMedia()),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled)
|
||||
)).done([=](
|
||||
const MTPUpdates &result,
|
||||
[[maybe_unused]] mtpRequestId requestId) {
|
||||
const auto apply = [=] { api->applyUpdates(result); };
|
||||
|
||||
if constexpr (WithId<DoneCallback>) {
|
||||
done(result, apply, requestId);
|
||||
} else if constexpr (WithoutId<DoneCallback>) {
|
||||
done(result, apply);
|
||||
} else if constexpr (WithoutCallback<DoneCallback>) {
|
||||
done(result);
|
||||
apply();
|
||||
} else {
|
||||
apply();
|
||||
}
|
||||
}).fail(
|
||||
fail
|
||||
).send();
|
||||
}
|
||||
|
||||
template <typename DoneCallback, typename FailCallback>
|
||||
mtpRequestId EditMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
SendOptions options,
|
||||
DoneCallback &&done,
|
||||
FailCallback &&fail,
|
||||
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
|
||||
const auto &text = item->originalText();
|
||||
return EditMessage(
|
||||
item,
|
||||
text,
|
||||
options,
|
||||
std::forward<DoneCallback>(done),
|
||||
std::forward<FailCallback>(fail),
|
||||
inputMedia);
|
||||
}
|
||||
|
||||
void EditMessageWithUploadedMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
SendOptions options,
|
||||
MTPInputMedia media) {
|
||||
const auto done = [=](const auto &result, Fn<void()> applyUpdates) {
|
||||
if (item) {
|
||||
item->clearSavedMedia();
|
||||
item->setIsLocalUpdateMedia(true);
|
||||
applyUpdates();
|
||||
item->setIsLocalUpdateMedia(false);
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const RPCError &error) {
|
||||
const auto err = error.type();
|
||||
const auto session = &item->history()->session();
|
||||
const auto notModified = (err == u"MESSAGE_NOT_MODIFIED"_q);
|
||||
const auto mediaInvalid = (err == u"MEDIA_NEW_INVALID"_q);
|
||||
if (notModified || mediaInvalid) {
|
||||
item->returnSavedMedia();
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
if (mediaInvalid) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
} else {
|
||||
session->api().sendMessageFail(error, item->history()->peer);
|
||||
}
|
||||
};
|
||||
|
||||
EditMessage(item, options, done, fail, media);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
SendOptions options) {
|
||||
const auto empty = [](const auto &r) {};
|
||||
EditMessage(item, options, empty, empty);
|
||||
}
|
||||
|
||||
void EditMessageWithUploadedDocument(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
SendOptions options) {
|
||||
if (!item || !item->media() || !item->media()->document()) {
|
||||
return;
|
||||
}
|
||||
const auto media = PrepareUploadedDocument(item, file, thumb);
|
||||
EditMessageWithUploadedMedia(item, options, media);
|
||||
}
|
||||
|
||||
void EditMessageWithUploadedPhoto(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
SendOptions options) {
|
||||
if (!item || !item->media() || !item->media()->photo()) {
|
||||
return;
|
||||
}
|
||||
const auto media = PrepareUploadedPhoto(file);
|
||||
EditMessageWithUploadedMedia(item, options, media);
|
||||
}
|
||||
|
||||
mtpRequestId EditCaption(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &caption,
|
||||
SendOptions options,
|
||||
Fn<void(const MTPUpdates &)> done,
|
||||
Fn<void(const RPCError &)> fail) {
|
||||
return EditMessage(item, caption, options, done, fail);
|
||||
}
|
||||
|
||||
mtpRequestId EditTextMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &caption,
|
||||
SendOptions options,
|
||||
Fn<void(const MTPUpdates &, mtpRequestId requestId)> done,
|
||||
Fn<void(const RPCError &, mtpRequestId requestId)> fail) {
|
||||
const auto callback = [=](
|
||||
const auto &result,
|
||||
Fn<void()> applyUpdates,
|
||||
auto id) {
|
||||
applyUpdates();
|
||||
done(result, id);
|
||||
};
|
||||
return EditMessage(item, caption, options, callback, fail);
|
||||
}
|
||||
|
||||
|
||||
} // namespace Api
|
||||
52
Telegram/SourceFiles/api/api_editing.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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
|
||||
|
||||
class HistoryItem;
|
||||
class RPCError;
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct SendOptions;
|
||||
|
||||
const auto kDefaultEditMessagesErrors = {
|
||||
u"MESSAGE_ID_INVALID"_q,
|
||||
u"CHAT_ADMIN_REQUIRED"_q,
|
||||
u"MESSAGE_EDIT_TIME_EXPIRED"_q,
|
||||
};
|
||||
|
||||
void RescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
SendOptions options);
|
||||
|
||||
void EditMessageWithUploadedDocument(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
SendOptions options);
|
||||
|
||||
void EditMessageWithUploadedPhoto(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
SendOptions options);
|
||||
|
||||
mtpRequestId EditCaption(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &caption,
|
||||
SendOptions options,
|
||||
Fn<void(const MTPUpdates &)> done,
|
||||
Fn<void(const RPCError &)> fail);
|
||||
|
||||
mtpRequestId EditTextMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &caption,
|
||||
SendOptions options,
|
||||
Fn<void(const MTPUpdates &, mtpRequestId requestId)> done,
|
||||
Fn<void(const RPCError &, mtpRequestId requestId)> fail);
|
||||
|
||||
} // namespace Api
|
||||
103
Telegram/SourceFiles/api/api_global_privacy.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
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 "api/api_global_privacy.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void GlobalPrivacy::reload(Fn<void()> callback) {
|
||||
if (callback) {
|
||||
_callbacks.push_back(std::move(callback));
|
||||
}
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPaccount_GetGlobalPrivacySettings(
|
||||
)).done([=](const MTPGlobalPrivacySettings &result) {
|
||||
_requestId = 0;
|
||||
apply(result);
|
||||
for (const auto &callback : base::take(_callbacks)) {
|
||||
callback();
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_requestId = 0;
|
||||
for (const auto &callback : base::take(_callbacks)) {
|
||||
callback();
|
||||
}
|
||||
}).send();
|
||||
|
||||
_session->account().appConfig().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
_showArchiveAndMute = _session->account().appConfig().get<bool>(
|
||||
u"autoarchive_setting_available"_q,
|
||||
false);
|
||||
}, _session->lifetime());
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::archiveAndMuteCurrent() const {
|
||||
return _archiveAndMute.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::archiveAndMute() const {
|
||||
return _archiveAndMute.value();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::showArchiveAndMute() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
return rpl::combine(
|
||||
archiveAndMute(),
|
||||
_showArchiveAndMute.value(),
|
||||
_1 || _2);
|
||||
}
|
||||
|
||||
rpl::producer<> GlobalPrivacy::suggestArchiveAndMute() const {
|
||||
return _session->account().appConfig().suggestionRequested(
|
||||
u"AUTOARCHIVE_POPULAR"_q);
|
||||
}
|
||||
|
||||
void GlobalPrivacy::dismissArchiveAndMuteSuggestion() {
|
||||
_session->account().appConfig().dismissSuggestion(
|
||||
u"AUTOARCHIVE_POPULAR"_q);
|
||||
}
|
||||
|
||||
void GlobalPrivacy::update(bool archiveAndMute) {
|
||||
using Flag = MTPDglobalPrivacySettings::Flag;
|
||||
|
||||
_api.request(_requestId).cancel();
|
||||
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
|
||||
MTP_globalPrivacySettings(
|
||||
MTP_flags(Flag::f_archive_and_mute_new_noncontact_peers),
|
||||
MTP_bool(archiveAndMute))
|
||||
)).done([=](const MTPGlobalPrivacySettings &result) {
|
||||
_requestId = 0;
|
||||
apply(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
_archiveAndMute = archiveAndMute;
|
||||
}
|
||||
|
||||
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
|
||||
data.match([&](const MTPDglobalPrivacySettings &data) {
|
||||
_archiveAndMute = data.varchive_and_mute_new_noncontact_peers()
|
||||
? mtpIsTrue(*data.varchive_and_mute_new_noncontact_peers())
|
||||
: false;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
45
Telegram/SourceFiles/api/api_global_privacy.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class GlobalPrivacy final {
|
||||
public:
|
||||
explicit GlobalPrivacy(not_null<ApiWrap*> api);
|
||||
|
||||
void reload(Fn<void()> callback = nullptr);
|
||||
void update(bool archiveAndMute);
|
||||
|
||||
[[nodiscard]] bool archiveAndMuteCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> archiveAndMute() const;
|
||||
[[nodiscard]] rpl::producer<bool> showArchiveAndMute() const;
|
||||
[[nodiscard]] rpl::producer<> suggestArchiveAndMute() const;
|
||||
void dismissArchiveAndMuteSuggestion();
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _requestId = 0;
|
||||
rpl::variable<bool> _archiveAndMute = false;
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
107
Telegram/SourceFiles/api/api_media.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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 "api/api_media.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "history/history_item.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto filenameAttribute = MTP_documentAttributeFilename(
|
||||
MTP_string(document->filename()));
|
||||
const auto dimensions = document->dimensions;
|
||||
auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
|
||||
if (dimensions.width() > 0 && dimensions.height() > 0) {
|
||||
const auto duration = document->getDuration();
|
||||
if (duration >= 0 && !document->hasMimeType(qstr("image/gif"))) {
|
||||
auto flags = MTPDdocumentAttributeVideo::Flags(0);
|
||||
using VideoFlag = MTPDdocumentAttributeVideo::Flag;
|
||||
if (document->isVideoMessage()) {
|
||||
flags |= VideoFlag::f_round_message;
|
||||
}
|
||||
if (document->supportsStreaming()) {
|
||||
flags |= VideoFlag::f_supports_streaming;
|
||||
}
|
||||
attributes.push_back(MTP_documentAttributeVideo(
|
||||
MTP_flags(flags),
|
||||
MTP_int(duration),
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
}
|
||||
}
|
||||
if (document->type == AnimatedDocument) {
|
||||
attributes.push_back(MTP_documentAttributeAnimated());
|
||||
} else if (document->type == StickerDocument && document->sticker()) {
|
||||
attributes.push_back(MTP_documentAttributeSticker(
|
||||
MTP_flags(0),
|
||||
MTP_string(document->sticker()->alt),
|
||||
document->sticker()->set,
|
||||
MTPMaskCoords()));
|
||||
} else if (const auto song = document->song()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_title
|
||||
| MTPDdocumentAttributeAudio::Flag::f_performer;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(song->duration),
|
||||
MTP_string(song->title),
|
||||
MTP_string(song->performer),
|
||||
MTPstring()));
|
||||
} else if (const auto voice = document->voice()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_voice
|
||||
| MTPDdocumentAttributeAudio::Flag::f_waveform;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(voice->duration),
|
||||
MTPstring(),
|
||||
MTPstring(),
|
||||
MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
|
||||
}
|
||||
return MTP_vector<MTPDocumentAttribute>(attributes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPInputMedia PrepareUploadedPhoto(const MTPInputFile &file) {
|
||||
return MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
file,
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb) {
|
||||
if (!item || !item->media() || !item->media()->document()) {
|
||||
return MTP_inputMediaEmpty();
|
||||
}
|
||||
const auto emptyFlag = MTPDinputMediaUploadedDocument::Flags(0);
|
||||
using DocFlags = MTPDinputMediaUploadedDocument::Flag;
|
||||
const auto flags = emptyFlag
|
||||
| (thumb ? DocFlags::f_thumb : emptyFlag)
|
||||
| (item->groupId() ? DocFlags::f_nosound_video : emptyFlag);
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
file,
|
||||
thumb.value_or(MTPInputFile()),
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
21
Telegram/SourceFiles/api/api_media.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
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
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Api {
|
||||
|
||||
MTPInputMedia PrepareUploadedPhoto(const MTPInputFile &file);
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb);
|
||||
|
||||
} // namespace Api
|
||||
108
Telegram/SourceFiles/api/api_send_progress.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
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 "api/api_send_progress.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCancelTypingActionTimeout = crl::time(5000);
|
||||
|
||||
} // namespace
|
||||
|
||||
SendProgressManager::SendProgressManager(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _stopTypingTimer([=] { cancelTyping(base::take(_stopTypingHistory)); }) {
|
||||
}
|
||||
|
||||
void SendProgressManager::cancel(
|
||||
not_null<History*> history,
|
||||
SendProgressType type) {
|
||||
const auto i = _requests.find({ history, type });
|
||||
if (i != _requests.end()) {
|
||||
_session->api().request(i->second).cancel();
|
||||
_requests.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void SendProgressManager::cancelTyping(not_null<History*> history) {
|
||||
_stopTypingTimer.cancel();
|
||||
cancel(history, SendProgressType::Typing);
|
||||
}
|
||||
|
||||
void SendProgressManager::update(
|
||||
not_null<History*> history,
|
||||
SendProgressType type,
|
||||
int32 progress) {
|
||||
const auto peer = history->peer;
|
||||
if (peer->isSelf() || (peer->isChannel() && !peer->isMegagroup())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto doing = (progress >= 0);
|
||||
if (history->mySendActionUpdated(type, doing)) {
|
||||
cancel(history, type);
|
||||
if (doing) {
|
||||
send(history, type, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendProgressManager::send(
|
||||
not_null<History*> history,
|
||||
SendProgressType type,
|
||||
int32 progress) {
|
||||
using Type = SendProgressType;
|
||||
const auto action = [&]() -> MTPsendMessageAction {
|
||||
const auto p = MTP_int(progress);
|
||||
switch (type) {
|
||||
case Type::Typing: return MTP_sendMessageTypingAction();
|
||||
case Type::RecordVideo: return MTP_sendMessageRecordVideoAction();
|
||||
case Type::UploadVideo: return MTP_sendMessageUploadVideoAction(p);
|
||||
case Type::RecordVoice: return MTP_sendMessageRecordAudioAction();
|
||||
case Type::UploadVoice: return MTP_sendMessageUploadAudioAction(p);
|
||||
case Type::RecordRound: return MTP_sendMessageRecordRoundAction();
|
||||
case Type::UploadRound: return MTP_sendMessageUploadRoundAction(p);
|
||||
case Type::UploadPhoto: return MTP_sendMessageUploadPhotoAction(p);
|
||||
case Type::UploadFile: return MTP_sendMessageUploadDocumentAction(p);
|
||||
case Type::ChooseLocation: return MTP_sendMessageGeoLocationAction();
|
||||
case Type::ChooseContact: return MTP_sendMessageChooseContactAction();
|
||||
case Type::PlayGame: return MTP_sendMessageGamePlayAction();
|
||||
default: return MTP_sendMessageTypingAction();
|
||||
}
|
||||
}();
|
||||
const auto requestId = _session->api().request(MTPmessages_SetTyping(
|
||||
history->peer->input,
|
||||
action
|
||||
)).done([=](const MTPBool &result, mtpRequestId requestId) {
|
||||
done(result, requestId);
|
||||
}).send();
|
||||
_requests.emplace(Key{ history, type }, requestId);
|
||||
|
||||
if (type == Type::Typing) {
|
||||
_stopTypingHistory = history;
|
||||
_stopTypingTimer.callOnce(kCancelTypingActionTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void SendProgressManager::done(
|
||||
const MTPBool &result,
|
||||
mtpRequestId requestId) {
|
||||
for (auto i = _requests.begin(), e = _requests.end(); i != e; ++i) {
|
||||
if (i->second == requestId) {
|
||||
_requests.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
88
Telegram/SourceFiles/api/api_send_progress.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 "api/api_common.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
enum class SendProgressType {
|
||||
Typing,
|
||||
RecordVideo,
|
||||
UploadVideo,
|
||||
RecordVoice,
|
||||
UploadVoice,
|
||||
RecordRound,
|
||||
UploadRound,
|
||||
UploadPhoto,
|
||||
UploadFile,
|
||||
ChooseLocation,
|
||||
ChooseContact,
|
||||
PlayGame,
|
||||
};
|
||||
|
||||
struct SendProgress {
|
||||
SendProgress(
|
||||
SendProgressType type,
|
||||
crl::time until,
|
||||
int progress = 0)
|
||||
: type(type)
|
||||
, until(until)
|
||||
, progress(progress) {
|
||||
}
|
||||
SendProgressType type = SendProgressType::Typing;
|
||||
crl::time until = 0;
|
||||
int progress = 0;
|
||||
|
||||
};
|
||||
|
||||
class SendProgressManager final {
|
||||
public:
|
||||
SendProgressManager(not_null<Main::Session*> session);
|
||||
|
||||
void update(
|
||||
not_null<History*> history,
|
||||
SendProgressType type,
|
||||
int32 progress = 0);
|
||||
void cancel(
|
||||
not_null<History*> history,
|
||||
SendProgressType type);
|
||||
void cancelTyping(not_null<History*> history);
|
||||
|
||||
private:
|
||||
struct Key {
|
||||
not_null<History*> history;
|
||||
SendProgressType type = SendProgressType();
|
||||
|
||||
inline bool operator<(const Key &other) const {
|
||||
return (history < other.history)
|
||||
|| (history == other.history && type < other.type);
|
||||
}
|
||||
};
|
||||
|
||||
void send(
|
||||
not_null<History*> history,
|
||||
SendProgressType type,
|
||||
int32 progress);
|
||||
void done(const MTPBool &result, mtpRequestId requestId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
base::flat_map<Key, mtpRequestId> _requests;
|
||||
base::Timer _stopTypingTimer;
|
||||
History *_stopTypingHistory = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -129,8 +129,7 @@ void SendExistingMedia(
|
||||
caption,
|
||||
MTPReplyMarkup());
|
||||
|
||||
auto failHandler = std::make_shared<Fn<void(const RPCError&, QByteArray)>>();
|
||||
auto performRequest = [=] {
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
@@ -149,28 +148,25 @@ void SendExistingMedia(
|
||||
api->applyUpdates(result, randomId);
|
||||
finish();
|
||||
}).fail([=](const RPCError &error) {
|
||||
(*failHandler)(error, usedFileReference);
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||
api->refreshFileReference(origin, [=](const auto &result) {
|
||||
if (media->fileReference() != usedFileReference) {
|
||||
repeatRequest(repeatRequest);
|
||||
} else {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
}
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
};
|
||||
*failHandler = [=](const RPCError &error, QByteArray usedFileReference) {
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||
api->refreshFileReference(origin, [=](const auto &result) {
|
||||
if (media->fileReference() != usedFileReference) {
|
||||
performRequest();
|
||||
} else {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
}
|
||||
};
|
||||
performRequest();
|
||||
performRequest(performRequest);
|
||||
|
||||
api->finishForwarding(message.action);
|
||||
}
|
||||
@@ -404,6 +400,8 @@ void SendConfirmedFile(
|
||||
}
|
||||
if (file->to.options.scheduled) {
|
||||
flags |= MTPDmessage::Flag::f_from_scheduled;
|
||||
// Scheduled messages have no the 'edited' badge.
|
||||
flags |= MTPDmessage::Flag::f_edit_hide;
|
||||
} else {
|
||||
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
|
||||
}
|
||||
@@ -527,9 +525,13 @@ void SendConfirmedFile(
|
||||
}
|
||||
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
session->changes().historyUpdated(
|
||||
history,
|
||||
Data::HistoryUpdate::Flag::MessageSent);
|
||||
if (!itemToEdit) {
|
||||
session->changes().historyUpdated(
|
||||
history,
|
||||
(action.options.scheduled
|
||||
? Data::HistoryUpdate::Flag::ScheduledSent
|
||||
: Data::HistoryUpdate::Flag::MessageSent));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -15,6 +15,7 @@ struct FileLoadResult;
|
||||
namespace Api {
|
||||
|
||||
struct MessageToSend;
|
||||
struct SendAction;
|
||||
|
||||
void SendExistingDocument(
|
||||
Api::MessageToSend &&message,
|
||||
|
||||
@@ -1642,15 +1642,21 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
if (auto user = session().data().userLoaded(d.vuser_id().v)) {
|
||||
user->setPhoto(d.vphoto());
|
||||
user->loadUserpic();
|
||||
if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) {
|
||||
// After that update we don't have enough information to
|
||||
// create a 'photo' with all necessary fields. So if
|
||||
// we receive second such update we end up with a 'photo_id'
|
||||
// in user_photos list without a loaded 'photo'.
|
||||
// It fails to show in media overview if you try to open it.
|
||||
//
|
||||
//if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) {
|
||||
session().storage().remove(Storage::UserPhotosRemoveAfter(
|
||||
user->bareId(),
|
||||
user->userpicPhotoId()));
|
||||
} else {
|
||||
session().storage().add(Storage::UserPhotosAddNew(
|
||||
user->bareId(),
|
||||
user->userpicPhotoId()));
|
||||
}
|
||||
//} else {
|
||||
// session().storage().add(Storage::UserPhotosAddNew(
|
||||
// user->bareId(),
|
||||
// user->userpicPhotoId()));
|
||||
//}
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
|
||||
#include "api/api_hash.h"
|
||||
#include "api/api_media.h"
|
||||
#include "api/api_sending.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_self_destruct.h"
|
||||
#include "api/api_sensitive_content.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_drafts.h"
|
||||
@@ -83,9 +85,6 @@ constexpr auto kReloadChannelMembersTimeout = 1000;
|
||||
// Save draft to the cloud with 1 sec extra delay.
|
||||
constexpr auto kSaveCloudDraftTimeout = 1000;
|
||||
|
||||
// Give the app 1.5 secs to save drafts to cloud when quitting.
|
||||
constexpr auto kSaveDraftBeforeQuitTimeout = 1500;
|
||||
|
||||
// Max users in one super group invite request.
|
||||
constexpr auto kMaxUsersPerInvite = 100;
|
||||
|
||||
@@ -114,63 +113,6 @@ using PhotoFileLocationId = Data::PhotoFileLocationId;
|
||||
using DocumentFileLocationId = Data::DocumentFileLocationId;
|
||||
using UpdatedFileReferences = Data::UpdatedFileReferences;
|
||||
|
||||
MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto filenameAttribute = MTP_documentAttributeFilename(
|
||||
MTP_string(document->filename()));
|
||||
const auto dimensions = document->dimensions;
|
||||
auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
|
||||
if (dimensions.width() > 0 && dimensions.height() > 0) {
|
||||
const auto duration = document->getDuration();
|
||||
if (duration >= 0 && !document->hasMimeType(qstr("image/gif"))) {
|
||||
auto flags = MTPDdocumentAttributeVideo::Flags(0);
|
||||
if (document->isVideoMessage()) {
|
||||
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
|
||||
}
|
||||
if (document->supportsStreaming()) {
|
||||
flags |= MTPDdocumentAttributeVideo::Flag::f_supports_streaming;
|
||||
}
|
||||
attributes.push_back(MTP_documentAttributeVideo(
|
||||
MTP_flags(flags),
|
||||
MTP_int(duration),
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
}
|
||||
}
|
||||
if (document->type == AnimatedDocument) {
|
||||
attributes.push_back(MTP_documentAttributeAnimated());
|
||||
} else if (document->type == StickerDocument && document->sticker()) {
|
||||
attributes.push_back(MTP_documentAttributeSticker(
|
||||
MTP_flags(0),
|
||||
MTP_string(document->sticker()->alt),
|
||||
document->sticker()->set,
|
||||
MTPMaskCoords()));
|
||||
} else if (const auto song = document->song()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_title
|
||||
| MTPDdocumentAttributeAudio::Flag::f_performer;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(song->duration),
|
||||
MTP_string(song->title),
|
||||
MTP_string(song->performer),
|
||||
MTPstring()));
|
||||
} else if (const auto voice = document->voice()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_voice
|
||||
| MTPDdocumentAttributeAudio::Flag::f_waveform;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(voice->duration),
|
||||
MTPstring(),
|
||||
MTPstring(),
|
||||
MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
|
||||
}
|
||||
return MTP_vector<MTPDocumentAttribute>(attributes);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPInputPrivacyKey ApiWrap::Privacy::Input(Key key) {
|
||||
@@ -245,7 +187,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _topPromotionTimer([=] { refreshTopPromotion(); })
|
||||
, _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); })
|
||||
, _selfDestruct(std::make_unique<Api::SelfDestruct>(this))
|
||||
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this)) {
|
||||
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
|
||||
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this)) {
|
||||
crl::on_main(session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
@@ -1704,7 +1647,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
}).fail([=](const RPCError &error) {
|
||||
_selfParticipantRequests.erase(channel);
|
||||
if (error.type() == qstr("CHANNEL_PRIVATE")) {
|
||||
channel->markForbidden();
|
||||
channel->privateErrorReceived();
|
||||
}
|
||||
finalize(-1, 0);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -2021,6 +1964,9 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
|
||||
applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
if (error.type() == qstr("CHANNEL_PRIVATE")
|
||||
&& channel->invitePeekExpires()) {
|
||||
channel->privateErrorReceived();
|
||||
} else if (error.type() == qstr("CHANNEL_PRIVATE")
|
||||
|| error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA")
|
||||
|| error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
Ui::show(Box<InformBox>(channel->isMegagroup()
|
||||
@@ -2998,8 +2944,7 @@ void ApiWrap::toggleFavedSticker(
|
||||
return;
|
||||
}
|
||||
|
||||
auto failHandler = std::make_shared<Fn<void(const RPCError&, QByteArray)>>();
|
||||
auto performRequest = [=] {
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
const auto usedFileReference = document->fileReference();
|
||||
request(MTPmessages_FaveSticker(
|
||||
document->mtpInput(),
|
||||
@@ -3009,21 +2954,18 @@ void ApiWrap::toggleFavedSticker(
|
||||
_session->data().stickers().setFaved(document, faved);
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
(*failHandler)(error, usedFileReference);
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||
auto refreshed = [=](const UpdatedFileReferences &data) {
|
||||
if (document->fileReference() != usedFileReference) {
|
||||
repeatRequest(repeatRequest);
|
||||
}
|
||||
};
|
||||
refreshFileReference(origin, std::move(refreshed));
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
*failHandler = [=](const RPCError &error, QByteArray usedFileReference) {
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||
auto refreshed = [=](const UpdatedFileReferences &data) {
|
||||
if (document->fileReference() != usedFileReference) {
|
||||
performRequest();
|
||||
}
|
||||
};
|
||||
refreshFileReference(origin, std::move(refreshed));
|
||||
}
|
||||
};
|
||||
performRequest();
|
||||
performRequest(performRequest);
|
||||
}
|
||||
|
||||
void ApiWrap::toggleSavedGif(
|
||||
@@ -3034,8 +2976,7 @@ void ApiWrap::toggleSavedGif(
|
||||
return;
|
||||
}
|
||||
|
||||
auto failHandler = std::make_shared<Fn<void(const RPCError&, QByteArray)>>();
|
||||
auto performRequest = [=] {
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
const auto usedFileReference = document->fileReference();
|
||||
request(MTPmessages_SaveGif(
|
||||
document->mtpInput(),
|
||||
@@ -3047,21 +2988,18 @@ void ApiWrap::toggleSavedGif(
|
||||
}
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
(*failHandler)(error, usedFileReference);
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||
auto refreshed = [=](const UpdatedFileReferences &data) {
|
||||
if (document->fileReference() != usedFileReference) {
|
||||
repeatRequest(repeatRequest);
|
||||
}
|
||||
};
|
||||
refreshFileReference(origin, std::move(refreshed));
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
*failHandler = [=](const RPCError & error, QByteArray usedFileReference) {
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||
auto refreshed = [=](const UpdatedFileReferences &data) {
|
||||
if (document->fileReference() != usedFileReference) {
|
||||
performRequest();
|
||||
}
|
||||
};
|
||||
refreshFileReference(origin, std::move(refreshed));
|
||||
}
|
||||
};
|
||||
performRequest();
|
||||
performRequest(performRequest);
|
||||
}
|
||||
|
||||
void ApiWrap::requestStickers(TimeId now) {
|
||||
@@ -3970,8 +3908,10 @@ void ApiWrap::userPhotosDone(
|
||||
//}
|
||||
|
||||
void ApiWrap::sendAction(const SendAction &action) {
|
||||
_session->data().histories().readInbox(action.history);
|
||||
action.history->getReadyFor(ShowAtTheEndMsgId);
|
||||
if (!action.options.scheduled) {
|
||||
_session->data().histories().readInbox(action.history);
|
||||
action.history->getReadyFor(ShowAtTheEndMsgId);
|
||||
}
|
||||
_sendActions.fire_copy(action);
|
||||
}
|
||||
|
||||
@@ -3991,7 +3931,9 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||
_session->data().sendHistoryChangeNotifications();
|
||||
_session->changes().historyUpdated(
|
||||
history,
|
||||
Data::HistoryUpdate::Flag::MessageSent);
|
||||
(action.options.scheduled
|
||||
? Data::HistoryUpdate::Flag::ScheduledSent
|
||||
: Data::HistoryUpdate::Flag::MessageSent));
|
||||
}
|
||||
|
||||
void ApiWrap::forwardMessages(
|
||||
@@ -4231,7 +4173,9 @@ void ApiWrap::sendSharedContact(
|
||||
_session->data().sendHistoryChangeNotifications();
|
||||
_session->changes().historyUpdated(
|
||||
history,
|
||||
Data::HistoryUpdate::Flag::MessageSent);
|
||||
(action.options.scheduled
|
||||
? Data::HistoryUpdate::Flag::ScheduledSent
|
||||
: Data::HistoryUpdate::Flag::MessageSent));
|
||||
}
|
||||
|
||||
void ApiWrap::sendVoiceMessage(
|
||||
@@ -4350,11 +4294,7 @@ void ApiWrap::sendUploadedPhoto(
|
||||
const MTPInputFile &file,
|
||||
Api::SendOptions options) {
|
||||
if (const auto item = _session->data().message(localId)) {
|
||||
const auto media = MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
file,
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
const auto media = Api::PrepareUploadedPhoto(file);
|
||||
if (const auto groupId = item->groupId()) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
@@ -4369,125 +4309,17 @@ void ApiWrap::sendUploadedDocument(
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
Api::SendOptions options) {
|
||||
if (const auto item = _session->data().message(localId)) {
|
||||
auto media = item->media();
|
||||
if (auto document = media ? media->document() : nullptr) {
|
||||
const auto groupId = item->groupId();
|
||||
const auto flags = MTPDinputMediaUploadedDocument::Flags(0)
|
||||
| (thumb
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_thumb
|
||||
: MTPDinputMediaUploadedDocument::Flag(0))
|
||||
| (groupId
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_nosound_video
|
||||
: MTPDinputMediaUploadedDocument::Flag(0));
|
||||
const auto media = MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
file,
|
||||
thumb ? *thumb : MTPInputFile(),
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
if (groupId) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
sendMedia(item, media, options);
|
||||
}
|
||||
if (!item->media() || !item->media()->document()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::editUploadedFile(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
Api::SendOptions options,
|
||||
bool isDocument) {
|
||||
const auto item = _session->data().message(localId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (!item->media()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
item->originalText().entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
auto flagsEditMsg = MTPmessages_EditMessage::Flag::f_message | 0;
|
||||
flagsEditMsg |= MTPmessages_EditMessage::Flag::f_no_webpage;
|
||||
flagsEditMsg |= MTPmessages_EditMessage::Flag::f_entities;
|
||||
flagsEditMsg |= MTPmessages_EditMessage::Flag::f_media;
|
||||
|
||||
const auto media = [&]() -> std::optional<MTPInputMedia> {
|
||||
if (!isDocument) {
|
||||
if (!item->media()->photo()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
file,
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
const auto document = item->media()->document();
|
||||
if (!document) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto flags = MTPDinputMediaUploadedDocument::Flags(0)
|
||||
| (thumb
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_thumb
|
||||
: MTPDinputMediaUploadedDocument::Flag(0))
|
||||
| (item->groupId()
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_nosound_video
|
||||
: MTPDinputMediaUploadedDocument::Flag(0));
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
file,
|
||||
thumb ? *thumb : MTPInputFile(),
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
}();
|
||||
|
||||
if (!media) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto peer = item->history()->peer;
|
||||
request(MTPmessages_EditMessage(
|
||||
MTP_flags(flagsEditMsg),
|
||||
peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_string(item->originalText().text),
|
||||
*media,
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(0) // schedule_date
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
item->clearSavedMedia();
|
||||
item->setIsLocalUpdateMedia(true);
|
||||
applyUpdates(result);
|
||||
item->setIsLocalUpdateMedia(false);
|
||||
}).fail([=](const RPCError &error) {
|
||||
QString err = error.type();
|
||||
if (err == qstr("MESSAGE_NOT_MODIFIED")) {
|
||||
item->returnSavedMedia();
|
||||
_session->data().sendHistoryChangeNotifications();
|
||||
} else if (err == qstr("MEDIA_NEW_INVALID")) {
|
||||
item->returnSavedMedia();
|
||||
_session->data().sendHistoryChangeNotifications();
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
const auto media = Api::PrepareUploadedDocument(item, file, thumb);
|
||||
const auto groupId = item->groupId();
|
||||
if (groupId) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
sendMessageFail(error, peer);
|
||||
sendMedia(item, media, options);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::cancelLocalItem(not_null<HistoryItem*> item) {
|
||||
@@ -5047,7 +4879,10 @@ void ApiWrap::photoUploadReady(
|
||||
};
|
||||
if (peer->isSelf()) {
|
||||
request(MTPphotos_UploadProfilePhoto(
|
||||
file
|
||||
MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble() // video_start_ts
|
||||
)).done([=](const MTPphotos_Photo &result) {
|
||||
result.match([&](const MTPDphotos_photo &data) {
|
||||
_session->data().processPhoto(data.vphoto());
|
||||
@@ -5058,13 +4893,21 @@ void ApiWrap::photoUploadReady(
|
||||
const auto history = _session->data().history(chat);
|
||||
history->sendRequestId = request(MTPmessages_EditChatPhoto(
|
||||
chat->inputChat,
|
||||
MTP_inputChatUploadedPhoto(file)
|
||||
MTP_inputChatUploadedPhoto(
|
||||
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble()) // video_start_ts
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
const auto history = _session->data().history(channel);
|
||||
history->sendRequestId = request(MTPchannels_EditPhoto(
|
||||
channel->inputChannel,
|
||||
MTP_inputChatUploadedPhoto(file)
|
||||
MTP_inputChatUploadedPhoto(
|
||||
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble()) // video_start_ts
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
}
|
||||
}
|
||||
@@ -5400,6 +5243,10 @@ Api::SensitiveContent &ApiWrap::sensitiveContent() {
|
||||
return *_sensitiveContent;
|
||||
}
|
||||
|
||||
Api::GlobalPrivacy &ApiWrap::globalPrivacy() {
|
||||
return *_globalPrivacy;
|
||||
}
|
||||
|
||||
void ApiWrap::createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
@@ -5530,44 +5377,6 @@ void ApiWrap::closePoll(not_null<HistoryItem*> item) {
|
||||
_pollCloseRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::rescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendOptions options) {
|
||||
const auto text = item->originalText().text;
|
||||
const auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
item->originalText().entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
const auto media = item->media();
|
||||
|
||||
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
||||
const auto flags = MTPmessages_EditMessage::Flag::f_schedule_date
|
||||
| (!text.isEmpty()
|
||||
? MTPmessages_EditMessage::Flag::f_message
|
||||
: emptyFlag)
|
||||
| ((!media || !media->webpage())
|
||||
? MTPmessages_EditMessage::Flag::f_no_webpage
|
||||
: emptyFlag)
|
||||
| (!sentEntities.v.isEmpty()
|
||||
? MTPmessages_EditMessage::Flag::f_entities
|
||||
: emptyFlag);
|
||||
|
||||
const auto id = _session->data().scheduledMessages().lookupId(item);
|
||||
request(MTPmessages_EditMessage(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_string(text),
|
||||
MTPInputMedia(),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).fail([](const RPCError &error) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (!IsServerMsgId(item->id)
|
||||
|
||||
@@ -50,6 +50,12 @@ struct CloudPasswordState;
|
||||
} // namespace Core
|
||||
|
||||
namespace Api {
|
||||
|
||||
class Updates;
|
||||
class SelfDestruct;
|
||||
class SensitiveContent;
|
||||
class GlobalPrivacy;
|
||||
|
||||
namespace details {
|
||||
|
||||
inline QString ToString(const QString &value) {
|
||||
@@ -66,8 +72,6 @@ inline QString ToString(uint64 value) {
|
||||
|
||||
} // namespace details
|
||||
|
||||
class Updates;
|
||||
|
||||
template <
|
||||
typename ...Types,
|
||||
typename = std::enable_if_t<(sizeof...(Types) > 0)>>
|
||||
@@ -86,9 +90,6 @@ QString RequestKey(Types &&...values) {
|
||||
return result;
|
||||
}
|
||||
|
||||
class SelfDestruct;
|
||||
class SensitiveContent;
|
||||
|
||||
} // namespace Api
|
||||
|
||||
class ApiWrap : public MTP::Sender, private base::Subscriber {
|
||||
@@ -368,7 +369,6 @@ public:
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<not_null<UserData*>> &users);
|
||||
|
||||
|
||||
rpl::producer<SendAction> sendActions() const {
|
||||
return _sendActions.events();
|
||||
}
|
||||
@@ -423,12 +423,6 @@ public:
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
Api::SendOptions options);
|
||||
void editUploadedFile(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
Api::SendOptions options,
|
||||
bool isDocument);
|
||||
|
||||
void cancelLocalItem(not_null<HistoryItem*> item);
|
||||
|
||||
@@ -467,6 +461,7 @@ public:
|
||||
|
||||
[[nodiscard]] Api::SelfDestruct &selfDestruct();
|
||||
[[nodiscard]] Api::SensitiveContent &sensitiveContent();
|
||||
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
||||
|
||||
void createPoll(
|
||||
const PollData &data,
|
||||
@@ -479,10 +474,6 @@ public:
|
||||
void closePoll(not_null<HistoryItem*> item);
|
||||
void reloadPollResults(not_null<HistoryItem*> item);
|
||||
|
||||
void rescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendOptions options);
|
||||
|
||||
private:
|
||||
struct MessageDataRequest {
|
||||
using Callbacks = QList<RequestMessageDataCallback>;
|
||||
@@ -832,6 +823,7 @@ private:
|
||||
|
||||
const std::unique_ptr<Api::SelfDestruct> _selfDestruct;
|
||||
const std::unique_ptr<Api::SensitiveContent> _sensitiveContent;
|
||||
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
|
||||
@@ -74,8 +74,6 @@ using CornersMap = QMap<uint32, CornersPixmaps>;
|
||||
CornersMap cornersMap;
|
||||
QImage cornersMaskLarge[4], cornersMaskSmall[4];
|
||||
|
||||
int32 serviceImageCacheSize = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace App {
|
||||
|
||||
@@ -85,7 +85,6 @@ namespace App {
|
||||
void setLaunchState(LaunchState state);
|
||||
void restart();
|
||||
|
||||
constexpr auto kFileSizeLimit = 1500 * 1024 * 1024; // Load files up to 1500mb
|
||||
constexpr auto kImageSizeLimit = 64 * 1024 * 1024; // Open images up to 64mb jpg/png/gif
|
||||
QImage readImage(QByteArray data, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr);
|
||||
QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0);
|
||||
|
||||
@@ -1364,7 +1364,10 @@ void RevokePublicLinkBox::prepare() {
|
||||
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
subscribe(_session->downloaderTaskFinished(), [=] { update(); });
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
_inner->resizeToWidth(st::boxWideWidth);
|
||||
setDimensions(st::boxWideWidth, _innerTop + _inner->height());
|
||||
|
||||
@@ -97,9 +97,6 @@ void AutoDownloadBox::setupContent() {
|
||||
setTitle(tr::lng_profile_settings_section());
|
||||
|
||||
const auto settings = &_session->settings().autoDownload();
|
||||
const auto checked = [=](Source source, Type type) {
|
||||
return (settings->bytesLimit(source, type) > 0);
|
||||
};
|
||||
|
||||
auto wrap = object_ptr<Ui::VerticalLayout>(this);
|
||||
const auto content = wrap.data();
|
||||
|
||||
@@ -203,7 +203,10 @@ BackgroundBox::Inner::Inner(
|
||||
}
|
||||
requestPapers();
|
||||
|
||||
subscribe(_session->downloaderTaskFinished(), [=] { update(); });
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
using Update = Window::Theme::BackgroundUpdate;
|
||||
subscribe(Window::Theme::Background(), [=](const Update &update) {
|
||||
if (update.paletteChanged()) {
|
||||
|
||||
@@ -348,7 +348,6 @@ QImage ColorizePattern(QImage image, QColor color) {
|
||||
const auto height = image.height();
|
||||
const auto pattern = anim::shifted(color);
|
||||
|
||||
const auto resultBytesPerPixel = (image.depth() >> 3);
|
||||
constexpr auto resultIntsPerPixel = 1;
|
||||
const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
|
||||
const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
|
||||
@@ -419,9 +418,10 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
if (_media) {
|
||||
_media->thumbnailWanted(_paper.fileOrigin());
|
||||
}
|
||||
subscribe(_controller->session().downloaderTaskFinished(), [=] {
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<HistoryView::ElementDelegate*> BackgroundPreviewBox::delegate() {
|
||||
@@ -743,7 +743,6 @@ void BackgroundPreviewBox::checkLoadedDocument() {
|
||||
guard = _generating.make_guard()
|
||||
]() mutable {
|
||||
auto scaled = PrepareScaledFromFull(image, patternBackground);
|
||||
const auto ms = crl::now();
|
||||
auto blurred = patternBackground
|
||||
? QImage()
|
||||
: PrepareScaledNonPattern(
|
||||
@@ -781,12 +780,12 @@ bool BackgroundPreviewBox::Start(
|
||||
Ui::show(Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
return false;
|
||||
}
|
||||
controller->session().api().requestWallPaper(slug, [=](
|
||||
controller->session().api().requestWallPaper(slug, crl::guard(controller, [=](
|
||||
const Data::WallPaper &result) {
|
||||
Ui::show(Box<BackgroundPreviewBox>(
|
||||
controller,
|
||||
result.withUrlParams(params)));
|
||||
}, [](const RPCError &error) {
|
||||
}), [](const RPCError &error) {
|
||||
Ui::show(Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -94,7 +94,6 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) {
|
||||
confirmInviteStatus: FlatLabel(boxLabel) {
|
||||
align: align(center);
|
||||
minWidth: 320px;
|
||||
maxHeight: 20px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
confirmInviteTitleTop: 106px;
|
||||
|
||||
@@ -122,8 +122,6 @@ void CalendarBox::Context::applyMonth(const QDate &month, bool forced) {
|
||||
_daysCount = month.daysInMonth();
|
||||
_daysShift = daysShiftForMonth(month);
|
||||
_rowsCount = rowsCountForMonth(month);
|
||||
auto yearIndex = month.year();
|
||||
auto monthIndex = month.month();
|
||||
_highlightedIndex = month.daysTo(_highlighted);
|
||||
_minDayIndex = _min.isNull() ? INT_MIN : month.daysTo(_min);
|
||||
_maxDayIndex = _max.isNull() ? INT_MAX : month.daysTo(_max);
|
||||
@@ -302,7 +300,6 @@ int CalendarBox::Inner::rowsTop() const {
|
||||
|
||||
void CalendarBox::Inner::paintRows(Painter &p, QRect clip) {
|
||||
p.setFont(st::calendarDaysFont);
|
||||
auto ms = crl::now();
|
||||
auto y = rowsTop();
|
||||
auto index = -_context->daysShift();
|
||||
auto highlightedIndex = _context->highlightedIndex();
|
||||
|
||||
@@ -861,145 +861,6 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
ConfirmInviteBox::ConfirmInviteBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data,
|
||||
Fn<void()> submit)
|
||||
: _session(session)
|
||||
, _submit(std::move(submit))
|
||||
, _title(this, st::confirmInviteTitle)
|
||||
, _status(this, st::confirmInviteStatus)
|
||||
, _participants(GetParticipants(_session, data))
|
||||
, _isChannel(data.is_channel() && !data.is_megagroup()) {
|
||||
const auto title = qs(data.vtitle());
|
||||
const auto count = data.vparticipants_count().v;
|
||||
const auto status = [&] {
|
||||
return (!_participants.empty() && _participants.size() < count)
|
||||
? tr::lng_group_invite_members(tr::now, lt_count, count)
|
||||
: (count > 0)
|
||||
? tr::lng_chat_status_members(tr::now, lt_count_decimal, count)
|
||||
: _isChannel
|
||||
? tr::lng_channel_status(tr::now)
|
||||
: tr::lng_group_status(tr::now);
|
||||
}();
|
||||
_title->setText(title);
|
||||
_status->setText(status);
|
||||
|
||||
const auto photo = _session->data().processPhoto(data.vphoto());
|
||||
if (!photo->isNull()) {
|
||||
_photo = photo->createMediaView();
|
||||
_photo->wanted(Data::PhotoSize::Small, Data::FileOrigin());
|
||||
if (!_photo->image(Data::PhotoSize::Small)) {
|
||||
subscribe(_session->downloaderTaskFinished(), [=] {
|
||||
update();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||
Data::PeerUserpicColor(0),
|
||||
title);
|
||||
}
|
||||
}
|
||||
|
||||
auto ConfirmInviteBox::GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data)
|
||||
-> std::vector<Participant> {
|
||||
const auto participants = data.vparticipants();
|
||||
if (!participants) {
|
||||
return {};
|
||||
}
|
||||
const auto &v = participants->v;
|
||||
auto result = std::vector<Participant>();
|
||||
result.reserve(v.size());
|
||||
for (const auto &participant : v) {
|
||||
if (const auto user = session->data().processUser(participant)) {
|
||||
result.push_back(Participant{ user });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::prepare() {
|
||||
addButton(
|
||||
(_isChannel
|
||||
? tr::lng_profile_join_channel()
|
||||
: tr::lng_profile_join_group()),
|
||||
_submit);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
while (_participants.size() > 4) {
|
||||
_participants.pop_back();
|
||||
}
|
||||
|
||||
auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom();
|
||||
if (!_participants.empty()) {
|
||||
int skip = (st::boxWideWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
|
||||
int padding = skip / 2;
|
||||
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (st::boxWideWidth - sumWidth) / 2;
|
||||
for (const auto &participant : _participants) {
|
||||
auto name = new Ui::FlatLabel(this, st::confirmInviteUserName);
|
||||
name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
|
||||
name->setText(participant.user->firstName.isEmpty()
|
||||
? participant.user->name
|
||||
: participant.user->firstName);
|
||||
name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
|
||||
left += _userWidth;
|
||||
}
|
||||
|
||||
newHeight += st::confirmInviteUserHeight;
|
||||
}
|
||||
setDimensions(st::boxWideWidth, newHeight);
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop);
|
||||
_status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop);
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
if (_photo) {
|
||||
if (const auto image = _photo->image(Data::PhotoSize::Small)) {
|
||||
p.drawPixmap(
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
image->pixCircled(
|
||||
st::confirmInvitePhotoSize,
|
||||
st::confirmInvitePhotoSize));
|
||||
}
|
||||
} else if (_photoEmpty) {
|
||||
_photoEmpty->paint(
|
||||
p,
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
width(),
|
||||
st::confirmInvitePhotoSize);
|
||||
}
|
||||
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (width() - sumWidth) / 2;
|
||||
for (auto &participant : _participants) {
|
||||
participant.user->paintUserpicLeft(
|
||||
p,
|
||||
participant.userpic,
|
||||
left + (_userWidth - st::confirmInviteUserPhotoSize) / 2,
|
||||
st::confirmInviteUserPhotoTop,
|
||||
width(),
|
||||
st::confirmInviteUserPhotoSize);
|
||||
left += _userWidth;
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmInviteBox::~ConfirmInviteBox() = default;
|
||||
|
||||
ConfirmDontWarnBox::ConfirmDontWarnBox(
|
||||
QWidget*,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
|
||||
@@ -206,46 +206,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class ConfirmInviteBox final
|
||||
: public Ui::BoxContent
|
||||
, private base::Subscriber {
|
||||
public:
|
||||
ConfirmInviteBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data,
|
||||
Fn<void()> submit);
|
||||
~ConfirmInviteBox();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
struct Participant {
|
||||
not_null<UserData*> user;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
static std::vector<Participant> GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
Fn<void()> _submit;
|
||||
object_ptr<Ui::FlatLabel> _title;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
|
||||
std::vector<Participant> _participants;
|
||||
bool _isChannel = false;
|
||||
|
||||
int _userWidth = 0;
|
||||
|
||||
};
|
||||
|
||||
class ConfirmDontWarnBox : public Ui::BoxContent {
|
||||
public:
|
||||
ConfirmDontWarnBox(
|
||||
|
||||
@@ -171,7 +171,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class ProxyBox : public Ui::BoxContent {
|
||||
class ProxyBox final : public Ui::BoxContent {
|
||||
public:
|
||||
ProxyBox(
|
||||
QWidget*,
|
||||
@@ -179,12 +179,14 @@ public:
|
||||
Fn<void(ProxyData)> callback,
|
||||
Fn<void(ProxyData)> shareCallback);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
using Type = ProxyData::Type;
|
||||
|
||||
void prepare() override;
|
||||
void setInnerFocus() override {
|
||||
_host->setFocusFast();
|
||||
}
|
||||
|
||||
void refreshButtons();
|
||||
ProxyData collectData();
|
||||
void save();
|
||||
@@ -693,8 +695,8 @@ void ProxiesBox::applyView(View &&view) {
|
||||
setupButtons(id, i->second.get());
|
||||
if (_noRows) {
|
||||
_noRows.reset();
|
||||
wrap->resizeToWidth(width());
|
||||
}
|
||||
wrap->resizeToWidth(width());
|
||||
} else if (view.host.isEmpty()) {
|
||||
_rows.erase(i);
|
||||
} else {
|
||||
@@ -765,6 +767,31 @@ ProxyBox::ProxyBox(
|
||||
void ProxyBox::prepare() {
|
||||
setTitle(tr::lng_proxy_edit());
|
||||
|
||||
connect(_host.data(), &Ui::InputField::changed, [=] {
|
||||
Ui::PostponeCall(_host, [=] {
|
||||
const auto host = _host->getLastText().trimmed();
|
||||
static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q;
|
||||
const auto match = QRegularExpression(mask).match(host);
|
||||
if (_host->textCursor().position() == host.size()
|
||||
&& match.hasMatch()) {
|
||||
const auto port = match.captured(1);
|
||||
_port->setText(port);
|
||||
_port->setCursorPosition(port.size());
|
||||
_port->setFocus();
|
||||
_host->setText(host.mid(0, host.size() - port.size() - 1));
|
||||
}
|
||||
});
|
||||
});
|
||||
_port.data()->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::KeyPress
|
||||
&& (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Backspace)
|
||||
&& _port->cursorPosition() == 0) {
|
||||
_host->setCursorPosition(_host->getLastText().size());
|
||||
_host->setFocus();
|
||||
}
|
||||
}, _port->lifetime());
|
||||
|
||||
refreshButtons();
|
||||
setDimensionsToContent(st::boxWideWidth, _content);
|
||||
}
|
||||
@@ -1388,7 +1415,6 @@ auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::updateView(const Item &item) {
|
||||
const auto ping = 0;
|
||||
const auto selected = (Global::SelectedProxy() == item.data);
|
||||
const auto deleted = item.deleted;
|
||||
const auto type = [&] {
|
||||
|
||||
@@ -259,9 +259,9 @@ auto AddButtonWithLoader(
|
||||
)
|
||||
);
|
||||
|
||||
*buttonState = localLoaderValues->events_starting_with(
|
||||
rawGlobalLoaderPtr() ? rawGlobalLoaderPtr() : localLoader->get()
|
||||
) | rpl::map([=](Loader *loader) {
|
||||
*buttonState = localLoaderValues->events_starting_with(
|
||||
rawGlobalLoaderPtr() ? rawGlobalLoaderPtr() : localLoader->get()
|
||||
) | rpl::map([=](Loader *loader) {
|
||||
return (loader && loader->id() == id)
|
||||
? loader->state()
|
||||
: rpl::single(
|
||||
@@ -388,7 +388,6 @@ void ManageDictionariesBox::setInnerFocus() {
|
||||
void ManageDictionariesBox::prepare() {
|
||||
const auto multiSelect = CreateMultiSelect(this);
|
||||
|
||||
const auto session = &_controller->session();
|
||||
const auto inner = setInnerWidget(
|
||||
object_ptr<Inner>(
|
||||
this,
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/edit_caption_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_editing.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
@@ -29,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_drag_area.h"
|
||||
#include "history/history_item.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -37,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/streaming/media_streaming_player.h"
|
||||
#include "media/streaming/media_streaming_document.h"
|
||||
#include "media/streaming/media_streaming_loader_local.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
@@ -63,6 +66,50 @@ namespace {
|
||||
using namespace ::Media::Streaming;
|
||||
using Data::PhotoSize;
|
||||
|
||||
auto ListFromMimeData(not_null<const QMimeData*> data) {
|
||||
using Error = Storage::PreparedList::Error;
|
||||
auto result = data->hasUrls()
|
||||
? Storage::PrepareMediaList(
|
||||
// When we edit media, we need only 1 file.
|
||||
data->urls().mid(0, 1),
|
||||
st::sendMediaPreviewSize)
|
||||
: Storage::PreparedList(Error::EmptyFile, QString());
|
||||
if (result.error == Error::None) {
|
||||
return result;
|
||||
} else if (data->hasImage()) {
|
||||
auto image = Platform::GetImageFromClipboard();
|
||||
if (image.isNull()) {
|
||||
image = qvariant_cast<QImage>(data->imageData());
|
||||
}
|
||||
if (!image.isNull()) {
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(image),
|
||||
QByteArray(),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CheckMimeData(not_null<const QMimeData*> data, bool isAlbum) {
|
||||
if (data->urls().size() > 1) {
|
||||
return false;
|
||||
} else if (data->hasImage()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isAlbum && data->hasUrls()) {
|
||||
const auto url = data->urls().front();
|
||||
if (url.isLocalFile()) {
|
||||
using namespace Core;
|
||||
const auto info = QFileInfo(Platform::File::UrlToLocal(url));
|
||||
return IsMimeAcceptedForAlbum(MimeTypeForFile(info).name());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
@@ -70,7 +117,6 @@ EditCaptionBox::EditCaptionBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item)
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _msgId(item->fullId()) {
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
@@ -291,7 +337,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
Assert(_thumbnailImageLoaded || _refreshThumbnail);
|
||||
|
||||
if (!_thumbnailImageLoaded) {
|
||||
subscribe(_controller->session().downloaderTaskFinished(), [=] {
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_thumbnailImageLoaded
|
||||
|| (_photoMedia && !_photoMedia->image(PhotoSize::Large))
|
||||
|| (_documentMedia && !_documentMedia->thumbnail())) {
|
||||
@@ -299,7 +346,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
}
|
||||
_refreshThumbnail();
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
_field.create(
|
||||
this,
|
||||
@@ -616,12 +663,8 @@ void EditCaptionBox::prepare() {
|
||||
if (action == Ui::InputField::MimeAction::Check) {
|
||||
if (!data->hasText() && !_isAllowedEditMedia) {
|
||||
return false;
|
||||
} else if (data->hasImage()) {
|
||||
} else if (CheckMimeData(data, _isAlbum)) {
|
||||
return true;
|
||||
} else if (const auto urls = data->urls(); !urls.empty()) {
|
||||
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return data->hasText();
|
||||
} else if (action == Ui::InputField::MimeAction::Insert) {
|
||||
@@ -639,6 +682,8 @@ void EditCaptionBox::prepare() {
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_field->setTextCursor(cursor);
|
||||
|
||||
setupDragArea();
|
||||
}
|
||||
|
||||
bool EditCaptionBox::fileFromClipboard(not_null<const QMimeData*> data) {
|
||||
@@ -646,50 +691,34 @@ bool EditCaptionBox::fileFromClipboard(not_null<const QMimeData*> data) {
|
||||
return false;
|
||||
}
|
||||
using Error = Storage::PreparedList::Error;
|
||||
using AlbumType = Storage::PreparedFile::AlbumType;
|
||||
auto list = ListFromMimeData(data);
|
||||
|
||||
auto list = [&] {
|
||||
auto url = QList<QUrl>();
|
||||
auto canAddUrl = false;
|
||||
// When we edit media, we need only 1 file.
|
||||
if (data->hasUrls()) {
|
||||
const auto first = data->urls().front();
|
||||
url.push_front(first);
|
||||
canAddUrl = first.isLocalFile();
|
||||
}
|
||||
auto result = canAddUrl
|
||||
? Storage::PrepareMediaList(url, st::sendMediaPreviewSize)
|
||||
: Storage::PreparedList(
|
||||
Error::EmptyFile,
|
||||
QString());
|
||||
if (result.error == Error::None) {
|
||||
return result;
|
||||
} else if (data->hasImage()) {
|
||||
auto image = Platform::GetImageFromClipboard();
|
||||
if (image.isNull()) {
|
||||
image = qvariant_cast<QImage>(data->imageData());
|
||||
}
|
||||
if (!image.isNull()) {
|
||||
_isImage = true;
|
||||
_photo = true;
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(image),
|
||||
QByteArray(),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
if (list.error != Error::None || list.files.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (list.files.front().type == Storage::PreparedFile::AlbumType::None
|
||||
&& _isAlbum) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
|
||||
const auto file = &list.files.front();
|
||||
if (_isAlbum && (file->type == AlbumType::None)) {
|
||||
const auto imageAsDoc = [&] {
|
||||
using Info = FileMediaInformation;
|
||||
const auto fileMedia = &file->information->media;
|
||||
if (const auto image = base::get_if<Info::Image>(fileMedia)) {
|
||||
return !Storage::ValidateThumbDimensions(
|
||||
image->data.width(),
|
||||
image->data.height());
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!data->hasText() || imageAsDoc) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_photo = _isImage = (file->type == AlbumType::Photo);
|
||||
_preparedList = std::move(list);
|
||||
updateEditPreview();
|
||||
return true;
|
||||
@@ -734,6 +763,35 @@ void EditCaptionBox::setupEmojiPanel() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void EditCaptionBox::setupDragArea() {
|
||||
auto enterFilter = [=](not_null<const QMimeData*> data) {
|
||||
return !_isAllowedEditMedia ? false : CheckMimeData(data, _isAlbum);
|
||||
};
|
||||
// Avoid both drag areas appearing at one time.
|
||||
auto computeState = [=](const QMimeData *data) {
|
||||
const auto state = Storage::ComputeMimeDataState(data);
|
||||
return (state == Storage::MimeDataState::PhotoFiles)
|
||||
? Storage::MimeDataState::Image
|
||||
: state;
|
||||
};
|
||||
const auto areas = DragArea::SetupDragAreaToContainer(
|
||||
this,
|
||||
std::move(enterFilter),
|
||||
[=](bool f) { _field->setAcceptDrops(f); },
|
||||
nullptr,
|
||||
std::move(computeState));
|
||||
|
||||
const auto droppedCallback = [=](bool compress) {
|
||||
return [=](const QMimeData *data) {
|
||||
fileFromClipboard(data);
|
||||
Window::ActivateWindow(_controller);
|
||||
};
|
||||
};
|
||||
areas.document->setDroppedCallback(droppedCallback(false));
|
||||
areas.photo->setDroppedCallback(droppedCallback(true));
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateBoxSize() {
|
||||
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
|
||||
if (_photo) {
|
||||
@@ -758,8 +816,7 @@ int EditCaptionBox::errorTopSkip() const {
|
||||
void EditCaptionBox::checkStreamedIsStarted() {
|
||||
if (!_streamed) {
|
||||
return;
|
||||
}
|
||||
if (_streamed->paused()) {
|
||||
} else if (_streamed->paused()) {
|
||||
_streamed->resume();
|
||||
}
|
||||
if (!_streamed->active() && !_streamed->failed()) {
|
||||
@@ -929,88 +986,60 @@ void EditCaptionBox::save() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = MTPmessages_EditMessage::Flag::f_message | 0;
|
||||
if (_previewCancelled) {
|
||||
flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
|
||||
}
|
||||
const auto textWithTags = _field->getTextWithAppliedMarkdown();
|
||||
auto sending = TextWithEntities{
|
||||
const auto sending = TextWithEntities{
|
||||
textWithTags.text,
|
||||
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
|
||||
};
|
||||
const auto prepareFlags = Ui::ItemTextOptions(
|
||||
item->history(),
|
||||
_controller->session().user()).flags;
|
||||
TextUtilities::PrepareForSending(sending, prepareFlags);
|
||||
TextUtilities::Trim(sending);
|
||||
|
||||
const auto sentEntities = Api::EntitiesToMTP(
|
||||
&item->history()->session(),
|
||||
sending.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
if (!sentEntities.v.isEmpty()) {
|
||||
flags |= MTPmessages_EditMessage::Flag::f_entities;
|
||||
}
|
||||
auto options = Api::SendOptions();
|
||||
options.scheduled = item->isScheduled() ? item->date() : 0;
|
||||
|
||||
if (!_preparedList.files.empty()) {
|
||||
const auto textWithTags = _field->getTextWithAppliedMarkdown();
|
||||
auto sending = TextWithEntities{
|
||||
textWithTags.text,
|
||||
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
|
||||
};
|
||||
item->setText(sending);
|
||||
auto action = Api::SendAction(item->history());
|
||||
action.options = options;
|
||||
|
||||
_controller->session().api().editMedia(
|
||||
std::move(_preparedList),
|
||||
(!_asFile && _photo) ? SendMediaType::Photo : SendMediaType::File,
|
||||
_field->getTextWithAppliedMarkdown(),
|
||||
Api::SendAction(item->history()),
|
||||
action,
|
||||
item->fullId().msg);
|
||||
closeBox();
|
||||
return;
|
||||
}
|
||||
|
||||
_saveRequestId = _api.request(MTPmessages_EditMessage(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_string(sending.text),
|
||||
MTPInputMedia(),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
saveDone(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
saveFail(error);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void EditCaptionBox::saveDone(const MTPUpdates &updates) {
|
||||
_saveRequestId = 0;
|
||||
const auto controller = _controller;
|
||||
closeBox();
|
||||
controller->session().api().applyUpdates(updates);
|
||||
}
|
||||
|
||||
void EditCaptionBox::saveFail(const RPCError &error) {
|
||||
_saveRequestId = 0;
|
||||
const auto &type = error.type();
|
||||
if (type == qstr("MESSAGE_ID_INVALID")
|
||||
|| type == qstr("CHAT_ADMIN_REQUIRED")
|
||||
|| type == qstr("MESSAGE_EDIT_TIME_EXPIRED")) {
|
||||
_error = tr::lng_edit_error(tr::now);
|
||||
update();
|
||||
} else if (type == qstr("MESSAGE_NOT_MODIFIED")) {
|
||||
const auto done = crl::guard(this, [=](const MTPUpdates &updates) {
|
||||
_saveRequestId = 0;
|
||||
closeBox();
|
||||
} else if (type == qstr("MESSAGE_EMPTY")) {
|
||||
_field->setFocus();
|
||||
_field->showError();
|
||||
update();
|
||||
} else {
|
||||
_error = tr::lng_edit_error(tr::now);
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
const auto fail = crl::guard(this, [=](const RPCError &error) {
|
||||
_saveRequestId = 0;
|
||||
const auto &type = error.type();
|
||||
if (ranges::contains(Api::kDefaultEditMessagesErrors, type)) {
|
||||
_error = tr::lng_edit_error(tr::now);
|
||||
update();
|
||||
} else if (type == u"MESSAGE_NOT_MODIFIED"_q) {
|
||||
closeBox();
|
||||
} else if (type == u"MESSAGE_EMPTY"_q) {
|
||||
_field->setFocus();
|
||||
_field->showError();
|
||||
update();
|
||||
} else {
|
||||
_error = tr::lng_edit_error(tr::now);
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
lifetime().add([=] {
|
||||
if (_saveRequestId) {
|
||||
auto &session = _controller->session();
|
||||
session.api().request(base::take(_saveRequestId)).cancel();
|
||||
}
|
||||
});
|
||||
|
||||
_saveRequestId = Api::EditCaption(item, sending, options, done, fail);
|
||||
}
|
||||
|
||||
void EditCaptionBox::setName(QString nameString, qint64 size) {
|
||||
|
||||
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class Image;
|
||||
|
||||
@@ -80,12 +79,11 @@ private:
|
||||
void updateEmojiPanelGeometry();
|
||||
void emojiFilterForGeometry(not_null<QEvent*> event);
|
||||
|
||||
void setupDragArea();
|
||||
|
||||
void save();
|
||||
void captionResized();
|
||||
|
||||
void saveDone(const MTPUpdates &updates);
|
||||
void saveFail(const RPCError &error);
|
||||
|
||||
void setName(QString nameString, qint64 size);
|
||||
bool fileFromClipboard(not_null<const QMimeData*> data);
|
||||
void updateEditPreview();
|
||||
@@ -102,7 +100,6 @@ private:
|
||||
}
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
FullMsgId _msgId;
|
||||
std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
@@ -135,7 +132,6 @@ private:
|
||||
|
||||
Storage::PreparedList _preparedList;
|
||||
|
||||
bool _previewCancelled = false;
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
object_ptr<Ui::IconButton> _editMedia = nullptr;
|
||||
|
||||
@@ -662,9 +662,10 @@ PeerListContent::PeerListContent(
|
||||
, _st(st)
|
||||
, _controller(controller)
|
||||
, _rowHeight(_st.item.height) {
|
||||
subscribe(_controller->session().downloaderTaskFinished(), [=] {
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
_controller->session().changes().peerUpdates(
|
||||
@@ -1298,7 +1299,6 @@ crl::time PeerListContent::paintRow(
|
||||
p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
|
||||
} else {
|
||||
grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth);
|
||||
auto grayedWidth = st::contactsStatusFont->width(grayedPart);
|
||||
p.setPen(_st.item.statusFgActive);
|
||||
p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
|
||||
p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg);
|
||||
|
||||
@@ -654,8 +654,6 @@ void AddSpecialBoxController::showRestricted(
|
||||
_editParticipantBox->closeBox();
|
||||
}
|
||||
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
const auto showRestrictedSure = crl::guard(this, [=] {
|
||||
showRestricted(user, true);
|
||||
});
|
||||
|
||||
@@ -62,7 +62,6 @@ void SelfDestructionBox::showContent() {
|
||||
_description->moveToLeft(st::boxPadding.left(), y);
|
||||
y += _description->height() + st::boxMediumSkip;
|
||||
|
||||
const auto count = int(_ttlValues.size());
|
||||
for (const auto value : _ttlValues) {
|
||||
const auto button = Ui::CreateChild<Ui::Radiobutton>(
|
||||
this,
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "confirm_box.h"
|
||||
#include "history/history_drag_area.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/mime_type.h"
|
||||
@@ -333,7 +334,7 @@ AlbumThumb::AlbumThumb(
|
||||
- st::sendMediaFileThumbSize
|
||||
// Right buttons.
|
||||
- st::sendBoxAlbumGroupButtonFile.width * 2
|
||||
- st::sendBoxAlbumGroupEditInternalSkip
|
||||
- st::sendBoxAlbumGroupEditInternalSkip * 2
|
||||
- st::sendBoxAlbumGroupSkipRight;
|
||||
const auto filepath = file.path;
|
||||
if (filepath.isEmpty()) {
|
||||
@@ -1858,6 +1859,40 @@ void SendFilesBox::prepare() {
|
||||
}));
|
||||
|
||||
updateLeftButtonVisibility();
|
||||
setupDragArea();
|
||||
}
|
||||
|
||||
void SendFilesBox::setupDragArea() {
|
||||
// Avoid both drag areas appearing at one time.
|
||||
auto computeState = [=](const QMimeData *data) {
|
||||
const auto state = Storage::ComputeMimeDataState(data);
|
||||
return (state == Storage::MimeDataState::PhotoFiles)
|
||||
? Storage::MimeDataState::Image
|
||||
: (state == Storage::MimeDataState::Files)
|
||||
// Temporary enable drag'n'drop only for images. TODO.
|
||||
? Storage::MimeDataState::None
|
||||
: state;
|
||||
};
|
||||
const auto areas = DragArea::SetupDragAreaToContainer(
|
||||
this,
|
||||
[=](not_null<const QMimeData*> d) { return canAddFiles(d); },
|
||||
[=](bool f) { _caption->setAcceptDrops(f); },
|
||||
[=] { updateControlsGeometry(); },
|
||||
std::move(computeState));
|
||||
|
||||
const auto droppedCallback = [=](bool compress) {
|
||||
return [=](const QMimeData *data) {
|
||||
addFiles(data);
|
||||
Window::ActivateWindow(_controller);
|
||||
};
|
||||
};
|
||||
areas.document->setDroppedCallback(droppedCallback(false));
|
||||
areas.photo->setDroppedCallback(droppedCallback(true));
|
||||
_albumChanged.events(
|
||||
) | rpl::start_with_next([=] {
|
||||
areas.document->raise();
|
||||
areas.photo->raise();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::updateLeftButtonVisibility() {
|
||||
@@ -1875,6 +1910,7 @@ void SendFilesBox::refreshAllAfterAlbumChanges() {
|
||||
preparePreview();
|
||||
captionResized();
|
||||
updateLeftButtonVisibility();
|
||||
_albumChanged.fire({});
|
||||
}
|
||||
|
||||
void SendFilesBox::openDialogToAddFileToAlbum() {
|
||||
|
||||
@@ -115,6 +115,7 @@ private:
|
||||
void sendScheduled();
|
||||
void captionResized();
|
||||
|
||||
void setupDragArea();
|
||||
void setupTitleText();
|
||||
void updateBoxSize();
|
||||
void updateControlsGeometry();
|
||||
@@ -163,6 +164,7 @@ private:
|
||||
std::shared_ptr<Ui::RadioenumGroup<SendFilesWay>> _sendWay;
|
||||
|
||||
rpl::variable<int> _footerHeight = 0;
|
||||
rpl::event_stream<> _albumChanged;
|
||||
|
||||
QWidget *_preview = nullptr;
|
||||
AlbumPreview *_albumPreview = nullptr;
|
||||
|
||||
@@ -570,9 +570,10 @@ ShareBox::Inner::Inner(
|
||||
update.oldFirstLetters);
|
||||
}, lifetime());
|
||||
|
||||
subscribe(_navigation->session().downloaderTaskFinished(), [=] {
|
||||
_navigation->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) {
|
||||
if (update.paletteChanged()) {
|
||||
|
||||
@@ -254,7 +254,10 @@ StickerSetBox::Inner::Inner(
|
||||
|
||||
_controller->session().api().updateStickers();
|
||||
|
||||
subscribe(_controller->session().downloaderTaskFinished(), [this] { update(); });
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
@@ -1002,10 +1002,12 @@ Main::Session &StickersBox::Inner::session() const {
|
||||
}
|
||||
|
||||
void StickersBox::Inner::setup() {
|
||||
subscribe(session().downloaderTaskFinished(), [this] {
|
||||
session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
readVisibleSets();
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
@@ -1433,12 +1435,12 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
|
||||
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
|
||||
auto &set = _rows[pressedIndex];
|
||||
auto rippleMask = Ui::RippleAnimation::rectMask(QSize(width(), _rowHeight));
|
||||
if (!_rows[pressedIndex]->ripple) {
|
||||
_rows[pressedIndex]->ripple = std::make_unique<Ui::RippleAnimation>(st::contactsRipple, std::move(rippleMask), [this, pressedIndex] {
|
||||
if (!set->ripple) {
|
||||
set->ripple = std::make_unique<Ui::RippleAnimation>(st::contactsRipple, std::move(rippleMask), [this, pressedIndex] {
|
||||
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
|
||||
});
|
||||
}
|
||||
_rows[pressedIndex]->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, _itemsTop + pressedIndex * _rowHeight));
|
||||
set->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, _itemsTop + pressedIndex * _rowHeight));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -446,9 +446,10 @@ void Panel::initLayout() {
|
||||
}, lifetime());
|
||||
processUserPhoto();
|
||||
|
||||
subscribe(_user->session().downloaderTaskFinished(), [=] {
|
||||
_user->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshUserPhoto();
|
||||
});
|
||||
}, lifetime());
|
||||
createDefaultCacheImage();
|
||||
|
||||
Ui::Platform::InitOnTopPanel(this);
|
||||
|
||||
@@ -161,12 +161,14 @@ bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
App::activateBotCommand(item, 0, index);
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Q) {
|
||||
if (const auto user = item->history()->peer->asUser()) {
|
||||
if (user->isBot() && item->from() == user) {
|
||||
} else if (const auto user = item->history()->peer->asUser()) {
|
||||
if (user->isBot() && item->from() == user) {
|
||||
if (key == Qt::Key_Q) {
|
||||
App::sendBotCommand(user, user, qsl("/translate"));
|
||||
return true;
|
||||
} else if (key == Qt::Key_W) {
|
||||
App::sendBotCommand(user, user, qsl("/eng"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,9 +698,6 @@ QString SuggestionsController::getEmojiQuery() {
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
return cursor.position();
|
||||
}();
|
||||
const auto is = [&](QLatin1String string) {
|
||||
return (text.compare(string, Qt::CaseInsensitive) == 0);
|
||||
};
|
||||
if (!_options.suggestExactFirstWord
|
||||
|| !length
|
||||
|| text[0].isSpace()
|
||||
|
||||
@@ -571,16 +571,20 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
|
||||
if (e->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *ev = static_cast<QKeyEvent*>(e);
|
||||
if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) {
|
||||
const auto key = ev->key();
|
||||
if (!hidden) {
|
||||
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.empty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
|
||||
return _inner->moveSel(ev->key());
|
||||
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
|
||||
if (key == Qt::Key_Up || key == Qt::Key_Down || (!_srows.empty() && (key == Qt::Key_Left || key == Qt::Key_Right))) {
|
||||
return _inner->moveSel(key);
|
||||
} else if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
return _inner->chooseSelected(ChooseMethod::ByEnter);
|
||||
}
|
||||
}
|
||||
if (moderate && ((ev->key() >= Qt::Key_1 && ev->key() <= Qt::Key_9) || ev->key() == Qt::Key_Q)) {
|
||||
if (moderate
|
||||
&& ((key >= Qt::Key_1 && key <= Qt::Key_9)
|
||||
|| key == Qt::Key_Q
|
||||
|| key == Qt::Key_W)) {
|
||||
bool handled = false;
|
||||
emit moderateKeyActivate(ev->key(), &handled);
|
||||
emit moderateKeyActivate(key, &handled);
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
@@ -604,9 +608,10 @@ FieldAutocompleteInner::FieldAutocompleteInner(
|
||||
, _brows(brows)
|
||||
, _srows(srows)
|
||||
, _previewTimer([=] { showPreview(); }) {
|
||||
subscribe(
|
||||
controller->session().downloaderTaskFinished(),
|
||||
[=] { update(); });
|
||||
controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
@@ -615,8 +620,6 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
QRect r(e->rect());
|
||||
if (r != rect()) p.setClipRect(r);
|
||||
|
||||
auto atwidth = st::mentionFont->width('@');
|
||||
auto hashwidth = st::mentionFont->width('#');
|
||||
auto mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
|
||||
auto mentionwidth = width()
|
||||
- mentionleft
|
||||
|
||||
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rp_widget.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
|
||||
namespace Ui {
|
||||
class ScrollArea;
|
||||
|
||||
@@ -36,7 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSaveChosenTabTimeout = 1000;
|
||||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kInlineItemsMaxPerRow = 5;
|
||||
constexpr auto kSearchBotUsername = "gif"_cs;
|
||||
@@ -153,9 +152,10 @@ GifsListWidget::GifsListWidget(
|
||||
refreshSavedGifs();
|
||||
}, lifetime());
|
||||
|
||||
subscribe(controller->session().downloaderTaskFinished(), [this] {
|
||||
controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
subscribe(controller->gifPauseLevelChanged(), [=] {
|
||||
if (!controller->isGifPausedAtLeastFor(
|
||||
@@ -802,7 +802,6 @@ bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layou
|
||||
auto col = position % MatrixRowShift;
|
||||
Assert((row < _rows.size()) && (col < _rows[row].items.size()));
|
||||
|
||||
auto &inlineItems = _rows[row].items;
|
||||
auto top = 0;
|
||||
for (auto i = 0; i != row; ++i) {
|
||||
top += _rows[i].height;
|
||||
@@ -951,9 +950,7 @@ void GifsListWidget::updateSelected() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto newSelected = -1;
|
||||
auto p = mapFromGlobal(_lastMousePos);
|
||||
|
||||
int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius);
|
||||
int sy = p.y() - st::stickerPanPadding;
|
||||
int row = -1, col = -1, sel = -1;
|
||||
|
||||
@@ -19,11 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtCore/QFileInfo>
|
||||
|
||||
namespace Stickers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kZeroDiceDocumentId = 0xa3b83c9f84fa9e83ULL;
|
||||
|
||||
} // namespace
|
||||
|
||||
DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji)
|
||||
: _session(session)
|
||||
|
||||
@@ -130,7 +130,6 @@ EmojiPack::EmojiPack(not_null<Main::Session*> session)
|
||||
EmojiPack::~EmojiPack() = default;
|
||||
|
||||
bool EmojiPack::add(not_null<HistoryItem*> item) {
|
||||
auto length = 0;
|
||||
if (const auto emoji = item->isolatedEmoji()) {
|
||||
_items[emoji].emplace(item);
|
||||
return true;
|
||||
@@ -141,7 +140,6 @@ bool EmojiPack::add(not_null<HistoryItem*> item) {
|
||||
void EmojiPack::remove(not_null<const HistoryItem*> item) {
|
||||
Expects(item->isIsolatedEmoji());
|
||||
|
||||
auto length = 0;
|
||||
const auto emoji = item->isolatedEmoji();
|
||||
const auto i = _items.find(emoji);
|
||||
Assert(i != end(_items));
|
||||
@@ -198,7 +196,7 @@ std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
|
||||
if (!strong->image) {
|
||||
strong->load = nullptr;
|
||||
strong->image.emplace(std::move(image));
|
||||
_session->downloaderTaskFinished().notify();
|
||||
_session->notifyDownloaderTaskFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,9 +105,7 @@ struct StickerIcon {
|
||||
|
||||
};
|
||||
|
||||
class StickersListWidget::Footer
|
||||
: public TabbedSelector::InnerFooter
|
||||
, private base::Subscriber {
|
||||
class StickersListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
explicit Footer(not_null<StickersListWidget*> parent);
|
||||
|
||||
@@ -132,7 +130,7 @@ protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
bool event(QEvent *e) override;
|
||||
bool eventHook(QEvent *e) override;
|
||||
|
||||
void processHideFinished() override;
|
||||
|
||||
@@ -249,16 +247,15 @@ StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent)
|
||||
|
||||
_iconsLeft = _iconsRight = st::emojiCategorySkip + st::stickerIconWidth;
|
||||
|
||||
subscribe(_pan->session().downloaderTaskFinished(), [=] {
|
||||
_pan->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::clearHeavyData() {
|
||||
const auto count = int(_icons.size());
|
||||
const auto iconsX = qRound(_iconsX.current());
|
||||
const auto index = iconsX / st::stickerIconWidth;
|
||||
const auto x = _iconsLeft - (iconsX % st::stickerIconWidth);
|
||||
const auto first = floorclamp(iconsX, st::stickerIconWidth, 0, count);
|
||||
const auto last = ceilclamp(
|
||||
iconsX + width(),
|
||||
@@ -600,7 +597,7 @@ void StickersListWidget::Footer::finishDragging() {
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
bool StickersListWidget::Footer::event(QEvent *e) {
|
||||
bool StickersListWidget::Footer::eventHook(QEvent *e) {
|
||||
if (e->type() == QEvent::TouchBegin) {
|
||||
} else if (e->type() == QEvent::Wheel) {
|
||||
if (!_icons.empty()
|
||||
@@ -609,7 +606,7 @@ bool StickersListWidget::Footer::event(QEvent *e) {
|
||||
scrollByWheelEvent(static_cast<QWheelEvent*>(e));
|
||||
}
|
||||
}
|
||||
return InnerFooter::event(e);
|
||||
return InnerFooter::eventHook(e);
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::scrollByWheelEvent(
|
||||
@@ -890,12 +887,13 @@ StickersListWidget::StickersListWidget(
|
||||
Box<StickersBox>(controller, StickersBox::Section::Installed));
|
||||
});
|
||||
|
||||
subscribe(session().downloaderTaskFinished(), [=] {
|
||||
session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (isVisible()) {
|
||||
update();
|
||||
readVisibleFeatured(getVisibleTop(), getVisibleBottom());
|
||||
}
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::StickersSet
|
||||
@@ -2508,7 +2506,11 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
|
||||
externalLayout,
|
||||
std::move(recentPack));
|
||||
if (recentIt == _mySets.end()) {
|
||||
_mySets.push_back(std::move(set));
|
||||
const auto where = (_mySets.empty()
|
||||
|| _mySets.begin()->id != Data::Stickers::FavedSetId)
|
||||
? _mySets.begin()
|
||||
: (_mySets.begin() + 1);
|
||||
_mySets.insert(where, std::move(set));
|
||||
} else {
|
||||
std::swap(*recentIt, set);
|
||||
takeHeavyData(*recentIt, set);
|
||||
@@ -2533,7 +2535,7 @@ void StickersListWidget::refreshFavedStickers() {
|
||||
const auto set = it->second.get();
|
||||
const auto externalLayout = false;
|
||||
const auto shortName = QString();
|
||||
_mySets.emplace_back(
|
||||
_mySets.insert(_mySets.begin(), Set{
|
||||
Data::Stickers::FavedSetId,
|
||||
nullptr,
|
||||
(MTPDstickerSet::Flag::f_official
|
||||
@@ -2542,7 +2544,8 @@ void StickersListWidget::refreshFavedStickers() {
|
||||
shortName,
|
||||
set->count,
|
||||
externalLayout,
|
||||
PrepareStickers(set->stickers));
|
||||
PrepareStickers(set->stickers)
|
||||
});
|
||||
_favedStickersMap = base::flat_set<not_null<DocumentData*>> {
|
||||
set->stickers.begin(),
|
||||
set->stickers.end()
|
||||
|
||||
@@ -369,6 +369,7 @@ TabbedSelector::TabbedSelector(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshStickers();
|
||||
}, lifetime());
|
||||
refreshStickers();
|
||||
}
|
||||
//setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
@@ -938,7 +939,7 @@ void TabbedSelector::Inner::panelHideFinished() {
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter::InnerFooter(QWidget *parent)
|
||||
: TWidget(parent) {
|
||||
: RpWidget(parent) {
|
||||
resize(st::emojiPanWidth, st::emojiFooterHeight);
|
||||
}
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TabbedSelector::InnerFooter : public TWidget {
|
||||
class TabbedSelector::InnerFooter : public Ui::RpWidget {
|
||||
public:
|
||||
InnerFooter(QWidget *parent);
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ enum {
|
||||
AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
|
||||
AudioVoiceMsgChannels = 2, // stereo
|
||||
|
||||
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
|
||||
|
||||
PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request
|
||||
|
||||
SearchPeopleLimit = 5,
|
||||
|
||||
@@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "core/launcher.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "chat_helpers/emoji_keywords.h"
|
||||
#include "chat_helpers/stickers_emoji_image_loader.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
@@ -153,6 +154,7 @@ Application::~Application() {
|
||||
|
||||
_window = nullptr;
|
||||
_mediaView = nullptr;
|
||||
_notifications->clearAllFast();
|
||||
_domain->finish();
|
||||
|
||||
Local::finish();
|
||||
@@ -183,6 +185,7 @@ void Application::run() {
|
||||
refreshGlobalProxy(); // Depends on Global::start().
|
||||
|
||||
// Depends on OpenSSL on macOS, so on ThirdParty::start().
|
||||
// Depends on notifications settings.
|
||||
_notifications = std::make_unique<Window::Notifications::System>();
|
||||
|
||||
startLocalStorage();
|
||||
@@ -202,6 +205,8 @@ void Application::run() {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::App().settings().setWindowControlsLayout(Platform::WindowControlsLayout());
|
||||
|
||||
_translator = std::make_unique<Lang::Translator>();
|
||||
QCoreApplication::instance()->installTranslator(_translator.get());
|
||||
|
||||
@@ -209,6 +214,7 @@ void Application::run() {
|
||||
Ui::InitTextOptions();
|
||||
Ui::Emoji::Init();
|
||||
startEmojiImageLoader();
|
||||
startSystemDarkModeViewer();
|
||||
Media::Player::start(_audio.get());
|
||||
|
||||
style::ShortAnimationPlaying(
|
||||
@@ -230,6 +236,7 @@ void Application::run() {
|
||||
QMimeDatabase().mimeTypeForName(qsl("text/plain"));
|
||||
|
||||
_window = std::make_unique<Window::Controller>();
|
||||
|
||||
_domain->activeChanges(
|
||||
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
|
||||
_window->showAccount(account);
|
||||
@@ -246,16 +253,8 @@ void Application::run() {
|
||||
|
||||
// Depend on activeWindow() for now :(
|
||||
startShortcuts();
|
||||
|
||||
App::initMedia();
|
||||
|
||||
const auto state = _domain->start(QByteArray());
|
||||
if (state == Storage::StartResult::IncorrectPasscode) {
|
||||
Global::SetLocalPasscode(true);
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
lockByPasscode();
|
||||
DEBUG_LOG(("Application Info: passcode needed..."));
|
||||
}
|
||||
startDomain();
|
||||
|
||||
_window->widget()->show();
|
||||
|
||||
@@ -277,6 +276,50 @@ void Application::run() {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::startDomain() {
|
||||
const auto state = _domain->start(QByteArray());
|
||||
if (state != Storage::StartResult::IncorrectPasscodeLegacy) {
|
||||
// In case of non-legacy passcoded app all global settings are ready.
|
||||
startSettingsAndBackground();
|
||||
}
|
||||
if (state != Storage::StartResult::Success) {
|
||||
Global::SetLocalPasscode(true);
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
lockByPasscode();
|
||||
DEBUG_LOG(("Application Info: passcode needed..."));
|
||||
}
|
||||
}
|
||||
|
||||
void Application::startSettingsAndBackground() {
|
||||
Local::rewriteSettingsIfNeeded();
|
||||
Window::Theme::Background()->start();
|
||||
checkSystemDarkMode();
|
||||
}
|
||||
|
||||
void Application::checkSystemDarkMode() {
|
||||
const auto maybeDarkMode = _settings.systemDarkMode();
|
||||
const auto darkModeEnabled = _settings.systemDarkModeEnabled();
|
||||
const auto needToSwitch = darkModeEnabled
|
||||
&& maybeDarkMode
|
||||
&& (*maybeDarkMode != Window::Theme::IsNightMode());
|
||||
if (needToSwitch) {
|
||||
Window::Theme::ToggleNightMode();
|
||||
Window::Theme::KeepApplied();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::startSystemDarkModeViewer() {
|
||||
if (Window::Theme::Background()->editingTheme()) {
|
||||
_settings.setSystemDarkModeEnabled(false);
|
||||
}
|
||||
rpl::merge(
|
||||
_settings.systemDarkModeChanges() | rpl::to_empty,
|
||||
_settings.systemDarkModeEnabledChanges() | rpl::to_empty
|
||||
) | rpl::start_with_next([=] {
|
||||
checkSystemDarkMode();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
auto Application::prepareEmojiSourceImages()
|
||||
-> std::shared_ptr<Ui::Emoji::UniversalImages> {
|
||||
const auto &images = Ui::Emoji::SourceImages();
|
||||
|
||||
@@ -140,6 +140,7 @@ public:
|
||||
[[nodiscard]] QWidget *getFileDialogParent();
|
||||
void notifyFileDialogShown(bool shown);
|
||||
[[nodiscard]] QWidget *getModalParent();
|
||||
void checkSystemDarkMode();
|
||||
|
||||
// Media view interface.
|
||||
void checkMediaViewActivation();
|
||||
@@ -161,6 +162,7 @@ public:
|
||||
return _logoNoMargin;
|
||||
}
|
||||
|
||||
void startSettingsAndBackground();
|
||||
[[nodiscard]] Settings &settings() {
|
||||
return _settings;
|
||||
}
|
||||
@@ -290,7 +292,9 @@ private:
|
||||
-> std::shared_ptr<Ui::Emoji::UniversalImages>;
|
||||
void startLocalStorage();
|
||||
void startShortcuts();
|
||||
void startDomain();
|
||||
void startEmojiImageLoader();
|
||||
void startSystemDarkModeViewer();
|
||||
|
||||
void stateChanged(Qt::ApplicationState state);
|
||||
|
||||
@@ -331,6 +335,11 @@ private:
|
||||
base::Timer _clearEmojiImageLoaderTimer;
|
||||
const std::unique_ptr<Media::Audio::Instance> _audio;
|
||||
mutable std::unique_ptr<MTP::Config> _fallbackProductionConfig;
|
||||
|
||||
// Notifications should be destroyed before _audio, after _domain.
|
||||
// Mutable because is created in run() after OpenSSL is inited.
|
||||
std::unique_ptr<Window::Notifications::System> _notifications;
|
||||
|
||||
const std::unique_ptr<Main::Domain> _domain;
|
||||
const std::unique_ptr<Export::Manager> _exportManager;
|
||||
const std::unique_ptr<Calls::Instance> _calls;
|
||||
@@ -347,10 +356,6 @@ private:
|
||||
Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr;
|
||||
Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr;
|
||||
|
||||
// Notifications should be destroyed before _audio.
|
||||
// Mutable because is created in run() after OpenSSL is inited.
|
||||
std::unique_ptr<Window::Notifications::System> _notifications;
|
||||
|
||||
const QImage _logo;
|
||||
const QImage _logoNoMargin;
|
||||
|
||||
|
||||
@@ -23,37 +23,78 @@ std::map<int, const char*> BetaLogs() {
|
||||
return {
|
||||
{
|
||||
1009020,
|
||||
"\xE2\x80\xA2 Fix crash in shared links search.\n"
|
||||
"- Fix crash in shared links search.\n"
|
||||
|
||||
"\xE2\x80\xA2 Fix blurred thumbnails in albums with video files.\n"
|
||||
"- Fix blurred thumbnails in albums with video files.\n"
|
||||
|
||||
"\xE2\x80\xA2 Fix a possible crash in animated stickers rendering."
|
||||
"- Fix a possible crash in animated stickers rendering."
|
||||
},
|
||||
{
|
||||
1009022,
|
||||
"\xE2\x80\xA2 Organize chats into Chat Folders "
|
||||
"if you have too many chats.\n"
|
||||
"- Organize chats into Chat Folders if you have too many chats.\n"
|
||||
},
|
||||
{
|
||||
2000001,
|
||||
"\xE2\x80\xA2 Switch between folders using Ctrl+1, ..., Ctrl+8.\n"
|
||||
"- Switch between folders using Ctrl+1, ..., Ctrl+8.\n"
|
||||
|
||||
"\xE2\x80\xA2 Fix crash when a pinned in folder chat "
|
||||
"was added to archive.\n"
|
||||
"- Fix crash when a pinned in folder chat was added to archive.\n"
|
||||
|
||||
"\xE2\x80\xA2 Fix font issues in Linux version."
|
||||
"- Fix font issues in Linux version."
|
||||
},
|
||||
{
|
||||
2001008,
|
||||
"\xE2\x80\xA2 Add support for full group message history export.\n"
|
||||
"- Add support for full group message history export.\n"
|
||||
|
||||
"\xE2\x80\xA2 Allow export of a single chat message history "
|
||||
"in JSON format."
|
||||
"- Allow export of a single chat message history in JSON format."
|
||||
},
|
||||
{
|
||||
2001014,
|
||||
"\xE2\x80\xA2 Support for multiple accounts."
|
||||
}
|
||||
"- Support for multiple accounts."
|
||||
},
|
||||
{
|
||||
2001017,
|
||||
"- Fix messages editing in a non-active account.\n"
|
||||
|
||||
"- Fix large animated emoji messages editing.\n"
|
||||
|
||||
"- Fix high definition GIF animations opening in media viewer.\n"
|
||||
|
||||
"- Multiple crash fixes."
|
||||
},
|
||||
{
|
||||
2001018,
|
||||
"- Fix a possible crash in Picture-in-Picture video player.\n"
|
||||
|
||||
"- Fix copying links from message texts.\n"
|
||||
|
||||
"- Raise file size limit to 2000 MB.\n"
|
||||
|
||||
"- Allow using system window frame in Windows and Linux."
|
||||
},
|
||||
{
|
||||
2001019,
|
||||
"- File uploading in an inactive account correctly finishes.\n"
|
||||
|
||||
"- Stickers panel works correctly after switching between accounts.\n"
|
||||
|
||||
"- Large .webp files are not shown as stickers.\n"
|
||||
|
||||
"- MacBook TouchBar support was fully rewritten with fixes for multiple accounts.\n"
|
||||
|
||||
"- Custom window title bar works in all Linux versions.\n"
|
||||
|
||||
"- Passcode doesn't auto-lock while you're active in other apps on Linux X11."
|
||||
},
|
||||
{
|
||||
2001021,
|
||||
"- Edit your scheduled messages.\n"
|
||||
|
||||
"- See the unread messages indicator for your additional accounts on the main menu button.\n"
|
||||
|
||||
"- Use Auto-Night Mode to make Telegram night mode match the system Dark Mode settings.\n"
|
||||
|
||||
"- Enjoy dark native window frame for Telegram night mode on Windows.\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -161,10 +202,18 @@ void Changelogs::addBetaLog(int changeVersion, const char *changes) {
|
||||
if (_oldVersion >= changeVersion) {
|
||||
return;
|
||||
}
|
||||
const auto text = [&] {
|
||||
static const auto simple = u"\n- "_q;
|
||||
static const auto separator = QString::fromUtf8("\n\xE2\x80\xA2 ");
|
||||
auto result = QString::fromUtf8(changes).trimmed();
|
||||
if (result.startsWith(simple.midRef(1))) {
|
||||
result = separator.mid(1) + result.mid(simple.size() - 1);
|
||||
}
|
||||
return result.replace(simple, separator);
|
||||
}();
|
||||
const auto version = FormatVersionDisplay(changeVersion);
|
||||
const auto text = qsl("New in version %1:\n\n").arg(version)
|
||||
+ QString::fromUtf8(changes).trimmed();
|
||||
addLocalLog(text);
|
||||
const auto log = qsl("New in version %1:\n\n").arg(version) + text;
|
||||
addLocalLog(log);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -94,7 +94,20 @@ QByteArray Settings::serialize() const {
|
||||
}
|
||||
stream
|
||||
<< qint32(_autoDownloadDictionaries.current() ? 1 : 0)
|
||||
<< qint32(_mainMenuAccountsShown.current() ? 1 : 0);
|
||||
<< qint32(_mainMenuAccountsShown.current() ? 1 : 0)
|
||||
<< qint32(_tabbedSelectorSectionEnabled ? 1 : 0)
|
||||
<< qint32(_floatPlayerColumn)
|
||||
<< qint32(_floatPlayerCorner)
|
||||
<< qint32(_thirdSectionInfoEnabled ? 1 : 0)
|
||||
<< qint32(snap(
|
||||
qRound(_dialogsWidthRatio.current() * 1000000),
|
||||
0,
|
||||
1000000))
|
||||
<< qint32(_thirdColumnWidth.current())
|
||||
<< qint32(_thirdSectionExtendedBy)
|
||||
<< qint32(_notifyFromAll ? 1 : 0)
|
||||
<< qint32(_nativeWindowFrame.current() ? 1 : 0)
|
||||
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -150,6 +163,16 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
std::vector<int> dictionariesEnabled;
|
||||
qint32 autoDownloadDictionaries = _autoDownloadDictionaries.current() ? 1 : 0;
|
||||
qint32 mainMenuAccountsShown = _mainMenuAccountsShown.current() ? 1 : 0;
|
||||
qint32 tabbedSelectorSectionEnabled = 1;
|
||||
qint32 floatPlayerColumn = static_cast<qint32>(Window::Column::Second);
|
||||
qint32 floatPlayerCorner = static_cast<qint32>(RectPart::TopRight);
|
||||
qint32 thirdSectionInfoEnabled = 0;
|
||||
float64 dialogsWidthRatio = _dialogsWidthRatio.current();
|
||||
qint32 thirdColumnWidth = _thirdColumnWidth.current();
|
||||
qint32 thirdSectionExtendedBy = _thirdSectionExtendedBy;
|
||||
qint32 notifyFromAll = _notifyFromAll ? 1 : 0;
|
||||
qint32 nativeWindowFrame = _nativeWindowFrame.current() ? 1 : 0;
|
||||
qint32 systemDarkModeEnabled = _systemDarkModeEnabled.current() ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -211,6 +234,25 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
>> autoDownloadDictionaries
|
||||
>> mainMenuAccountsShown;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
auto dialogsWidthRatioInt = qint32();
|
||||
stream
|
||||
>> tabbedSelectorSectionEnabled
|
||||
>> floatPlayerColumn
|
||||
>> floatPlayerCorner
|
||||
>> thirdSectionInfoEnabled
|
||||
>> dialogsWidthRatioInt
|
||||
>> thirdColumnWidth
|
||||
>> thirdSectionExtendedBy
|
||||
>> notifyFromAll;
|
||||
dialogsWidthRatio = snap(dialogsWidthRatioInt / 1000000., 0., 1.);
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> nativeWindowFrame;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -281,6 +323,30 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_dictionariesEnabled = std::move(dictionariesEnabled);
|
||||
_autoDownloadDictionaries = (autoDownloadDictionaries == 1);
|
||||
_mainMenuAccountsShown = (mainMenuAccountsShown == 1);
|
||||
_tabbedSelectorSectionEnabled = (tabbedSelectorSectionEnabled == 1);
|
||||
auto uncheckedColumn = static_cast<Window::Column>(floatPlayerColumn);
|
||||
switch (uncheckedColumn) {
|
||||
case Window::Column::First:
|
||||
case Window::Column::Second:
|
||||
case Window::Column::Third: _floatPlayerColumn = uncheckedColumn; break;
|
||||
}
|
||||
auto uncheckedCorner = static_cast<RectPart>(floatPlayerCorner);
|
||||
switch (uncheckedCorner) {
|
||||
case RectPart::TopLeft:
|
||||
case RectPart::TopRight:
|
||||
case RectPart::BottomLeft:
|
||||
case RectPart::BottomRight: _floatPlayerCorner = uncheckedCorner; break;
|
||||
}
|
||||
_thirdSectionInfoEnabled = thirdSectionInfoEnabled;
|
||||
_dialogsWidthRatio = dialogsWidthRatio;
|
||||
_thirdColumnWidth = thirdColumnWidth;
|
||||
_thirdSectionExtendedBy = thirdSectionExtendedBy;
|
||||
if (_thirdSectionInfoEnabled) {
|
||||
_tabbedSelectorSectionEnabled = false;
|
||||
}
|
||||
_notifyFromAll = (notifyFromAll == 1);
|
||||
_nativeWindowFrame = (nativeWindowFrame == 1);
|
||||
_systemDarkModeEnabled = (systemDarkModeEnabled == 1);
|
||||
}
|
||||
|
||||
bool Settings::chatWide() const {
|
||||
@@ -404,7 +470,7 @@ void Settings::resetOnLastLogout() {
|
||||
//_videoPipGeometry = QByteArray();
|
||||
_dictionariesEnabled = std::vector<int>();
|
||||
_autoDownloadDictionaries = true;
|
||||
_mainMenuAccountsShown = false;
|
||||
_mainMenuAccountsShown = true;
|
||||
_tabbedSelectorSectionEnabled = false; // per-window
|
||||
_floatPlayerColumn = Window::Column::Second; // per-window
|
||||
_floatPlayerCorner = RectPart::TopRight; // per-window
|
||||
@@ -412,7 +478,9 @@ void Settings::resetOnLastLogout() {
|
||||
_thirdSectionExtendedBy = -1; // per-window
|
||||
_dialogsWidthRatio = DefaultDialogsWidthRatio(); // per-window
|
||||
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
}
|
||||
|
||||
bool Settings::ThirdColumnByDefault() {
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "window/themes/window_themes_embedded.h"
|
||||
#include "window/window_controls_layout.h"
|
||||
|
||||
enum class SendFilesWay;
|
||||
enum class RectPart;
|
||||
@@ -401,6 +402,57 @@ public:
|
||||
void setThirdColumnWidth(int width);
|
||||
[[nodiscard]] int thirdColumnWidth() const;
|
||||
[[nodiscard]] rpl::producer<int> thirdColumnWidthChanges() const;
|
||||
void setNotifyFromAll(bool value) {
|
||||
_notifyFromAll = value;
|
||||
}
|
||||
[[nodiscard]] bool notifyFromAll() const {
|
||||
return _notifyFromAll;
|
||||
}
|
||||
void setNativeWindowFrame(bool value) {
|
||||
_nativeWindowFrame = value;
|
||||
}
|
||||
[[nodiscard]] bool nativeWindowFrame() const {
|
||||
return _nativeWindowFrame.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> nativeWindowFrameChanges() const {
|
||||
return _nativeWindowFrame.changes();
|
||||
}
|
||||
void setSystemDarkMode(std::optional<bool> value) {
|
||||
_systemDarkMode = value;
|
||||
}
|
||||
[[nodiscard]] std::optional<bool> systemDarkMode() const {
|
||||
return _systemDarkMode.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<std::optional<bool>> systemDarkModeValue() const {
|
||||
return _systemDarkMode.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<std::optional<bool>> systemDarkModeChanges() const {
|
||||
return _systemDarkMode.changes();
|
||||
}
|
||||
void setSystemDarkModeEnabled(bool value) {
|
||||
_systemDarkModeEnabled = value;
|
||||
}
|
||||
[[nodiscard]] bool systemDarkModeEnabled() const {
|
||||
return _systemDarkModeEnabled.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> systemDarkModeEnabledValue() const {
|
||||
return _systemDarkModeEnabled.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> systemDarkModeEnabledChanges() const {
|
||||
return _systemDarkModeEnabled.changes();
|
||||
}
|
||||
void setWindowControlsLayout(Window::ControlsLayout value) {
|
||||
_windowControlsLayout = value;
|
||||
}
|
||||
[[nodiscard]] Window::ControlsLayout windowControlsLayout() const {
|
||||
return _windowControlsLayout.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Window::ControlsLayout> windowControlsLayoutValue() const {
|
||||
return _windowControlsLayout.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Window::ControlsLayout> windowControlsLayoutChanges() const {
|
||||
return _windowControlsLayout.changes();
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] float64 DefaultDialogsWidthRatio();
|
||||
@@ -458,7 +510,7 @@ private:
|
||||
QByteArray _videoPipGeometry;
|
||||
rpl::variable<std::vector<int>> _dictionariesEnabled;
|
||||
rpl::variable<bool> _autoDownloadDictionaries = true;
|
||||
rpl::variable<bool> _mainMenuAccountsShown = false;
|
||||
rpl::variable<bool> _mainMenuAccountsShown = true;
|
||||
bool _tabbedSelectorSectionEnabled = false; // per-window
|
||||
Window::Column _floatPlayerColumn; // per-window
|
||||
RectPart _floatPlayerCorner; // per-window
|
||||
@@ -467,6 +519,11 @@ private:
|
||||
int _thirdSectionExtendedBy = -1; // per-window
|
||||
rpl::variable<float64> _dialogsWidthRatio; // per-window
|
||||
rpl::variable<int> _thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
|
||||
bool _notifyFromAll = true;
|
||||
rpl::variable<bool> _nativeWindowFrame = false;
|
||||
rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = false;
|
||||
rpl::variable<Window::ControlsLayout> _windowControlsLayout;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -65,8 +65,6 @@ AnnotationRefs ProcessAnnotationRefs;
|
||||
QString ReportPath;
|
||||
FILE *ReportFile = nullptr;
|
||||
int ReportFileNo = 0;
|
||||
char LaunchedDateTimeStr[32] = { 0 };
|
||||
char LaunchedBinaryName[256] = { 0 };
|
||||
|
||||
void SafeWriteChar(char ch) {
|
||||
fwrite(&ch, 1, 1, ReportFile);
|
||||
|
||||
@@ -282,6 +282,19 @@ void Launcher::init() {
|
||||
|
||||
prepareSettings();
|
||||
|
||||
static QtMessageHandler originalMessageHandler = nullptr;
|
||||
originalMessageHandler = qInstallMessageHandler([](
|
||||
QtMsgType type,
|
||||
const QMessageLogContext &context,
|
||||
const QString &msg) {
|
||||
if (originalMessageHandler) {
|
||||
originalMessageHandler(type, context, msg);
|
||||
}
|
||||
if (Logs::DebugEnabled() || !Logs::started()) {
|
||||
LOG((msg));
|
||||
}
|
||||
});
|
||||
|
||||
QApplication::setApplicationName(qsl("TelegramDesktop"));
|
||||
|
||||
#if defined Q_OS_UNIX && !defined Q_OS_MAC && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/local_url_handlers.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_chat_invite.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
@@ -50,31 +51,7 @@ bool JoinGroupByHash(
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto hash = match->captured(1);
|
||||
const auto session = &controller->session();
|
||||
const auto weak = base::make_weak(controller);
|
||||
session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
|
||||
Core::App().hideMediaView();
|
||||
result.match([=](const MTPDchatInvite &data) {
|
||||
Ui::show(Box<ConfirmInviteBox>(session, data, [=] {
|
||||
session->api().importChatInvite(hash);
|
||||
}));
|
||||
}, [=](const MTPDchatInviteAlready &data) {
|
||||
if (const auto chat = session->data().processChat(data.vchat())) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showPeerHistory(
|
||||
chat,
|
||||
Window::SectionShow::Way::Forward);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [=](const RPCError &error) {
|
||||
if (error.code() != 400) {
|
||||
return;
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
});
|
||||
Api::CheckChatInvite(controller, match->captured(1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -298,15 +275,12 @@ bool ResolveUsername(
|
||||
post = ShowAtGameShareMsgId;
|
||||
}
|
||||
const auto clickFromMessageId = context.value<FullMsgId>();
|
||||
if (const auto controller = App::wnd()->sessionController()) {
|
||||
controller->content()->openPeerByName(
|
||||
domain,
|
||||
post,
|
||||
startToken,
|
||||
clickFromMessageId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
controller->content()->openPeerByName(
|
||||
domain,
|
||||
post,
|
||||
startToken,
|
||||
clickFromMessageId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolvePrivatePost(
|
||||
@@ -324,12 +298,12 @@ bool ResolvePrivatePost(
|
||||
if (!channelId || !IsServerMsgId(msgId)) {
|
||||
return false;
|
||||
}
|
||||
const auto done = [=](not_null<PeerData*> peer) {
|
||||
App::wnd()->sessionController()->showPeerHistory(
|
||||
const auto done = crl::guard(controller, [=](not_null<PeerData*> peer) {
|
||||
controller->showPeerHistory(
|
||||
peer->id,
|
||||
Window::SectionShow::Way::Forward,
|
||||
msgId);
|
||||
};
|
||||
});
|
||||
const auto fail = [=] {
|
||||
Ui::show(Box<InformBox>(tr::lng_error_post_link_invalid(tr::now)));
|
||||
};
|
||||
@@ -391,7 +365,7 @@ bool HandleUnknown(
|
||||
return false;
|
||||
}
|
||||
const auto request = match->captured(1);
|
||||
const auto callback = [=](const MTPDhelp_deepLinkInfo &result) {
|
||||
const auto callback = crl::guard(controller, [=](const MTPDhelp_deepLinkInfo &result) {
|
||||
const auto text = TextWithEntities{
|
||||
qs(result.vmessage()),
|
||||
Api::EntitiesFromMTP(
|
||||
@@ -411,7 +385,7 @@ bool HandleUnknown(
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(text));
|
||||
}
|
||||
};
|
||||
});
|
||||
controller->session().api().requestDeepLinkInfo(request, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -102,9 +102,20 @@ MimeType MimeTypeForData(const QByteArray &data) {
|
||||
return MimeType(QMimeDatabase().mimeTypeForData(data));
|
||||
}
|
||||
|
||||
bool IsMimeStickerAnimated(const QString &mime) {
|
||||
return mime == qsl("application/x-tgsticker");
|
||||
}
|
||||
|
||||
bool IsMimeSticker(const QString &mime) {
|
||||
return mime == qsl("image/webp")
|
||||
|| mime == qsl("application/x-tgsticker");
|
||||
|| IsMimeStickerAnimated(mime);
|
||||
}
|
||||
|
||||
bool IsMimeAcceptedForAlbum(const QString &mime) {
|
||||
return (mime == u"image/jpeg"_q)
|
||||
|| (mime == u"image/png"_q)
|
||||
|| (mime == u"video/mp4"_q)
|
||||
|| (mime == u"video/quicktime"_q);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -39,6 +39,8 @@ MimeType MimeTypeForName(const QString &mime);
|
||||
MimeType MimeTypeForFile(const QFileInfo &file);
|
||||
MimeType MimeTypeForData(const QByteArray &data);
|
||||
|
||||
bool IsMimeStickerAnimated(const QString &mime);
|
||||
bool IsMimeSticker(const QString &mime);
|
||||
bool IsMimeAcceptedForAlbum(const QString &mime);
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -74,11 +74,11 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
||||
{ qsl("first_chat") , Command::ChatFirst },
|
||||
{ qsl("last_chat") , Command::ChatLast },
|
||||
{ qsl("self_chat") , Command::ChatSelf },
|
||||
|
||||
|
||||
{ qsl("previous_folder") , Command::FolderPrevious },
|
||||
{ qsl("next_folder") , Command::FolderNext },
|
||||
{ qsl("all_chats") , Command::ShowAllChats },
|
||||
|
||||
|
||||
{ qsl("folder1") , Command::ShowFolder1 },
|
||||
{ qsl("folder2") , Command::ShowFolder2 },
|
||||
{ qsl("folder3") , Command::ShowFolder3 },
|
||||
@@ -116,11 +116,11 @@ const auto CommandNames = base::flat_map<Command, QString>{
|
||||
{ Command::ChatFirst , qsl("first_chat") },
|
||||
{ Command::ChatLast , qsl("last_chat") },
|
||||
{ Command::ChatSelf , qsl("self_chat") },
|
||||
|
||||
|
||||
{ Command::FolderPrevious , qsl("previous_folder") },
|
||||
{ Command::FolderNext , qsl("next_folder") },
|
||||
{ Command::ShowAllChats , qsl("all_chats") },
|
||||
|
||||
|
||||
{ Command::ShowFolder1 , qsl("folder1") },
|
||||
{ Command::ShowFolder2 , qsl("folder2") },
|
||||
{ Command::ShowFolder3 , qsl("folder3") },
|
||||
@@ -559,7 +559,6 @@ bool HandleEvent(not_null<QShortcutEvent*> event) {
|
||||
void ToggleMediaShortcuts(bool toggled) {
|
||||
Data.toggleMedia(toggled);
|
||||
Platform::SetWatchingMediaKeys(toggled);
|
||||
Media::Player::instance()->playerWidgetToggledNotify(toggled);
|
||||
}
|
||||
|
||||
void ToggleSupportShortcuts(bool toggled) {
|
||||
|
||||
@@ -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 = 2001014;
|
||||
constexpr auto AppVersionStr = "2.1.14";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 2002000;
|
||||
constexpr auto AppVersionStr = "2.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -309,6 +309,18 @@ bool ShouldAutoPlay(
|
||||
document->size);
|
||||
}
|
||||
|
||||
bool ShouldAutoPlay(
|
||||
const Full &data,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo) {
|
||||
const auto source = SourceFromPeer(peer);
|
||||
const auto size = photo->videoByteSize();
|
||||
return photo->hasVideo()
|
||||
&& (data.shouldDownload(source, Type::AutoPlayGIF, size)
|
||||
|| data.shouldDownload(source, Type::AutoPlayVideo, size)
|
||||
|| data.shouldDownload(source, Type::AutoPlayVideoMessage, size));
|
||||
}
|
||||
|
||||
Full WithDisabledAutoPlay(const Full &data) {
|
||||
auto result = data;
|
||||
for (const auto source : enums_view<Source>(kSourcesCount)) {
|
||||
|
||||
@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Data {
|
||||
namespace AutoDownload {
|
||||
|
||||
constexpr auto kMaxBytesLimit = 3000 * 512 * 1024;
|
||||
constexpr auto kMaxBytesLimit = 4000 * 512 * 1024;
|
||||
|
||||
enum class Source {
|
||||
User = 0x00,
|
||||
@@ -120,6 +120,10 @@ private:
|
||||
const Full &data,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<DocumentData*> document);
|
||||
[[nodiscard]] bool ShouldAutoPlay(
|
||||
const Full &data,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo);
|
||||
|
||||
[[nodiscard]] Full WithDisabledAutoPlay(const Full &data);
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ template <typename DataType, typename UpdateType>
|
||||
void Changes::Manager<DataType, UpdateType>::sendRealtimeNotifications(
|
||||
not_null<DataType*> data,
|
||||
Flags flags) {
|
||||
auto clearRealtime = false;
|
||||
for (auto i = 0; i != kCount; ++i) {
|
||||
const auto flag = static_cast<Flag>(1U << i);
|
||||
if (flags & flag) {
|
||||
|
||||
@@ -109,12 +109,13 @@ struct HistoryUpdate {
|
||||
LocalMessages = (1 << 5),
|
||||
ChatOccupied = (1 << 6),
|
||||
MessageSent = (1 << 7),
|
||||
ForwardDraft = (1 << 8),
|
||||
OutboxRead = (1 << 9),
|
||||
BotKeyboard = (1 << 10),
|
||||
CloudDraft = (1 << 11),
|
||||
ScheduledSent = (1 << 8),
|
||||
ForwardDraft = (1 << 9),
|
||||
OutboxRead = (1 << 10),
|
||||
BotKeyboard = (1 << 11),
|
||||
CloudDraft = (1 << 12),
|
||||
|
||||
LastUsedBit = (1 << 11),
|
||||
LastUsedBit = (1 << 12),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
@@ -134,8 +135,9 @@ struct MessageUpdate {
|
||||
DialogRowRefresh = (1 << 3),
|
||||
CallAdded = (1 << 4),
|
||||
ReplyMarkup = (1 << 5),
|
||||
BotCallbackSent = (1 << 6),
|
||||
|
||||
LastUsedBit = (1 << 5),
|
||||
LastUsedBit = (1 << 6),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "api/api_chat_invite.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace {
|
||||
@@ -632,6 +633,40 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
|
||||
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
|
||||
}
|
||||
|
||||
void ChannelData::setInvitePeek(const QString &hash, TimeId expires) {
|
||||
if (!_invitePeek) {
|
||||
_invitePeek = std::make_unique<InvitePeek>();
|
||||
}
|
||||
_invitePeek->hash = hash;
|
||||
_invitePeek->expires = expires;
|
||||
}
|
||||
|
||||
void ChannelData::clearInvitePeek() {
|
||||
_invitePeek = nullptr;
|
||||
}
|
||||
|
||||
TimeId ChannelData::invitePeekExpires() const {
|
||||
return _invitePeek ? _invitePeek->expires : 0;
|
||||
}
|
||||
|
||||
QString ChannelData::invitePeekHash() const {
|
||||
return _invitePeek ? _invitePeek->hash : QString();
|
||||
}
|
||||
|
||||
void ChannelData::privateErrorReceived() {
|
||||
if (const auto expires = invitePeekExpires()) {
|
||||
const auto hash = invitePeekHash();
|
||||
for (const auto window : session().windows()) {
|
||||
clearInvitePeek();
|
||||
Api::CheckChatInvite(window, hash, this);
|
||||
return;
|
||||
}
|
||||
_invitePeek->expires = base::unixtime::now();
|
||||
} else {
|
||||
markForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
void ApplyMigration(
|
||||
|
||||
@@ -386,6 +386,12 @@ public:
|
||||
[[nodiscard]] TimeId slowmodeLastMessage() const;
|
||||
void growSlowmodeLastMessage(TimeId when);
|
||||
|
||||
void setInvitePeek(const QString &hash, TimeId expires);
|
||||
void clearInvitePeek();
|
||||
[[nodiscard]] TimeId invitePeekExpires() const;
|
||||
[[nodiscard]] QString invitePeekHash() const;
|
||||
void privateErrorReceived();
|
||||
|
||||
// Still public data members.
|
||||
uint64 access = 0;
|
||||
|
||||
@@ -401,6 +407,11 @@ public:
|
||||
TimeId inviteDate = 0;
|
||||
|
||||
private:
|
||||
struct InvitePeek {
|
||||
QString hash;
|
||||
TimeId expires = 0;
|
||||
};
|
||||
|
||||
auto unavailableReasons() const
|
||||
-> const std::vector<Data::UnavailableReason> & override;
|
||||
bool canEditLastAdmin(not_null<UserData*> user) const;
|
||||
@@ -423,6 +434,7 @@ private:
|
||||
TimeId _restrictedUntil;
|
||||
|
||||
std::vector<Data::UnavailableReason> _unavailableReasons;
|
||||
std::unique_ptr<InvitePeek> _invitePeek;
|
||||
QString _inviteLink;
|
||||
ChannelData *_linkedChat = nullptr;
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
|
||||
}
|
||||
|
||||
ChatFilters::ChatFilters(not_null<Session*> owner) : _owner(owner) {
|
||||
load();
|
||||
crl::on_main(&owner->session(), [=] { load(); });
|
||||
}
|
||||
|
||||
ChatFilters::~ChatFilters() = default;
|
||||
@@ -232,6 +232,16 @@ not_null<Dialogs::MainList*> ChatFilters::chatsList(FilterId filterId) {
|
||||
return pointer.get();
|
||||
}
|
||||
|
||||
void ChatFilters::setPreloaded(const QVector<MTPDialogFilter> &result) {
|
||||
_loadRequestId = -1;
|
||||
received(result);
|
||||
crl::on_main(&_owner->session(), [=] {
|
||||
if (_loadRequestId == -1) {
|
||||
_loadRequestId = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ChatFilters::load() {
|
||||
load(false);
|
||||
}
|
||||
@@ -244,40 +254,44 @@ void ChatFilters::load(bool force) {
|
||||
api.request(_loadRequestId).cancel();
|
||||
_loadRequestId = api.request(MTPmessages_GetDialogFilters(
|
||||
)).done([=](const MTPVector<MTPDialogFilter> &result) {
|
||||
auto position = 0;
|
||||
auto changed = false;
|
||||
for (const auto &filter : result.v) {
|
||||
auto parsed = ChatFilter::FromTL(filter, _owner);
|
||||
const auto b = begin(_list) + position, e = end(_list);
|
||||
const auto i = ranges::find(b, e, parsed.id(), &ChatFilter::id);
|
||||
if (i == e) {
|
||||
applyInsert(std::move(parsed), position);
|
||||
changed = true;
|
||||
} else if (i == b) {
|
||||
if (applyChange(*b, std::move(parsed))) {
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
std::swap(*i, *b);
|
||||
applyChange(*b, std::move(parsed));
|
||||
changed = true;
|
||||
}
|
||||
++position;
|
||||
}
|
||||
while (position < _list.size()) {
|
||||
applyRemove(position);
|
||||
changed = true;
|
||||
}
|
||||
if (changed || !_loaded) {
|
||||
_loaded = true;
|
||||
_listChanged.fire({});
|
||||
}
|
||||
received(result.v);
|
||||
_loadRequestId = 0;
|
||||
}).fail([=](const RPCError &error) {
|
||||
_loadRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ChatFilters::received(const QVector<MTPDialogFilter> &list) {
|
||||
auto position = 0;
|
||||
auto changed = false;
|
||||
for (const auto &filter : list) {
|
||||
auto parsed = ChatFilter::FromTL(filter, _owner);
|
||||
const auto b = begin(_list) + position, e = end(_list);
|
||||
const auto i = ranges::find(b, e, parsed.id(), &ChatFilter::id);
|
||||
if (i == e) {
|
||||
applyInsert(std::move(parsed), position);
|
||||
changed = true;
|
||||
} else if (i == b) {
|
||||
if (applyChange(*b, std::move(parsed))) {
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
std::swap(*i, *b);
|
||||
applyChange(*b, std::move(parsed));
|
||||
changed = true;
|
||||
}
|
||||
++position;
|
||||
}
|
||||
while (position < _list.size()) {
|
||||
applyRemove(position);
|
||||
changed = true;
|
||||
}
|
||||
if (changed || !_loaded) {
|
||||
_loaded = true;
|
||||
_listChanged.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
void ChatFilters::apply(const MTPUpdate &update) {
|
||||
update.match([&](const MTPDupdateDialogFilter &data) {
|
||||
if (const auto filter = data.vfilter()) {
|
||||
|
||||
@@ -95,6 +95,8 @@ public:
|
||||
explicit ChatFilters(not_null<Session*> owner);
|
||||
~ChatFilters();
|
||||
|
||||
void setPreloaded(const QVector<MTPDialogFilter> &result);
|
||||
|
||||
void load();
|
||||
void apply(const MTPUpdate &update);
|
||||
void set(ChatFilter filter);
|
||||
@@ -125,6 +127,7 @@ public:
|
||||
|
||||
private:
|
||||
void load(bool force);
|
||||
void received(const QVector<MTPDialogFilter> &list);
|
||||
bool applyOrder(const QVector<MTPint> &order);
|
||||
bool applyChange(ChatFilter &filter, ChatFilter &&updated);
|
||||
void applyInsert(ChatFilter filter, int position);
|
||||
|
||||
@@ -26,7 +26,7 @@ void CloudImageView::set(
|
||||
not_null<Main::Session*> session,
|
||||
QImage image) {
|
||||
_image.emplace(std::move(image));
|
||||
session->downloaderTaskFinished().notify();
|
||||
session->notifyDownloaderTaskFinished();
|
||||
}
|
||||
|
||||
CloudImage::CloudImage() = default;
|
||||
@@ -100,8 +100,6 @@ bool CloudImage::failed() const {
|
||||
}
|
||||
|
||||
void CloudImage::load(not_null<Main::Session*> session, FileOrigin origin) {
|
||||
const auto fromCloud = LoadFromCloudOrLocal;
|
||||
const auto cacheTag = kImageCacheTag;
|
||||
const auto autoLoading = false;
|
||||
const auto finalCheck = [=] {
|
||||
if (const auto active = activeView()) {
|
||||
|
||||
@@ -222,8 +222,7 @@ void CloudThemes::loadDocumentAndInvoke(
|
||||
return;
|
||||
}
|
||||
if (!alreadyWaiting) {
|
||||
base::ObservableViewer(
|
||||
_session->downloaderTaskFinished()
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::filter([=, &value] {
|
||||
return value.documentMedia->loaded();
|
||||
}) | rpl::start_with_next([=, &value] {
|
||||
|
||||
@@ -29,8 +29,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/file_download_mtproto.h"
|
||||
#include "storage/file_download_web.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/media/history_view_gif.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
@@ -44,7 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
const auto kAnimatedStickerDimensions = QSize(512, 512);
|
||||
const auto kAnimatedStickerDimensions = QSize(
|
||||
kStickerSideSize,
|
||||
kStickerSideSize);
|
||||
|
||||
QString JoinStringList(const QStringList &list, const QString &separator) {
|
||||
const auto count = list.size();
|
||||
@@ -79,11 +84,21 @@ void LaunchWithWarning(
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
if (!warn) {
|
||||
const auto extension = '.' + Data::FileExtension(name);
|
||||
if (Platform::IsWindows() && extension == u"."_q) {
|
||||
// If you launch a file without extension, like "test", in case
|
||||
// there is an executable file with the same name in this folder,
|
||||
// like "test.bat", the executable file will be launched.
|
||||
//
|
||||
// Now we always force an Open With dialog box for such files.
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeShowOpenWith(name);
|
||||
});
|
||||
return;
|
||||
} else if (!warn) {
|
||||
File::Launch(name);
|
||||
return;
|
||||
}
|
||||
const auto extension = '.' + Data::FileExtension(name);
|
||||
const auto callback = [=](bool checked) {
|
||||
if (checked) {
|
||||
Core::App().settings().setExeLaunchWarning(false);
|
||||
@@ -340,7 +355,9 @@ void DocumentOpenClickHandler::Open(
|
||||
|| data->isVideoMessage()) {
|
||||
const auto msgId = context ? context->fullId() : FullMsgId();
|
||||
Media::Player::instance()->playPause({ data, msgId });
|
||||
} else if (context && data->isAnimation()) {
|
||||
} else if (context
|
||||
&& data->isAnimation()
|
||||
&& HistoryView::Gif::CanPlayInline(data)) {
|
||||
data->owner().requestAnimationPlayInline(context);
|
||||
} else {
|
||||
Core::App().showDocument(data, context);
|
||||
@@ -555,15 +572,14 @@ void DocumentData::setattributes(
|
||||
}, [&](const MTPDdocumentAttributeHasStickers &data) {
|
||||
});
|
||||
}
|
||||
if (type == StickerDocument) {
|
||||
if (dimensions.width() <= 0
|
||||
|| dimensions.height() <= 0
|
||||
|| dimensions.width() > StickerMaxSize
|
||||
|| dimensions.height() > StickerMaxSize
|
||||
|| !saveToCache()) {
|
||||
type = FileDocument;
|
||||
_additional = nullptr;
|
||||
}
|
||||
if (type == StickerDocument
|
||||
&& ((size > Storage::kMaxStickerBytesSize)
|
||||
|| (!sticker()->animated
|
||||
&& !GoodStickerDimensions(
|
||||
dimensions.width(),
|
||||
dimensions.height())))) {
|
||||
type = FileDocument;
|
||||
_additional = nullptr;
|
||||
}
|
||||
if (isAudioFile() || isAnimation() || isVoiceMessage()) {
|
||||
setMaybeSupportsStreaming(true);
|
||||
|
||||
@@ -166,8 +166,8 @@ public:
|
||||
[[nodiscard]] bool videoThumbnailLoading() const;
|
||||
[[nodiscard]] bool videoThumbnailFailed() const;
|
||||
void loadVideoThumbnail(Data::FileOrigin origin);
|
||||
const ImageLocation &videoThumbnailLocation() const;
|
||||
int videoThumbnailByteSize() const;
|
||||
[[nodiscard]] const ImageLocation &videoThumbnailLocation() const;
|
||||
[[nodiscard]] int videoThumbnailByteSize() const;
|
||||
|
||||
void updateThumbnails(
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace {
|
||||
|
||||
constexpr auto kReadAreaLimit = 12'032 * 9'024;
|
||||
constexpr auto kWallPaperThumbnailLimit = 960;
|
||||
constexpr auto kMaxVideoFrameArea = 7'680 * 4'320;
|
||||
constexpr auto kGoodThumbQuality = 87;
|
||||
|
||||
enum class FileType {
|
||||
@@ -168,7 +167,7 @@ void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
|
||||
return;
|
||||
}
|
||||
_goodThumbnail = std::make_unique<Image>(std::move(thumbnail));
|
||||
_owner->session().downloaderTaskFinished().notify();
|
||||
_owner->session().notifyDownloaderTaskFinished();
|
||||
}
|
||||
|
||||
Image *DocumentMedia::thumbnailInline() const {
|
||||
@@ -206,7 +205,7 @@ QSize DocumentMedia::thumbnailSize() const {
|
||||
|
||||
void DocumentMedia::setThumbnail(QImage thumbnail) {
|
||||
_thumbnail = std::make_unique<Image>(std::move(thumbnail));
|
||||
_owner->session().downloaderTaskFinished().notify();
|
||||
_owner->session().notifyDownloaderTaskFinished();
|
||||
}
|
||||
|
||||
QByteArray DocumentMedia::videoThumbnailContent() const {
|
||||
|
||||
@@ -71,7 +71,7 @@ void Groups::refreshMessage(
|
||||
unregisterMessage(item);
|
||||
return;
|
||||
}
|
||||
if (!IsServerMsgId(item->id)) {
|
||||
if (!IsServerMsgId(item->id) && !item->isScheduled()) {
|
||||
return;
|
||||
}
|
||||
const auto groupId = item->groupId();
|
||||
|
||||
@@ -98,7 +98,6 @@ void MessagesList::addRange(
|
||||
bool incrementCount) {
|
||||
Expects(!count || !incrementCount);
|
||||
|
||||
auto wasCount = _count;
|
||||
auto update = MessagesSliceUpdate();
|
||||
auto result = addRangeItemsAndCountNew(
|
||||
update,
|
||||
|
||||
@@ -178,7 +178,7 @@ bool NotifySettings::change(
|
||||
MTP_flags(flags),
|
||||
MTPBool(),
|
||||
silentPosts ? MTP_bool(*silentPosts) : MTPBool(),
|
||||
muteForSeconds ? MTP_int(base::unixtime::now() + *muteForSeconds) : MTPint(),
|
||||
MTP_int(muteUntil),
|
||||
MTPstring()));
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +475,11 @@ void PeerData::setPinnedMessageId(MsgId messageId) {
|
||||
}
|
||||
|
||||
bool PeerData::canExportChatHistory() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
if (!channel->amIn() && channel->invitePeekExpires()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto &block : _owner->history(id)->blocks) {
|
||||
for (const auto &message : block->messages) {
|
||||
if (!message->data()->serviceMsg()) {
|
||||
|
||||
@@ -370,7 +370,7 @@ protected:
|
||||
void updateUserpic(
|
||||
PhotoId photoId,
|
||||
MTP::DcId dcId,
|
||||
const MTPFileLocation &location);
|
||||
const MTPFileLocation &small);
|
||||
void clearUserpic();
|
||||
|
||||
private:
|
||||
|
||||
@@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo_media.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/streaming/media_streaming_loader_local.h"
|
||||
#include "media/streaming/media_streaming_loader_mtproto.h"
|
||||
#include "mainwidget.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "core/application.h"
|
||||
@@ -39,6 +41,7 @@ PhotoData::~PhotoData() {
|
||||
for (auto &image : _images) {
|
||||
base::take(image.loader).reset();
|
||||
}
|
||||
base::take(_video.loader).reset();
|
||||
}
|
||||
|
||||
Data::Session &PhotoData::owner() const {
|
||||
@@ -294,7 +297,9 @@ void PhotoData::updateImages(
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const ImageWithLocation &small,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &large) {
|
||||
const ImageWithLocation &large,
|
||||
const ImageWithLocation &video,
|
||||
crl::time videoStartTime) {
|
||||
if (!inlineThumbnailBytes.isEmpty()
|
||||
&& _inlineThumbnailBytes.isEmpty()) {
|
||||
_inlineThumbnailBytes = inlineThumbnailBytes;
|
||||
@@ -315,6 +320,16 @@ void PhotoData::updateImages(
|
||||
update(PhotoSize::Small, small);
|
||||
update(PhotoSize::Thumbnail, thumbnail);
|
||||
update(PhotoSize::Large, large);
|
||||
|
||||
if (video.location.valid()) {
|
||||
_videoStartTime = videoStartTime;
|
||||
}
|
||||
Data::UpdateCloudFile(
|
||||
_video,
|
||||
video,
|
||||
owner().cache(),
|
||||
Data::kAnimationCacheTag,
|
||||
[&](Data::FileOrigin origin) { loadVideo(origin); });
|
||||
}
|
||||
|
||||
int PhotoData::width() const {
|
||||
@@ -325,6 +340,76 @@ int PhotoData::height() const {
|
||||
return _images[PhotoSizeIndex(PhotoSize::Large)].location.height();
|
||||
}
|
||||
|
||||
bool PhotoData::hasVideo() const {
|
||||
return _video.location.valid();
|
||||
}
|
||||
|
||||
bool PhotoData::videoLoading() const {
|
||||
return _video.loader != nullptr;
|
||||
}
|
||||
|
||||
bool PhotoData::videoFailed() const {
|
||||
return (_video.flags & Data::CloudFile::Flag::Failed);
|
||||
}
|
||||
|
||||
void PhotoData::loadVideo(Data::FileOrigin origin) {
|
||||
const auto autoLoading = false;
|
||||
const auto finalCheck = [=] {
|
||||
if (const auto active = activeMediaView()) {
|
||||
return active->videoContent().isEmpty();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto done = [=](QByteArray result) {
|
||||
if (const auto active = activeMediaView()) {
|
||||
active->setVideo(std::move(result));
|
||||
}
|
||||
};
|
||||
Data::LoadCloudFile(
|
||||
&session(),
|
||||
_video,
|
||||
origin,
|
||||
LoadFromCloudOrLocal,
|
||||
autoLoading,
|
||||
Data::kAnimationCacheTag,
|
||||
finalCheck,
|
||||
done);
|
||||
}
|
||||
|
||||
const ImageLocation &PhotoData::videoLocation() const {
|
||||
return _video.location;
|
||||
}
|
||||
|
||||
int PhotoData::videoByteSize() const {
|
||||
return _video.byteSize;
|
||||
}
|
||||
|
||||
bool PhotoData::videoCanBePlayed() const {
|
||||
return hasVideo() && !videoPlaybackFailed();
|
||||
}
|
||||
|
||||
auto PhotoData::createStreamingLoader(
|
||||
Data::FileOrigin origin,
|
||||
bool forceRemoteLoader) const
|
||||
-> std::unique_ptr<Media::Streaming::Loader> {
|
||||
if (!hasVideo()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!forceRemoteLoader) {
|
||||
const auto media = activeMediaView();
|
||||
if (media && !media->videoContent().isEmpty()) {
|
||||
return Media::Streaming::MakeBytesLoader(media->videoContent());
|
||||
}
|
||||
}
|
||||
return videoLocation().file().data.is<StorageFileLocation>()
|
||||
? std::make_unique<Media::Streaming::LoaderMtproto>(
|
||||
&session().downloader(),
|
||||
videoLocation().file().data.get_unchecked<StorageFileLocation>(),
|
||||
videoByteSize(),
|
||||
origin)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
PhotoClickHandler::PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context,
|
||||
|
||||