Compare commits

...

137 Commits

Author SHA1 Message Date
John Preston
cc48afac1c Beta version 2.4.11.
- Improve locked voice message recording.
- Fix main window closing to tray on Windows.
- Fix crash in bot command sending.
- Fix adding additional photos when sending an album
to a group with enabled slow mode.
2020-11-19 19:05:46 +03:00
23rd
0a0dcb9054 Delegated responsibility for clearing listen state to sections. 2020-11-19 18:41:13 +03:00
Ilya Fedin
b1b01385d0 Restore 16px tray icon size
Looks like there are support for this size since b703f4e555
2020-11-19 18:36:57 +03:00
23rd
a2e4403b28 Slightly refactored code for menu with send options. 2020-11-19 18:11:37 +03:00
23rd
e1017380ec Fixed filling menu with send options for inline bots and autocomplete. 2020-11-19 18:11:37 +03:00
23rd
a6e4ac679c Fixed reply stuck display at sending voice in Replies section. 2020-11-19 18:11:37 +03:00
23rd
f75fb33c29 Removed delay for voice lock widget appearing. 2020-11-19 18:11:37 +03:00
23rd
cbaca6382e Fixed unwanted flickering of record button when recorded data is empty. 2020-11-19 18:11:37 +03:00
23rd
1758f0fd8f Added send icon to VoiceRecordButton. 2020-11-19 18:11:37 +03:00
John Preston
e29ee439cf Improve media viewer hiding workaround on macOS. 2020-11-19 17:59:43 +03:00
John Preston
51f960442e Make caption area height bigger. 2020-11-19 17:42:57 +03:00
John Preston
fff2ee2758 Use 'Next' in send media box in Scheduled section. 2020-11-19 17:16:56 +03:00
John Preston
b1ed15447b Scroll to bottom when sending a poll. 2020-11-19 17:01:50 +03:00
John Preston
a086afb152 Fix legacy group service message in chats list. 2020-11-19 16:47:17 +03:00
John Preston
639e6d8e28 Fix sending albums in slowmode groups.
Fixes #9106.
2020-11-19 16:47:17 +03:00
John Preston
00504b61cd Allow all messages silent in support mode. 2020-11-19 16:23:57 +03:00
John Preston
1affb8172f Fix hime_qt build with -Werror. 2020-11-18 19:15:26 +03:00
John Preston
c98a3825a5 Fix sending bot commands from autocomplete. 2020-11-18 14:50:10 +03:00
Ilya Fedin
c3b0e6c503 Move -s to CMAKE_EXE_LINKER_FLAGS 2020-11-18 14:29:00 +03:00
Ilya Fedin
76a7cc9229 Don't set screen for media viewer on Wayland 2020-11-18 14:29:00 +03:00
John Preston
b2047c9558 Fix first media viewer open zoom. 2020-11-18 14:11:23 +03:00
John Preston
4a73bb7872 Fix main window on Windows.
Fixes #9089, fixes #9090.
2020-11-18 13:32:30 +03:00
John Preston
91b8ad171a Beta version 2.4.10.
- Use inline bots and sticker by emoji suggestions
in channel comments.
- Lock voice message recording,
listen to your voice message before sending.
2020-11-17 18:21:19 +03:00
John Preston
4f6f654e34 Check replies_pts before applying.
Fixes #9062.
2020-11-17 18:05:35 +03:00
John Preston
ed50aa0d8e Fix build with Qt < 5.14. 2020-11-17 17:14:21 +03:00
mid-kid
49480001f7 Move IsWayland() checks into WaylandIntegration 2020-11-17 16:23:54 +03:00
Ilya Fedin
4ed6918a5e Disable some static plugins when building without wayland 2020-11-17 16:23:54 +03:00
Ilya Fedin
0563e1f878 Have the wayland build-time toggle affect the native window title 2020-11-17 16:23:54 +03:00
mid-kid
17e8e0a7b0 Add workflow for DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION 2020-11-17 16:23:54 +03:00
mid-kid
96b2e26f42 Build wayland support optionally 2020-11-17 16:23:54 +03:00
Ilya Fedin
c0142726f8 Remove unneeded Xi and Xrender from docker build and add repo with new git 2020-11-17 15:49:50 +03:00
Newbyte
c52e914060 Fix WebRTC licence link 2020-11-17 15:49:02 +03:00
Ilya Fedin
985956e625 Update lib_base 2020-11-17 15:46:42 +03:00
Ilya Fedin
facbaecf30 Add -Werror to actions 2020-11-17 15:45:27 +03:00
23rd
04c068d8b3 Added filling send context menu to TabbedSelector from ComposeControls. 2020-11-17 12:58:08 +03:00
23rd
980ce9fba3 Replaced auto types with explicit types in VoiceRecordBar's lambdas. 2020-11-17 12:58:08 +03:00
23rd
024a35d770 Added ability to send recorded voice data from ComposeControls. 2020-11-17 12:58:08 +03:00
23rd
ab38ddc21d Added ability to fill send context menu in ComposeControls. 2020-11-17 12:58:08 +03:00
23rd
79cc4da626 Reduced block size for voice record lock. 2020-11-17 12:58:08 +03:00
23rd
92298316ab Added transform animation from lock to stop icon for recording voice. 2020-11-17 12:58:08 +03:00
23rd
c9314e5e5e Added ripple animation to stop recording voice button. 2020-11-17 12:58:07 +03:00
23rd
eadd952e66 Added animation for recorded voice data delete. 2020-11-17 12:58:07 +03:00
23rd
fb2924f2d6 Fixed size and position of lock/stop button at end of animation. 2020-11-17 12:58:07 +03:00
23rd
7dac42b523 Added ability to play/pause recorded voice data with Space key. 2020-11-17 12:58:07 +03:00
23rd
d2defabd4b Improved waveform display of recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
e0cc3791ff Added edit message prevent when there is unsent recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
6ecc446a8a Added ability to schedule and send without sound recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
2668619758 Added ability to seek recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
5eba680483 Added play button for recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
7826d0246d Added duration display of recorded voice data.
const qptr
2020-11-17 12:58:07 +03:00
23rd
189c940710 Added waveform display of recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
8b2bb722de Added ability to send and cancel recorded voice data with keys. 2020-11-17 12:58:07 +03:00
23rd
a19e3ca3dc Added initial ability to send recorded voice data from listen state. 2020-11-17 12:58:07 +03:00
23rd
647cbc5464 Added initial ability to delete recorded voice data. 2020-11-17 12:58:07 +03:00
23rd
131c2e1c56 Slightly refactored waveform paint in voice messages. 2020-11-17 12:58:07 +03:00
23rd
81723a5d19 Slightly improved voice lock design. 2020-11-17 12:58:07 +03:00
John Preston
b3eb7858e6 Save local drafts in scheduled / replies sections.
Fix inline bot switch inline in scheduled / replies sections.
2020-11-17 12:58:07 +03:00
John Preston
4a0efb9114 Remove Q_OBJECT from HistoryWidget. 2020-11-17 12:58:07 +03:00
John Preston
f04b3da76a Add return from bot switch_pm to Scheduled/Replies. 2020-11-17 12:58:06 +03:00
John Preston
4a8b59b788 Pass reply info to Window::PeerMenu. 2020-11-17 12:58:06 +03:00
John Preston
4f22171dd6 Add start bot command and handle via@ links. 2020-11-17 12:57:03 +03:00
John Preston
10adbecb9c Support creating polls in RepliesSection. 2020-11-17 12:56:51 +03:00
John Preston
a8564b166b Add inline bots to ComposeControls. 2020-11-17 12:56:51 +03:00
John Preston
cf6ca3b1ac Handle bot command clicks in Replies / Scheduled. 2020-11-17 12:56:51 +03:00
John Preston
ac02e2be9e Add FieldAutocomplete to ComposeControls. 2020-11-17 12:56:51 +03:00
23rd
5d2ffae215 Improved VoiceRecordButton colors. 2020-11-17 12:56:51 +03:00
23rd
1d120092cf Fixed previous values resetting in VoiceRecordButton. 2020-11-17 12:56:51 +03:00
23rd
2035392564 Removed bezier circle paint when animations are disabled. 2020-11-17 12:56:51 +03:00
23rd
c4897cec0a Replaced dummy lock icons with lock animation. 2020-11-17 12:56:51 +03:00
23rd
dd462eb8cf Slightly improved animation of bezier circle in VoiceRecordButton. 2020-11-17 12:56:51 +03:00
23rd
71e8bda7bb Replaced raw variables for animations in VoiceRecordButton. 2020-11-17 12:56:51 +03:00
23rd
ce1ae5ba12 Removed redundant code from VoiceRecordButton. 2020-11-17 12:56:51 +03:00
23rd
6ae15485ad Added show animation to VoiceRecordButton. 2020-11-17 12:56:51 +03:00
23rd
7a32d78689 Replaced record circle button with bezier circle. 2020-11-17 12:56:51 +03:00
23rd
cb84e70bdc Improved display management of voice record controls. 2020-11-17 12:56:51 +03:00
23rd
b3925a3bec Added touchbar hiding while recording voice message. 2020-11-17 12:56:51 +03:00
23rd
041d8571c2 Added delay for start recording voice message. 2020-11-17 12:56:50 +03:00
23rd
367b487a6c Prettified fast locking of voice record. 2020-11-17 12:56:50 +03:00
23rd
57eb4f8234 Disabled drag'n'drop area when user is recording voice message. 2020-11-17 12:56:50 +03:00
23rd
ad4bf9b5c8 Improved VoiceRecordBar support in ComposeControls. 2020-11-17 12:56:50 +03:00
23rd
6ed7615653 Removed redundant methods for record from SendButton. 2020-11-17 12:56:50 +03:00
23rd
cab22c07a5 Replaced recording animation with simple red circle animation. 2020-11-17 12:56:50 +03:00
23rd
ba3862e70f Added new send recorded voice button with recording animation. 2020-11-17 12:56:50 +03:00
23rd
4970740739 Fixed voice recording lock position on resizing. 2020-11-17 12:56:50 +03:00
23rd
cdb77d46b1 Slightly refactored text drawing in VoiceRecordBar. 2020-11-17 12:56:50 +03:00
23rd
b6743feec1 Added ability to send recording voice message with Enter key. 2020-11-17 12:56:50 +03:00
23rd
fe5242d6d2 Added ability to pass filter for Escape key to voice record bar. 2020-11-17 12:56:50 +03:00
23rd
914e40fb62 Added red coloring of record button. 2020-11-17 12:56:50 +03:00
23rd
326342420d Added animation of voice recording lock with dummy lock icons. 2020-11-17 12:56:50 +03:00
23rd
478f5f671c Added initial implementation of voice recording lock. 2020-11-17 12:56:50 +03:00
23rd
43635f6e4b Slightly optimized paint in VoiceRecordBar. 2020-11-17 12:56:50 +03:00
23rd
ebe1fa7408 Improved duration text paint in VoiceRecordBar. 2020-11-17 12:56:50 +03:00
23rd
5c006002b6 Added appearance animation to VoiceRecordBar. 2020-11-17 12:56:50 +03:00
23rd
e7454e3849 Removed redundant record methods from SendButton. 2020-11-17 12:56:50 +03:00
23rd
3e4866d3b7 Moved active animation processing from SendButton to VoiceRecordBar. 2020-11-17 12:56:50 +03:00
23rd
db564ca486 Replaced voice record processing with VoiceRecordBar in HistoryWidget. 2020-11-17 12:56:50 +03:00
23rd
fd76b44dbd Replaced voice record processing with VoiceRecordBar in ComposeControls. 2020-11-17 12:56:50 +03:00
23rd
112dea8594 Created voice record bar as separated history view class. 2020-11-17 12:56:50 +03:00
23rd
6d775d6f45 Moved structures of compose controls to separated header. 2020-11-17 12:56:50 +03:00
23rd
f7c6876e1b Moved history_view_compose_controls to controls folder. 2020-11-17 12:56:50 +03:00
23rd
8845652f77 Fixed macOS build. 2020-11-17 12:56:50 +03:00
Ilya Fedin
cd67cb1c62 Update media viewer geometry on showing and screen change 2020-11-17 12:49:05 +03:00
John Preston
b71f61dec3 Update submodules. 2020-11-16 19:37:29 +03:00
John Preston
e4d2a66f45 Revert "Re-check the screen media viewer appears on before adjusting geometry"
This reverts commit c3b638449a.

It doesn't work as it was supposed to :(
2020-11-16 19:31:34 +03:00
John Preston
02eea38724 Remove color space before sending in JPG. 2020-11-16 14:22:19 +03:00
John Preston
358228ce00 Update submodules. 2020-11-16 13:09:40 +03:00
John Preston
0089692b52 Fix build for Mac App Store. 2020-11-16 13:08:58 +03:00
Ilya Fedin
9d6e5f2a5b Adapt linux tray icon implementation to the new QIcon::pixmap behavior
More info: https://codereview.qt-project.org/c/qt/qtbase/+/314618
2020-11-16 13:03:43 +03:00
Ilya Fedin
c3b638449a Re-check the screen media viewer appears on before adjusting geometry 2020-11-16 12:39:16 +03:00
Ilya Fedin
2df7e4181f Add a comment about GTK_USE_PORTAL in snap 2020-11-16 12:37:42 +03:00
Ilya Fedin
b4cb47cf7f Prefer gtk3 headers 2020-11-16 12:37:42 +03:00
Ilya Fedin
e4b9900a06 Construct WindowControlsLayout without variable 2020-11-16 12:35:01 +03:00
Ilya Fedin
5c8a19b7f7 Use only really supported icon sizes 2020-11-16 12:34:19 +03:00
Ilya Fedin
620c596200 Remove the last workaround in tray implementation
Since tdesktop gets icon theme pretty well now, there's no need for any workaround.
2020-11-16 12:34:19 +03:00
Ilya Fedin
d7ef484aec Use QWindow::setFlag that doesn't hide the windw 2020-11-16 12:33:55 +03:00
Ilya Fedin
772bd81ea5 Fix typo in the installlauncher cheat code 2020-11-16 12:33:22 +03:00
Ilya Fedin
c8ce5dfa8b Fix escaping in scheme creation on Linux and set -workdir 2020-11-16 12:33:22 +03:00
Ilya Fedin
e64f6f7266 Since changing the ibus portal check, it is not compatible with snap anymore 2020-11-12 18:12:17 +03:00
Ilya Fedin
21133abe13 Fix 30s hang in case ibus portal couldn't be started 2020-11-12 15:51:00 +03:00
Ilya Fedin
8b0fcee6a6 Use docker build in linux action 2020-11-12 14:29:42 +03:00
zurg3
a768b65295 Updated FFmpeg version in GitHub Actions workflows 2020-11-10 16:23:37 +03:00
zurg3
a68d9b4522 Updated Qt version in GitHub Actions workflows 2020-11-10 16:23:37 +03:00
John Preston
8a9317f9e1 Download video avatars as .mp4 in media viewer.
Fixes #9017.
2020-11-09 15:05:36 +03:00
John Preston
db23485fa2 Fix quit from fullscreen on macOS. 2020-11-09 13:47:53 +03:00
John Preston
87e4bb1059 Fix scroll position restore with pinned bar. 2020-11-09 12:57:49 +03:00
John Preston
091b62bed4 Allow cancel search-in-chat and keep search query. 2020-11-09 12:41:59 +03:00
John Preston
167a73ef1b Add sent/read checks for some service messages. 2020-11-09 12:36:15 +03:00
Ilya Fedin
91a2ec225a Add support for open with on linux 2020-11-09 11:23:01 +03:00
Ilya Fedin
3a45957ceb Set parent window ID for portal autostart dialog 2020-11-09 11:19:03 +03:00
Ilya Fedin
acaf8e4931 Use g_filename_to_uri 2020-11-09 11:19:03 +03:00
Ilya Fedin
e0de4dbc5e Replace new #ifdef Q_OS_LINUX in main_window.cpp added a month ago 2020-11-09 11:19:03 +03:00
Ilya Fedin
876c57dcfb Fix getting FileChooser portal version 2020-11-09 11:19:03 +03:00
Ilya Fedin
f980cade39 Use static QFile methods in linux platform code 2020-11-09 11:19:03 +03:00
Ilya Fedin
3d18d28dc5 Use kIconName on icon creating 2020-11-09 11:19:03 +03:00
Ilya Fedin
e04598835b Move _monitorRect and _monitorLastGot to psDesktopRect method 2020-11-09 11:19:03 +03:00
Ilya Fedin
eee3049fdd Remove definitions of unused psLocalServerPrefix and psInitLogs 2020-11-09 11:19:03 +03:00
John Preston
d97dcaec62 Add possibility to build on Windows for x64. 2020-11-06 20:22:02 +03:00
161 changed files with 7957 additions and 3722 deletions

View File

@@ -45,71 +45,38 @@ on:
jobs:
linux:
name: Ubuntu 14.04
name: CentOS 7
runs-on: ubuntu-latest
container: ubuntu:trusty
container:
image: docker.pkg.github.com/telegramdesktop/tdesktop/centos_env
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: scl enable devtoolset-8 -- bash --noprofile --norc -eo pipefail {0}
strategy:
matrix:
defines:
- ""
- "DESKTOP_APP_DISABLE_DBUS_INTEGRATION"
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
- "TDESKTOP_DISABLE_GTK_INTEGRATION"
env:
GIT: "https://github.com"
QT: "5_12_8"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.8"
OPENSSL_VER: "1_1_1"
OPENSSL_PREFIX: "/usr/local/desktop-app/openssl-1.1.1"
CMAKE_VER: "3.17.0"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
MANUAL_CACHING: "6"
DOC_PATH: "docs/building-cmake.md"
AUTO_CACHING: "1"
steps:
- name: Get repository name.
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Disable man for further package installs.
- name: Yum install.
run: |
cfgFile="/etc/dpkg/dpkg.cfg.d/no_man"
sudo touch $cfgFile
p() {
sudo echo "path-exclude=/usr/share/$1/*" >> $cfgFile
}
p man
p locale
p doc
- name: Apt install.
shell: bash
run: |
sudo apt-get update
sudo apt-get install software-properties-common -y && \
sudo add-apt-repository ppa:git-core/ppa -y && \
sudo apt-get update && \
sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \
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 \
libxcb-screensaver0-dev libjpeg-dev ninja-build \
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 \
libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html bison yasm \
zlib1g-dev xutils-dev python-xcbgen chrpath gperf wget -y --force-yes && \
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
sudo apt-get update && \
sudo apt-get install gcc-8 g++-8 -y && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 60 && \
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 60 && \
sudo update-alternatives --config gcc && \
sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test -y
yum -y autoremove git
yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm
yum -y install git
- name: Clone.
uses: actions/checkout@v2
@@ -118,452 +85,11 @@ jobs:
path: ${{ env.REPO_NAME }}
- name: First set up.
shell: bash
run: |
gcc --version
gcc --version > CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/linux.yml
echo `md5sum $thisFile | cut -c -32` >> CACHE_KEY.txt
fi
md5cache=$(md5sum CACHE_KEY.txt | cut -c -32)
echo "CACHE_KEY=$md5cache" >> $GITHUB_ENV
mkdir -p Libraries
cd Libraries
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
- name: Patches.
run: |
echo "Find necessary commit from doc."
checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$DOC_PATH | sed -n 2p)
cd $LibrariesPath
git clone $GIT/desktop-app/patches.git
cd patches
eval $checkoutCommit
- name: CMake.
run: |
cd $LibrariesPath
file=cmake-$CMAKE_VER-Linux-x86_64.sh
wget $GIT/Kitware/CMake/releases/download/v$CMAKE_VER/$file
sudo mkdir /opt/cmake
sudo sh $file --prefix=/opt/cmake --skip-license
sudo ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
rm $file
cmake --version
- name: MozJPEG.
run: |
cd $LibrariesPath
git clone -b v4.0.1-rc2 $GIT/mozilla/mozjpeg.git
cd mozjpeg
cmake -B build . \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DWITH_JPEG8=ON \
-DPNG_SUPPORTED=OFF
cmake --build build -j$(nproc)
sudo cmake --install build
cd ..
rm -rf mozjpeg
- name: Opus cache.
id: cache-opus
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/opus
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
- name: Opus.
if: steps.cache-opus.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
git clone -b v1.3 --depth=1 $GIT/xiph/opus
cd opus
./autogen.sh
./configure
make -j$(nproc)
- name: Opus install.
run: |
cd $LibrariesPath/opus
sudo make install
- name: Libva.
run: |
cd $LibrariesPath
git clone $GIT/intel/libva.git
cd libva
./autogen.sh --enable-static
make -j$(nproc)
sudo make install
cd ..
rm -rf libva
- name: Libvdpau.
run: |
cd $LibrariesPath
git clone -b libvdpau-1.2 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git
cd libvdpau
./autogen.sh --enable-static
make -j$(nproc)
sudo make install
cd ..
rm -rf libvdpau
- name: FFmpeg cache.
id: cache-ffmpeg
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/ffmpeg-cache
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}
- name: FFmpeg build.
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
git clone --branch release/3.4 $GIT/FFmpeg/FFmpeg ffmpeg
cd ffmpeg
./configure \
--disable-debug \
--disable-programs \
--disable-doc \
--disable-network \
--disable-autodetect \
--disable-everything \
--disable-alsa \
--disable-iconv \
--enable-libopus \
--enable-vaapi \
--enable-vdpau \
--enable-protocol=file \
--enable-hwaccel=h264_vaapi \
--enable-hwaccel=h264_vdpau \
--enable-hwaccel=mpeg4_vaapi \
--enable-hwaccel=mpeg4_vdpau \
--enable-decoder=aac \
--enable-decoder=aac_fixed \
--enable-decoder=aac_latm \
--enable-decoder=aasc \
--enable-decoder=alac \
--enable-decoder=flac \
--enable-decoder=gif \
--enable-decoder=h264 \
--enable-decoder=h264_vdpau \
--enable-decoder=hevc \
--enable-decoder=mp1 \
--enable-decoder=mp1float \
--enable-decoder=mp2 \
--enable-decoder=mp2float \
--enable-decoder=mp3 \
--enable-decoder=mp3adu \
--enable-decoder=mp3adufloat \
--enable-decoder=mp3float \
--enable-decoder=mp3on4 \
--enable-decoder=mp3on4float \
--enable-decoder=mpeg4 \
--enable-decoder=mpeg4_vdpau \
--enable-decoder=msmpeg4v2 \
--enable-decoder=msmpeg4v3 \
--enable-decoder=opus \
--enable-decoder=pcm_alaw \
--enable-decoder=pcm_f32be \
--enable-decoder=pcm_f32le \
--enable-decoder=pcm_f64be \
--enable-decoder=pcm_f64le \
--enable-decoder=pcm_lxf \
--enable-decoder=pcm_mulaw \
--enable-decoder=pcm_s16be \
--enable-decoder=pcm_s16be_planar \
--enable-decoder=pcm_s16le \
--enable-decoder=pcm_s16le_planar \
--enable-decoder=pcm_s24be \
--enable-decoder=pcm_s24daud \
--enable-decoder=pcm_s24le \
--enable-decoder=pcm_s24le_planar \
--enable-decoder=pcm_s32be \
--enable-decoder=pcm_s32le \
--enable-decoder=pcm_s32le_planar \
--enable-decoder=pcm_s64be \
--enable-decoder=pcm_s64le \
--enable-decoder=pcm_s8 \
--enable-decoder=pcm_s8_planar \
--enable-decoder=pcm_u16be \
--enable-decoder=pcm_u16le \
--enable-decoder=pcm_u24be \
--enable-decoder=pcm_u24le \
--enable-decoder=pcm_u32be \
--enable-decoder=pcm_u32le \
--enable-decoder=pcm_u8 \
--enable-decoder=pcm_zork \
--enable-decoder=vorbis \
--enable-decoder=wavpack \
--enable-decoder=wmalossless \
--enable-decoder=wmapro \
--enable-decoder=wmav1 \
--enable-decoder=wmav2 \
--enable-decoder=wmavoice \
--enable-encoder=libopus \
--enable-parser=aac \
--enable-parser=aac_latm \
--enable-parser=flac \
--enable-parser=h264 \
--enable-parser=hevc \
--enable-parser=mpeg4video \
--enable-parser=mpegaudio \
--enable-parser=opus \
--enable-parser=vorbis \
--enable-demuxer=aac \
--enable-demuxer=flac \
--enable-demuxer=gif \
--enable-demuxer=h264 \
--enable-demuxer=hevc \
--enable-demuxer=m4v \
--enable-demuxer=mov \
--enable-demuxer=mp3 \
--enable-demuxer=ogg \
--enable-demuxer=wav \
--enable-muxer=ogg \
--enable-muxer=opus
make -j$(nproc)
sudo make DESTDIR="$LibrariesPath/ffmpeg-cache" install
cd ..
rm -rf ffmpeg
- name: FFmpeg install.
run: |
cd $LibrariesPath
#List of files from cmake/external/ffmpeg/CMakeLists.txt.
copyLib() {
mkdir -p ffmpeg/$1
yes | cp -i ffmpeg-cache/usr/local/lib/$1.a ffmpeg/$1/$1.a
}
copyLib libavformat
copyLib libavcodec
copyLib libswresample
copyLib libswscale
copyLib libavutil
sudo cp -R ffmpeg-cache/. /
- name: OpenAL Soft.
run: |
cd $LibrariesPath
git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git
cd openal-soft/build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DLIBTYPE:STRING=STATIC \
-DALSOFT_EXAMPLES=OFF \
-DALSOFT_TESTS=OFF \
-DALSOFT_UTILS=OFF \
-DALSOFT_CONFIG=OFF
make -j$(nproc)
sudo make install
cd -
rm -rf openal-soft
- name: OpenSSL cache.
id: cache-openssl
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/openssl-cache
key: ${{ runner.OS }}-${{ env.OPENSSL_VER }}-${{ env.CACHE_KEY }}
- name: OpenSSL build.
if: steps.cache-openssl.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
opensslDir=openssl_${OPENSSL_VER}
git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \
$GIT/openssl/openssl $opensslDir
cd $opensslDir
./config --prefix="$OPENSSL_PREFIX" no-tests
make -j$(nproc)
sudo make DESTDIR="$LibrariesPath/openssl-cache" install_sw
cd ..
# rm -rf $opensslDir # Keep this folder for WebRTC.
- name: OpenSSL install.
run: |
cd $LibrariesPath
sudo cp -R openssl-cache/. /
- name: Libwayland.
run: |
cd $LibrariesPath
git clone -b 1.18.0 https://gitlab.freedesktop.org/wayland/wayland
cd wayland
./autogen.sh \
--enable-static \
--disable-documentation \
--disable-dtd-validation
make -j$(nproc)
sudo make install
cd ..
rm -rf wayland
- name: Libxkbcommon.
run: |
cd $LibrariesPath
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
cd libxkbcommon
./autogen.sh \
--disable-docs \
--disable-wayland \
--with-xkb-config-root=/usr/share/X11/xkb \
--with-x-locale-root=/usr/share/X11/locale
make -j$(nproc)
sudo make install
cd ..
rm -rf libxkbcommon
- name: Qt 5.12.8 cache.
id: cache-qt
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/qt-cache
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_12_8/*') }}
- name: Qt 5.12.8 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
git clone -b v5.12.8 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT}
cd qt_${QT}
perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg
git submodule update qtbase qtwayland qtimageformats qtsvg
cd qtbase
find ../../patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply
cd ..
cd qtwayland
find ../../patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply
cd ..
./configure -prefix "$QT_PREFIX" \
-release \
-opensource \
-confirm-license \
-qt-zlib \
-qt-libpng \
-qt-harfbuzz \
-qt-pcre \
-qt-xcb \
-no-icu \
-no-gtk \
-static \
-dbus-runtime \
-openssl-linked \
-I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -ldl -lpthread" \
-nomake examples \
-nomake tests
make -j$(nproc)
sudo make INSTALL_ROOT="$LibrariesPath/qt-cache" install
cd ..
rm -rf qt_${QT}
- name: Qt 5.12.8 install.
run: |
cd $LibrariesPath
sudo cp -R qt-cache/. /
- name: Breakpad cache.
id: cache-breakpad
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/breakpad-cache
key: ${{ runner.OS }}-breakpad-${{ env.CACHE_KEY }}
- name: Breakpad clone.
run: |
cd $LibrariesPath
git clone https://chromium.googlesource.com/breakpad/breakpad
cd breakpad
git checkout bc8fb886
git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss
cd src/third_party/lss
git checkout a91633d1
- name: Breakpad build.
if: steps.cache-breakpad.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
BreakpadCache=$LibrariesPath/breakpad-cache
git clone https://chromium.googlesource.com/external/gyp
cd gyp
git checkout 9f2a7bb1
git apply ../patches/gyp.diff
cd ..
cd breakpad
./configure
make -j$(nproc)
sudo make DESTDIR="$BreakpadCache" install
cd src
rm -r testing
git clone $GIT/google/googletest testing
cd tools
sed -i 's/minidump_upload.m/minidump_upload.cc/' linux/tools_linux.gypi
../../../gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out tools.gyp --format=cmake
cd ../../out/Default
cmake .
make -j$(nproc) dump_syms
mv dump_syms $BreakpadCache/
cd ..
rm -rf gyp breakpad
- name: Breakpad install.
run: |
cd $LibrariesPath
sudo cp -R breakpad-cache/. /
mkdir -p breakpad/out/Default/
cp breakpad-cache/dump_syms breakpad/out/Default/dump_syms
- name: WebRTC cache.
id: cache-webrtc
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/tg_owt
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
- name: WebRTC.
if: steps.cache-webrtc.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
git clone $GIT/desktop-app/tg_owt.git
mkdir -p tg_owt/out/Debug
cd tg_owt/out/Debug
cmake -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DTG_OWT_SPECIAL_TARGET=linux \
-DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \
-DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \
-DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \
-DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include \
../..
ninja
# Cleanup.
cd $LibrariesPath/tg_owt
mv out/Debug/libtg_owt.a libtg_owt.a
rm -rf out
mkdir -p out/Debug
mv libtg_owt.a out/Debug/libtg_owt.a
rm -rf $LibrariesPath/openssl_${OPENSSL_VER}
ln -s $LibrariesPath Libraries
- name: Telegram Desktop build.
if: env.ONLY_CACHE == 'false'
run: |
cd $REPO_NAME/Telegram
@@ -577,7 +103,9 @@ jobs:
fi
./configure.sh \
-D CMAKE_CXX_FLAGS="-s" \
-D CMAKE_C_FLAGS="-Werror" \
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_EXE_LINKER_FLAGS="-s" \
-D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_USE_PACKAGED=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
@@ -587,7 +115,6 @@ jobs:
make -j$(nproc)
- name: Check.
if: env.ONLY_CACHE == 'false'
run: |
filePath="$REPO_NAME/out/Debug/bin/Telegram"
if test -f "$filePath"; then

View File

@@ -57,9 +57,9 @@ jobs:
PREFIX: "/usr/local/macos"
MACOSX_DEPLOYMENT_TARGET: "10.12"
XZ: "xz-5.2.4"
QT: "5_12_8"
QT: "5_15_1"
OPENSSL_VER: "1_1_1"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.8"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.15.1"
LIBICONV_VER: "libiconv-1.16"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
@@ -235,7 +235,7 @@ jobs:
git clone $GIT/FFmpeg/FFmpeg.git ffmpeg
cd ffmpeg
git checkout release/3.4
git checkout release/4.2
CFLAGS=`freetype-config --cflags`
LDFLAGS=`freetype-config --libs`
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig
@@ -244,7 +244,9 @@ jobs:
--extra-cflags="$MIN_MAC $UNGUARDED" \
--extra-cxxflags="$MIN_MAC $UNGUARDED" \
--extra-ldflags="$MIN_MAC" \
--enable-protocol=file --enable-libopus \
--x86asmexe=`pwd`/macos_yasm_wrap.sh \
--enable-protocol=file \
--enable-libopus \
--disable-programs \
--disable-doc \
--disable-network \
@@ -364,9 +366,9 @@ jobs:
run: |
cd $LibrariesPath
git clone $GIT/kcat/openal-soft.git
git clone https://github.com/telegramdesktop/openal-soft.git
cd openal-soft
git checkout openal-soft-1.19.1
git checkout fix_mono
cd build
CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake \
@@ -417,20 +419,20 @@ jobs:
build/gyp_crashpad.py -Dmac_deployment_target=10.10
ninja -C out/Debug
- name: Qt 5.12.8 cache.
- name: Qt 5.15.1 cache.
id: cache-qt
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/qt-cache
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }}
- name: Use cached Qt 5.12.8.
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_15_1/*') }}
- name: Use cached Qt 5.15.1.
if: steps.cache-qt.outputs.cache-hit == 'true'
run: |
cd $LibrariesPath
mv qt-cache Qt-5.12.8
mv qt-cache Qt-5.15.1
sudo mkdir -p $QT_PREFIX
sudo mv -f Qt-5.12.8 "$(dirname "$QT_PREFIX")"/
- name: Qt 5.12.8 build.
sudo mv -f Qt-5.15.1 "$(dirname "$QT_PREFIX")"/
- name: Qt 5.15.1 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
@@ -438,7 +440,7 @@ jobs:
git clone git://code.qt.io/qt/qt5.git qt_$QT
cd qt_$QT
perl init-repository --module-subset=qtbase,qtimageformats
git checkout v5.12.8
git checkout v5.15.1
git submodule update qtbase
git submodule update qtimageformats
cd qtbase
@@ -513,6 +515,8 @@ jobs:
fi
./configure.sh \
-D CMAKE_C_FLAGS="-Werror" \
-D CMAKE_CXX_FLAGS="-Werror" \
-D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_USE_PACKAGED=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \

View File

@@ -59,8 +59,8 @@ jobs:
SDK: "10.0.18362.0"
VC: "call vcvars32.bat && cd Libraries"
GIT: "https://github.com"
QT: "5_12_8"
QT_VER: "5.12.8"
QT: "5_15_1"
QT_VER: "5.15.1"
OPENSSL_VER: "1_1_1"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
@@ -294,20 +294,20 @@ jobs:
git clone %GIT%/FFmpeg/FFmpeg.git ffmpeg
cd ffmpeg
git checkout release/3.4
git checkout release/4.2
set CHERE_INVOKING=enabled_from_arguments
set MSYS2_PATH_TYPE=inherit
call c:\tools\msys64\usr\bin\bash --login ../../%REPO_NAME%/Telegram/Patches/build_ffmpeg_win.sh
call c:\tools\msys64\usr\bin\bash --login ../patches/build_ffmpeg_win.sh
rmdir /S /Q .git
- name: Qt 5.12.8 cache.
- name: Qt 5.15.1 cache.
id: cache-qt
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/Qt-${{ env.QT_VER }}
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }}
- name: Configure Qt 5.12.8.
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_15_1/*') }}
- name: Configure Qt 5.15.1.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
%VC%
@@ -344,7 +344,7 @@ jobs:
-I "%LibrariesPath%\mozjpeg" ^
LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^
LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib"
- name: Qt 5.12.8 build.
- name: Qt 5.15.1 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
%VC%

View File

@@ -34,7 +34,7 @@ Version **1.8.15** was the last that supports older systems
* Qt 5.12.8, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/src/LICENSE))
* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE))
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))
* LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html))
* liblzma ([public domain](http://tukaani.org/xz/))

View File

@@ -70,7 +70,6 @@ PRIVATE
if (LINUX)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_materialdecoration
desktop-app::external_nimf_qt5
desktop-app::external_qt5ct_support
desktop-app::external_xcb_screensaver
@@ -89,14 +88,22 @@ if (LINUX)
)
endif()
if (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
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
target_link_libraries(Telegram
PRIVATE
${WAYLAND_CLIENT_INCLUDE_DIRS}
desktop-app::external_materialdecoration
)
if (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()
endif()
if (NOT TDESKTOP_DISABLE_GTK_INTEGRATION)
@@ -112,7 +119,7 @@ if (LINUX)
PkgConfig::X11
)
else()
pkg_search_module(GTK REQUIRED gtk+-2.0 gtk+-3.0)
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0)
target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS})
target_link_libraries(Telegram PRIVATE X11)
endif()
@@ -474,6 +481,13 @@ PRIVATE
history/admin_log/history_admin_log_section.h
# history/feed/history_feed_section.cpp
# history/feed/history_feed_section.h
history/view/controls/compose_controls_common.h
history/view/controls/history_view_compose_controls.cpp
history/view/controls/history_view_compose_controls.h
history/view/controls/history_view_voice_record_bar.cpp
history/view/controls/history_view_voice_record_bar.h
history/view/controls/history_view_voice_record_button.cpp
history/view/controls/history_view_voice_record_button.h
history/view/media/history_view_call.h
history/view/media/history_view_call.cpp
history/view/media/history_view_contact.h
@@ -514,8 +528,6 @@ PRIVATE
history/view/media/history_view_theme_document.cpp
history/view/media/history_view_web_page.h
history/view/media/history_view_web_page.cpp
history/view/history_view_compose_controls.cpp
history/view/history_view_compose_controls.h
history/view/history_view_contact_status.cpp
history/view/history_view_contact_status.h
history/view/history_view_context_menu.cpp
@@ -642,6 +654,8 @@ PRIVATE
inline_bots/inline_bot_result.h
inline_bots/inline_bot_send_data.cpp
inline_bots/inline_bot_send_data.h
inline_bots/inline_results_inner.cpp
inline_bots/inline_results_inner.h
inline_bots/inline_results_widget.cpp
inline_bots/inline_results_widget.h
intro/intro_code.cpp
@@ -796,6 +810,8 @@ PRIVATE
platform/linux/linux_gdk_helper.h
platform/linux/linux_libs.cpp
platform/linux/linux_libs.h
platform/linux/linux_wayland_integration.cpp
platform/linux/linux_wayland_integration.h
platform/linux/linux_xlib_helper.cpp
platform/linux/linux_xlib_helper.h
platform/linux/file_utilities_linux.cpp
@@ -808,6 +824,7 @@ PRIVATE
platform/linux/notifications_manager_linux.h
platform/linux/specific_linux.cpp
platform/linux/specific_linux.h
platform/linux/window_title_linux.cpp
platform/linux/window_title_linux.h
platform/mac/file_utilities_mac.mm
platform/mac/file_utilities_mac.h
@@ -1081,6 +1098,11 @@ if (NOT LINUX)
)
endif()
if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp)
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)
endif()
if (NOT DESKTOP_APP_USE_PACKAGED)
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
endif()

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -184,6 +184,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_media_album_error" = "This file cannot be saved as a part of an album.";
"lng_edit_media_invalid_file" = "Sorry, no way to use this file.";
"lng_edit_caption_attach" = "Sorry, you can't attach a new media while you're editing your message.";
"lng_edit_caption_voice" = "Sorry, you can't edit your message while you're having an unsent voice message.";
"lng_intro_about" = "Welcome to the official Telegram Desktop app.\nIt's fast and secure.";
"lng_start_msgs" = "START MESSAGING";
@@ -1341,6 +1342,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_broadcast_silent_ph" = "Silent broadcast...";
"lng_send_anonymous_ph" = "Send anonymously...";
"lng_record_cancel" = "Release outside this field to cancel";
"lng_record_lock_cancel" = "Click outside of circle to cancel";
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
"lng_record_lock_discard" = "Discard";
"lng_will_be_notified" = "Members will be notified when you post";
"lng_wont_be_notified" = "Members will not be notified when you post";
"lng_willbe_history" = "Please select a chat to start messaging";

View File

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

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,4,9,0
PRODUCTVERSION 2,4,9,0
FILEVERSION 2,4,11,0
PRODUCTVERSION 2,4,11,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.4.9.0"
VALUE "FileVersion", "2.4.11.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.4.9.0"
VALUE "ProductVersion", "2.4.11.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,4,9,0
PRODUCTVERSION 2,4,9,0
FILEVERSION 2,4,11,0
PRODUCTVERSION 2,4,11,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.4.9.0"
VALUE "FileVersion", "2.4.11.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.4.9.0"
VALUE "ProductVersion", "2.4.11.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -86,8 +86,7 @@ void SendExistingMedia(
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = message.action.options.silent
|| (peer->isBroadcast() && session->data().notifySilentPosts(peer));
const auto silentPost = ShouldSendSilent(peer, message.action.options);
InnerFillMessagePostFlags(message.action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
@@ -261,8 +260,7 @@ bool SendDice(Api::MessageToSend &message) {
}
const auto replyHeader = NewMessageReplyHeader(message.action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = message.action.options.silent
|| (peer->isBroadcast() && session->data().notifySilentPosts(peer));
const auto silentPost = ShouldSendSilent(peer, message.action.options);
InnerFillMessagePostFlags(message.action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
@@ -403,7 +401,7 @@ void SendConfirmedFile(
}
const auto replyHeader = NewMessageReplyHeader(action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = file->to.options.silent;
const auto silentPost = ShouldSendSilent(peer, file->to.options);
Api::FillMessagePostFlags(action, peer, flags);
if (silentPost) {
flags |= MTPDmessage::Flag::f_silent;

View File

@@ -791,6 +791,10 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
}
}
int32 Updates::pts() const {
return _ptsWaiter.current();
}
void Updates::updateOnline() {
updateOnline(false);
}

View File

@@ -33,6 +33,8 @@ public:
void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
void applyUpdateNoPtsCheck(const MTPUpdate &update);
[[nodiscard]] int32 pts() const;
void updateOnline();
[[nodiscard]] bool isIdle() const;
void checkIdleFinish();

View File

@@ -2441,16 +2441,14 @@ void ApiWrap::saveCurrentDraftToCloud() {
Core::App().saveCurrentDraftsToHistories();
for (const auto controller : _session->windows()) {
if (const auto peer = controller->activeChatCurrent().peer()) {
if (const auto history = _session->data().historyLoaded(peer)) {
_session->local().writeDrafts(history);
if (const auto history = controller->activeChatCurrent().history()) {
_session->local().writeDrafts(history);
const auto localDraft = history->localDraft();
const auto cloudDraft = history->cloudDraft();
if (!Data::draftsAreEqual(localDraft, cloudDraft)
&& !_session->supportMode()) {
saveDraftToCloudDelayed(history);
}
const auto localDraft = history->localDraft();
const auto cloudDraft = history->cloudDraft();
if (!Data::draftsAreEqual(localDraft, cloudDraft)
&& !_session->supportMode()) {
saveDraftToCloudDelayed(history);
}
}
}
@@ -3957,8 +3955,7 @@ void ApiWrap::forwardMessages(
histories.readInbox(history);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = action.options.silent
|| (peer->isBroadcast() && _session->data().notifySilentPosts(peer));
const auto silentPost = ShouldSendSilent(peer, action.options);
auto flags = MTPDmessage::Flags(0);
auto clientFlags = MTPDmessage_ClientFlags();
@@ -4157,11 +4154,7 @@ void ApiWrap::sendSharedContact(
MTP_string(firstName),
MTP_string(lastName),
MTP_string(vcard));
auto options = action.options;
if (_session->data().notifySilentPosts(peer)) {
options.silent = true;
}
sendMedia(item, media, options);
sendMedia(item, media, action.options);
_session->data().sendHistoryChangeNotifications();
_session->changes().historyUpdated(
@@ -4376,8 +4369,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
flags |= MTPDmessage::Flag::f_media;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = action.options.silent
|| (peer->isBroadcast() && _session->data().notifySilentPosts(peer));
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
@@ -4518,8 +4510,7 @@ void ApiWrap::sendInlineResult(
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id;
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = action.options.silent
|| (peer->isBroadcast() && _session->data().notifySilentPosts(peer));
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent;
@@ -4685,7 +4676,7 @@ void ApiWrap::sendMediaWithRandomId(
| (replyTo
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMedia::Flag(0))
| (options.silent
| (ShouldSendSilent(history->peer, options)
? MTPmessages_SendMedia::Flag::f_silent
: MTPmessages_SendMedia::Flag(0))
| (!sentEntities.v.isEmpty()
@@ -4792,7 +4783,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
| (replyTo
? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMultiMedia::Flag(0))
| (album->options.silent
| (ShouldSendSilent(history->peer, album->options)
? MTPmessages_SendMultiMedia::Flag::f_silent
: MTPmessages_SendMultiMedia::Flag(0))
| (album->options.scheduled
@@ -4830,10 +4821,6 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
const auto peer = action.history->peer;
auto options = action.options;
if (_session->data().notifySilentPosts(peer)) {
options.silent = true;
}
return FileLoadTo(peer->id, action.options, action.replyTo);
}
@@ -5263,8 +5250,7 @@ void ApiWrap::createPoll(
history->clearLocalDraft();
history->clearCloudDraft();
}
const auto silentPost = action.options.silent
|| (peer->isBroadcast() && _session->data().notifySilentPosts(peer));
const auto silentPost = ShouldSendSilent(peer, action.options);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
@@ -5287,6 +5273,11 @@ void ApiWrap::createPoll(
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) mutable {
applyUpdates(result);
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
done();
finish();
}).fail([=](const RPCError &error) mutable {

View File

@@ -446,7 +446,7 @@ autoDownloadLimitPadding: margins(22px, 8px, 22px, 8px);
confirmCaptionArea: InputField(defaultInputField) {
textMargins: margins(1px, 26px, 31px, 4px);
heightMax: 78px;
heightMax: 158px;
}
confirmBg: windowBgOver;
confirmMaxHeight: 245px;

View File

@@ -280,8 +280,9 @@ void SendFilesBox::enqueueNextPrepare() {
}
while (!_list.filesToProcess.empty()
&& _list.filesToProcess.front().information) {
addFile(std::move(_list.filesToProcess.front()));
auto file = std::move(_list.filesToProcess.front());
_list.filesToProcess.pop_front();
addFile(std::move(file));
}
if (_list.filesToProcess.empty()) {
return;
@@ -329,7 +330,11 @@ void SendFilesBox::setupShadows() {
}
void SendFilesBox::prepare() {
_send = addButton(tr::lng_send_button(), [=] { send({}); });
_send = addButton(
(_sendType == Api::SendType::Normal
? tr::lng_send_button()
: tr::lng_create_group_next()),
[=] { send({}); });
if (_sendType == Api::SendType::Normal) {
SendMenu::SetupMenuAndShortcuts(
_send,
@@ -638,9 +643,6 @@ void SendFilesBox::setupSendWayControls() {
}
void SendFilesBox::updateSendWayControlsVisibility() {
if (_sendLimit == SendLimit::One) {
return;
}
const auto onlyOne = (_sendLimit == SendLimit::One);
_groupFiles->setVisible(_list.hasGroupOption(onlyOne));
_sendImagesAsPhotos->setVisible(
@@ -817,10 +819,13 @@ void SendFilesBox::addPreparedAsyncFile(Ui::PreparedFile &&file) {
}
void SendFilesBox::addFile(Ui::PreparedFile &&file) {
// canBeSentInSlowmode checks for non empty filesToProcess.
auto saved = base::take(_list.filesToProcess);
_list.files.push_back(std::move(file));
if (_sendLimit == SendLimit::One && !_list.canBeSentInSlowmode()) {
_list.files.pop_back();
}
_list.filesToProcess = std::move(saved);
}
void SendFilesBox::refreshTitleText() {

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_stickers.h"
#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu
#include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/message_field.h" // PrepareMentionTag.
#include "mainwindow.h"
#include "apiwrap.h"
#include "main/main_session.h"
@@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_single_player.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
#include "ui/image/image.h"
#include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
@@ -39,15 +41,108 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication>
class FieldAutocomplete::Inner final
: public Ui::RpWidget
, private base::Subscriber {
public:
struct ScrollTo {
int top;
int bottom;
};
Inner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
bool chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options = Api::SendOptions()) const;
void setRecentInlineBotsInRows(int32 bots);
void setSendMenuType(Fn<SendMenu::Type()> &&callback);
void rowsUpdated();
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
rpl::producer<FieldAutocomplete::BotCommandChosen>
botCommandChosen() const;
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
rpl::producer<ScrollTo> scrollToRequested() const;
void onParentGeometryChanged();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
base::unique_qptr<Ui::PopupMenu> _menu;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;
bool _previewShown = false;
Fn<SendMenu::Type()> _sendMenuType;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
rpl::event_stream<ScrollTo> _scrollToRequested;
base::Timer _previewTimer;
};
FieldAutocomplete::FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _controller(controller)
, _scroll(this, st::mentionScroll) {
_scroll->setGeometry(rect());
hide();
using Inner = internal::FieldAutocompleteInner;
_scroll->setGeometry(rect());
_inner = _scroll->setOwnedWidget(
object_ptr<Inner>(
@@ -76,6 +171,10 @@ FieldAutocomplete::FieldAutocomplete(
&Inner::onParentGeometryChanged);
}
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
return _controller;
}
auto FieldAutocomplete::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> {
return _inner->mentionChosen();
@@ -125,9 +224,9 @@ void FieldAutocomplete::showFiltered(
if (query.isEmpty()) {
_type = Type::Mentions;
rowsUpdated(
internal::MentionRows(),
internal::HashtagRows(),
internal::BotCommandRows(),
MentionRows(),
HashtagRows(),
BotCommandRows(),
base::take(_srows),
false);
return;
@@ -171,7 +270,7 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
base::take(_mrows),
base::take(_hrows),
base::take(_brows),
internal::StickerRows(),
StickerRows(),
false);
return;
}
@@ -203,7 +302,7 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
}
}
internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto list = _controller->session().data().stickers().getListByEmoji(
_emoji,
_stickersSeed
@@ -211,7 +310,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
auto result = ranges::view::all(
list
) | ranges::view::transform([](not_null<DocumentData*> sticker) {
return internal::StickerSuggestion{
return StickerSuggestion{
sticker,
sticker->createMediaView()
};
@@ -223,7 +322,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto i = ranges::find(
result,
suggestion.document,
&internal::StickerSuggestion::document);
&StickerSuggestion::document);
if (i != end(result)) {
i->animated = std::move(suggestion.animated);
}
@@ -233,10 +332,10 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
void FieldAutocomplete::updateFiltered(bool resetScroll) {
int32 now = base::unixtime::now(), recentInlineBots = 0;
internal::MentionRows mrows;
internal::HashtagRows hrows;
internal::BotCommandRows brows;
internal::StickerRows srows;
MentionRows mrows;
HashtagRows hrows;
BotCommandRows brows;
StickerRows srows;
if (_emoji) {
srows = getStickerSuggestions();
} else if (_type == Type::Mentions) {
@@ -435,10 +534,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
}
void FieldAutocomplete::rowsUpdated(
internal::MentionRows &&mrows,
internal::HashtagRows &&hrows,
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
MentionRows &&mrows,
HashtagRows &&hrows,
BotCommandRows &&brows,
StickerRows &&srows,
bool resetScroll) {
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
if (!isHidden()) {
@@ -590,6 +689,10 @@ bool FieldAutocomplete::chooseSelected(ChooseMethod method) const {
return _inner->chooseSelected(method);
}
void FieldAutocomplete::setSendMenuType(Fn<SendMenu::Type()> &&callback) {
_inner->setSendMenuType(std::move(callback));
}
bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
auto hidden = isHidden();
auto moderate = Core::App().settings().moderateModeEnabled();
@@ -620,9 +723,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
return QWidget::eventFilter(obj, e);
}
namespace internal {
FieldAutocompleteInner::FieldAutocompleteInner(
FieldAutocomplete::Inner::Inner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
@@ -642,7 +743,7 @@ FieldAutocompleteInner::FieldAutocompleteInner(
}, lifetime());
}
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
@@ -841,11 +942,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
}
void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) {
void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) {
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
}
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) {
const auto globalPosition = e->globalPos();
if (!_lastMousePosition) {
_lastMousePosition = globalPosition;
@@ -857,7 +958,7 @@ void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
selectByMouse(globalPosition);
}
void FieldAutocompleteInner::clearSel(bool hidden) {
void FieldAutocomplete::Inner::clearSel(bool hidden) {
_overDelete = false;
_mouseSelection = false;
_lastMousePosition = std::nullopt;
@@ -868,7 +969,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
}
}
bool FieldAutocompleteInner::moveSel(int key) {
bool FieldAutocomplete::Inner::moveSel(int key) {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
@@ -903,12 +1004,12 @@ bool FieldAutocompleteInner::moveSel(int key) {
return true;
}
bool FieldAutocompleteInner::chooseSelected(
bool FieldAutocomplete::Inner::chooseSelected(
FieldAutocomplete::ChooseMethod method) const {
return chooseAtIndex(method, _sel);
}
bool FieldAutocompleteInner::chooseAtIndex(
bool FieldAutocomplete::Inner::chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options) const {
@@ -955,11 +1056,11 @@ bool FieldAutocompleteInner::chooseAtIndex(
return false;
}
void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
void FieldAutocomplete::Inner::setRecentInlineBotsInRows(int32 bots) {
_recentInlineBotsInRows = bots;
}
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
void FieldAutocomplete::Inner::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
@@ -999,7 +1100,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
}
}
void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
void FieldAutocomplete::Inner::mouseReleaseEvent(QMouseEvent *e) {
_previewTimer.cancel();
int32 pressed = _down;
@@ -1017,12 +1118,14 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
}
void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
if (_sel < 0 || _srows->empty() || _down >= 0) {
return;
}
const auto index = _sel;
const auto type = SendMenu::Type::Scheduled;
const auto type = _sendMenuType
? _sendMenuType()
: SendMenu::Type::Disabled;
const auto method = FieldAutocomplete::ChooseMethod::ByClick;
_menu = base::make_unique_q<Ui::PopupMenu>(this);
@@ -1031,7 +1134,7 @@ void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
};
SendMenu::FillSendMenu(
_menu,
[&] { return type; },
type,
SendMenu::DefaultSilentCallback(send),
SendMenu::DefaultScheduleCallback(this, type, send));
@@ -1040,11 +1143,11 @@ void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
}
}
void FieldAutocompleteInner::enterEventHook(QEvent *e) {
void FieldAutocomplete::Inner::enterEventHook(QEvent *e) {
setMouseTracking(true);
}
void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
void FieldAutocomplete::Inner::leaveEventHook(QEvent *e) {
setMouseTracking(false);
if (_mouseSelection) {
setSel(-1);
@@ -1053,7 +1156,7 @@ void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
}
}
void FieldAutocompleteInner::updateSelectedRow() {
void FieldAutocomplete::Inner::updateSelectedRow() {
if (_sel >= 0) {
if (_srows->empty()) {
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
@@ -1064,7 +1167,7 @@ void FieldAutocompleteInner::updateSelectedRow() {
}
}
void FieldAutocompleteInner::setSel(int sel, bool scroll) {
void FieldAutocomplete::Inner::setSel(int sel, bool scroll) {
updateSelectedRow();
_sel = sel;
updateSelectedRow();
@@ -1084,13 +1187,13 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
}
}
void FieldAutocompleteInner::rowsUpdated() {
void FieldAutocomplete::Inner::rowsUpdated() {
if (_srows->empty()) {
_stickersLifetime.destroy();
}
}
auto FieldAutocompleteInner::getLottieRenderer()
auto FieldAutocomplete::Inner::getLottieRenderer()
-> std::shared_ptr<Lottie::FrameRenderer> {
if (auto result = _lottieRenderer.lock()) {
return result;
@@ -1100,7 +1203,7 @@ auto FieldAutocompleteInner::getLottieRenderer()
return result;
}
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
suggestion.animated = ChatHelpers::LottiePlayerFromDocument(
suggestion.documentMedia.get(),
@@ -1115,13 +1218,13 @@ void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
}, _stickersLifetime);
}
QSize FieldAutocompleteInner::stickerBoundingBox() const {
QSize FieldAutocomplete::Inner::stickerBoundingBox() const {
return QSize(
st::stickerPanSize.width() - st::buttonRadius * 2,
st::stickerPanSize.height() - st::buttonRadius * 2);
}
void FieldAutocompleteInner::repaintSticker(
void FieldAutocomplete::Inner::repaintSticker(
not_null<DocumentData*> document) {
const auto i = ranges::find(
*_srows,
@@ -1140,7 +1243,7 @@ void FieldAutocompleteInner::repaintSticker(
st::stickerPanSize.height());
}
void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;
const auto mouse = mapFromGlobal(globalPosition);
@@ -1186,7 +1289,7 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
}
}
void FieldAutocompleteInner::onParentGeometryChanged() {
void FieldAutocomplete::Inner::onParentGeometryChanged() {
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
@@ -1196,7 +1299,7 @@ void FieldAutocompleteInner::onParentGeometryChanged() {
}
}
void FieldAutocompleteInner::showPreview() {
void FieldAutocomplete::Inner::showPreview() {
if (_down >= 0 && _down < _srows->size()) {
if (const auto w = App::wnd()) {
w->showMediaPreview(
@@ -1207,29 +1310,32 @@ void FieldAutocompleteInner::showPreview() {
}
}
auto FieldAutocompleteInner::mentionChosen() const
void FieldAutocomplete::Inner::setSendMenuType(
Fn<SendMenu::Type()> &&callback) {
_sendMenuType = std::move(callback);
}
auto FieldAutocomplete::Inner::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> {
return _mentionChosen.events();
}
auto FieldAutocompleteInner::hashtagChosen() const
auto FieldAutocomplete::Inner::hashtagChosen() const
-> rpl::producer<FieldAutocomplete::HashtagChosen> {
return _hashtagChosen.events();
}
auto FieldAutocompleteInner::botCommandChosen() const
auto FieldAutocomplete::Inner::botCommandChosen() const
-> rpl::producer<FieldAutocomplete::BotCommandChosen> {
return _botCommandChosen.events();
}
auto FieldAutocompleteInner::stickerChosen() const
auto FieldAutocomplete::Inner::stickerChosen() const
-> rpl::producer<FieldAutocomplete::StickerChosen> {
return _stickerChosen.events();
}
auto FieldAutocompleteInner::scrollToRequested() const
auto FieldAutocomplete::Inner::scrollToRequested() const
-> rpl::producer<ScrollTo> {
return _scrollToRequested.events();
}
} // namespace internal

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class PopupMenu;
class ScrollArea;
class InputField;
} // namespace Ui
namespace Lottie {
@@ -32,42 +33,20 @@ class DocumentMedia;
class CloudImageView;
} // namespace Data
namespace internal {
namespace SendMenu {
enum class Type;
} // namespace SendMenu
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
class FieldAutocompleteInner;
} // namespace internal
class FieldAutocomplete final : public Ui::RpWidget {
public:
FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller);
~FieldAutocomplete();
[[nodiscard]] not_null<Window::SessionController*> controller() const;
bool clearFilteredBotCommands();
void showFiltered(
not_null<PeerData*> peer,
@@ -124,6 +103,7 @@ public:
void setModerateKeyActivateCallback(Fn<bool(int)> callback) {
_moderateKeyActivateCallback = std::move(callback);
}
void setSendMenuType(Fn<SendMenu::Type()> &&callback);
void hideFast();
@@ -140,29 +120,54 @@ protected:
void paintEvent(QPaintEvent *e) override;
private:
class Inner;
friend class Inner;
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct MentionRow {
not_null<UserData*> user;
std::shared_ptr<Data::CloudImageView> userpic;
};
struct BotCommandRow {
not_null<UserData*> user;
not_null<const BotCommand*> command;
std::shared_ptr<Data::CloudImageView> userpic;
};
using HashtagRows = std::vector<QString>;
using BotCommandRows = std::vector<BotCommandRow>;
using StickerRows = std::vector<StickerSuggestion>;
using MentionRows = std::vector<MentionRow>;
void animationCallback();
void hideFinish();
void updateFiltered(bool resetScroll = false);
void recount(bool resetScroll = false);
internal::StickerRows getStickerSuggestions();
StickerRows getStickerSuggestions();
const not_null<Window::SessionController*> _controller;
QPixmap _cache;
internal::MentionRows _mrows;
internal::HashtagRows _hrows;
internal::BotCommandRows _brows;
internal::StickerRows _srows;
MentionRows _mrows;
HashtagRows _hrows;
BotCommandRows _brows;
StickerRows _srows;
void rowsUpdated(
internal::MentionRows &&mrows,
internal::HashtagRows &&hrows,
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
MentionRows &&mrows,
HashtagRows &&hrows,
BotCommandRows &&brows,
StickerRows &&srows,
bool resetScroll);
object_ptr<Ui::ScrollArea> _scroll;
QPointer<internal::FieldAutocompleteInner> _inner;
QPointer<Inner> _inner;
ChatData *_chat = nullptr;
UserData *_user = nullptr;
@@ -186,100 +191,4 @@ private:
Fn<bool(int)> _moderateKeyActivateCallback;
friend class internal::FieldAutocompleteInner;
};
namespace internal {
class FieldAutocompleteInner final
: public Ui::RpWidget
, private base::Subscriber {
public:
struct ScrollTo {
int top;
int bottom;
};
FieldAutocompleteInner(
not_null<Window::SessionController*> controller,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
bool chooseAtIndex(
FieldAutocomplete::ChooseMethod method,
int index,
Api::SendOptions options = Api::SendOptions()) const;
void setRecentInlineBotsInRows(int32 bots);
void rowsUpdated();
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
rpl::producer<FieldAutocomplete::BotCommandChosen>
botCommandChosen() const;
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
rpl::producer<ScrollTo> scrollToRequested() const;
void onParentGeometryChanged();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
const not_null<MentionRows*> _mrows;
const not_null<HashtagRows*> _hrows;
const not_null<BotCommandRows*> _brows;
const not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
base::unique_qptr<Ui::PopupMenu> _menu;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;
bool _previewShown = false;
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
rpl::event_stream<ScrollTo> _scrollToRequested;
base::Timer _previewTimer;
};
} // namespace internal

View File

@@ -377,7 +377,7 @@ void GifsListWidget::fillContextMenu(
};
SendMenu::FillSendMenu(
menu,
[&] { return type; },
type,
SendMenu::DefaultSilentCallback(send),
SendMenu::DefaultScheduleCallback(this, type, send));

View File

@@ -40,13 +40,13 @@ Fn<void()> DefaultScheduleCallback(
FillMenuResult FillSendMenu(
not_null<Ui::PopupMenu*> menu,
Fn<Type()> type,
Type type,
Fn<void()> silent,
Fn<void()> schedule) {
if (!silent && !schedule) {
return FillMenuResult::None;
}
const auto now = type();
const auto now = type;
if (now == Type::Disabled
|| (!silent && now == Type::SilentOnly)) {
return FillMenuResult::None;
@@ -76,7 +76,7 @@ void SetupMenuAndShortcuts(
const auto menu = std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
const auto showMenu = [=] {
*menu = base::make_unique_q<Ui::PopupMenu>(button);
const auto result = FillSendMenu(*menu, type, silent, schedule);
const auto result = FillSendMenu(*menu, type(), silent, schedule);
const auto success = (result == FillMenuResult::Success);
if (success) {
(*menu)->popup(QCursor::pos());

View File

@@ -40,7 +40,7 @@ Fn<void()> DefaultScheduleCallback(
FillMenuResult FillSendMenu(
not_null<Ui::PopupMenu*> menu,
Fn<Type()> type,
Type type,
Fn<void()> silent,
Fn<void()> schedule);

View File

@@ -2077,7 +2077,7 @@ void StickersListWidget::fillContextMenu(
};
SendMenu::FillSendMenu(
menu,
[&] { return type; },
type,
SendMenu::DefaultSilentCallback(send),
SendMenu::DefaultScheduleCallback(this, type, send));

View File

@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
namespace InlineBots {
class Result;
struct ResultSelected;
} // namespace InlineBots
namespace Main {
@@ -60,11 +60,7 @@ public:
not_null<PhotoData*> photo;
Api::SendOptions options;
};
struct InlineChosen {
not_null<InlineBots::Result*> result;
not_null<UserData*> bot;
Api::SendOptions options;
};
using InlineChosen = InlineBots::ResultSelected;
enum class Mode {
Full,
EmojiOnly
@@ -113,7 +109,7 @@ public:
_beforeHidingCallback = std::move(callback);
}
void setSendMenuType(Fn<SendMenu::Type()> callback) {
void setSendMenuType(Fn<SendMenu::Type()> &&callback) {
_sendMenuType = std::move(callback);
}

View File

@@ -29,9 +29,6 @@ enum {
LinksOverviewPerPage = 12,
MediaOverviewStartPerPage = 5,
AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
AudioVoiceMsgChannels = 2, // stereo
PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request
SearchPeopleLimit = 5,

View File

@@ -89,6 +89,22 @@ std::map<int, const char*> BetaLogs() {
2004008,
"- Upgrade several third party libraries to latest versions.\n"
},
{
2004010,
"- Use inline bots and sticker by emoji suggestions in channel comments.\n"
"- Lock voice message recording, listen to your voice message before sending.\n"
},
{
2004011,
"- Improve locked voice message recording.\n"
"- Fix main window closing to tray on Windows.\n"
"- Fix crash in bot command sending.\n"
"- Fix adding additional photos when sending an album to a group with enabled slow mode.\n"
},
};
};

View File

@@ -180,20 +180,14 @@ auto CashtagClickHandler::getTextEntity() const -> TextEntity {
return { EntityType::Cashtag };
}
PeerData *BotCommandClickHandler::_peer = nullptr;
UserData *BotCommandClickHandler::_bot = nullptr;
void BotCommandClickHandler::onClick(ClickContext context) const {
const auto button = context.button;
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
if (auto peer = peerForCommand()) {
if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) {
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
App::sendBotCommand(peer, bot, _cmd);
return;
}
}
if (auto peer = Ui::getPeerForMouseAction()) { // old way
const auto my = context.other.value<ClickHandlerContext>();
if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) {
delegate->elementSendBotCommand(_cmd, my.itemId);
return;
} else if (auto peer = Ui::getPeerForMouseAction()) { // old way
auto bot = peer->isUser() ? peer->asUser() : nullptr;
if (!bot) {
if (const auto view = App::hoveredLinkItem()) {

View File

@@ -13,8 +13,18 @@ namespace Main {
class Session;
} // namespace Main
namespace HistoryView {
class ElementDelegate;
} // namespace HistoryView
[[nodiscard]] bool UrlRequiresConfirmation(const QUrl &url);
struct ClickHandlerContext {
FullMsgId itemId;
Fn<HistoryView::ElementDelegate*()> elementDelegate;
};
Q_DECLARE_METATYPE(ClickHandlerContext);
class HiddenUrlClickHandler : public UrlClickHandler {
public:
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
@@ -165,30 +175,14 @@ public:
return _cmd;
}
static void setPeerForCommand(PeerData *peer) {
_peer = peer;
}
static void setBotForCommand(UserData *bot) {
_bot = bot;
}
TextEntity getTextEntity() const override;
protected:
QString url() const override {
return _cmd;
}
static PeerData *peerForCommand() {
return _peer;
}
static UserData *botForCommand() {
return _bot;
}
private:
QString _cmd;
static PeerData *_peer;
static UserData *_bot;
};

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "core/update_checker.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "boxes/confirm_phone_box.h"
#include "boxes/background_preview_box.h"
#include "boxes/confirm_box.h"
@@ -76,11 +77,11 @@ bool ShowTheme(
if (!controller) {
return false;
}
const auto clickFromMessageId = context.value<FullMsgId>();
const auto fromMessageId = context.value<ClickHandlerContext>().itemId;
Core::App().hideMediaView();
controller->session().data().cloudThemes().resolve(
match->captured(1),
clickFromMessageId);
fromMessageId);
return true;
}
@@ -280,7 +281,7 @@ bool ResolveUsername(
startToken = gameParam;
post = ShowAtGameShareMsgId;
}
const auto clickFromMessageId = context.value<FullMsgId>();
const auto fromMessageId = context.value<ClickHandlerContext>().itemId;
using Navigation = Window::SessionNavigation;
controller->showPeerByLink(Navigation::PeerByLinkInfo{
.usernameOrId = domain,
@@ -295,7 +296,7 @@ bool ResolveUsername(
}
: Navigation::RepliesByLinkInfo{ v::null },
.startToken = startToken,
.clickFromMessageId = clickFromMessageId,
.clickFromMessageId = fromMessageId,
});
return true;
}
@@ -319,7 +320,7 @@ bool ResolvePrivatePost(
if (!channelId || !IsServerMsgId(msgId)) {
return false;
}
const auto clickFromMessageId = context.value<FullMsgId>();
const auto fromMessageId = context.value<ClickHandlerContext>().itemId;
using Navigation = Window::SessionNavigation;
controller->showPeerByLink(Navigation::PeerByLinkInfo{
.usernameOrId = channelId,
@@ -333,7 +334,7 @@ bool ResolvePrivatePost(
Navigation::ThreadId{ threadId }
}
: Navigation::RepliesByLinkInfo{ v::null },
.clickFromMessageId = clickFromMessageId,
.clickFromMessageId = fromMessageId,
});
return true;
}

View File

@@ -215,7 +215,7 @@ void Sandbox::setupScreenScale() {
Sandbox::~Sandbox() = default;
bool Sandbox::event(QEvent *e) {
if (e->type() == QEvent::Close) {
if (e->type() == QEvent::Close || e->type() == QEvent::Quit) {
App::quit();
}
return QApplication::event(e);

View File

@@ -65,7 +65,7 @@ QString UiIntegration::timeFormat() {
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
const EntityLinkData &data,
const std::any &context) {
const auto my = std::any_cast<Context>(&context);
const auto my = std::any_cast<MarkedTextContext>(&context);
switch (data.type) {
case EntityType::Url:
return (!data.data.isEmpty()
@@ -82,6 +82,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
return std::make_shared<BotCommandClickHandler>(data.data);
case EntityType::Hashtag:
using HashtagMentionType = MarkedTextContext::HashtagMentionType;
if (my && my->type == HashtagMentionType::Twitter) {
return std::make_shared<UrlClickHandler>(
(qsl("https://twitter.com/hashtag/")
@@ -101,6 +102,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
return std::make_shared<CashtagClickHandler>(data.data);
case EntityType::Mention:
using HashtagMentionType = MarkedTextContext::HashtagMentionType;
if (my && my->type == HashtagMentionType::Twitter) {
return std::make_shared<UrlClickHandler>(
qsl("https://twitter.com/") + data.data.mid(1),

View File

@@ -13,20 +13,25 @@ namespace Main {
class Session;
} // namespace Main
namespace HistoryView {
class ElementDelegate;
} // namespace HistoryView
namespace Core {
class UiIntegration : public Ui::Integration {
public:
struct MarkedTextContext {
enum class HashtagMentionType : uchar {
Telegram,
Twitter,
Instagram,
};
struct Context {
Main::Session *session = nullptr;
HashtagMentionType type = HashtagMentionType::Telegram;
};
Main::Session *session = nullptr;
HashtagMentionType type = HashtagMentionType::Telegram;
};
class UiIntegration : public Ui::Integration {
public:
void postponeCall(FnMut<void()> &&callable) override;
void registerLeaveSubscription(not_null<QWidget*> widget) override;
void unregisterLeaveSubscription(not_null<QWidget*> widget) override;

View File

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

View File

@@ -48,6 +48,77 @@ struct Draft {
mtpRequestId saveRequestId = 0;
};
class DraftKey {
public:
[[nodiscard]] static DraftKey None() {
return 0;
}
[[nodiscard]] static DraftKey Local() {
return kLocalDraftIndex;
}
[[nodiscard]] static DraftKey LocalEdit() {
return kLocalDraftIndex + kEditDraftShift;
}
[[nodiscard]] static DraftKey Cloud() {
return kCloudDraftIndex;
}
[[nodiscard]] static DraftKey Scheduled() {
return kScheduledDraftIndex;
}
[[nodiscard]] static DraftKey ScheduledEdit() {
return kScheduledDraftIndex + kEditDraftShift;
}
[[nodiscard]] static DraftKey Replies(MsgId rootId) {
return rootId;
}
[[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) {
return rootId + kEditDraftShift;
}
[[nodiscard]] static DraftKey FromSerialized(int32 value) {
return value;
}
[[nodiscard]] int32 serialize() const {
return _value;
}
inline bool operator<(const DraftKey &other) const {
return _value < other._value;
}
inline bool operator==(const DraftKey &other) const {
return _value == other._value;
}
inline bool operator>(const DraftKey &other) const {
return (other < *this);
}
inline bool operator<=(const DraftKey &other) const {
return !(other < *this);
}
inline bool operator>=(const DraftKey &other) const {
return !(*this < other);
}
inline bool operator!=(const DraftKey &other) const {
return !(*this == other);
}
inline explicit operator bool() const {
return _value != 0;
}
private:
DraftKey(int value) : _value(value) {
}
static constexpr auto kLocalDraftIndex = -1;
static constexpr auto kCloudDraftIndex = -2;
static constexpr auto kScheduledDraftIndex = -3;
static constexpr auto kEditDraftShift = ServerMaxMsgId;
int _value = 0;
};
using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
inline bool draftStringIsEmpty(const QString &text) {
for_const (auto ch, text) {
if (!ch.isSpace()) {

View File

@@ -89,7 +89,7 @@ void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
return;
}
if (const auto user = item->history()->peer->asUser()) {
if (!user->isBot() || !user->botInfo->inlineReturnPeerId) {
if (!user->isBot() || !user->botInfo->inlineReturnTo.key) {
return;
}
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {

View File

@@ -434,6 +434,12 @@ inline bool operator==(
&& (a.scroll == b.scroll);
}
inline bool operator!=(
const MessageCursor &a,
const MessageCursor &b) {
return !(a == b);
}
class FileClickHandler : public LeftButtonClickHandler {
public:
FileClickHandler(

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "data/data_peer.h"
#include "dialogs/dialogs_key.h"
class BotCommand {
public:
@@ -34,7 +35,7 @@ struct BotInfo {
Ui::Text::String text = { int(st::msgMinWidth) }; // description
QString startToken, startGroupToken, shareGameShortName;
PeerId inlineReturnPeerId = 0;
Dialogs::EntryState inlineReturnTo;
};
class UserData : public PeerData {

View File

@@ -1814,23 +1814,17 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
} else {
fillArchiveSearchMenu(_menu.get());
}
} else if (const auto history = row.key.history()) {
Window::FillPeerMenu(
} else {
Window::FillDialogsEntryMenu(
_controller,
history->peer,
_filterId,
Dialogs::EntryState{
.key = row.key,
.section = Dialogs::EntryState::Section::ChatsList,
.filterId = _filterId,
},
[&](const QString &text, Fn<void()> callback) {
return _menu->addAction(text, std::move(callback));
},
Window::PeerMenuSource::ChatsList);
} else if (const auto folder = row.key.folder()) {
Window::FillFolderMenu(
_controller,
folder,
[&](const QString &text, Fn<void()> callback) {
return _menu->addAction(text, std::move(callback));
},
Window::PeerMenuSource::ChatsList);
});
}
connect(_menu.get(), &QObject::destroyed, [=] {
if (_menuRow.key) {

View File

@@ -109,4 +109,21 @@ inline bool operator>=(const RowDescriptor &a, const RowDescriptor &b) {
return !(a < b);
}
struct EntryState {
enum class Section {
History,
Profile,
ChatsList,
Scheduled,
Pinned,
Replies,
};
Key key;
Section section = Section::History;
FilterId filterId = 0;
MsgId rootId = 0;
MsgId currentReplyToId = 0;
};
} // namespace Dialogs

View File

@@ -523,8 +523,10 @@ void Widget::refreshFolderTopBar() {
updateControlsGeometry();
}
_folderTopBar->setActiveChat(
_openedFolder,
HistoryView::TopBarWidget::Section::History,
HistoryView::TopBarWidget::ActiveChat{
.key = _openedFolder,
.section = Dialogs::EntryState::Section::ChatsList,
},
nullptr);
} else {
_folderTopBar.destroy();
@@ -1784,7 +1786,9 @@ bool Widget::onCancelSearch() {
void Widget::onCancelSearchInChat() {
cancelSearchRequest();
if (_searchInChat) {
if (Adaptive::OneColumn() && !controller()->selectingPeer()) {
if (Adaptive::OneColumn()
&& !controller()->selectingPeer()
&& _filter->getLastText().trimmed().isEmpty()) {
if (const auto peer = _searchInChat.peer()) {
Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
//} else if (const auto feed = _searchInChat.feed()) { // #feed

View File

@@ -171,7 +171,13 @@ void activateBotCommand(
}
}
if (const auto m = CheckMainWidget(&msg->history()->session())) {
Window::PeerMenuCreatePoll(m->controller(), msg->history()->peer, chosen, disabled);
const auto replyToId = MsgId(0);
Window::PeerMenuCreatePoll(
m->controller(),
msg->history()->peer,
replyToId,
chosen,
disabled);
}
} break;
@@ -185,7 +191,7 @@ void activateBotCommand(
if (samePeer) {
Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data), bot, msg->id);
return true;
} else if (bot->isBot() && bot->botInfo->inlineReturnPeerId) {
} else if (bot->isBot() && bot->botInfo->inlineReturnTo.key) {
if (Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data))) {
return true;
}

View File

@@ -600,6 +600,14 @@ bool InnerWidget::elementShownUnread(not_null<const Element*> view) {
return view->data()->unread();
}
void InnerWidget::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void InnerWidget::elementHandleViaClick(not_null<UserData*> bot) {
}
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
memento->setFilter(std::move(_filter));
memento->setAdmins(std::move(_admins));

View File

@@ -116,6 +116,10 @@ public:
not_null<const HistoryView::Element*> view) override;
bool elementShownUnread(
not_null<const HistoryView::Element*> view) override;
void elementSendBotCommand(
const QString &command,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
~InnerWidget();

View File

@@ -179,22 +179,22 @@ void History::itemVanished(not_null<HistoryItem*> item) {
}
}
void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
_localDraft = std::move(draft);
}
void History::takeLocalDraft(History *from) {
if (auto &draft = from->_localDraft) {
if (!draft->textWithTags.text.isEmpty() && !_localDraft) {
_localDraft = std::move(draft);
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
_localDraft->msgId = 0;
}
from->clearLocalDraft();
session().api().saveDraftToCloudDelayed(from);
void History::takeLocalDraft(not_null<History*> from) {
const auto i = from->_drafts.find(Data::DraftKey::Local());
if (i == end(from->_drafts)) {
return;
}
auto &draft = i->second;
if (!draft->textWithTags.text.isEmpty()
&& !_drafts.contains(Data::DraftKey::Local())) {
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
draft->msgId = 0;
setLocalDraft(std::move(draft));
}
from->clearLocalDraft();
session().api().saveDraftToCloudDelayed(from);
}
void History::createLocalDraftFromCloud() {
@@ -227,9 +227,51 @@ void History::createLocalDraftFromCloud() {
}
}
void History::setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
_cloudDraft = std::move(draft);
cloudDraftTextCache.clear();
Data::Draft *History::draft(Data::DraftKey key) const {
if (!key) {
return nullptr;
}
const auto i = _drafts.find(key);
return (i != _drafts.end()) ? i->second.get() : nullptr;
}
void History::setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft) {
if (!key) {
return;
}
const auto changingCloudDraft = (key == Data::DraftKey::Cloud());
if (changingCloudDraft) {
cloudDraftTextCache.clear();
}
if (draft) {
_drafts[key] = std::move(draft);
} else if (_drafts.remove(key) && changingCloudDraft) {
updateChatListSortPosition();
}
}
const Data::HistoryDrafts &History::draftsMap() const {
return _drafts;
}
void History::setDraftsMap(Data::HistoryDrafts &&map) {
for (auto &[key, draft] : _drafts) {
map[key] = std::move(draft);
}
_drafts = std::move(map);
}
void History::clearDraft(Data::DraftKey key) {
setDraft(key, nullptr);
}
void History::clearDrafts() {
const auto changingCloudDraft = _drafts.contains(Data::DraftKey::Cloud());
_drafts.clear();
if (changingCloudDraft) {
cloudDraftTextCache.clear();
updateChatListSortPosition();
}
}
Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) {
@@ -287,22 +329,6 @@ void History::clearSentDraftText(const QString &text) {
accumulate_max(_lastSentDraftTime, base::unixtime::now());
}
void History::setEditDraft(std::unique_ptr<Data::Draft> &&draft) {
_editDraft = std::move(draft);
}
void History::clearLocalDraft() {
_localDraft = nullptr;
}
void History::clearCloudDraft() {
if (_cloudDraft) {
_cloudDraft = nullptr;
cloudDraftTextCache.clear();
updateChatListSortPosition();
}
}
void History::applyCloudDraft() {
if (session().supportMode()) {
updateChatListEntry();
@@ -314,10 +340,6 @@ void History::applyCloudDraft() {
}
}
void History::clearEditDraft() {
_editDraft = nullptr;
}
void History::draftSavedToCloud() {
updateChatListEntry();
session().local().writeDrafts(this);

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_types.h"
#include "data/data_peer.h"
#include "data/data_drafts.h"
#include "dialogs/dialogs_entry.h"
#include "history/view/history_view_send_action.h"
#include "base/observer.h"
@@ -302,31 +303,48 @@ public:
void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
Data::Draft *draft(Data::DraftKey key) const;
void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft);
void clearDraft(Data::DraftKey key);
[[nodiscard]] const Data::HistoryDrafts &draftsMap() const;
void setDraftsMap(Data::HistoryDrafts &&map);
Data::Draft *localDraft() const {
return _localDraft.get();
return draft(Data::DraftKey::Local());
}
Data::Draft *localEditDraft() const {
return draft(Data::DraftKey::LocalEdit());
}
Data::Draft *cloudDraft() const {
return _cloudDraft.get();
return draft(Data::DraftKey::Cloud());
}
Data::Draft *editDraft() const {
return _editDraft.get();
void setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::Local(), std::move(draft));
}
void setLocalDraft(std::unique_ptr<Data::Draft> &&draft);
void takeLocalDraft(History *from);
void setCloudDraft(std::unique_ptr<Data::Draft> &&draft);
void setLocalEditDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::LocalEdit(), std::move(draft));
}
void setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::Cloud(), std::move(draft));
}
void clearLocalDraft() {
clearDraft(Data::DraftKey::Local());
}
void clearCloudDraft() {
clearDraft(Data::DraftKey::Cloud());
}
void clearLocalEditDraft() {
clearDraft(Data::DraftKey::LocalEdit());
}
void clearDrafts();
Data::Draft *createCloudDraft(const Data::Draft *fromDraft);
bool skipCloudDraft(const QString &text, MsgId replyTo, TimeId date) const;
void setSentDraftText(const QString &text);
void clearSentDraftText(const QString &text);
void setEditDraft(std::unique_ptr<Data::Draft> &&draft);
void clearLocalDraft();
void clearCloudDraft();
void takeLocalDraft(not_null<History*> from);
void applyCloudDraft();
void clearEditDraft();
void draftSavedToCloud();
Data::Draft *draft() {
return _editDraft ? editDraft() : localDraft();
}
const MessageIdsList &forwardDraft() const {
return _forwardDraft;
@@ -560,8 +578,7 @@ private:
};
std::unique_ptr<BuildingBlock> _buildingFrontBlock;
std::unique_ptr<Data::Draft> _localDraft, _cloudDraft;
std::unique_ptr<Data::Draft> _editDraft;
Data::HistoryDrafts _drafts;
std::optional<QString> _lastSentDraftText;
TimeId _lastSentDraftTime = 0;
MessageIdsList _forwardDraft;

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <rpl/merge.h>
#include "core/file_utilities.h"
#include "core/crash_reports.h"
#include "core/click_handler_types.h"
#include "history/history.h"
#include "history/history_message.h"
#include "history/view/media/history_view_media.h"
@@ -1330,7 +1331,14 @@ void HistoryInner::mouseActionFinish(
: FullMsgId();
ActivateClickHandler(window(), activated, {
button,
QVariant::fromValue(pressedItemId)
QVariant::fromValue(ClickHandlerContext{
.itemId = pressedItemId,
.elementDelegate = [weak = Ui::MakeWeak(this)] {
return weak
? HistoryInner::ElementDelegate().get()
: nullptr;
},
})
});
return;
}
@@ -2540,6 +2548,28 @@ bool HistoryInner::elementIsGifPaused() {
return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
}
void HistoryInner::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
if (auto peer = Ui::getPeerForMouseAction()) { // old way
auto bot = peer->isUser() ? peer->asUser() : nullptr;
if (!bot) {
if (const auto view = App::hoveredLinkItem()) {
// may return nullptr
bot = view->data()->fromOriginal()->asUser();
}
}
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
App::sendBotCommand(peer, bot, command);
} else {
App::insertBotCommand(command);
}
}
void HistoryInner::elementHandleViaClick(not_null<UserData*> bot) {
App::insertBotCommand('@' + bot->username);
}
auto HistoryInner::getSelectionState() const
-> HistoryView::TopBarWidget::SelectedState {
auto result = HistoryView::TopBarWidget::SelectedState {};
@@ -3434,6 +3464,18 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
bool elementShownUnread(not_null<const Element*> view) override {
return view->data()->unread();
}
void elementSendBotCommand(
const QString &command,
const FullMsgId &context) override {
if (Instance) {
Instance->elementSendBotCommand(command, context);
}
}
void elementHandleViaClick(not_null<UserData*> bot) override {
if (Instance) {
Instance->elementHandleViaClick(bot);
}
}
};
static Result result;

View File

@@ -92,6 +92,10 @@ public:
const TextWithEntities &text,
Fn<void()> hiddenCallback);
bool elementIsGifPaused();
void elementSendBotCommand(
const QString &command,
const FullMsgId &context);
void elementHandleViaClick(not_null<UserData*> bot);
void updateBotInfo(bool recount = true);

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_message.h"
#include "history/view/history_view_service_message.h"
#include "history/view/media/history_view_document.h"
#include "core/click_handler_types.h"
#include "mainwindow.h"
#include "media/audio/media_audio.h"
#include "media/player/media_player_instance.h"
@@ -45,7 +46,8 @@ void HistoryMessageVia::create(
bot = owner->user(userId);
maxWidth = st::msgServiceNameFont->width(
tr::lng_inline_bot_via(tr::now, lt_inline_bot, '@' + bot->username));
link = std::make_shared<LambdaClickHandler>([bot = this->bot] {
link = std::make_shared<LambdaClickHandler>([bot = this->bot](
ClickContext context) {
if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
if (const auto window = App::wnd()) {
if (const auto controller = window->sessionController()) {
@@ -54,7 +56,12 @@ void HistoryMessageVia::create(
}
}
}
App::insertBotCommand('@' + bot->username);
const auto my = context.other.value<ClickHandlerContext>();
if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) {
delegate->elementHandleViaClick(bot);
} else {
App::insertBotCommand('@' + bot->username);
}
});
}

View File

@@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media.h" // AddTimestampLinks.
#include "chat_helpers/stickers_emoji_pack.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "api/api_updates.h"
#include "boxes/share_box.h"
#include "boxes/confirm_box.h"
#include "ui/toast/toast.h"
@@ -276,11 +278,8 @@ void FastShareMessage(not_null<HistoryItem*> item) {
return;
}
const auto sendFlags = MTPmessages_ForwardMessages::Flag(0)
const auto commonSendFlags = MTPmessages_ForwardMessages::Flag(0)
| MTPmessages_ForwardMessages::Flag::f_with_my_score
| (options.silent
? MTPmessages_ForwardMessages::Flag::f_silent
: MTPmessages_ForwardMessages::Flag(0))
| (options.scheduled
? MTPmessages_ForwardMessages::Flag::f_schedule_date
: MTPmessages_ForwardMessages::Flag(0));
@@ -310,13 +309,17 @@ void FastShareMessage(not_null<HistoryItem*> item) {
}
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
auto &api = history->session().api();
const auto sendFlags = commonSendFlags
| (ShouldSendSilent(peer, options)
? MTPmessages_ForwardMessages::Flag::f_silent
: MTPmessages_ForwardMessages::Flag(0));
history->sendRequestId = api.request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
data->peer->input,
MTP_vector<MTPint>(msgIds),
MTP_vector<MTPlong>(generateRandom()),
peer->input,
MTP_int(options.scheduled)
MTP_flags(sendFlags),
data->peer->input,
MTP_vector<MTPint>(msgIds),
MTP_vector<MTPlong>(generateRandom()),
peer->input,
MTP_int(options.scheduled)
)).done([=](const MTPUpdates &updates, mtpRequestId requestId) {
history->session().api().applyUpdates(updates);
data->requests.remove(requestId);
@@ -374,6 +377,15 @@ MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer) {
return result;
}
bool ShouldSendSilent(
not_null<PeerData*> peer,
const Api::SendOptions &options) {
return options.silent
|| (peer->isBroadcast() && peer->owner().notifySilentPosts(peer))
|| (peer->session().supportMode()
&& peer->session().settings().supportAllSilent());
}
MsgId LookupReplyToTop(not_null<History*> history, MsgId replyToId) {
const auto &owner = history->owner();
if (const auto item = owner.message(history->channelId(), replyToId)) {
@@ -1083,6 +1095,17 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
_fromNameVersion = from ? from->nameVersion : 1;
}
bool HistoryMessage::checkRepliesPts(const MTPMessageReplies &data) const {
const auto channel = history()->peer->asChannel();
const auto pts = channel
? channel->pts()
: history()->session().updates().pts();
const auto repliesPts = data.match([&](const MTPDmessageReplies &data) {
return data.vreplies_pts().v;
});
return (repliesPts >= pts);
}
void HistoryMessage::setupForwardedComponent(const CreateConfig &config) {
const auto forwarded = Get<HistoryMessageForwarded>();
if (!forwarded) {
@@ -1317,7 +1340,9 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
setForwardsCount(message.vforwards().value_or(-1));
setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities));
if (const auto replies = message.vreplies()) {
setReplies(*replies);
if (checkRepliesPts(*replies)) {
setReplies(*replies);
}
} else {
clearReplies();
}
@@ -1464,7 +1489,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
}
clearIsolatedEmoji();
const auto context = Core::UiIntegration::Context{
const auto context = Core::MarkedTextContext{
.session = &history()->session()
};
_text.setMarkedText(

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
struct SendAction;
struct SendOptions;
} // namespace Api
namespace HistoryView {
@@ -24,6 +25,9 @@ struct HistoryMessageViews;
[[nodiscard]] Fn<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
not_null<HistoryItem*> item);
[[nodiscard]] MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer);
[[nodiscard]] bool ShouldSendSilent(
not_null<PeerData*> peer,
const Api::SendOptions &options);
[[nodiscard]] MTPDmessage_ClientFlags NewMessageClientFlags();
[[nodiscard]] MsgId LookupReplyToTop(
not_null<History*> history,
@@ -236,6 +240,8 @@ private:
const TextWithEntities &textWithEntities) const;
void reapplyText();
[[nodiscard]] bool checkRepliesPts(const MTPMessageReplies &data) const;
QString _timeText;
int _timeWidth = 0;

View File

@@ -617,6 +617,11 @@ bool HistoryService::updateDependencyItem() {
return HistoryItem::updateDependencyItem();
}
bool HistoryService::needCheck() const {
return (GetDependentData() != nullptr)
|| Has<HistoryServiceSelfDestruct>();
}
QString HistoryService::inDialogsText(DrawInDialog way) const {
return textcmdLink(1, TextUtilities::Clean(notificationText()));
}
@@ -813,8 +818,9 @@ void HistoryService::updateDependentText() {
setServiceText(text);
history()->owner().requestItemResize(this);
if (history()->textCachedFor == this) {
history()->textCachedFor = nullptr;
const auto inDialogsHistory = history()->migrateToOrMe();
if (inDialogsHistory->textCachedFor == this) {
inDialogsHistory->textCachedFor = nullptr;
}
//if (const auto feed = history()->peer->feed()) { // #TODO archive
// if (feed->textCachedFor == this) {

View File

@@ -95,9 +95,7 @@ public:
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
bool needCheck() const override {
return false;
}
bool needCheck() const override;
bool serviceMsg() const override {
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,13 +38,9 @@ namespace Layout {
class ItemBase;
class Widget;
} // namespace Layout
class Result;
struct ResultSelected;
} // namespace InlineBots
namespace Data {
struct Draft;
} // namespace Data
namespace Support {
class Autocomplete;
struct Contact;
@@ -94,6 +90,10 @@ class TopBarWidget;
class ContactStatus;
class Element;
class PinnedTracker;
namespace Controls {
class RecordLock;
class VoiceRecordBar;
} // namespace Controls
} // namespace HistoryView
class DragArea;
@@ -104,10 +104,10 @@ class HistoryInner;
struct HistoryMessageMarkupButton;
class HistoryWidget final : public Window::AbstractSectionWidget {
Q_OBJECT
public:
using FieldHistoryAction = Ui::InputField::HistoryAction;
using RecordLock = HistoryView::Controls::RecordLock;
using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar;
HistoryWidget(
QWidget *parent,
@@ -201,9 +201,6 @@ public:
void updatePreview();
void previewCancel();
bool recordingAnimationCallback(crl::time now);
void stopRecording(bool send);
void escape();
void sendBotCommand(
@@ -238,7 +235,11 @@ public:
void updateFieldSubmitSettings();
void activate();
void setInnerFocus();
[[nodiscard]] rpl::producer<> cancelRequests() const {
return _cancelRequests.events();
}
void updateNotifyControls();
@@ -287,40 +288,6 @@ protected:
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
signals:
void cancelled();
public slots:
void onScroll();
void activate();
void onTextChange();
void onFieldTabbed();
void onWindowVisibleChanged();
void onFieldFocused();
void onFieldResize();
void onCheckFieldAutocomplete();
void onScrollTimer();
void onDraftSaveDelayed();
void onDraftSave(bool delayed = false);
void onCloudDraftSave();
void onUpdateHistoryItems();
// checks if we are too close to the top or to the bottom
// in the scroll area and preloads history if needed
void preloadHistoryIfNeeded();
private slots:
void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method);
void onMentionInsert(UserData *user);
void onInlineBotCancel();
void onMembersDropdownShow();
private:
using TabbedPanel = ChatHelpers::TabbedPanel;
using TabbedSelector = ChatHelpers::TabbedSelector;
@@ -345,10 +312,38 @@ private:
friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
void initTabbedSelector();
void initVoiceRecordBar();
void refreshTabbedPanel();
void createTabbedPanel();
void setTabbedPanel(std::unique_ptr<TabbedPanel> panel);
void updateField();
void fieldChanged();
void fieldTabbed();
void fieldFocused();
void fieldResized();
void insertHashtagOrBotCommand(
QString str,
FieldAutocomplete::ChooseMethod method);
void insertMention(UserData *user);
void cancelInlineBot();
void saveDraft(bool delayed = false);
void saveCloudDraft();
void saveDraftDelayed();
void checkFieldAutocomplete();
void showMembersDropdown();
void windowIsVisibleChanged();
// Checks if we are too close to the top or to the bottom
// in the scroll area and preloads history if needed.
void preloadHistoryIfNeeded();
void handleScroll();
void scrollByTimer();
void updateHistoryItemsByTimer();
[[nodiscard]] Dialogs::EntryState computeDialogsEntryState() const;
void refreshTopBarActiveChat();
void requestMessageData(MsgId msgId);
void messageDataReceived(ChannelData *channel, MsgId msgId);
@@ -395,11 +390,6 @@ private:
void animationCallback();
void updateOverStates(QPoint pos);
void recordDone(QByteArray result, VoiceWaveform waveform, int samples);
void recordUpdate(ushort level, int samples);
void recordStartCallback();
void recordStopCallback(bool active);
void recordUpdateCallback(QPoint globalPos);
void chooseAttach();
void historyDownAnimationFinish();
void unreadMentionsAnimationFinish();
@@ -457,6 +447,7 @@ private:
bool replyToNextMessage();
[[nodiscard]] bool showSlowmodeError();
void hideChildWidgets();
void hideSelectorControlsAnimated();
int countMembersDropdownHeightMax() const;
@@ -484,10 +475,7 @@ private:
int wasScrollTop,
int nowScrollTop);
void sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot,
Api::SendOptions options);
void sendInlineResult(InlineBots::ResultSelected result);
void drawField(Painter &p, const QRect &rect);
void paintEditHeader(
@@ -495,7 +483,6 @@ private:
const QRect &rect,
int left,
int top) const;
void drawRecording(Painter &p, float64 recordActive);
void drawRestrictedWrite(Painter &p, const QString &error);
bool paintShowAnimationFrame();
@@ -535,8 +522,9 @@ private:
// This one is syntetic.
void synteticScrollToY(int y);
void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft);
void writeDrafts(History *history);
void writeDrafts();
void writeDraftTexts();
void writeDraftCursors();
void setFieldText(
const TextWithTags &textWithTags,
TextUpdateEvents events = 0,
@@ -566,6 +554,8 @@ private:
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
void inlineBotResolveFail(const RPCError &error, const QString &username);
bool isRecording() const;
bool isBotStart() const;
bool isBlocked() const;
bool isJoinChannel() const;
@@ -579,6 +569,8 @@ private:
void setupScheduledToggle();
void refreshScheduledToggle();
bool kbWasHidden() const;
MTP::Sender _api;
MsgId _replyToId = 0;
Ui::Text::String _replyToName;
@@ -647,7 +639,7 @@ private:
int _lastScrollTop = 0; // gifs optimization
crl::time _lastScrolled = 0;
QTimer _updateHistoryItems;
base::Timer _updateHistoryItems;
crl::time _lastUserScrolled = 0;
bool _synteticScrollEvent = false;
@@ -673,7 +665,7 @@ private:
std::unique_ptr<HistoryView::ContactStatus> _contactStatus;
object_ptr<Ui::SendButton> _send;
const std::shared_ptr<Ui::SendButton> _send;
object_ptr<Ui::FlatButton> _unblock;
object_ptr<Ui::FlatButton> _botStart;
object_ptr<Ui::FlatButton> _joinChannel;
@@ -685,22 +677,11 @@ private:
object_ptr<Ui::IconButton> _botCommandStart;
object_ptr<Ui::SilentToggle> _silent = { nullptr };
object_ptr<Ui::IconButton> _scheduled = { nullptr };
const std::unique_ptr<VoiceRecordBar> _voiceRecordBar;
bool _cmdStartShown = false;
object_ptr<Ui::InputField> _field;
bool _recording = false;
bool _inField = false;
bool _inReplyEditForward = false;
bool _inClickable = false;
int _recordingSamples = 0;
int _recordCancelWidth;
rpl::lifetime _recordingLifetime;
// This can animate for a very long time (like in music playing),
// so it should be a Basic, not a Simple animation.
Ui::Animations::Basic _recordingAnimation;
anim::value _recordingLevel;
bool kbWasHidden() const;
bool _kbShown = false;
HistoryItem *_kbReplyTo = nullptr;
@@ -708,7 +689,7 @@ private:
QPointer<BotKeyboard> _keyboard;
object_ptr<Ui::InnerDropdown> _membersDropdown = { nullptr };
QTimer _membersDropdownShowTimer;
base::Timer _membersDropdownShowTimer;
object_ptr<InlineBots::Layout::Widget> _inlineResults = { nullptr };
std::unique_ptr<TabbedPanel> _tabbedPanel;
@@ -727,7 +708,7 @@ private:
Window::SlideDirection _showDirection;
QPixmap _cacheUnder, _cacheOver;
QTimer _scrollTimer;
base::Timer _scrollTimer;
int32 _scrollDelta = 0;
MsgId _highlightedMessageId = 0;
@@ -737,7 +718,8 @@ private:
crl::time _saveDraftStart = 0;
bool _saveDraftText = false;
QTimer _saveDraftTimer, _saveCloudDraftTimer;
base::Timer _saveDraftTimer;
base::Timer _saveCloudDraftTimer;
base::weak_ptr<Ui::Toast::Instance> _topToast;
@@ -746,4 +728,6 @@ private:
int _topDelta = 0;
rpl::event_stream<> _cancelRequests;
};

View File

@@ -0,0 +1,43 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Api {
enum class SendProgressType;
struct SendOptions;
} // namespace Api
class History;
namespace HistoryView::Controls {
struct MessageToEdit {
FullMsgId fullId;
Api::SendOptions options;
TextWithTags textWithTags;
};
struct VoiceToSend {
QByteArray bytes;
VoiceWaveform waveform;
int duration = 0;
Api::SendOptions options;
};
struct SendActionUpdate {
Api::SendProgressType type = Api::SendProgressType();
int progress = 0;
};
struct SetHistoryArgs {
required<History*> history;
Fn<bool()> showSlowmodeError;
rpl::producer<int> slowmodeSecondsLeft;
rpl::producer<bool> sendDisabledBySlowmode;
rpl::producer<std::optional<QString>> writeRestriction;
};
} // namespace HistoryView::Controls

View File

@@ -10,12 +10,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/required.h"
#include "api/api_common.h"
#include "base/unique_qptr.h"
#include "base/timer.h"
#include "dialogs/dialogs_key.h"
#include "history/view/controls/compose_controls_common.h"
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "ui/widgets/input_fields.h"
#include "chat_helpers/tabbed_selector.h"
class History;
class FieldAutocomplete;
namespace SendMenu {
enum class Type;
} // namespace SendMenu
namespace ChatHelpers {
class TabbedPanel;
@@ -24,6 +32,8 @@ class TabbedSelector;
namespace Data {
struct MessagePosition;
struct Draft;
class DraftKey;
} // namespace Data
namespace InlineBots {
@@ -55,67 +65,60 @@ enum class SendProgressType;
namespace HistoryView {
namespace Controls {
class VoiceRecordBar;
} // namespace Controls
class FieldHeader;
class ComposeControls final {
public:
using FileChosen = ChatHelpers::TabbedSelector::FileChosen;
using PhotoChosen = ChatHelpers::TabbedSelector::PhotoChosen;
using InlineChosen = ChatHelpers::TabbedSelector::InlineChosen;
using MessageToEdit = Controls::MessageToEdit;
using VoiceToSend = Controls::VoiceToSend;
using SendActionUpdate = Controls::SendActionUpdate;
using SetHistoryArgs = Controls::SetHistoryArgs;
using FieldHistoryAction = Ui::InputField::HistoryAction;
enum class Mode {
Normal,
Scheduled,
};
struct MessageToEdit {
FullMsgId fullId;
Api::SendOptions options;
TextWithTags textWithTags;
};
struct VoiceToSend {
QByteArray bytes;
VoiceWaveform waveform;
int duration = 0;
};
struct SendActionUpdate {
Api::SendProgressType type = Api::SendProgressType();
int progress = 0;
};
ComposeControls(
not_null<QWidget*> parent,
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionController*> window,
Mode mode);
Mode mode,
SendMenu::Type sendMenuType);
~ComposeControls();
[[nodiscard]] Main::Session &session() const;
struct SetHistoryArgs {
required<History*> history;
Fn<bool()> showSlowmodeError;
rpl::producer<int> slowmodeSecondsLeft;
rpl::producer<bool> sendDisabledBySlowmode;
rpl::producer<std::optional<QString>> writeRestriction;
};
void setHistory(SetHistoryArgs &&args);
void setCurrentDialogsEntryState(Dialogs::EntryState state);
void finishAnimating();
void move(int x, int y);
void resizeToWidth(int width);
void setAutocompleteBoundingRect(QRect rect);
[[nodiscard]] rpl::producer<int> height() const;
[[nodiscard]] int heightCurrent() const;
bool focus();
[[nodiscard]] rpl::producer<> cancelRequests() const;
[[nodiscard]] rpl::producer<> sendRequests() const;
[[nodiscard]] rpl::producer<Api::SendOptions> sendRequests() const;
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
[[nodiscard]] rpl::producer<QString> sendCommandRequests() const;
[[nodiscard]] rpl::producer<MessageToEdit> editRequests() const;
[[nodiscard]] rpl::producer<> attachRequests() const;
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
[[nodiscard]] rpl::producer<PhotoChosen> photoChosen() const;
[[nodiscard]] rpl::producer<Data::MessagePosition> scrollRequests() const;
[[nodiscard]] rpl::producer<not_null<QKeyEvent*>> keyEvents() const;
[[nodiscard]] auto inlineResultChosen() const
-> rpl::producer<ChatHelpers::TabbedSelector::InlineChosen>;
[[nodiscard]] rpl::producer<InlineChosen> inlineResultChosen() const;
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
using MimeDataHook = Fn<bool(
@@ -134,6 +137,7 @@ public:
void showForGrab();
void showStarted();
void showFinished();
void raisePanels();
void editMessage(FullMsgId id);
void cancelEditMessage();
@@ -141,17 +145,35 @@ public:
void replyToMessage(FullMsgId id);
void cancelReplyMessage();
bool handleCancelRequest();
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
[[nodiscard]] WebPageId webPageId() const;
void setText(const TextWithTags &text);
void clear();
void hidePanelsAnimated();
void clearListenState();
[[nodiscard]] rpl::producer<bool> lockShowStarts() const;
[[nodiscard]] bool isLockPresent() const;
[[nodiscard]] bool isRecording() const;
void applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
private:
enum class TextUpdateEvent {
//SaveDraft = (1 << 0),
SaveDraft = (1 << 0),
SendTyping = (1 << 1),
};
enum class DraftType {
Normal,
Edit,
};
enum class SendRequestType {
Text,
Voice,
};
using TextUpdateEvents = base::flags<TextUpdateEvent>;
friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
@@ -161,6 +183,9 @@ private:
void initSendButton();
void initWebpageProcess();
void initWriteRestriction();
void initVoiceRecordBar();
void initAutocomplete();
void updateSubmitSettings();
void updateSendButtonType();
void updateHeight();
void updateWrappingVisibility();
@@ -169,28 +194,62 @@ private:
void updateOuterGeometry(QRect rect);
void paintBackground(QRect clip);
[[nodiscard]] auto computeSendButtonType() const;
[[nodiscard]] SendMenu::Type sendMenuType() const;
[[nodiscard]] SendMenu::Type sendButtonMenuType() const;
void sendSilent();
void sendScheduled();
[[nodiscard]] auto sendContentRequests(
SendRequestType requestType = SendRequestType::Text) const;
void orderControls();
void checkAutocomplete();
void updateStickersByEmoji();
void updateFieldPlaceholder();
void editMessage(not_null<HistoryItem*> item);
void escape();
void fieldChanged();
void fieldTabbed();
void toggleTabbedSelectorMode();
void createTabbedPanel();
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
void setTextFromEditingMessage(not_null<HistoryItem*> item);
void recordUpdated(quint16 level, int samples);
void recordDone(QByteArray result, VoiceWaveform waveform, int samples);
bool recordingAnimationCallback(crl::time now);
void stopRecording(bool send);
void recordStartCallback();
void recordStopCallback(bool active);
void recordUpdateCallback(QPoint globalPos);
bool showRecordButton() const;
void drawRecording(Painter &p, float64 recordActive);
void drawRestrictedWrite(Painter &p, const QString &error);
void updateOverStates(QPoint pos);
bool updateBotCommandShown();
void cancelInlineBot();
void clearInlineBot();
void inlineBotChanged();
// Look in the _field for the inline bot and query string.
void updateInlineBotQuery();
// Request to show results in the emoji panel.
void applyInlineBotQuery(UserData *bot, const QString &query);
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
void inlineBotResolveFail(const RPCError &error, const QString &username);
[[nodiscard]] Data::DraftKey draftKey(
DraftType type = DraftType::Normal) const;
[[nodiscard]] Data::DraftKey draftKeyCurrent() const;
void saveDraft(bool delayed = false);
void saveDraftDelayed();
void writeDrafts();
void writeDraftTexts();
void writeDraftCursors();
void setFieldText(
const TextWithTags &textWithTags,
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void clearFieldText(
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void saveFieldToHistoryLocalDraft();
const not_null<QWidget*> _parent;
const not_null<Window::SessionController*> _window;
@@ -204,41 +263,51 @@ private:
const std::unique_ptr<Ui::RpWidget> _wrap;
const std::unique_ptr<Ui::RpWidget> _writeRestricted;
const not_null<Ui::SendButton*> _send;
const std::shared_ptr<Ui::SendButton> _send;
const not_null<Ui::IconButton*> _attachToggle;
const not_null<Ui::EmojiButton*> _tabbedSelectorToggle;
const not_null<Ui::InputField*> _field;
const not_null<Ui::IconButton*> _botCommandStart;
std::unique_ptr<InlineBots::Layout::Widget> _inlineResults;
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
std::unique_ptr<FieldAutocomplete> _autocomplete;
friend class FieldHeader;
const std::unique_ptr<FieldHeader> _header;
const std::unique_ptr<Controls::VoiceRecordBar> _voiceRecordBar;
const SendMenu::Type _sendMenuType;
rpl::event_stream<Api::SendOptions> _sendCustomRequests;
rpl::event_stream<> _cancelRequests;
rpl::event_stream<FileChosen> _fileChosen;
rpl::event_stream<PhotoChosen> _photoChosen;
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
rpl::event_stream<InlineChosen> _inlineResultChosen;
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
rpl::event_stream<VoiceToSend> _sendVoiceRequests;
rpl::event_stream<QString> _sendCommandRequests;
TextWithTags _localSavedText;
TextUpdateEvents _textUpdateEvents;
TextUpdateEvents _textUpdateEvents = TextUpdateEvents()
| TextUpdateEvent::SaveDraft
| TextUpdateEvent::SendTyping;
Dialogs::EntryState _currentDialogsEntryState;
bool _recording = false;
bool _inField = false;
//bool _inReplyEditForward = false;
//bool _inClickable = false;
int _recordingSamples = 0;
int _recordCancelWidth;
rpl::lifetime _recordingLifetime;
crl::time _saveDraftStart = 0;
bool _saveDraftText = false;
base::Timer _saveDraftTimer;
UserData *_inlineBot = nullptr;
QString _inlineBotUsername;
bool _inlineLookingUpBot = false;
mtpRequestId _inlineBotResolveRequestId = 0;
bool _isInlineBot = false;
bool _botCommandShown = false;
Fn<void()> _previewCancel;
bool _previewCancelled = false;
rpl::lifetime _uploaderSubscriptions;
// This can animate for a very long time (like in music playing),
// so it should be a Basic, not a Simple animation.
Ui::Animations::Basic _recordingAnimation;
anim::value _recordingLevel;
Fn<void()> _raiseEmojiSuggestions;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
/*
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"
#include "history/view/controls/compose_controls_common.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
struct VoiceData;
namespace Ui {
class SendButton;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
namespace HistoryView::Controls {
class VoiceRecordButton;
class ListenWrap;
class RecordLock;
class VoiceRecordBar final : public Ui::RpWidget {
public:
using SendActionUpdate = Controls::SendActionUpdate;
using VoiceToSend = Controls::VoiceToSend;
VoiceRecordBar(
not_null<Ui::RpWidget*> parent,
not_null<Ui::RpWidget*> sectionWidget,
not_null<Window::SessionController*> controller,
std::shared_ptr<Ui::SendButton> send,
int recorderHeight);
VoiceRecordBar(
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionController*> controller,
std::shared_ptr<Ui::SendButton> send,
int recorderHeight);
~VoiceRecordBar();
void startRecording();
void finishAnimating();
void hideAnimated();
void hideFast();
void clearListenState();
void orderControls();
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
[[nodiscard]] rpl::producer<bool> recordingStateChanges() const;
[[nodiscard]] rpl::producer<bool> lockShowStarts() const;
[[nodiscard]] rpl::producer<> updateSendButtonTypeRequests() const;
void requestToSendWithOptions(Api::SendOptions options);
void setLockBottom(rpl::producer<int> &&bottom);
void setSendButtonGeometryValue(rpl::producer<QRect> &&geometry);
void setEscFilter(Fn<bool()> &&callback);
void setStartRecordingFilter(Fn<bool()> &&callback);
[[nodiscard]] bool isRecording() const;
[[nodiscard]] bool isLockPresent() const;
[[nodiscard]] bool isListenState() const;
private:
enum class StopType {
Cancel,
Send,
Listen,
};
void init();
void updateMessageGeometry();
void updateLockGeometry();
void recordUpdated(quint16 level, int samples);
bool recordingAnimationCallback(crl::time now);
void stop(bool send);
void stopRecording(StopType type);
void visibilityAnimate(bool show, Fn<void()> &&callback);
bool showRecordButton() const;
void drawDuration(Painter &p);
void drawRedCircle(Painter &p);
void drawMessage(Painter &p, float64 recordActive);
void startRedCircleAnimation();
void installClickOutsideFilter();
void installListenStateFilter();
bool isTypeRecord() const;
bool hasDuration() const;
void finish();
void activeAnimate(bool active);
float64 showAnimationRatio() const;
float64 showListenAnimationRatio() const;
float64 activeAnimationRatio() const;
void computeAndSetLockProgress(QPoint globalPos);
const not_null<Ui::RpWidget*> _sectionWidget;
const not_null<Window::SessionController*> _controller;
const std::shared_ptr<Ui::SendButton> _send;
const std::unique_ptr<RecordLock> _lock;
const std::unique_ptr<VoiceRecordButton> _level;
std::unique_ptr<ListenWrap> _listen;
base::Timer _startTimer;
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
rpl::event_stream<VoiceToSend> _sendVoiceRequests;
rpl::event_stream<> _listenChanges;
int _centerY = 0;
QRect _redCircleRect;
QRect _durationRect;
QRect _messageRect;
Ui::Text::String _message;
Fn<bool()> _escFilter;
Fn<bool()> _startRecordingFilter;
rpl::variable<bool> _recording = false;
rpl::variable<bool> _inField = false;
rpl::variable<bool> _lockShowing = false;
int _recordingSamples = 0;
float64 _redCircleProgress = 0.;
const style::font &_cancelFont;
rpl::lifetime _recordingLifetime;
Ui::Animations::Simple _showLockAnimation;
Ui::Animations::Simple _showListenAnimation;
Ui::Animations::Simple _activeAnimation;
Ui::Animations::Simple _showAnimation;
};
} // namespace HistoryView::Controls

View File

@@ -0,0 +1,789 @@
/*
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 "history/view/controls/history_view_voice_record_button.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include <QMatrix>
namespace HistoryView::Controls {
namespace {
constexpr auto kSegmentsCount = 12;
constexpr auto kMajorDegreeOffset = 360 / kSegmentsCount;
constexpr auto kSixtyDegrees = 60;
constexpr auto kEnterIdleAnimationDuration = crl::time(1200);
constexpr auto kRotationSpeed = 0.36 * 0.1;
constexpr auto kRandomAdditionFactor = 0.15;
constexpr auto kIdleRadiusGlobalFactor = 0.56;
constexpr auto kIdleRadiusFactor = 0.15 * 0.5;
constexpr auto kOpacityMajor = 0.30;
constexpr auto kOpacityMinor = 0.15;
constexpr auto kIdleRotationSpeed = 0.2;
constexpr auto kIdleRotateDiff = 0.1 * kIdleRotationSpeed;
constexpr auto kWaveAngle = 0.03;
constexpr auto kAnimationSpeedMajor = 1.5 - 0.65;
constexpr auto kAnimationSpeedMinor = 1.5 - 0.45;
constexpr auto kAnimationSpeedCircle = 1.5 - 0.25;
constexpr auto kAmplitudeDiffFactorMax = 500. - 100.;
constexpr auto kAmplitudeDiffFactorMajor = 300. - 100.;
constexpr auto kAmplitudeDiffFactorMinor = 400. - 100.;
constexpr auto kFlingDistanceFactorMajor = 8 * 16;
constexpr auto kFlingDistanceFactorMinor = 20 * 16;
constexpr auto kFlingInAnimationDurationMajor = 200;
constexpr auto kFlingInAnimationDurationMinor = 350;
constexpr auto kFlingOutAnimationDurationMajor = 220;
constexpr auto kFlingOutAnimationDurationMinor = 380;
constexpr auto kSineWaveSpeedMajor = 0.02 * 0.2;
constexpr auto kSineWaveSpeedMinor = 0.026 * 0.2;
constexpr auto kSmallWaveRadius = 0.55;
constexpr auto kFlingDistance = 0.50;
constexpr auto kMinDivider = 100.;
constexpr auto kMaxAmplitude = 1800.;
constexpr auto kZeroPoint = QPointF(0, 0);
template <typename Number>
void Normalize(Number &value, Number right) {
if (value >= right) {
value -= right;
}
}
float64 RandomAdditional() {
return (rand_value<int>() % 100 / 100.);
}
void PerformAnimation(
rpl::producer<crl::time> &&animationTicked,
Fn<void(float64)> &&applyValue,
Fn<void()> &&finishCallback,
float64 duration,
float64 from,
float64 to,
rpl::lifetime &lifetime) {
lifetime.destroy();
const auto animValue =
lifetime.make_state<anim::value>(from, to);
const auto animStarted = crl::now();
std::move(
animationTicked
) | rpl::start_with_next([=,
applyValue = std::move(applyValue),
finishCallback = std::move(finishCallback),
&lifetime](crl::time now) mutable {
const auto dt = anim::Disabled()
? 1.
: ((now - animStarted) / duration);
if (dt >= 1.) {
animValue->finish();
applyValue(animValue->current());
lifetime.destroy();
if (finishCallback) {
finishCallback();
}
} else {
animValue->update(dt, anim::linear);
applyValue(animValue->current());
}
}, lifetime);
}
} // namespace
class ContinuousValue {
public:
ContinuousValue() = default;
ContinuousValue(float64 duration) : _duration(duration) {
}
void start(float64 to, float64 duration) {
_to = to;
_delta = (_to - _cur) / duration;
}
void start(float64 to) {
start(to, _duration);
}
void reset() {
_to = _cur = _delta = 0.;
}
float64 current() const {
return _cur;
}
float64 to() const {
return _to;
}
float64 delta() const {
return _delta;
}
void update(crl::time dt, Fn<void(float64 &)> &&callback = nullptr) {
if (_to != _cur) {
_cur += _delta * dt;
if ((_to != _cur) && ((_delta > 0) == (_cur > _to))) {
_cur = _to;
}
if (callback) {
callback(_cur);
}
}
}
private:
float64 _duration = 0.;
float64 _to = 0.;
float64 _cur = 0.;
float64 _delta = 0.;
};
class CircleBezier final {
public:
CircleBezier(int n);
void computeRandomAdditionals();
void paintCircle(
Painter &p,
const QColor &c,
float64 radius,
float64 cubicBezierFactor,
float64 idleStateDiff,
float64 radiusDiff,
float64 randomFactor);
private:
struct Points {
QPointF point;
QPointF control;
};
const int _segmentsCount;
const float64 _segmentLength;
std::vector<float64> _randomAdditionals;
};
class Wave final {
public:
Wave(
rpl::producer<crl::time> animationTicked,
int n,
float64 rotationOffset,
float64 amplitudeRadius,
float64 amplitudeWaveDiff,
float64 fling,
int flingDistanceFactor,
int flingInAnimationDuration,
int flingOutAnimationDuration,
float64 amplitudeDiffSpeed,
float64 amplitudeDiffFactor,
bool isDirectionClockwise);
void setValue(float64 to);
void tick(float64 circleRadius, crl::time dt);
void reset();
void paint(Painter &p, QColor c);
private:
void initEnterIdleAnimation(rpl::producer<crl::time> animationTicked);
void initFlingAnimation(rpl::producer<crl::time> animationTicked);
const std::unique_ptr<CircleBezier> _circleBezier;
const float _rotationOffset;
const float64 _idleGlobalRadius;
const float64 _amplitudeRadius;
const float64 _amplitudeWaveDiff;
const float64 _randomAdditions;
const float64 _fling;
const int _flingDistanceFactor;
const int _flingInAnimationDuration;
const int _flingOutAnimationDuration;
const float64 _amplitudeInAnimationDuration;
const float64 _amplitudeOutAnimationDuration;
const int _directionClockwise;
bool _incRandomAdditionals = false;
bool _isIdle = true;
bool _wasFling = false;
float64 _flingRadius = 0.;
float64 _idleRadius = 0.;
float64 _idleRotation = 0.;
float64 _lastRadius = 0.;
float64 _rotation = 0.;
float64 _sineAngleMax = 0.;
float64 _waveAngle = 0.;
float64 _waveDiff = 0.;
ContinuousValue _levelValue;
rpl::event_stream<float64> _flingAnimationRequests;
rpl::event_stream<> _enterIdleAnimationRequests;
rpl::lifetime _animationEnterIdleLifetime;
rpl::lifetime _animationFlingLifetime;
rpl::lifetime _lifetime;
};
class RecordCircle final {
public:
RecordCircle(rpl::producer<crl::time> animationTicked);
void reset();
void setAmplitude(float64 value);
void paint(Painter &p, QColor c);
private:
const std::unique_ptr<Wave> _majorWave;
const std::unique_ptr<Wave> _minorWave;
crl::time _lastUpdateTime = 0;
ContinuousValue _levelValue;
};
CircleBezier::CircleBezier(int n)
: _segmentsCount(n)
, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n)))
, _randomAdditionals(n) {
}
void CircleBezier::computeRandomAdditionals() {
ranges::generate(_randomAdditionals, RandomAdditional);
}
void CircleBezier::paintCircle(
Painter &p,
const QColor &c,
float64 radius,
float64 cubicBezierFactor,
float64 idleStateDiff,
float64 radiusDiff,
float64 randomFactor) {
PainterHighQualityEnabler hq(p);
const auto r1 = radius - idleStateDiff / 2. - radiusDiff / 2.;
const auto r2 = radius + radiusDiff / 2. + idleStateDiff / 2.;
const auto l = _segmentLength * std::max(r1, r2) * cubicBezierFactor;
auto m = QMatrix();
const auto preparePoints = [&](int i, bool isStart) -> Points {
Normalize(i, _segmentsCount);
const auto randomAddition = randomFactor * _randomAdditionals[i];
const auto r = ((i % 2 == 0) ? r1 : r2) + randomAddition;
m.reset();
m.rotate(360. / _segmentsCount * i);
const auto sign = isStart ? 1 : -1;
return {
(isStart && i) ? QPointF() : m.map(QPointF(0, -r)),
m.map(QPointF(sign * (l + randomAddition * _segmentLength), -r)),
};
};
const auto &[startPoint, _] = preparePoints(0, true);
auto path = QPainterPath();
path.moveTo(startPoint);
for (auto i = 0; i < _segmentsCount; i++) {
const auto &[_, startControl] = preparePoints(i, true);
const auto &[end, endControl] = preparePoints(i + 1, false);
path.cubicTo(startControl, endControl, end);
}
p.setBrush(Qt::NoBrush);
auto pen = QPen(Qt::NoPen);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
p.setPen(pen);
p.fillPath(path, c);
p.drawPath(path);
}
Wave::Wave(
rpl::producer<crl::time> animationTicked,
int n,
float64 rotationOffset,
float64 amplitudeRadius,
float64 amplitudeWaveDiff,
float64 fling,
int flingDistanceFactor,
int flingInAnimationDuration,
int flingOutAnimationDuration,
float64 amplitudeDiffSpeed,
float64 amplitudeDiffFactor,
bool isDirectionClockwise)
: _circleBezier(std::make_unique<CircleBezier>(n))
, _rotationOffset(rotationOffset)
, _idleGlobalRadius(st::historyRecordRadiusDiffMin * kIdleRadiusGlobalFactor)
, _amplitudeRadius(amplitudeRadius)
, _amplitudeWaveDiff(amplitudeWaveDiff)
, _randomAdditions(st::historyRecordRandomAddition * kRandomAdditionFactor)
, _fling(fling)
, _flingDistanceFactor(flingDistanceFactor)
, _flingInAnimationDuration(flingInAnimationDuration)
, _flingOutAnimationDuration(flingOutAnimationDuration)
, _amplitudeInAnimationDuration(kMinDivider
+ amplitudeDiffFactor * amplitudeDiffSpeed)
, _amplitudeOutAnimationDuration(kMinDivider
+ kAmplitudeDiffFactorMax * amplitudeDiffSpeed)
, _directionClockwise(isDirectionClockwise ? 1 : -1)
, _rotation(rotationOffset) {
initEnterIdleAnimation(rpl::duplicate(animationTicked));
initFlingAnimation(std::move(animationTicked));
}
void Wave::reset() {
_incRandomAdditionals = false;
_isIdle = true;
_wasFling = false;
_flingRadius = 0.;
_idleRadius = 0.;
_idleRotation = 0.;
_lastRadius = 0.;
_rotation = 0.;
_sineAngleMax = 0.;
_waveAngle = 0.;
_waveDiff = 0.;
_levelValue.reset();
}
void Wave::setValue(float64 to) {
const auto duration = (to <= _levelValue.current())
? _amplitudeOutAnimationDuration
: _amplitudeInAnimationDuration;
_levelValue.start(to, duration);
const auto idle = to < 0.1;
if (_isIdle != idle && idle) {
_enterIdleAnimationRequests.fire({});
}
_isIdle = idle;
if (!_isIdle) {
_animationEnterIdleLifetime.destroy();
}
}
void Wave::initEnterIdleAnimation(rpl::producer<crl::time> animationTicked) {
_enterIdleAnimationRequests.events(
) | rpl::start_with_next([=] {
const auto &k = kSixtyDegrees;
const auto rotation = _rotation;
const auto rotationTo = std::round(rotation / k) * k
+ _rotationOffset;
const auto waveDiff = _waveDiff;
auto applyValue = [=](float64 v) {
_rotation = rotationTo + (rotation - rotationTo) * v;
_waveDiff = 1. + (waveDiff - 1.) * v;
_waveAngle = std::acos(_waveDiff * _directionClockwise);
};
PerformAnimation(
rpl::duplicate(animationTicked),
std::move(applyValue),
nullptr,
kEnterIdleAnimationDuration,
1,
0,
_animationEnterIdleLifetime);
}, _lifetime);
}
void Wave::initFlingAnimation(rpl::producer<crl::time> animationTicked) {
_flingAnimationRequests.events(
) | rpl::start_with_next([=](float64 delta) {
const auto fling = _fling * 2;
const auto flingDistance = delta
* _amplitudeRadius
* _flingDistanceFactor
* fling;
const auto applyValue = [=](float64 v) {
_flingRadius = v;
};
auto finishCallback = [=] {
PerformAnimation(
rpl::duplicate(animationTicked),
applyValue,
nullptr,
_flingOutAnimationDuration * fling,
flingDistance,
0,
_animationFlingLifetime);
};
PerformAnimation(
rpl::duplicate(animationTicked),
applyValue,
std::move(finishCallback),
_flingInAnimationDuration * fling,
_flingRadius,
flingDistance,
_animationFlingLifetime);
}, _lifetime);
}
void Wave::tick(float64 circleRadius, crl::time dt) {
auto amplitudeCallback = [&](float64 &value) {
if (std::abs(value - _levelValue.to()) * _amplitudeRadius
< (st::historyRecordRandomAddition / 2)) {
if (!_wasFling) {
_flingAnimationRequests.fire_copy(_levelValue.delta());
_wasFling = true;
}
} else {
_wasFling = false;
}
};
_levelValue.update(dt, std::move(amplitudeCallback));
_idleRadius = circleRadius * kIdleRadiusFactor;
{
const auto to = _levelValue.to();
const auto delta = (_sineAngleMax - to);
if (std::abs(delta) - 0.25 < 0) {
_sineAngleMax = to;
} else {
_sineAngleMax -= 0.25 * ((delta < 0) ? -1 : 1);
}
}
if (!_isIdle) {
_rotation += dt
* (kRotationSpeed * 4. * std::min(_levelValue.current() / .5, 1.)
+ kRotationSpeed * 0.5);
Normalize(_rotation, 360.);
} else {
_idleRotation += kIdleRotateDiff * dt;
Normalize(_idleRotation, 360.);
}
_lastRadius = circleRadius;
if (!_isIdle) {
_waveAngle += (_amplitudeWaveDiff * _sineAngleMax) * dt;
_waveDiff = std::cos(_waveAngle) * _directionClockwise;
if ((_waveDiff != 0) && ((_waveDiff > 0) == _incRandomAdditionals)) {
_circleBezier->computeRandomAdditionals();
_incRandomAdditionals = !_incRandomAdditionals;
}
}
}
void Wave::paint(Painter &p, QColor c) {
const auto amplitude = _levelValue.current();
const auto waveAmplitude = std::min(amplitude / .3, 1.);
const auto radiusDiff = st::historyRecordRadiusDiffMin
+ st::historyRecordRadiusDiff * kWaveAngle * _levelValue.to();
const auto diffFactor = 0.35 * waveAmplitude * _waveDiff;
const auto radius = (_lastRadius + _amplitudeRadius * amplitude)
+ _idleGlobalRadius
+ (_flingRadius * waveAmplitude);
const auto cubicBezierFactor = 1.
+ std::abs(diffFactor) * waveAmplitude
+ (1. - waveAmplitude) * kIdleRadiusFactor;
const auto circleRadiusDiff = std::max(
radiusDiff * diffFactor,
st::historyRecordLevelMainRadius - radius);
p.rotate((_rotation + _idleRotation) * _directionClockwise);
_circleBezier->paintCircle(
p,
c,
radius,
cubicBezierFactor,
_idleRadius * (1. - waveAmplitude),
circleRadiusDiff,
waveAmplitude * _waveDiff * _randomAdditions);
p.rotate(0);
}
RecordCircle::RecordCircle(rpl::producer<crl::time> animationTicked)
: _majorWave(std::make_unique<Wave>(
rpl::duplicate(animationTicked),
kSegmentsCount,
kMajorDegreeOffset,
st::historyRecordMajorAmplitudeRadius,
kSineWaveSpeedMajor,
0.,
kFlingDistanceFactorMajor,
kFlingInAnimationDurationMajor,
kFlingOutAnimationDurationMajor,
kAnimationSpeedMajor,
kAmplitudeDiffFactorMajor,
true))
, _minorWave(std::make_unique<Wave>(
std::move(animationTicked),
kSegmentsCount,
0,
st::historyRecordMinorAmplitudeRadius
+ st::historyRecordMinorAmplitudeRadius * kSmallWaveRadius,
kSineWaveSpeedMinor,
kFlingDistance,
kFlingDistanceFactorMinor,
kFlingInAnimationDurationMinor,
kFlingOutAnimationDurationMinor,
kAnimationSpeedMinor,
kAmplitudeDiffFactorMinor,
false))
, _levelValue(kMinDivider
+ kAmplitudeDiffFactorMax * kAnimationSpeedCircle) {
}
void RecordCircle::reset() {
_majorWave->reset();
_minorWave->reset();
_levelValue.reset();
}
void RecordCircle::setAmplitude(float64 value) {
const auto to = std::min(kMaxAmplitude, value) / kMaxAmplitude;
_levelValue.start(to);
_majorWave->setValue(to);
_minorWave->setValue(to);
}
void RecordCircle::paint(Painter &p, QColor c) {
const auto dt = crl::now() - _lastUpdateTime;
_levelValue.update(dt);
const auto &mainRadius = st::historyRecordLevelMainRadiusAmplitude;
const auto radius = (st::historyRecordLevelMainRadius
+ (anim::Disabled() ? 0 : mainRadius * _levelValue.current()));
if (!anim::Disabled()) {
_majorWave->tick(radius, dt);
_minorWave->tick(radius, dt);
_lastUpdateTime = crl::now();
const auto opacity = p.opacity();
p.setOpacity(kOpacityMajor);
_majorWave->paint(p, c);
p.setOpacity(kOpacityMinor);
_minorWave->paint(p, c);
p.setOpacity(opacity);
}
p.setPen(Qt::NoPen);
p.setBrush(c);
p.drawEllipse(kZeroPoint, radius, radius);
}
VoiceRecordButton::VoiceRecordButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<> leaveWindowEventProducer)
: AbstractButton(parent)
, _recordCircle(std::make_unique<RecordCircle>(
_recordAnimationTicked.events()))
, _center(st::historyRecordLevelMaxRadius)
, _recordingAnimation([=](crl::time now) {
if (!anim::Disabled()) {
update();
}
_recordAnimationTicked.fire_copy(now);
return true;
}) {
const auto h = st::historyRecordLevelMaxRadius * 2;
resize(h, h);
std::move(
leaveWindowEventProducer
) | rpl::start_with_next([=] {
_inCircle = false;
}, lifetime());
init();
}
VoiceRecordButton::~VoiceRecordButton() = default;
void VoiceRecordButton::requestPaintLevel(quint16 level) {
_recordCircle->setAmplitude(level);
update();
}
void VoiceRecordButton::init() {
const auto hasProgress = [](auto value) { return value != 0.; };
const auto stateChangedAnimation =
lifetime().make_state<Ui::Animations::Simple>();
const auto currentState = lifetime().make_state<Type>(_state.current());
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
Painter p(this);
const auto progress = _showProgress.current();
const auto complete = (progress == 1.);
p.translate(_center, _center);
if (!complete) {
p.scale(progress, progress);
}
PainterHighQualityEnabler hq(p);
const auto color = anim::color(
st::historyRecordVoiceFgInactive,
st::historyRecordVoiceFgActive,
_colorProgress.current());
_recordCircle->paint(p, color);
p.resetTransform();
if (!complete) {
p.setOpacity(progress);
}
// Paint icon.
{
const auto stateProgress = stateChangedAnimation->value(0.);
const auto scale = (std::cos(M_PI * 2 * stateProgress) + 1.) * .5;
p.translate(_center, _center);
if (scale < 1.) {
p.scale(scale, scale);
}
const auto state = *currentState;
const auto icon = (state == Type::Send)
? st::historySendIcon
: st::historyRecordVoiceActive;
const auto position = (state == Type::Send)
? st::historyRecordSendIconPosition
: QPoint(0, 0);
icon.paint(
p,
-icon.width() / 2 + position.x(),
-icon.height() / 2 + position.y(),
0,
st::historyRecordVoiceFgActiveIcon->c);
}
}, lifetime());
rpl::merge(
shownValue(),
_showProgress.value(
) | rpl::map(hasProgress) | rpl::distinct_until_changed()
) | rpl::start_with_next([=](bool show) {
setVisible(show);
setMouseTracking(show);
if (!show) {
_recordingAnimation.stop();
_showProgress = 0.;
_recordCircle->reset();
_state = Type::Record;
} else {
if (!_recordingAnimation.animating()) {
_recordingAnimation.start();
}
}
}, lifetime());
actives(
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool active) {
setPointerCursor(active);
}, lifetime());
_state.changes(
) | rpl::start_with_next([=](Type newState) {
const auto to = 1.;
auto callback = [=](float64 value) {
if (value >= (to * .5)) {
*currentState = newState;
}
update();
};
const auto duration = st::historyRecordVoiceDuration * 2;
stateChangedAnimation->start(std::move(callback), 0., to, duration);
}, lifetime());
}
rpl::producer<bool> VoiceRecordButton::actives() const {
return events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::MouseMove
|| e->type() == QEvent::Leave
|| e->type() == QEvent::Enter);
}) | rpl::map([=](not_null<QEvent*> e) {
switch(e->type()) {
case QEvent::MouseMove:
return inCircle((static_cast<QMouseEvent*>(e.get()))->pos());
case QEvent::Leave: return false;
case QEvent::Enter: return inCircle(mapFromGlobal(QCursor::pos()));
default: return false;
}
});
}
bool VoiceRecordButton::inCircle(const QPoint &localPos) const {
const auto &radii = st::historyRecordLevelMaxRadius;
const auto dx = std::abs(localPos.x() - _center);
if (dx > radii) {
return false;
}
const auto dy = std::abs(localPos.y() - _center);
if (dy > radii) {
return false;
} else if (dx + dy <= radii) {
return true;
}
return ((dx * dx + dy * dy) <= (radii * radii));
}
void VoiceRecordButton::requestPaintProgress(float64 progress) {
_showProgress = progress;
update();
}
void VoiceRecordButton::requestPaintColor(float64 progress) {
_colorProgress = progress;
update();
}
void VoiceRecordButton::setType(Type state) {
_state = state;
}
} // namespace HistoryView::Controls

View File

@@ -0,0 +1,58 @@
/*
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/abstract_button.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
namespace HistoryView::Controls {
class RecordCircle;
class VoiceRecordButton final : public Ui::AbstractButton {
public:
VoiceRecordButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<> leaveWindowEventProducer);
~VoiceRecordButton();
enum class Type {
Send,
Record,
};
void setType(Type state);
void requestPaintColor(float64 progress);
void requestPaintProgress(float64 progress);
void requestPaintLevel(quint16 level);
[[nodiscard]] rpl::producer<bool> actives() const;
[[nodiscard]] bool inCircle(const QPoint &localPos) const;
private:
void init();
rpl::event_stream<crl::time> _recordAnimationTicked;
std::unique_ptr<RecordCircle> _recordCircle;
const int _center;
rpl::variable<float64> _showProgress = 0.;
rpl::variable<float64> _colorProgress = 0.;
rpl::variable<bool> _inCircle = false;
rpl::variable<Type> _state = Type::Record;
// This can animate for a very long time (like in music playing),
// so it should be a Basic, not a Simple animation.
Ui::Animations::Basic _recordingAnimation;
};
} // namespace HistoryView::Controls

View File

@@ -121,6 +121,14 @@ bool SimpleElementDelegate::elementShownUnread(
return view->data()->unread();
}
void SimpleElementDelegate::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void SimpleElementDelegate::elementHandleViaClick(not_null<UserData*> bot) {
}
TextSelection UnshiftItemSelection(
TextSelection selection,
uint16 byLength) {

View File

@@ -67,6 +67,10 @@ public:
virtual bool elementIsGifPaused() = 0;
virtual bool elementHideReply(not_null<const Element*> view) = 0;
virtual bool elementShownUnread(not_null<const Element*> view) = 0;
virtual void elementSendBotCommand(
const QString &command,
const FullMsgId &context) = 0;
virtual void elementHandleViaClick(not_null<UserData*> bot) = 0;
};
@@ -99,6 +103,10 @@ public:
bool elementIsGifPaused() override;
bool elementHideReply(not_null<const Element*> view) override;
bool elementShownUnread(not_null<const Element*> view) override;
void elementSendBotCommand(
const QString &command,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
private:
const not_null<Window::SessionController*> _controller;

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "mainwidget.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "apiwrap.h"
#include "layout.h"
#include "window/window_session_controller.h"
@@ -37,6 +38,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "facades.h"
#include "styles/style_chat.h"
@@ -1290,6 +1294,16 @@ bool ListWidget::elementShownUnread(not_null<const Element*> view) {
return _delegate->listElementShownUnread(view);
}
void ListWidget::elementSendBotCommand(
const QString &command,
const FullMsgId &context) {
_delegate->listSendBotCommand(command, context);
}
void ListWidget::elementHandleViaClick(not_null<UserData*> bot) {
_delegate->listHandleViaClick(bot);
}
void ListWidget::saveState(not_null<ListMemento*> memento) {
memento->setAroundPosition(_aroundPosition);
auto state = countScrollState();
@@ -2186,7 +2200,14 @@ void ListWidget::mouseActionFinish(
mouseActionCancel();
ActivateClickHandler(window(), activated, {
button,
QVariant::fromValue(pressState.itemId)
QVariant::fromValue(ClickHandlerContext{
.itemId = pressState.itemId,
.elementDelegate = [weak = Ui::MakeWeak(this)] {
return weak
? (ElementDelegate*)weak
: nullptr;
},
})
});
return;
}
@@ -2786,4 +2807,34 @@ void ConfirmSendNowSelectedItems(not_null<ListWidget*> widget) {
[=] { navigation->showBackFromStack(); });
}
QString WrapBotCommandInChat(
not_null<PeerData*> peer,
const QString &command,
const FullMsgId &context) {
auto result = command;
if (const auto item = peer->owner().message(context)) {
if (const auto user = item->fromOriginal()->asUser()) {
return WrapBotCommandInChat(peer, command, user);
}
}
return result;
}
QString WrapBotCommandInChat(
not_null<PeerData*> peer,
const QString &command,
not_null<UserData*> bot) {
if (!bot->isBot() || bot->username.isEmpty()) {
return command;
}
const auto botStatus = peer->isChat()
? peer->asChat()->botStatus
: peer->isMegagroup()
? peer->asChannel()->mgInfo->botStatus
: -1;
return ((command.indexOf('@') < 2) && (botStatus == 0 || botStatus == 2))
? command + '@' + bot->username
: command;
}
} // namespace HistoryView

View File

@@ -87,6 +87,10 @@ public:
virtual bool listElementShownUnread(not_null<const Element*> view) = 0;
virtual bool listIsGoodForAroundPosition(
not_null<const Element*> view) = 0;
virtual void listSendBotCommand(
const QString &command,
const FullMsgId &context) = 0;
virtual void listHandleViaClick(not_null<UserData*> bot) = 0;
};
@@ -233,6 +237,10 @@ public:
bool elementIsGifPaused() override;
bool elementHideReply(not_null<const Element*> view) override;
bool elementShownUnread(not_null<const Element*> view) override;
void elementSendBotCommand(
const QString &command,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
~ListWidget();
@@ -556,4 +564,13 @@ void ConfirmDeleteSelectedItems(not_null<ListWidget*> widget);
void ConfirmForwardSelectedItems(not_null<ListWidget*> widget);
void ConfirmSendNowSelectedItems(not_null<ListWidget*> widget);
[[nodiscard]] QString WrapBotCommandInChat(
not_null<PeerData*> peer,
const QString &command,
const FullMsgId &context);
[[nodiscard]] QString WrapBotCommandInChat(
not_null<PeerData*> peer,
const QString &command,
not_null<UserData*> bot);
} // namespace HistoryView

View File

@@ -102,8 +102,10 @@ PinnedWidget::PinnedWidget(
st::historyComposeButton))
, _scrollDown(_scroll.get(), st::historyToDown) {
_topBar->setActiveChat(
_history,
TopBarWidget::Section::Pinned,
TopBarWidget::ActiveChat{
.key = _history,
.section = Dialogs::EntryState::Section::Pinned,
},
nullptr);
_topBar->move(0, 0);
@@ -639,6 +641,14 @@ bool PinnedWidget::listIsGoodForAroundPosition(
return IsServerMsgId(view->data()->id);
}
void PinnedWidget::listSendBotCommand(
const QString &command,
const FullMsgId &context) {
}
void PinnedWidget::listHandleViaClick(not_null<UserData*> bot) {
}
void PinnedWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}

View File

@@ -93,6 +93,10 @@ public:
bool listElementHideReply(not_null<const Element*> view) override;
bool listElementShownUnread(not_null<const Element*> view) override;
bool listIsGoodForAroundPosition(not_null<const Element*> view) override;
void listSendBotCommand(
const QString &command,
const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override;
protected:
void resizeEvent(QResizeEvent *e) override;

View File

@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_replies_section.h"
#include "history/view/history_view_compose_controls.h"
#include "history/view/controls/history_view_compose_controls.h"
#include "history/view/history_view_top_bar_widget.h"
#include "history/view/history_view_list_widget.h"
#include "history/view/history_view_schedule_box.h"
@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_replies_list.h"
#include "data/data_changes.h"
@@ -157,7 +158,8 @@ RepliesWidget::RepliesWidget(
, _composeControls(std::make_unique<ComposeControls>(
this,
controller,
ComposeControls::Mode::Normal))
ComposeControls::Mode::Normal,
SendMenu::Type::SilentOnly))
, _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false))
, _scrollDown(_scroll.get(), st::historyToDown)
, _readRequestTimer([=] { sendReadTillRequest(); }) {
@@ -166,10 +168,7 @@ RepliesWidget::RepliesWidget(
session().api().requestFullPeer(_history->peer);
_topBar->setActiveChat(
_history,
TopBarWidget::Section::Replies,
_sendAction.get());
refreshTopBarActiveChat();
_topBar->move(0, 0);
_topBar->resizeToWidth(width());
@@ -219,7 +218,7 @@ RepliesWidget::RepliesWidget(
_inner->replyToMessageRequested(
) | rpl::start_with_next([=](auto fullId) {
_composeControls->replyToMessage(fullId);
replyToMessage(fullId);
}, _inner->lifetime());
_composeControls->sendActionUpdates(
@@ -253,6 +252,7 @@ RepliesWidget::RepliesWidget(
setupScrollDownButton();
setupComposeControls();
orderWidgets();
}
RepliesWidget::~RepliesWidget() {
@@ -263,6 +263,17 @@ RepliesWidget::~RepliesWidget() {
_history->owner().repliesSendActionPainterRemoved(_history, _rootId);
}
void RepliesWidget::orderWidgets() {
if (_topBar) {
_topBar->raise();
}
if (_rootView) {
_rootView->raise();
}
_topBarShadow->raise();
_composeControls->raisePanels();
}
void RepliesWidget::sendReadTillRequest() {
if (!_root) {
_readRequestPending = true;
@@ -419,13 +430,21 @@ void RepliesWidget::setupComposeControls() {
}, lifetime());
_composeControls->sendRequests(
) | rpl::start_with_next([=] {
send();
) | rpl::start_with_next([=](Api::SendOptions options) {
send(options);
}, lifetime());
_composeControls->sendVoiceRequests(
) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) {
sendVoice(data.bytes, data.waveform, data.duration);
sendVoice(std::move(data));
}, lifetime());
_composeControls->sendCommandRequests(
) | rpl::start_with_next([=](const QString &command) {
if (showSlowmodeError()) {
return;
}
listSendBotCommand(command, FullMsgId());
}, lifetime());
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
@@ -451,17 +470,17 @@ void RepliesWidget::setupComposeControls() {
_composeControls->fileChosen(
) | rpl::start_with_next([=](Selector::FileChosen chosen) {
sendExistingDocument(chosen.document);
sendExistingDocument(chosen.document, chosen.options);
}, lifetime());
_composeControls->photoChosen(
) | rpl::start_with_next([=](Selector::PhotoChosen chosen) {
sendExistingPhoto(chosen.photo);
sendExistingPhoto(chosen.photo, chosen.options);
}, lifetime());
_composeControls->inlineResultChosen(
) | rpl::start_with_next([=](Selector::InlineChosen chosen) {
sendInlineResult(chosen.result, chosen.bot);
sendInlineResult(chosen.result, chosen.bot, chosen.options);
}, lifetime());
_composeControls->scrollRequests(
@@ -507,6 +526,11 @@ void RepliesWidget::setupComposeControls() {
Unexpected("action in MimeData hook.");
});
_composeControls->lockShowStarts(
) | rpl::start_with_next([=] {
updateScrollDownVisibility();
}, lifetime());
_composeControls->finishAnimating();
}
@@ -688,6 +712,7 @@ void RepliesWidget::sendingFilesConfirmed(
}
if (_composeControls->replyingToMessage().msg == replyTo) {
_composeControls->cancelReplyMessage();
refreshTopBarActiveChat();
}
}
@@ -854,13 +879,19 @@ void RepliesWidget::send() {
// Ui::LayerOption::KeepOther);
}
void RepliesWidget::sendVoice(
QByteArray bytes,
VoiceWaveform waveform,
int duration) {
void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) {
auto action = Api::SendAction(_history);
action.replyTo = replyToId();
session().api().sendVoiceMessage(bytes, waveform, duration, action);
action.options = data.options;
session().api().sendVoiceMessage(
data.bytes,
data.waveform,
data.duration,
std::move(action));
_composeControls->cancelReplyMessage();
_composeControls->clearListenState();
finishSending();
}
void RepliesWidget::send(Api::SendOptions options) {
@@ -1010,14 +1041,6 @@ bool RepliesWidget::sendExistingDocument(
message.action.options = options;
Api::SendExistingDocument(std::move(message), document);
//if (_fieldAutocomplete->stickersShown()) {
// clearFieldText();
// //_saveDraftText = true;
// //_saveDraftStart = crl::now();
// //onDraftSave();
// onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
//}
_composeControls->cancelReplyMessage();
finishSending();
return true;
@@ -1112,6 +1135,17 @@ SendMenu::Type RepliesWidget::sendMenuType() const {
: SendMenu::Type::Scheduled;
}
void RepliesWidget::refreshTopBarActiveChat() {
const auto state = Dialogs::EntryState{
.key = _history,
.section = Dialogs::EntryState::Section::Replies,
.rootId = _rootId,
.currentReplyToId = _composeControls->replyingToMessage().msg,
};
_topBar->setActiveChat(state, _sendAction.get());
_composeControls->setCurrentDialogsEntryState(state);
}
MsgId RepliesWidget::replyToId() const {
const auto custom = _composeControls->replyingToMessage().msg;
return custom ? custom : _rootId;
@@ -1155,6 +1189,7 @@ void RepliesWidget::finishSending() {
//if (_previewData && _previewData->pendingTill) previewCancel();
doSetInnerFocus();
showAtEnd();
refreshTopBarActiveChat();
}
void RepliesWidget::showAtPosition(
@@ -1213,6 +1248,9 @@ void RepliesWidget::updateScrollDownVisibility() {
}
const auto scrollDownIsVisible = [&]() -> std::optional<bool> {
if (_composeControls->isLockPresent()) {
return false;
}
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax() || _replyReturn) {
return true;
@@ -1365,10 +1403,15 @@ bool RepliesWidget::replyToMessage(not_null<HistoryItem*> item) {
if (item->history() != _history || item->replyToTop() != _rootId) {
return false;
}
_composeControls->replyToMessage(item->fullId());
replyToMessage(item->fullId());
return true;
}
void RepliesWidget::replyToMessage(FullMsgId itemId) {
_composeControls->replyToMessage(itemId);
refreshTopBarActiveChat();
}
void RepliesWidget::saveState(not_null<RepliesMemento*> memento) {
memento->setReplies(_replies);
memento->setReplyReturns(_replyReturns);
@@ -1466,6 +1509,7 @@ void RepliesWidget::updateControlsGeometry() {
updateInnerVisibleArea();
}
_composeControls->move(0, bottom - controlsHeight);
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
updateScrollDownPosition();
}
@@ -1590,12 +1634,8 @@ void RepliesWidget::listCancelRequest() {
if (_inner && !_inner->getSelectedItems().empty()) {
clearSelected();
return;
}
if (_composeControls->isEditingMessage()) {
_composeControls->cancelEditMessage();
return;
} else if (_composeControls->replyingToMessage()) {
_composeControls->cancelReplyMessage();
} else if (_composeControls->handleCancelRequest()) {
refreshTopBarActiveChat();
return;
}
controller()->showBackFromStack();
@@ -1738,6 +1778,21 @@ bool RepliesWidget::listIsGoodForAroundPosition(
return IsServerMsgId(view->data()->id);
}
void RepliesWidget::listSendBotCommand(
const QString &command,
const FullMsgId &context) {
const auto text = WrapBotCommandInChat(_history->peer, command, context);
auto message = ApiWrap::MessageToSend(_history);
message.textWithTags = { text };
message.action.replyTo = replyToId();
session().api().sendMessage(std::move(message));
finishSending();
}
void RepliesWidget::listHandleViaClick(not_null<UserData*> bot) {
_composeControls->setText({ '@' + bot->username + ' ' });
}
void RepliesWidget::confirmDeleteSelected() {
ConfirmDeleteSelectedItems(_inner);
}
@@ -1753,7 +1808,7 @@ void RepliesWidget::clearSelected() {
void RepliesWidget::setupDragArea() {
const auto areas = DragArea::SetupDragAreaToContainer(
this,
[=](not_null<const QMimeData*> d) { return _history; },
[=](auto d) { return _history && !_composeControls->isRecording(); },
nullptr,
[=] { updateControlsGeometry(); });

View File

@@ -52,6 +52,10 @@ class RepliesList;
namespace HistoryView {
namespace Controls {
struct VoiceToSend;
} // namespace Controls
class Element;
class TopBarWidget;
class RepliesMemento;
@@ -126,6 +130,10 @@ public:
bool listElementHideReply(not_null<const Element*> view) override;
bool listElementShownUnread(not_null<const Element*> view) override;
bool listIsGoodForAroundPosition(not_null<const Element*> view) override;
void listSendBotCommand(
const QString &command,
const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override;
protected:
void resizeEvent(QResizeEvent *e) override;
@@ -176,7 +184,7 @@ private:
void send();
void send(Api::SendOptions options);
void sendVoice(QByteArray bytes, VoiceWaveform waveform, int duration);
void sendVoice(Controls::VoiceToSend &&data);
void edit(
not_null<HistoryItem*> item,
Api::SendOptions options,
@@ -186,6 +194,7 @@ private:
[[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] bool computeAreComments() const;
void orderWidgets();
void pushReplyReturn(not_null<HistoryItem*> item);
void computeCurrentReplyReturn();
@@ -193,6 +202,8 @@ private:
void restoreReplyReturns(const std::vector<MsgId> &list);
void checkReplyReturns();
void recountChatWidth();
void replyToMessage(FullMsgId itemId);
void refreshTopBarActiveChat();
void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles(

View File

@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_scheduled_section.h"
#include "history/view/history_view_compose_controls.h"
#include "history/view/controls/history_view_compose_controls.h"
#include "history/view/history_view_top_bar_widget.h"
#include "history/view/history_view_list_widget.h"
#include "history/view/history_view_schedule_box.h"
@@ -97,12 +97,15 @@ ScheduledWidget::ScheduledWidget(
, _composeControls(std::make_unique<ComposeControls>(
this,
controller,
ComposeControls::Mode::Scheduled))
ComposeControls::Mode::Scheduled,
SendMenu::Type::Disabled))
, _scrollDown(_scroll, st::historyToDown) {
_topBar->setActiveChat(
_history,
TopBarWidget::Section::Scheduled,
nullptr);
const auto state = Dialogs::EntryState{
.key = _history,
.section = Dialogs::EntryState::Section::Scheduled,
};
_topBar->setActiveChat(state, nullptr);
_composeControls->setCurrentDialogsEntryState(state);
_topBar->move(0, 0);
_topBar->resizeToWidth(width());
@@ -180,6 +183,11 @@ void ScheduledWidget::setupComposeControls() {
sendVoice(data.bytes, data.waveform, data.duration);
}, lifetime());
_composeControls->sendCommandRequests(
) | rpl::start_with_next([=](const QString &command) {
listSendBotCommand(command, FullMsgId());
}, lifetime());
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
_composeControls->editRequests(
) | rpl::start_with_next([=](auto data) {
@@ -253,6 +261,11 @@ void ScheduledWidget::setupComposeControls() {
}
Unexpected("action in MimeData hook.");
});
_composeControls->lockShowStarts(
) | rpl::start_with_next([=] {
updateScrollDownVisibility();
}, lifetime());
}
void ScheduledWidget::chooseAttach() {
@@ -560,6 +573,7 @@ void ScheduledWidget::sendVoice(
auto action = Api::SendAction(_history);
action.options = options;
session().api().sendVoiceMessage(bytes, waveform, duration, action);
_composeControls->clearListenState();
}
void ScheduledWidget::edit(
@@ -661,14 +675,6 @@ bool ScheduledWidget::sendExistingDocument(
message.action.options = options;
Api::SendExistingDocument(std::move(message), document);
//if (_fieldAutocomplete->stickersShown()) {
// clearFieldText();
// //_saveDraftText = true;
// //_saveDraftStart = crl::now();
// //onDraftSave();
// onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
//}
_composeControls->hidePanelsAnimated();
_composeControls->focus();
return true;
@@ -820,6 +826,9 @@ void ScheduledWidget::updateScrollDownVisibility() {
}
const auto scrollDownIsVisible = [&]() -> std::optional<bool> {
if (_composeControls->isLockPresent()) {
return false;
}
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax()) {
return true;
@@ -971,6 +980,7 @@ void ScheduledWidget::updateControlsGeometry() {
updateInnerVisibleArea();
}
_composeControls->move(0, bottom - controlsHeight);
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
updateScrollDownPosition();
}
@@ -1049,9 +1059,7 @@ void ScheduledWidget::listCancelRequest() {
if (_inner && !_inner->getSelectedItems().empty()) {
clearSelected();
return;
}
if (_composeControls->isEditingMessage()) {
_composeControls->cancelEditMessage();
} else if (_composeControls->handleCancelRequest()) {
return;
}
controller()->showBackFromStack();
@@ -1165,6 +1173,25 @@ bool ScheduledWidget::listIsGoodForAroundPosition(
return true;
}
void ScheduledWidget::listSendBotCommand(
const QString &command,
const FullMsgId &context) {
const auto callback = [=](Api::SendOptions options) {
const auto text = WrapBotCommandInChat(_history->peer, command, context);
auto message = ApiWrap::MessageToSend(_history);
message.textWithTags = { text };
message.action.options = options;
session().api().sendMessage(std::move(message));
};
Ui::show(
PrepareScheduleBox(this, sendMenuType(), callback),
Ui::LayerOption::KeepOther);
}
void ScheduledWidget::listHandleViaClick(not_null<UserData*> bot) {
_composeControls->setText({ '@' + bot->username + ' ' });
}
void ScheduledWidget::confirmSendNowSelected() {
ConfirmSendNowSelectedItems(_inner);
}
@@ -1180,7 +1207,7 @@ void ScheduledWidget::clearSelected() {
void ScheduledWidget::setupDragArea() {
const auto areas = DragArea::SetupDragAreaToContainer(
this,
[=](not_null<const QMimeData*> d) { return _history; },
[=](auto d) { return _history && !_composeControls->isRecording(); },
nullptr,
[=] { updateControlsGeometry(); });

View File

@@ -110,6 +110,10 @@ public:
bool listElementHideReply(not_null<const Element*> view) override;
bool listElementShownUnread(not_null<const Element*> view) override;
bool listIsGoodForAroundPosition(not_null<const Element *> view) override;
void listSendBotCommand(
const QString &command,
const FullMsgId &context) override;
void listHandleViaClick(not_null<UserData*> bot) override;
protected:
void resizeEvent(QResizeEvent *e) override;

View File

@@ -115,7 +115,7 @@ TopBarWidget::TopBarWidget(
using AnimationUpdate = Data::Session::SendActionAnimationUpdate;
session().data().sendActionAnimationUpdated(
) | rpl::filter([=](const AnimationUpdate &update) {
return (update.history == _activeChat.history());
return (update.history == _activeChat.key.history());
}) | rpl::start_with_next([=] {
update();
}, lifetime());
@@ -191,13 +191,13 @@ void TopBarWidget::refreshLang() {
}
void TopBarWidget::onSearch() {
if (_activeChat) {
_controller->content()->searchInChat(_activeChat);
if (_activeChat.key) {
_controller->content()->searchInChat(_activeChat.key);
}
}
void TopBarWidget::onCall() {
if (const auto peer = _activeChat.peer()) {
if (const auto peer = _activeChat.key.peer()) {
if (const auto user = peer->asUser()) {
Core::App().calls().startOutgoingCall(user, false);
}
@@ -205,7 +205,7 @@ void TopBarWidget::onCall() {
}
void TopBarWidget::showMenu() {
if (!_activeChat || _menu) {
if (!_activeChat.key || _menu) {
return;
}
_menu.create(parentWidget());
@@ -228,28 +228,14 @@ void TopBarWidget::showMenu() {
}));
_menuToggle->installEventFilter(_menu);
const auto addAction = [&](
const QString & text,
const QString &text,
Fn<void()> callback) {
return _menu->addAction(text, std::move(callback));
};
if (const auto peer = _activeChat.peer()) {
Window::FillPeerMenu(
_controller,
peer,
FilterId(),
addAction,
(_section == Section::Scheduled)
? Window::PeerMenuSource::ScheduledSection
: Window::PeerMenuSource::History);
} else if (const auto folder = _activeChat.folder()) {
Window::FillFolderMenu(
_controller,
folder,
addAction,
Window::PeerMenuSource::History);
} else {
Unexpected("Empty active chat in TopBarWidget::showMenu.");
}
Window::FillDialogsEntryMenu(
_controller,
_activeChat,
addAction);
if (_menu->actions().empty()) {
_menu.destroy();
} else {
@@ -263,13 +249,13 @@ void TopBarWidget::toggleInfoSection() {
&& (Core::App().settings().thirdSectionInfoEnabled()
|| Core::App().settings().tabbedReplacedWithInfo())) {
_controller->closeThirdSection();
} else if (_activeChat.peer()) {
} else if (_activeChat.key.peer()) {
if (_controller->canShowThirdSection()) {
Core::App().settings().setThirdSectionInfoEnabled(true);
Core::App().saveSettingsDelayed();
if (Adaptive::ThreeColumn()) {
_controller->showSection(
Info::Memento::Default(_activeChat.peer()),
Info::Memento::Default(_activeChat.key.peer()),
Window::SectionShow().withThirdColumn());
} else {
_controller->resizeForThirdSection();
@@ -323,7 +309,7 @@ void TopBarWidget::paintEvent(QPaintEvent *e) {
}
void TopBarWidget::paintTopBar(Painter &p) {
if (!_activeChat) {
if (!_activeChat.key) {
return;
}
auto nameleft = _leftTaken;
@@ -331,18 +317,18 @@ void TopBarWidget::paintTopBar(Painter &p) {
auto statustop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height;
auto availableWidth = width() - _rightTaken - nameleft;
const auto history = _activeChat.history();
const auto folder = _activeChat.folder();
const auto history = _activeChat.key.history();
const auto folder = _activeChat.key.folder();
if (folder
|| history->peer->sharedMediaInfo()
|| (_section == Section::Scheduled)
|| (_section == Section::Pinned)) {
|| (_activeChat.section == Section::Scheduled)
|| (_activeChat.section == Section::Pinned)) {
// #TODO feed name emoji.
auto text = (_section == Section::Scheduled)
auto text = (_activeChat.section == Section::Scheduled)
? ((history && history->peer->isSelf())
? tr::lng_reminder_messages(tr::now)
: tr::lng_scheduled_messages(tr::now))
: (_section == Section::Pinned)
: (_activeChat.section == Section::Pinned)
? _customTitleText
: folder
? folder->chatListName()
@@ -360,7 +346,7 @@ void TopBarWidget::paintTopBar(Painter &p) {
(height() - st::historySavedFont->height) / 2,
width(),
text);
} else if (_section == Section::Replies) {
} else if (_activeChat.section == Section::Replies) {
p.setPen(st::dialogsNameFg);
p.setFont(st::semiboldFont);
p.drawTextLeft(
@@ -382,7 +368,7 @@ void TopBarWidget::paintTopBar(Painter &p) {
p.setPen(st::historyStatusFg);
p.drawTextLeft(nameleft, statustop, width(), _customTitleText);
}
} else if (const auto history = _activeChat.history()) {
} else if (const auto history = _activeChat.key.history()) {
const auto peer = history->peer;
const auto &text = peer->topBarNameText();
const auto badgeStyle = Ui::PeerBadgeStyle{
@@ -481,29 +467,30 @@ void TopBarWidget::mousePressEvent(QMouseEvent *e) {
}
void TopBarWidget::infoClicked() {
if (!_activeChat) {
const auto key = _activeChat.key;
if (!key) {
return;
} else if (_activeChat.folder()) {
} else if (key.folder()) {
_controller->closeFolder();
//} else if (const auto feed = _activeChat.feed()) { // #feed
// _controller->showSection(Info::Memento(
// feed,
// Info::Section(Info::Section::Type::Profile)));
} else if (_activeChat.peer()->isSelf()) {
} else if (key.peer()->isSelf()) {
_controller->showSection(Info::Memento(
_activeChat.peer(),
key.peer(),
Info::Section(Storage::SharedMediaType::Photo)));
} else if (_activeChat.peer()->isRepliesChat()) {
} else if (key.peer()->isRepliesChat()) {
_controller->showSection(Info::Memento(
_activeChat.peer(),
key.peer(),
Info::Section(Storage::SharedMediaType::Photo)));
} else {
_controller->showPeerInfo(_activeChat.peer());
_controller->showPeerInfo(key.peer());
}
}
void TopBarWidget::backClicked() {
if (_activeChat.folder()) {
if (_activeChat.key.folder()) {
_controller->closeFolder();
} else {
_controller->showBackFromStack();
@@ -511,14 +498,14 @@ void TopBarWidget::backClicked() {
}
void TopBarWidget::setActiveChat(
Dialogs::Key chat,
Section section,
ActiveChat activeChat,
SendActionPainter *sendAction) {
if (_activeChat == chat && _section == section) {
if (_activeChat.key == activeChat.key
&& _activeChat.section == activeChat.section) {
_activeChat = activeChat;
return;
}
_activeChat = chat;
_section = section;
_activeChat = activeChat;
_sendAction = sendAction;
_back->clearState();
update();
@@ -542,7 +529,7 @@ void TopBarWidget::setCustomTitle(const QString &title) {
}
void TopBarWidget::refreshInfoButton() {
if (const auto peer = _activeChat.peer()) {
if (const auto peer = _activeChat.key.peer()) {
auto info = object_ptr<Ui::UserpicButton>(
this,
_controller,
@@ -575,14 +562,14 @@ int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) {
}
void TopBarWidget::updateSearchVisibility() {
const auto historyMode = (_section == Section::History);
const auto smallDialogsColumn = _activeChat.folder()
const auto historyMode = (_activeChat.section == Section::History);
const auto smallDialogsColumn = _activeChat.key.folder()
&& (width() < _back->width() + _search->width());
_search->setVisible(historyMode && !smallDialogsColumn);
}
void TopBarWidget::updateControlsGeometry() {
if (!_activeChat) {
if (!_activeChat.key) {
return;
}
auto hasSelected = (_selectedCount > 0);
@@ -619,7 +606,7 @@ void TopBarWidget::updateControlsGeometry() {
if (_back->isHidden()) {
_leftTaken = st::topBarArrowPadding.right();
} else {
const auto smallDialogsColumn = _activeChat.folder()
const auto smallDialogsColumn = _activeChat.key.folder()
&& (width() < _back->width() + _search->width());
_leftTaken = smallDialogsColumn ? (width() - _back->width()) / 2 : 0;
_back->moveToLeft(_leftTaken, otherButtonsTop);
@@ -664,7 +651,7 @@ void TopBarWidget::setAnimatingMode(bool enabled) {
}
void TopBarWidget::updateControlsVisibility() {
if (!_activeChat) {
if (!_activeChat.key) {
return;
} else if (_animatingMode) {
hideChildren();
@@ -677,7 +664,7 @@ void TopBarWidget::updateControlsVisibility() {
auto backVisible = Adaptive::OneColumn()
|| !_controller->content()->stackIsEmpty()
|| _activeChat.folder();
|| _activeChat.key.folder();
_back->setVisible(backVisible);
if (_info) {
_info->setVisible(Adaptive::OneColumn());
@@ -685,19 +672,22 @@ void TopBarWidget::updateControlsVisibility() {
if (_unreadBadge) {
_unreadBadge->show();
}
const auto historyMode = (_section == Section::History);
const auto scheduledMode = (_section == Section::Scheduled);
const auto showInScheduledMode = (_activeChat.peer()
&& _activeChat.peer()->canSendPolls());
const auto section = _activeChat.section;
const auto historyMode = (section == Section::History);
const auto hasPollsMenu = _activeChat.key.peer()
&& _activeChat.key.peer()->canSendPolls();
const auto hasMenu = !_activeChat.key.folder()
&& ((section == Section::Scheduled || section == Section::Replies)
? hasPollsMenu
: historyMode);
updateSearchVisibility();
_menuToggle->setVisible(!_activeChat.folder()
&& (scheduledMode ? showInScheduledMode : historyMode));
_menuToggle->setVisible(hasMenu);
_infoToggle->setVisible(historyMode
&& !_activeChat.folder()
&& !_activeChat.key.folder()
&& !Adaptive::OneColumn()
&& _controller->canShowThirdSection());
const auto callsEnabled = [&] {
if (const auto peer = _activeChat.peer()) {
if (const auto peer = _activeChat.key.peer()) {
if (const auto user = peer->asUser()) {
return session().serverConfig().phoneCallsEnabled.current()
&& user->hasCalls();
@@ -715,7 +705,7 @@ void TopBarWidget::updateControlsVisibility() {
void TopBarWidget::updateMembersShowArea() {
const auto membersShowAreaNeeded = [&] {
const auto peer = _activeChat.peer();
const auto peer = _activeChat.key.peer();
if ((_selectedCount > 0) || !peer) {
return false;
} else if (const auto chat = peer->asChat()) {
@@ -801,7 +791,7 @@ void TopBarWidget::updateAdaptiveLayout() {
}
void TopBarWidget::refreshUnreadBadge() {
if (!Adaptive::OneColumn() && !_activeChat.folder()) {
if (!Adaptive::OneColumn() && !_activeChat.key.folder()) {
_unreadBadge.destroy();
return;
} else if (_unreadBadge) {
@@ -830,8 +820,9 @@ void TopBarWidget::refreshUnreadBadge() {
void TopBarWidget::updateUnreadBadge() {
if (!_unreadBadge) return;
const auto muted = session().data().unreadBadgeMutedIgnoreOne(_activeChat);
const auto counter = session().data().unreadBadgeIgnoreOne(_activeChat);
const auto key = _activeChat.key;
const auto muted = session().data().unreadBadgeMutedIgnoreOne(key);
const auto counter = session().data().unreadBadgeIgnoreOne(key);
const auto text = [&] {
if (!counter) {
return QString();
@@ -858,12 +849,15 @@ void TopBarWidget::updateInfoToggleActive() {
}
void TopBarWidget::updateOnlineDisplay() {
if (!_activeChat.peer()) return;
const auto peer = _activeChat.key.peer();
if (!peer) {
return;
}
QString text;
const auto now = base::unixtime::now();
bool titlePeerTextOnline = false;
if (const auto user = _activeChat.peer()->asUser()) {
if (const auto user = peer->asUser()) {
if (session().supportMode()
&& !session().supportHelper().infoCurrent(user).text.empty()) {
text = QString::fromUtf8("\xe2\x9a\xa0\xef\xb8\x8f check info");
@@ -872,7 +866,7 @@ void TopBarWidget::updateOnlineDisplay() {
text = Data::OnlineText(user, now);
titlePeerTextOnline = Data::OnlineTextActive(user, now);
}
} else if (const auto chat = _activeChat.peer()->asChat()) {
} else if (const auto chat = peer->asChat()) {
if (!chat->amIn()) {
text = tr::lng_chat_status_unaccessible(tr::now);
} else if (chat->participants.empty()) {
@@ -903,7 +897,7 @@ void TopBarWidget::updateOnlineDisplay() {
text = tr::lng_group_status(tr::now);
}
}
} else if (const auto channel = _activeChat.peer()->asChannel()) {
} else if (const auto channel = peer->asChannel()) {
if (channel->isMegagroup()
&& (channel->membersCount() > 0)
&& (channel->membersCount()
@@ -950,7 +944,10 @@ void TopBarWidget::updateOnlineDisplay() {
}
void TopBarWidget::updateOnlineDisplayTimer() {
if (!_activeChat.peer()) return;
const auto peer = _activeChat.key.peer();
if (!peer) {
return;
}
const auto now = base::unixtime::now();
auto minTimeout = crl::time(86400);
@@ -958,13 +955,13 @@ void TopBarWidget::updateOnlineDisplayTimer() {
auto hisTimeout = Data::OnlineChangeTimeout(user, now);
accumulate_min(minTimeout, hisTimeout);
};
if (const auto user = _activeChat.peer()->asUser()) {
if (const auto user = peer->asUser()) {
handleUser(user);
} else if (auto chat = _activeChat.peer()->asChat()) {
} else if (const auto chat = peer->asChat()) {
for (const auto user : chat->participants) {
handleUser(user);
}
} else if (_activeChat.peer()->isChannel()) {
} else if (peer->isChannel()) {
}
updateOnlineDisplayIn(minTimeout);
}

View File

@@ -43,12 +43,8 @@ public:
int canForwardCount = 0;
int canSendNowCount = 0;
};
enum class Section {
History,
Scheduled,
Pinned,
Replies,
};
using ActiveChat = Dialogs::EntryState;
using Section = ActiveChat::Section;
TopBarWidget(
QWidget *parent,
@@ -66,8 +62,7 @@ public:
void setAnimatingMode(bool enabled);
void setActiveChat(
Dialogs::Key chat,
Section section,
ActiveChat activeChat,
SendActionPainter *sendAction);
void setCustomTitle(const QString &title);
@@ -131,8 +126,7 @@ private:
void updateUnreadBadge();
const not_null<Window::SessionController*> _controller;
Dialogs::Key _activeChat;
Section _section = Section::History;
ActiveChat _activeChat;
QString _customTitleText;
int _selectedCount = 0;

View File

@@ -61,6 +61,85 @@ constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
return result;
}
void PaintWaveform(
Painter &p,
const VoiceData *voiceData,
int availableWidth,
bool selected,
bool outbg,
float64 progress) {
const auto wf = [&]() -> const VoiceWaveform* {
if (!voiceData) {
return nullptr;
}
if (voiceData->waveform.isEmpty()) {
return nullptr;
} else if (voiceData->waveform.at(0) < 0) {
return nullptr;
}
return &voiceData->waveform;
}();
// Rescale waveform by going in waveform.size * bar_count 1D grid.
const auto active = outbg
? (selected
? st::msgWaveformOutActiveSelected
: st::msgWaveformOutActive)
: (selected
? st::msgWaveformInActiveSelected
: st::msgWaveformInActive);
const auto inactive = outbg
? (selected
? st::msgWaveformOutInactiveSelected
: st::msgWaveformOutInactive)
: (selected
? st::msgWaveformInInactiveSelected
: st::msgWaveformInInactive);
const auto wfSize = wf
? wf->size()
: ::Media::Player::kWaveformSamplesCount;
const auto activeWidth = std::round(availableWidth * progress);
const auto &barWidth = st::msgWaveformBar;
const auto barCount = std::min(
availableWidth / (barWidth + st::msgWaveformSkip),
wfSize);
const auto barNormValue = (wf ? voiceData->wavemax : 0) + 1;
const auto maxDelta = st::msgWaveformMax - st::msgWaveformMin;
const auto &bottom = st::msgWaveformMax;
p.setPen(Qt::NoPen);
for (auto i = 0, barLeft = 0, sum = 0, maxValue = 0; i < wfSize; ++i) {
const auto value = wf ? wf->at(i) : 0;
if (sum + barCount < wfSize) {
maxValue = std::max(maxValue, value);
sum += barCount;
continue;
}
// Draw bar.
sum = sum + barCount - wfSize;
if (sum < (barCount + 1) / 2) {
maxValue = std::max(maxValue, value);
}
const auto barValue = ((maxValue * maxDelta) + (barNormValue / 2))
/ barNormValue;
const auto barHeight = st::msgWaveformMin + barValue;
const auto barTop = bottom - barValue;
if ((barLeft < activeWidth) && (barLeft + barWidth > activeWidth)) {
const auto leftWidth = activeWidth - barLeft;
const auto rightWidth = barWidth - leftWidth;
p.fillRect(barLeft, barTop, leftWidth, barHeight, active);
p.fillRect(activeWidth, barTop, rightWidth, barHeight, inactive);
} else {
const auto &color = (barLeft >= activeWidth) ? inactive : active;
p.fillRect(barLeft, barTop, barWidth, barHeight, color);
}
barLeft += barWidth + st::msgWaveformSkip;
maxValue = (sum < (barCount + 1) / 2) ? 0 : value;
}
}
} // namespace
Document::Document(
@@ -418,79 +497,42 @@ void Document::draw(
if (const auto voice = Get<HistoryDocumentVoice>()) {
ensureDataMediaCreated();
const VoiceWaveform *wf = nullptr;
uchar norm_value = 0;
if (const auto voiceData = _data->voice()) {
wf = &voiceData->waveform;
if (wf->isEmpty()) {
wf = nullptr;
if (voiceData->waveform.isEmpty()) {
if (loaded) {
Local::countVoiceWaveform(_dataMedia.get());
}
} else if (wf->at(0) < 0) {
wf = nullptr;
} else {
norm_value = voiceData->wavemax;
}
}
auto progress = ([voice] {
const auto progress = [&] {
if (!outbg
&& !voice->_playback
&& _realParent->hasUnreadMediaFlag()) {
return 1.;
}
if (voice->seeking()) {
return voice->seekingCurrent();
} else if (voice->_playback) {
return voice->_playback->progress.current();
}
return 0.;
})();
}();
if (voice->seeking()) {
voiceStatusOverride = Ui::FormatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000);
voiceStatusOverride = Ui::FormatPlayedText(
std::round(progress * voice->_lastDurationMs) / 1000,
voice->_lastDurationMs / 1000);
}
// rescale waveform by going in waveform.size * bar_count 1D grid
auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive);
auto wf_size = wf ? wf->size() : ::Media::Player::kWaveformSamplesCount;
auto availw = namewidth + st::msgWaveformSkip;
auto activew = qRound(availw * progress);
if (!outbg
&& !voice->_playback
&& _realParent->hasUnreadMediaFlag()) {
activew = availw;
}
auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
auto max_value = 0;
auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
auto bottom = st.padding.top() - topMinus + st::msgWaveformMax;
p.setPen(Qt::NoPen);
for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
auto value = wf ? wf->at(i) : 0;
if (sum_i + bar_count >= wf_size) { // draw bar
sum_i = sum_i + bar_count - wf_size;
if (sum_i < (bar_count + 1) / 2) {
if (max_value < value) max_value = value;
}
auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
if (bar_x >= activew) {
p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
} else if (bar_x + st::msgWaveformBar <= activew) {
p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
} else {
p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active);
p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive);
}
bar_x += st::msgWaveformBar + st::msgWaveformSkip;
if (sum_i < (bar_count + 1) / 2) {
max_value = 0;
} else {
max_value = value;
}
} else {
if (max_value < value) max_value = value;
sum_i += bar_count;
}
}
p.save();
p.translate(nameleft, st.padding.top() - topMinus);
PaintWaveform(p,
_data->voice(),
namewidth + st::msgWaveformSkip,
selected,
outbg,
progress);
p.restore();
} else if (auto named = Get<HistoryDocumentNamed>()) {
p.setFont(st::semiboldFont);
p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));

View File

@@ -33,7 +33,7 @@ Game::Game(
, _title(st::msgMinWidth - st::webPageLeft)
, _description(st::msgMinWidth - st::webPageLeft) {
if (!consumed.text.isEmpty()) {
const auto context = Core::UiIntegration::Context{
const auto context = Core::MarkedTextContext{
.session = &history()->session()
};
_description.setMarkedText(
@@ -418,7 +418,7 @@ void Game::parentTextUpdated() {
if (const auto media = _parent->data()->media()) {
const auto consumed = media->consumedMessageText();
if (!consumed.text.isEmpty()) {
const auto context = Core::UiIntegration::Context{
const auto context = Core::MarkedTextContext{
.session = &history()->session()
};
_description.setMarkedText(

View File

@@ -139,7 +139,7 @@ Ui::Text::String Media::createCaption(
- st::msgPadding.left()
- st::msgPadding.right();
auto result = Ui::Text::String(minResizeWidth);
const auto context = Core::UiIntegration::Context{
const auto context = Core::MarkedTextContext{
.session = &history()->session()
};
result.setMarkedText(

View File

@@ -201,11 +201,12 @@ QSize WebPage::countOptimalSize() {
- st::msgPadding.right()
- st::webPageLeft);
}
auto context = Core::UiIntegration::Context();
auto context = Core::MarkedTextContext();
using MarkedTextContext = Core::MarkedTextContext;
if (_data->siteName == qstr("Twitter")) {
context.type = Core::UiIntegration::HashtagMentionType::Twitter;
context.type = MarkedTextContext::HashtagMentionType::Twitter;
} else if (_data->siteName == qstr("Instagram")) {
context.type = Core::UiIntegration::HashtagMentionType::Instagram;
context.type = MarkedTextContext::HashtagMentionType::Instagram;
}
_description.setMarkedText(
st::webPageDescriptionStyle,

View File

@@ -575,12 +575,13 @@ void WrapWidget::showTopBarMenu() {
return _topBarMenu->addAction(text, std::move(callback));
};
if (const auto peer = key().peer()) {
Window::FillPeerMenu(
Window::FillDialogsEntryMenu(
_controller->parentController(),
peer,
FilterId(),
addAction,
Window::PeerMenuSource::Profile);
Dialogs::EntryState{
.key = peer->owner().history(peer),
.section = Dialogs::EntryState::Section::Profile,
},
addAction);
//} else if (const auto feed = key().feed()) { // #feed
// Window::FillFeedMenu(
// _controller->parentController(),

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